| name | survey-report-builder |
| description | CSVアンケートデータから Quarto レポート(PDF)と発表用スライド(PPTX)を構築するスキル。「アンケートを分析して」「survey analysis」「レポートを作成」「集計して」などのリクエストで使用する。 |
| disable-model-invocation | true |
| argument-hint | [csv-file-path] |
アンケート分析レポート構築
data/ に配置された CSV アンケートデータから、Quarto レポート(PDF)と発表用スライド(PPTX)を構築する。
目次
- 前提チェック・CSV読み込み
- 列の分類
- metadata.json 作成
- index.qmd 構築
- スクリプト追加
- quarto render
- PPTX生成(任意)
進捗チェックリスト
- [ ] Step 1: 前提チェック・CSV読み込み・列構造把握
- [ ] Step 2: ユーザーと列分類を確認
- [ ] Step 3: exam_metadata.json 作成
- [ ] Step 4: index.qmd 構築(setup → セクション)
- [ ] Step 5: pyproject.toml にスクリプト追加
- [ ] Step 6: quarto render → エラー修正 → 再render
- [ ] Step 7: PPTX スライド生成(該当時)
ステップ1: 前提チェックとCSV読み込み
まず前提条件を確認する:
uv --version && quarto --version
python -c "import pandas; import matplotlib; print('OK')"
いずれかが失敗する場合は /quarto-survey-setup を先に実行するよう案内する。
次に引数 [csv-file-path] を読み込み、列構造を把握する:
import pandas as pd
df = pd.read_csv("[csv-file-path]", encoding="utf-8-sig")
print(f"回答数: {len(df)}, 列数: {len(df.columns)}")
for col in df.columns:
print(f" {col}: {df[col].nunique()} unique, {df[col].isna().mean()*100:.0f}% missing")
ステップ2: 列の分類
各列を以下の4カテゴリに分類する。必ずユーザーに確認しながら進める:
| カテゴリ | 判定基準 | 分析方法 |
|---|
| タイムスタンプ | 日時形式 | 回答期間の算出のみ |
| Likert尺度 | 5段階の順序カテゴリ | 度数分布 + 横棒グラフ + クロス集計 |
| カテゴリ | 有限個の名義変数 | 度数分布 + 横棒グラフ |
| 自由記述 | テキスト長が長い / ユニーク値が多い | キーワード分類 + 質的分析 |
ユーザーへの確認事項:
- 各列がどのカテゴリに該当するか
- Likert尺度の順序(ネガティブ→ポジティブ or 逆)
- クロス集計に使用する属性列
- 自由記述のキーワード分類ルール
ステップ3: data/exam_metadata.json 作成
ユーザーからのヒアリングを基にメタデータ JSON を作成する:
{
"exam": {
"name": "試験・調査の名称",
"date": "YYYY-MM-DD",
"date_display": "YYYY年MM月DD日(曜日)"
},
"survey": {
"target": "対象者の説明",
"target_total": 0,
"respondents": 0,
"response_rate": 0.0
},
"label_map": {
"column_name": "日本語表示ラベル"
}
}
label_map: CSV の全列名をキーとし、日本語表示ラベルを設定する。グラフのタイトル・凡例・軸ラベルで使用される。
ステップ4: index.qmd 構築
4-1. YAML ヘッダ
---
title: "レポートタイトル"
subtitle: "サブタイトル"
date: today
editor:
render-on-save: true
---
4-2. setup ブロック
以下を含む setup コードブロックを作成する:
import json
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from pathlib import Path
matplotlib.rcParams["svg.fonttype"] = "none"
matplotlib.rcParams["figure.dpi"] = 150
matplotlib.rcParams["font.family"] = "Yu Gothic"
OUTPUT_DIR = Path("output")
FIGURES_DIR = OUTPUT_DIR / "figures"
TABLES_DIR = OUTPUT_DIR / "tables"
FIGURES_DIR.mkdir(parents=True, exist_ok=True)
TABLES_DIR.mkdir(parents=True, exist_ok=True)
4-3. ヘルパー関数
setup ブロック内に以下の4関数を定義する。実装は $SKILL_ROOT/scripts/helpers.py を参照してそのままコピーする:
cat "$SKILL_ROOT/scripts/helpers.py"
| 関数 | 用途 |
|---|
save_summary(series, name, label) | 度数分布表の CSV 保存 |
plot_horizontal_bar(series, title, order, name, ...) | 横棒グラフ(件数・割合ラベル付き) |
plot_cross_tab(df, row_col, col_col, ...) | クロス集計の積み上げ棒グラフ |
save_table_svg(df, name, title, ...) | DataFrame のテーブル SVG 化 |
カラーパレットも helpers.py に定義済み:
COLORS — 10色メインパレット
DIFFICULTY_CMAP — 5段階 Likert 用(ネガティブ→ポジティブ)
4-4. セクション構成
index.qmd の本文を以下の順序で構成する。各セクションは独立した Quarto コードブロックとする:
A. はじめに
- 調査の概要説明テキスト
- 試験・調査概要テーブル(
save_table_svg() 使用)
- アンケート概要(対象者数、回答率、回答期間)
B. 回答者の属性
- 属性列ごとに
save_summary() + plot_horizontal_bar()
- 属性同士のクロス集計(
plot_cross_tab())
C. 各設問の集計・可視化
- Likert尺度: 順序定義 →
save_summary() → plot_horizontal_bar() → plot_cross_tab()
- カテゴリ:
save_summary() + plot_horizontal_bar()(自由記述混合時は正規化関数を定義)
D. 自由記述の分析
- キーワードマッチングによる分類関数を定義
- 複数カテゴリ該当の処理(1回答 → 複数カテゴリ)
- 「なし」「特になし」系の事前除外
- 分類結果の横棒グラフ + 利用率スタック棒グラフ
E. 質的分析セクション(テーマ分析がある場合)
output/improvement_qualitative.csv を読み込み
- テーマ別件数の横棒グラフ + 代表的引用一覧
F. まとめ
- 各設問の主要所見を自動集計・出力(最頻値・割合を動的算出)
4-5. コードブロックの規約
#| label: fig-{topic} # 図の場合
#| label: {topic} # テキスト出力の場合
#| fig-cap: "キャプション" # 図の場合
#| output: asis # Markdown テキスト出力の場合
#| include: false # setup ブロックのみ
ステップ5: pyproject.toml にスクリプトを追加
[project.scripts] に以下を追加(まだ存在しない場合):
[project.scripts]
render = "run_commands:render"
preview = "run_commands:preview"
質的分析から PPTX を生成する場合:
slides = "generate_theme_slides:main"
ステップ6: quarto render でPDF生成
uv run render
エラー時のフィードバックループ: エラーが出たら修正して再度 render する。よくあるエラー:
- KeyError: 列名が CSV と不一致 →
df.columns で確認
- FileNotFoundError: データファイルのパス誤り
- フォントエラー:
matplotlib-fontja 未インストール → uv sync
ステップ7: PPTX スライド生成(任意)
質的分析結果がある場合、$SKILL_ROOT/scripts/generate_theme_slides.py を参考にスライド生成スクリプトを作成する:
cat "$SKILL_ROOT/scripts/generate_theme_slides.py"
プロジェクトルートにコピーしてデータパスを調整する。前提ファイル:
output/improvement_qualitative.csv — 質的分析コーディング結果
output/summary_improvement_themes.csv — テーマ別集計
出力規約
ファイル命名
| 種類 | パス | 命名規則 |
|---|
| 度数分布 CSV | output/summary_{topic}.csv | 列名に対応 |
| クロス集計 CSV | output/summary_{topic}_cross.csv | 行列列名を結合 |
| 横棒グラフ SVG | output/figures/{topic}.svg | 列名に対応 |
| テーブル SVG | output/tables/{topic}.svg | テーブル名に対応 |
| ID付き全回答 | output/responses_with_id.csv | 固定名 |
| 自由記述抜粋 | output/free_text.csv | 固定名 |
| PPTX | output/{topic}.pptx | テーマ名に対応 |
CSV フォーマット
- エンコーディング: UTF-8 with BOM (
encoding="utf-8-sig") — Excel互換
SVG 設定
matplotlib.rcParams["svg.fonttype"] = "none" — テキスト要素を保持
- フォント: Yu Gothic(日本語対応)
- DPI: 150
自由記述のキーワード分類パターン
def classify_responses(text):
"""自由記述を複数カテゴリに分類する."""
text_lower = str(text).lower()
categories = []
if "キーワードA" in text or "keyword_a" in text_lower:
categories.append("カテゴリA")
if "キーワードB" in text:
categories.append("カテゴリB")
if not categories:
categories.append("その他")
return categories
分類時の注意:
str.lower() で英字を統一、日本語は原文のまま比較
- 1回答が複数カテゴリに該当する場合は全て返す
- 「なし」「特になし」系は専用リストで先に除外
- 「その他」は末尾に配置
質的分析の CSV フォーマット
id,テーマ,引用,代表的
R001,テーマA,引用テキスト,1
R001,テーマB,引用テキスト,0
R002,テーマA,引用テキスト,0
- id: 回答者ID(R001形式)
- テーマ: 分類先テーマ名
- 引用: 該当テキスト
- 代表的: 代表的引用は
1、それ以外は 0
注意事項
- Likert尺度の順序は必ずユーザーに確認する
- 日本語ラベルは
label_map から取得する
- 自由記述の分類ルールはデータ固有(汎用ルールはない)
quarto render 前に uv sync で依存関係を最新にする