skills/skills.netease.im/spss-cross-analysis

spss-cross-analysis

SKILL.md

SPSS 问卷交叉分析报表生成

核心脚本

scripts/spss_cross_analyzer.py — 完整的 SPSS 交叉分析引擎。

依赖

pandas, openpyxl, scipy, numpy

可选:pyreadstat(性能更好)。无 pyreadstat 时自动使用内置纯 Python SAV 读取器。

安装依赖:

pip install pandas openpyxl scipy numpy
# 可选
pip install pyreadstat

工作流程

1. 加载数据

import sys
sys.path.insert(0, "<skill_dir>/scripts")
from spss_cross_analyzer import SPSSCrossAnalyzer

analyzer = SPSSCrossAnalyzer()
analyzer.load_spss_file("/path/to/file.sav")

加载后默认去掉前两列(通常为编号/ID),可在脚本中调整。

2. 分析题型(可选但推荐)

results = analyzer.analyze_question_types()
# 或导出 Markdown 分析报告
analyzer.export_analysis_report_markdown("/path/to/report.md")

输出每道题的类型(单选/多选/矩阵/打分/填空/三维矩阵多选)、取值范围、子字段等。 将此报告展示给用户,帮助用户决定列字段和行字段的选择。

3. 选择列字段和行字段(交互决策点)

原始脚本使用 input() 交互,在 Skill 模式下需由 Agent 代替用户做出或引导选择。

列字段选择(交叉表的表头):

# 方式A:自动检测 banner 字段
banner_fields = [col for col in analyzer.df.columns if 'banner' in col.lower()]
if banner_fields:
    analyzer.column_fields = banner_fields
    analyzer._is_banner_column = True
    analyzer.column_question_mapping = analyzer._build_banner_question_mapping(banner_fields)
    analyzer._build_banner_display_info(analyzer._get_banner_groups_ordered())

# 方式B:手动指定字段列表
analyzer.column_fields = ["S1", "S2", "S3"]
analyzer._is_banner_column = False
analyzer.column_question_mapping = analyzer._build_question_mapping(analyzer.column_fields)

行字段选择(交叉的维度,交互决策点):

先列出所有可选行字段(排除列字段后),展示给用户,让用户选择排除哪些。

# 第一步:列出候选行字段(排除列字段)
col_set = set(analyzer.column_fields)
candidate_fields = [c for c in analyzer.df.columns if c not in col_set]

# 第二步:自动识别填空题和元数据字段
open_fields = [c for c in candidate_fields if analyzer._is_open_ended_field(c)]
# 识别元数据字段(取值极多的非问卷字段)
meta_fields = []
for c in candidate_fields:
    if c in set(open_fields):
        continue
    nunique = analyzer.df[c].nunique()
    dtype = str(analyzer.df[c].dtype)
    if dtype == 'object' or nunique > 200:
        meta_fields.append(c)

交互流程(必须严格按此步骤向用户展示和提问):

第一步:展示自动识别的排除列表,让用户确认。

向用户展示以下信息:

📝 自动识别的填空题/开放题(将排除):
  - S8_其他 [S8_xxx_其他]
  - A1_输入文本 [A1_xxx_输入文本]
  ...

⚠️ 建议额外排除的元数据字段:
  - 答题时长 [取值1749种]
  - 用户ID [取值263种]
  ...

以上共 XX 个字段将被排除。

然后提问(用 AskUserQuestion):

  • 选项1: "同意排除"(确认使用上述排除列表)
  • 选项2: "我来调整"(用户要手动增减排除字段)

第二步:如果用户选"我来调整",展示输入规范并等待用户输入。

向用户发送以下说明:

请输入要调整的排除字段,格式说明:
- 添加排除:直接输入字段名或前缀,如 BC_S2, D4, Q89
- 移除排除(保留该字段):在前面加 !,如 !答题时长, !D4_L2
- 多个用逗号分隔
- 前缀匹配:输入 BC 会排除所有 BC 开头的字段(BC_1, BC_2, ...)
- 留空或输入"确认"表示不做调整

示例:"BC_S2, Q89, !答题时长" 表示额外排除 BC_S2 和 Q89,但保留答题时长

等待用户输入后解析:

# 解析用户输入
user_input = "BC_S2, Q89, !答题时长"  # 示例
extra_exclude = set()
remove_exclude = set()

for item in user_input.split(','):
    item = item.strip()
    if not item or item == '确认':
        continue
    if item.startswith('!'):
        remove_exclude.add(item[1:].strip())
    else:
        extra_exclude.add(item.strip())

# 构建最终排除集
exclude_set = set(open_fields) | set(meta_fields)

# 移除用户指定保留的
for rm in remove_exclude:
    exclude_set.discard(rm)
    # 前缀移除
    exclude_set = {f for f in exclude_set if not f.startswith(rm + '_') and f != rm}

# 添加用户指定排除的(支持前缀展开)
for prefix in extra_exclude:
    for c in candidate_fields:
        if c.startswith(prefix + '_') or c == prefix:
            exclude_set.add(c)

row_fields = [c for c in candidate_fields if c not in exclude_set]
analyzer.row_fields = row_fields
analyzer.row_question_mapping = analyzer._build_question_mapping(row_fields)

关键:必须向用户展示将被排除的字段列表,让用户确认或补充。不要静默排除。 用户选"我来调整"时,必须先展示输入格式说明,等用户输入后再继续。

识别题型和配置矩阵模式

analyzer.field_types, analyzer.matrix_groups, analyzer.multi_groups = \
    analyzer._identify_field_types(analyzer.row_fields)

# 矩阵题处理模式:'independent'(每个子题单独表) 或 'merged'(合并为一张表)
analyzer.matrix_mode = 'independent'  # 或 'merged'

三维矩阵多选题处理模式(交互决策点——逐题选择):

三维矩阵多选题(如 D8、Q4-1 等)有三种拆分模式。必须逐道题向用户展示结构并让用户选择

交互流程:

  1. 先打印 len(analyzer.tri_matrix_groups) 和所有 key(注意 key 可能带尾部空格,如 'Q12 ',展示时用 .strip()),确认总数。
  2. 遍历 analyzer.tri_matrix_groups每一个 key,对每道题:
    • 展示该题的字段结构、中间维度、最后维度的含义
    • 让用户选择该题的拆分模式
  3. 校验:展示的题目数量必须等于 len(analyzer.tri_matrix_groups)。如有遗漏,停下来补全。
  4. 分别设置每道题的模式(设置 tri_matrix_mode 时使用原始 key(含空格),不要 strip)

示例展示格式:

🔷 三维矩阵题 D8(15个子字段,5×3 结构):
  标签:最近一年,您在各平台使用客服的方式
  中间维度(5组):携程 / 去哪儿 / 飞猪 / 美团 / 同程
  最后维度(3项):在线客服 / 电话客服 / 两者都用

  请选择此题的拆分方式:
  1. 按最后段拆分 → 生成 3 张子表(在线客服、电话客服、两者都用)
  2. 按中间段拆分 → 生成 5 张子表(携程、去哪儿、飞猪、美团、同程)
  3. 多选模式 → 不拆分,当作普通多选题处理

对每道题用 AskUserQuestion 提问,或在有多道题时先一次性展示所有题目的结构, 然后让用户逐个或批量指定(如"D8,D8-1,D8-2 按中间段;Q4-1,Q11-1 按最后段")。

# 逐题设置
analyzer.tri_matrix_mode = {}  # 题号 -> 模式

# 示例:用户逐题指定的结果
tri_matrix_choices = {
    'D8': 'split_by_mid',      # 按中间段(平台)拆分
    'D8-1': 'split_by_mid',
    'D8-2': 'split_by_mid',
    'Q4-1': 'split_by_last',   # 按最后段拆分
    'Q11-1': 'split_by_last',
}

for question, mode in tri_matrix_choices.items():
    analyzer.tri_matrix_mode[question] = mode
    # 可选: 'split_by_last' / 'split_by_mid' / 'multi'

analyzer._build_row_field_groups()

关键规则:

  • 不能对所有三维矩阵题统一使用同一种拆分模式。每道题的维度含义不同,必须分别展示并让用户选择。
  • 必须展示 tri_matrix_groups 中的全部题目,不得遗漏。展示后核对数量 = len(analyzer.tri_matrix_groups)
  • tri_matrix_groups 的 key 可能包含尾部空格(如 'Q12 ''Q20 '),设置 tri_matrix_mode 时必须使用原始 key,不要 strip。展示给用户时可以 strip。

4. 生成交叉表

analyzer.generate_cross_tables()

5. 配置百分比格式(交互决策点)

# 是否保留百分号
analyzer._keep_percent_sign = True   # True=显示%, False=纯数字

# 小数位数
analyzer._decimal_places = 0         # 0=整数, 1-4=保留小数

6. Grid Summary(交互决策点)

将多道同类题的关键汇总指标压缩到一张交叉表中。必须向用户主动提问是否需要。

交互流程:

  1. 向用户解释 Grid Summary 功能:将多道同类题(如 B9-B14 品牌选择题)的汇总压缩到一张表。
  2. 提问:"是否需要生成 Grid Summary?如果需要,请输入题目编号前缀(用逗号分隔多组,如 B9,B10 或 D8-1)。留空则跳过。"
  3. 如果用户输入了题号,按以下代码匹配字段并配置:
# 用户输入的 Grid Summary 题号组
grid_groups = ["B9", "B10"]  # 示例,由用户提供

all_columns = list(analyzer.df.columns)
for group_prefix in grid_groups:
    matched = analyzer._match_grid_summary_fields(group_prefix, all_columns)
    if not matched:
        print(f"⚠ '{group_prefix}' 未匹配到任何字段,跳过")
        continue
    config = analyzer._analyze_grid_summary_structure(group_prefix, matched)
    if config:
        analyzer.grid_summary_configs.append(config)

if analyzer.grid_summary_configs:
    analyzer._generate_grid_summary_data()
    analyzer._compute_grid_summary_insert_positions()
  1. 如果用户说"不需要"或留空,跳过此步。

7. 导出 Excel

analyzer.create_header_config()
output_path = "/path/to/output.xlsx"
result = analyzer.generate_excel_with_complex_header(output_path, config_path=None)
# config_path 可传入自定义表头配置 JSON

Banner 模式下会自动生成 3 个文件(频数/百分比/显著性),每个 banner 组一个 sheet。

8. 导出表头配置(可选)

analyzer.export_header_config("/path/to/config.json")
# 用户可修改 custom_name、custom_question_name 后通过 --config 参数重新生成

交互决策点汇总

以下节点需要向用户确认或根据数据自动判断:

步骤 决策 默认建议
列字段 使用 banner 还是手动选择 有 banner 字段时自动使用
行字段排除 展示自动排除列表 → 用户确认或调整(展示输入格式说明) 排除填空题+元数据
矩阵题 独立模式还是合并模式 独立模式
三维矩阵 逐题展示维度结构,让用户选拆分模式 按最后段拆分
三维矩阵附加行 是否有选项需要作为附加行 无附加行
百分比格式 百分号+小数位数 保留百分号,不保留小数
Grid Summary 主动提问是否需要,需要则输入题号前缀 不启用

处理策略

  1. 先运行 analyze_question_types() 生成分析报告给用户看。
  2. 逐步向用户提问上述决策点(每个都必须问,不能跳过或静默使用默认值)。
  3. 特别注意:
    • 行字段排除:必须列出将被排除的填空题,问用户是否还需额外排除其他字段。
    • Grid Summary:必须主动问用户是否需要,不能默认跳过不问。
  4. 如果用户明确说"默认"或"自动",才使用上表的默认建议。
  5. 设置完毕后一次性执行生成流程。

输出文件

  • Excel 交叉表:频数表(ABS)、百分比表(%)、显著性表(%sig)
    • Banner 模式:3个 xlsx 文件(_频数/_百分比/_显著性),每个文件包含多个 banner sheet
    • 非 Banner 模式:1个 xlsx 文件,3个 sheet
  • 表头配置 JSON:可修改自定义名称后重新生成
  • Markdown 分析报告:题目类型、数据质量、命名模式等

显著性检验

  • 方法:Z检验 (两比例检验) + Bessel校正
  • 多重比较校正:Benjamini-Hochberg FDR (α=0.05)
  • Banner 模式下按子维度分组进行跨列显著性检验
  • 显著的单元格标注字母标记(如 "56%ab"),黄色高亮

注意事项

  • 前两列默认被去掉(通常是编号/受访者ID),如需保留需修改脚本
  • 填空题/开放题自动排除出行字段
  • Banner 列字段不做行交叉(避免自交叉)
  • 非 Banner 模式下列字段会自动加入行字段(表头题也做行交叉)
  • 打分题自动补全缺失分值(如 0-10 量表中数据缺少某些值也会显示完整行)
Installs
1
First Seen
10 days ago