尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python问卷数据统计实战:pandas清洗与多选频次分析
简介这是一份2024年发布的Python实现调查问卷数据统计资料包面向数据分析初学者、产品运营及市场调研人员也可用于课程设计或内部培训。内容围绕问卷数据的清洗、分类汇总、频次分析与可视化展示等常见环节展开覆盖单选题、多选题及量表题的处理思路帮助用户掌握从原始答卷到统计结果的完整流程。压缩包共129个文件大小约185.63MB主要包含pptx演示文稿、doc/docx文档、md笔记、pdf说明和xlsx表格并附有Python脚本与示例图片便于对照学习、改造复用。已有208人学习下载整体内容兼具教学讲解与实操代码。资料中还收录了软件工程标准、测试用例编写规范、软件设计规格说明书等配套文档适合在规范流程下推进数据统计任务目录分类较清晰基本覆盖项目准备、方案设计到结果输出的关键资料。1. Python调查问卷数据统计洗净数据、算清多选、交付一份能用的报告2024年了收到的问卷导出表最常见的依然是几百行Excel里塞着“非常满意|满意|一般”这类文本以及平台把多选题拆成五六个布尔列的“中国特色”格式。手动用透视表做统计一次两次还行题目一多、样本一涨人就成了大型机器。Python做调查问卷数据统计核心就三件事把脏数据洗干净、按单选和多选分别算频次、把结果变成图和表。这篇文章不聊框架也不上复杂模型就讲怎么用pandas把统计跑出来踩过哪些雷也一并交代。适合手里攥着问卷导出表、要交统计结果但不想重复劳动的人。2. 先拿到能统计的数据问卷导出表的清洗与规范化2.1 为什么先统一列名和值域再谈统计多数问卷平台导出的Excel表头往往带着题号比如“Q1.您的性别”“Q4_2.您选择以下哪些渠道”。直接拿这些列名写统计代码十几个题还行三十个题一过就分不清谁是谁。我一般先在脚本里打印列名人工把需要的题改成短名字再往下做。不要嫌这一步麻烦统计翻车几乎都从列名和值域不统一开始。import pandas as pd # 读取问卷导出表强制按字符串读入避免手机号、日期被自动转换 df pd.read_excel(survey_export.xlsx, header0, dtypestr) print(shape:, df.shape) print(columns:) for idx, col in enumerate(df.columns): print(idx, col)这里header0表示第一个非空行作为列名。如果平台在表头前加了两三行说明就要把header参数改成对应的行号。dtypestr是常用的保险写法读进来后所有列都是字符串之后再按业务逻辑转类型这能防止选项编码、手机号、日期被pandas自作主张改成float或datetime。拿到列名后我习惯建一个映射字典把别扭的题目名改成语义明确的短名字rename_map { Q1.您的性别: gender, Q2.您的年龄段: age_group, Q3.您的满意度: satisfaction, Q4.您常使用的功能多选: features, Q5.您提交的建议: suggestion, } df df.rename(columnsrename_map) print(df[[gender, age_group, satisfaction]].head())如果原始列名已经混乱到没有规律临时用位置索引比如df.columns[2]也能跑但不建议长期依赖。平台更新一次导出模板列顺序一变你的脚本就得跟着重改。统一列名的过程本质上是把“平台导出的结构”翻译成“统计脚本能理解的结构”。2.2 缺失值、乱码值和“未选”怎么处理问卷平台对未答题目的导出记号很不统一有的是空单元格有的塞一个空格有的写“(空)”有的用“未填”。如果直接统计这些符号都会变成独立的“选项”饼图里会多出一堆莫名其妙的分类。missing_tokens [, , (空), 未填, NULL, NA] # replace会把列表里的每一个值都替换为pd.NA df df.replace(missing_tokens, pd.NA) # 查看每列的缺失占比用来决定哪些题适合继续分析 missing df.isna().mean().sort_values(ascendingFalse) print(missing)pd.NA是pandas推荐的缺失标记后面用dropnaFalse可以把缺失单独列出来看。不要一上来就dropna整行问卷有人跳过一两题很常见整行删除会白丢样本。平台偶尔会把“其他”选项的文本写到“其他_文本”这类附加列里清洗时要把这种附加列和主选项分开不要混进单选统计。除了缺失还要防全角空格和不可见字符。中文问卷里经常混入全角空格导致“非常满意”和“非常满意 ”被当成两个选项。常见做法是统一去掉首尾空格for col in [gender, age_group, satisfaction]: df[col] df[col].str.strip()str.strip()删掉的是首尾空白不会动选项内部的空格。处理完后再跑一次value_counts(dropnaFalse)看一眼还剩下哪些奇怪的值。这一步是最值的投资能省掉后面无数个图表排错的夜晚。2.3 多选题的两种存储方式与标准化多选题是问卷统计的第一个分水岭。有的平台把每个选项单独做成一列值填0/1也有的平台把勾选项用竖线拼在一列里比如“功能A|功能C”。两种格式都得标准化统计口径才稳。对于“一列多值”的格式我通常写一个小函数拆成列表def parse_multi(row_value): 把竖线分隔的多选内容拆成列表空值返回空列表 if pd.isna(row_value): return [] # 去掉头尾空格后再按竖线拆 return [x.strip() for x in str(row_value).split(|) if x.strip()] df[features_list] df[features].apply(parse_multi) print(df[features_list].head())split(|)之后每个元素可能有空格所以接一个strip()。if x.strip()把空字符串过滤掉避免出现逗号或空选项。这里返回的是列表对象不要试图用apply直接生成多列后续统计时用Counter展开更方便。对于“多列每列一个选项”的格式先确认每列的值是不是只有0和1。平台偶尔会用“是/否”或者“True/False”要先统一bool_cols [c for c in df.columns if c.startswith(features_)] for c in bool_cols: df[c] df[c].replace({是: 1, 否: 0, True: 1, False: 0}) df[c] pd.to_numeric(df[c], errorscoerce).fillna(0) print(df[bool_cols].head())errorscoerce会把没法解析成数字的文本变成NaN再用fillna(0)补成0。这样做的好处是异常值不会悄悄混进统计结果。如果你想知道到底有多少异常值可以把coerce前后的列做一下差异对比比如df[c].isna().sum()。等到确认值域只含0和1再继续做交叉分析。3. 频次统计与交叉分析单选题、多选题和分组对比的参数选择3.1 单选题频次统计value_counts排序会打乱量表顺序单选题统计本身不复杂但有两个坑。第一value_counts默认按频次降序“非常满意”这种高频选项会排在最前面报告里想按量表顺序展示反而乱。第二dropna参数默认是False缺失值会被静默丢掉你根本不知道有多少人跳过这题。col satisfaction order [非常不满意, 不满意, 一般, 满意, 非常满意] freq df[col].value_counts(dropnaFalse) pct df[col].value_counts(normalizeTrue, dropnaFalse) * 100 stat pd.DataFrame({人数: freq, 占比%: pct.round(2)}) # 缺失值在占比里已经出现但需要单独标记 stat stat.reindex(order).fillna(0) stat.loc[(缺失)] int(df[col].isna().sum()) print(stat)value_counts(normalizeTrue)返回的是比例乘100后变成百分比。dropnaFalse让NaN也参与计数否则你就看不到被跳过的样本量。这里最容易写错的是normalizeTrue会忽略dropnaFalse的交互当dropnaFalse时NaN会计入总数所以占比是“占全部样本”的比例而不是“占有效样本”的比例。如果报告里需要区分可以在计算前先筛掉NaN或者把缺失单独列一行。reindex(order)按指定顺序排列顺序里没出现的选项会得到NaN用fillna(0)填0。这样“无人选择”和“数据缺失”不会混在一起。最后一行手动加(缺失)这比默认的NaN直观得多。3.2 多选题统计的两种口径按人次与按人数多选题的统计必须分清两个概念选项被选了多少次和多少人选了它。比如“功能A”被选了300次但其中200次来自100个人重复选这对统计“普及率”没有意义。平台一般不允许多选里重复勾同一项但导入手工录入数据时可能出现不去重会把人数虚高。from collections import Counter all_lists df[features_list].dropna().tolist() # 把所有列表展平成一个长列表 all_items [item for sublist in all_lists for item in sublist] # 被选次数一个人选了两次也计入两次 times_count Counter(all_items) # 人数按人先去重再统计 person_lists df[features_list].apply(lambda x: set(x)).dropna().tolist() person_count Counter(item for sublist in person_lists for item in sublist) summary pd.DataFrame({ 被选次数: times_count, 选择人数: person_count, }) summary.fillna(0, inplaceTrue) print(summary)all_lists先丢掉缺失值避免空列表混进来。展平列表用双重循环这是Python里最直接的写法。set(x)会去掉每个样本内部的重复选项所以person_count统计的是“多少人至少选了一次”。最终表格里两个口径要同时呈现否则读者分不清你是按人次还是按人数。如果问卷平台是0/1多列可以先sum()得到人次再gt(0).sum()得到人数multi_cols [c for c in df.columns if c.startswith(features_)] choice_count df[multi_cols].sum() # 人次 person_count (df[multi_cols] 0).sum() # 人数这个写法更短但前提是列里确实只有0和1。出现2或者其他值时gt(0)会把异常值也当成选中所以清洗阶段的值域检查不能省。3.3 交叉分析crosstab与pivot_table的取舍交叉分析最常见的场景是“性别×满意度”看不同性别内部的满意比例。pandas里crosstab专门做频数交叉pivot_table则适合算均值、求和等汇总统计。区别在于crosstab不需要指定聚合函数pivot_table必须给aggfunc。ct pd.crosstab(df[gender], df[satisfaction], dropnaFalse) print(ct) # 行百分比用于对比不同性别内部的比例 row_pct ct.div(ct.sum(axis1), axis0) * 100 print(row_pct.round(2)) # 总百分比 total_pct ct / ct.values.sum() * 100 print(total_pct.round(2))dropnaFalse在这里很关键crosstab默认会把带NaN的行列剔除导致结果表里少了“跳过性别”或“跳过满意度”的人群。如果只想看有效样本就显式设置dropnaTrue并在报告里注明筛选条件。ct.div(ct.sum(axis1), axis0)算行百分比时axis0代表按行对齐。如果把axis写成1会按列除结果就变成每列内部的比例含义全变。写完后建议打印几行检查比如看每一行是否加起来约等于100。如果问卷里有李克特量表题比如满意度映射成1到5分用pivot_table更合适score_map {非常不满意: 1, 不满意: 2, 一般: 3, 满意: 4, 非常满意: 5} df[score] df[satisfaction].map(score_map) pv pd.pivot_table(df, valuesscore, indexgender, aggfuncmean) print(pv.round(2))pivot_table的values必须是数值列index支持多级索引aggfunc还能传列表比如aggfunc[mean, count]一次算均值又算样本量。注意map时如果遇到没匹配上的选项默认得到NaN这正好帮我们发现数据里是否存在意料之外的文本。4. 让统计结果变成报告图表字体、Excel样式与Word输出4.1 matplotlib中文字体注册字体文件比改rcParams更稳问卷统计至少要有饼图或条形图而matplotlib默认字体没有中文字符图一出来全是方块。网上最常教的是plt.rcParams[font.sans-serif][SimHei]这在本地Windows有效但换到公司内网服务器或同学电脑上字体不存在还是会翻车。更稳的写法是先确认系统中文字体路径或直接注册一个字体文件进来。import matplotlib.pyplot as plt from matplotlib import font_manager # 常见中文字体路径按需改成自己系统的实际路径 font_path /usr/share/fonts/truetype/wqy/wqy-microhei.ttc # Linux常见 # 或者使用 Windows 路径C:/Windows/Fonts/msyh.ttc if font_path: font_manager.fontManager.addfont(font_path) prop font_manager.FontProperties(fnamefont_path) plt.rcParams[font.sans-serif] [prop.get_name()] plt.rcParams[axes.unicode_minus] False如果找不到字体文件可以先用font_manager.fontManager.ttflist列出所有已注册字体再挑一个含中文的。用addfont方式的好处是即便系统没有预装只要把ttc文件放到工程目录下脚本也能稳定工作。axes.unicode_minusFalse必须写否则坐标轴上的负号会显示成方块。画图本身不复杂关键是把统计结果传进去import matplotlib.pyplot as plt plt.figure(figsize(8, 5)) plt.bar(stat.index, stat[人数], color#4C72B0) plt.title(满意度分布) plt.xticks(rotation30) plt.tight_layout() plt.savefig(满意度分布.png, dpi200)tight_layout()能避免标题和坐标轴被裁掉。dpi200适合直接贴进Word或PPT太低会糊。4.2 用openpyxl给统计表加格式让数值表直接可发pandas的to_excel能把DataFrame写进Excel但默认没有任何格式。交给别人的统计表里表头要加粗列宽要调关键数字可以标色。常见做法是先用pandas写原始表再用openpyxl加载改样式。from openpyxl import load_workbook from openpyxl.styles import Font, PatternFill stat.to_excel(统计结果.xlsx, sheet_name单选统计, indexTrue) wb load_workbook(统计结果.xlsx) ws wb[单选统计] # 表头加粗并填充浅灰色 for cell in ws[1]: cell.font Font(boldTrue) cell.fill PatternFill(start_colorF2F2F2, end_colorF2F2F2, fill_typesolid) # 设置A列宽度避免中文标题被截断 ws.column_dimensions[A].width 20 for col_letter in [B, C]: ws.column_dimensions[col_letter].width 12 wb.save(统计结果.xlsx)to_excel的indexTrue表示保留DataFrame索引这正好把选项名写在A列。如果不想要索引就把indexFalse再用df.to_excel把索引转成普通列。openpyxl只能加载xlsx不能加载旧的xls如果你的文件是旧格式先用pandas转一次。4.3 生成Word版报告python-docx适合交付有人要Excel有人要Word。Word报告生成不需要自己排版python-docx可以把结论、表格、截图按顺序拼出来。关键是用add_table时先按列数建空表再逐行填值。from docx import Document doc Document() doc.add_heading(2024年问卷统计报告, level1) doc.add_paragraph(本次共回收有效问卷 {} 份。.format(len(df))) # 把单选题统计表写入Word table doc.add_table(rows1, cols3) table.style Light Shading Accent 1 hdr table.rows[0].cells hdr[0].text, hdr[1].text, hdr[2].text 选项, 人数, 占比 for option, row in stat.iterrows(): cells table.add_row().cells cells[0].text option cells[1].text str(int(row[人数])) cells[2].text f{row[占比%]:.1f}% doc.save(问卷统计报告.docx)每次add_row()都会返回一行新单元格需要先把选项名转成字符串否则非字符串类型会报错。Light Shading Accent 1是python-docx内置表格样式如果不想依赖样式名可以删掉style行用默认样式。截图可以先用doc.add_picture(满意度分布.png)插入再紧跟着写一段解读文字。5. 问卷数据统计的5个常见坑从数据错位到中文乱码的排查手册问卷统计的坑集中在三个环节读数据时类型被改、多选题被当成单选、图表中文乱码。下面5条是我反复踩过的每条按“现象→原因→解决”记录。5.1 选项顺序错乱value_counts不按量表顺序排现象条形图出来“一般”在最前面“非常满意”在最后报告顺序看着很别扭。原因value_counts默认按频次降序文本列不会按报表定义排序。解决用显式order列表重排或者把列转成pd.Categorical并指定categories顺序。这样图标和交叉表都会跟随这个顺序。cat pd.Categorical(df[satisfaction], categories[非常不满意, 不满意, 一般, 满意, 非常满意], orderedTrue) df[satisfaction_cat] cat print(df[satisfaction_cat].value_counts(dropnaFalse))排查时先看一眼当前列的所有取值确认没有不可见空格print(df[satisfaction].astype(str).unique())如果打印出来看到“非常满意 ”这样的带空格值说明清洗阶段的strip()没生效需要回头处理。别小看这个问题它能让reindex后的缺失选项变成0直接覆盖掉真实数据。5.2 多选题漏算直接把长文本丢给value_counts现象某个选项统计人数比平台后台少很多而且少掉的都是选了多个选项的人。原因整列value_counts统计的是整条字符串没有拆开。比如“功能A|功能C”会被当成一个独立选项不会被计入“功能A”和“功能C”。解决先split再Counter统计人数前用set去重。排查时先看这列的样本长什么样print(df[features].dropna().head(10)) # 再看拆分后的列表是否包含所有预期选项 from collections import Counter all_items [item for sublist in df[features_list] for item in sublist] print(set(all_items))如果发现“功能A”和“功能A ”同时存在说明分隔符前后的空格没处理干净。如果发现“功能C”完全没有出现可能是这个选项只在某个少见分隔符后面比如用了“、”而不是“|”。最省事的办法是把所有可能的分隔符统一替换成“|”再做split。5.3 量表均值算错负值和0混进评分现象满意度平均分算出2.3但分发问卷的人说结果没那么差。原因平台把“未作答”记为-1把“跳过”记为0这些值参与了mean()运算。你看到的平均分是被无效负值拉低的。解决先用pd.to_numeric(errorscoerce)把非数值转NaN再对分数范围做约束。df[score_num] pd.to_numeric(df[score], errorscoerce) df.loc[~df[score_num].between(1, 5), score_num] pd.NA print(df[score_num].mean())排查时先看value_counts有哪些奇怪值print(df[score].value_counts(dropnaFalse).sort_index())如果看到-1、0或者6这些都要统一处理。between(1, 5)会筛掉所有不在量表范围内的值。注意errorscoerce本身会丢掉无法转成数字的文本但不会丢掉合法的-1所以约束条件必须自己写。5.4 手机号变科学计数法日期变成一串秒现象导出表里手机号列显示成9.18E10日期列变成datetime带时分秒统计时一排序就乱套。原因pandas的read_excel会自动推断类型长数字被当成浮点日期被当成时间戳。解决读文件时用dtypestr这是成本最低的后悔药。df_raw pd.read_excel(问卷.xlsx, dtypestr)如果已经用错误类型读进来了再想恢复长整型手机号很难因为浮点精度已经把尾数改掉了。所以我的习惯是只要是从问卷平台导出的表一律dtypestr读入后续按列转类型。日期列如果确实需要分析再用pd.to_datetime(df[提交时间], format%Y-%m-%d %H:%M:%S)转换。5.5 中文字体在不同机器上消失现象本机画图正常同一段脚本放到服务器跑输出的png全是方块。原因服务器没有安装中文字体matplotlib找不到可用的字体文件。解决不要只依赖系统已有字体用font_manager.addfont指定随脚本带的字体文件或者先装好wqy-microhei。排查时列出所有已注册字体看哪些含中文from matplotlib import font_manager names {f.name for f in font_manager.fontManager.ttflist} print([n for n in names if Hei in n or YaHei in n or WenQuanYi in n])如果列表为空就去下载一个开源中文字体文件放到工程目录再用addfont注册。这一步做完脚本换到哪台机器都不会再因为字体翻车。6. 把统计脚本变成可复用的模板函数封装与批处理技巧6.1 用配置字典驱动统计函数如果每次拿到新问卷都重新写一遍清洗和统计那前面节省的时间又会被浪费。常见做法是把统计逻辑封装成函数用配置字典驱动。这样换问卷时只改配置不动代码。def single_freq(series, orderNone): 单选频次统计按给定顺序输出 freq series.value_counts(dropnaFalse) pct series.value_counts(normalizeTrue, dropnaFalse) * 100 stat pd.DataFrame({人数: freq, 占比%: pct.round(2)}) if order: stat stat.reindex(order).fillna(0) return stat def multi_expand(series, splitter|): 把多选文本拆成列表 return series.apply( lambda x: [i.strip() for i in str(x).split(splitter) if i.strip()] if pd.notna(x) else [] )使用示例single_config { satisfaction: [非常不满意, 不满意, 一般, 满意, 非常满意], gender: [男, 女, 其他], } for name, order in single_config.items(): result single_freq(df[name], order) result.to_excel(f{name}_统计.xlsx)把清洗步骤放在主流程统计函数只接收已经干净的Series这样每个函数只干一件事出错时也容易定位。6.2 这套模板的边界在哪里这套模板能解决频次和交叉统计但不解决主观题文本聚类、量表加权、信度效度计算。如果问卷里大量开放题需要另上NLP如果涉及李克特量表先读一点心理测量学别直接按均值排名。批量统计时还要注意不同批次问卷的题号可能不同配置字典里的列名要仔细核对。我之前有次拿平台导出的原始表直接跑了均值结果被某导师一句“这个负分哪来的”问住翻车之后才认真写了前面的清洗前置检查。现在拿到任何问卷导出表第一件事永远是先打印shape、columns和value_counts确认没有脏值再往下走。做问卷统计最值钱的部分不是pandas函数而是你愿不愿意花十分钟确认数据没被平台“预加工”过。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

CAD/CAE工业智能体:从侧边栏到内核级嵌入的技术架构与落地实践

CAD/CAE工业智能体:从侧边栏到内核级嵌入的技术架构与落地实践

1. 从侧边栏困局说起:工业智能体到底该长什么样CAD 和 CAE 这两个词,干过制造业的人都不陌生。CAD 负责画图建模,CAE 负责仿真验证,一个管“长什么样”,一个管“扛不扛得住”。过去三十年,这两类软件的核心…

📅 2026/10/12 16:09:28
开源安全工具怎么选怎么用:从入口仓库到落地部署指南

开源安全工具怎么选怎么用:从入口仓库到落地部署指南

简介:《探索hack仓库入口Hack91:网络安全开源项目的宝藏》是一份面向网络安全学习者、渗透测试爱好者与安全评估人员的图文笔记,系统梳理了Hack91项目的来龙去脉与核心价值。文档从诞生背景与创始团队讲起,说明该项目在SQL注入、X…

📅 2026/10/12 16:09:28
安全隔离与信息交换系统Web界面配置指南:从策略下发到日志审计

安全隔离与信息交换系统Web界面配置指南:从策略下发到日志审计

简介:天融信安全隔离与信息交换系统V3版Web界面安装使用手册,面向网络安全运维人员、系统集成工程师及负责内外网隔离部署的技术人员。该手册针对隔离设备在保证内外网物理隔离前提下实现适度数据交换的部署与运维需求,系统讲解设备从硬件上架…

📅 2026/10/12 16:09:28
MORE NEWS

更多资讯

📰

Mangos服务端改端利器:物品、任务、BOSS编辑器实战详解

简介:这是一款面向Mangos魔兽世界模拟器服务端开发与维护者的数据编辑工具包,用于解决物品、任务、BOSS、NPC等核心数据手工修改数据库时繁琐易错的问题。压缩包共66个文件,大小仅1.63MB,内含主程序及依赖库,并配有55个…

📰

VC中集成SQLite:从编译到增删改查的避坑指南

简介:本资源面向在Visual C环境下进行桌面开发的程序员,聚焦SQLite嵌入式数据库的集成与实战应用,帮助解决VC项目中数据库连接、大批量数据快速插入以及查询结果在ListCtrl控件中展示等常见问题。压缩包共61个文件,约32.07MB&…

📰

模拟传输课件精讲:从AM到FM的调制主线与教学技巧

简介:这是一份电子科技大学《通信原理》(李晓峰版)第3章“模拟传输”的讲课课件,适合通信工程、电子信息类学生及考研复习者梳理模拟通信系统核心框架,也可作为教师备课参考。内容完整覆盖模拟通信系统组成、信号模型、…

📰

关闭不必要的网络服务:提升安全访问与网络稳定性的实操指南

1. 从一台“越跑越慢”的办公终端说起前阵子帮一个朋友处理他工作室里的网络问题,现象很典型:十几台办公终端,白天用着用着就开始卡,网页打开转圈,内部文件共享时断时续,重启路由器能好一阵,过几…

📰

Python抓取A股个股与指数历史数据:复权处理与增量更新实战

1. 数据抓取这件事,为什么值得认真做一遍做量化研究、写复盘工具、跑回测策略,甚至只是想在本地建一个自己的行情数据库,第一步永远绕不开同一件事:把历史数据拿到手。A股市场的数据获取渠道看起来不少,但真正用起来就…

📰

5G信令分析实战:从抓包到注册失败定位的排查指南

简介:这份5G信令分析指导书以docx文档形式整理,面向5G网络优化工程师、通信专业学生及从事信令排查的一线网优人员。内容按开机入网、上下文管理、会话管理、寻呼流程、切换流程、NAS流程六大模块展开,系统覆盖小区搜索与选择、系统消息广播、…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬