拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python问卷数据分析与Word报告自动化:大学生网购调查实战

简介这份《大学生网上购物情况调查报告》是一份面向高校学生、市场调研学习者及消费行为研究者的实证资料文档可用于课程作业、社会调查方法练习或校园消费分析参考。文档以问卷调研为主线围绕大学生网购的年级分布、店铺搜寻路径、网店选择要素、常用购物平台、商品类别偏好、下单频率以及购物安全性等维度展开附有统计表格与柱状图呈现随机抽取30个样本、回收28份有效问卷后的分析过程。资源包仅含1个doc文件压缩包大小约1.49MB打开即可查阅完整调查框架与结论便于直接引用数据或模仿其问卷设计与分析写法。目前已有86人学习适合需要快速获取大学生网购行为调研素材、撰写调查报告或进行课堂展示的读者参考。1. 先想清楚这份 .doc 到底要交付什么课程作业里出现「大学生网上购物情况调查报告.doc」这个文件名通常意味着两天后要交一份带图表、有结论、格式像样的 Word 文档。真正让人熬夜的不是问卷没人填而是回收了四百多份答卷之后用 Excel 一格一格数频次改一个口径就要把所有图表重画一遍。所以这件事的正确姿势不是「写报告」而是搭一条从原始答卷到 Word 文档的流水线字段命名规范 → 清洗规则 → 指标口径 → 统计检验 → 自动排版。数据换一批报告一分钟重生成。做这件事的人有三类交课程报告的学生、做校园项目立项调研的团队、以及需要给业务方出问卷结论的产品岗。下面按采集、清洗、分析、成文、校验五个环节讲透每一段代码都能直接跑。2. 问卷设计与数据采集让答卷能直接进 pandas2.1 题目类型与字段命名决定后期能不能自动化一份合格的大学生网购问卷通常 1825 题覆盖网购平台偏好、常购品类、月均消费、支付方式、促销敏感度、物流与退货体验、信息获取渠道再加性别、年级、月生活费这几项背景变量。多数人栽在第一步列名直接用中文题干于是后面每一行分析代码都变成df[您平均每月的网购消费金额大约是]题干改一个字整份脚本全线报错。常见做法是在设计阶段就定死一套短字段名导出后立刻重命名图表标题再从字段名字典里取。字段名对应题干题型取值示例q1_gender性别单选男 / 女q2_grade年级单选大一 / 大二 / 大三 / 大四 / 研究生q3_monthly月均网购金额数值填空03000q5_platform常用购物平台多选淘宝|京东|拼多多|抖音q8_promo促销敏感度五级量表15q12_duration作答时长系统字段秒量表题要保持方向一致。如果问卷里混了「我更愿意等大促再买」和「我不在意价格波动」这种反向题清洗时必须先把反向题的 15 翻成 51否则算出来的均值毫无意义。我的习惯是在字段名后缀标注方向比如q8_promo_pos、q9_price_neg翻转过一次之后代码里一眼能看出来。2.2 样本量与发放渠道384 份不是拍脑袋在 95% 置信水平、±5% 误差的常规要求下简单随机抽样需要 384 份有效样本考虑到秒答、重复提交这类无效答卷通常占 10%20%发放量按 480520 份准备比较稳妥。渠道上班级群、社团群、宿舍楼栋群各投一部分避免样本全部集中在同一个学院——工科和文科的消费结构差异足够大样本偏了后面卡方检验算出来的显著性其实是抽样偏差带来的假象。回收结束后做一次结构比对把样本的性别、年级分布和学校公开的在校生结构放在一起看偏差超过 10 个百分点就要在报告的「样本说明」里写清楚局限。这一步看起来像形式实际上是让结论站得住脚的关键。2.3 从平台导出 CSV 并用 pandas 做首轮体检问卷平台一般都能导出 CSV 或 Excel。CSV 中文乱码基本是编码问题先按utf-8-sig读失败再退回gbk。读进来之后不要急着统计先做一次结构体检。import pandas as pd # dtypestr 先全部按字符串读避免平台把「1」当成数字、把长数字 ID 变成科学计数法 raw pd.read_csv(answers.csv, encodingutf-8-sig, dtypestr) print(形状:, raw.shape) # 行数答卷数列数题目数 print(缺失比例前 10:) print(raw.isna().mean().sort_values(ascendingFalse).head(10)) # 多选题的分隔符各平台不同先抽 3 行看一眼实际长什么样 for col in [q5_platform, q6_category]: print(col, raw[col].dropna().head(3).tolist()) # 数值题确认能被转换转换失败的数量就是脏数据量 monthly pd.to_numeric(raw[q3_monthly], errorscoerce) print(金额无法解析:, monthly.isna().sum(), 最大值:, monthly.max())dtypestr是关键它保住了两类容易出事的数据手机号、学号这类长数字以及平台把「1」和「1.0」混写的数值列。errorscoerce把无法解析的值转成 NaN 而不是抛异常这样你能先看到脏数据有多少再决定是删还是回填。多选题那三行样本必须人工看一眼分隔符是竖线、逗号还是全角分号直接决定split()里写什么。3. 数据清洗与指标口径把原始答卷变成分析表3.1 无效答卷的四条判定规则清洗不是把空值删掉就完事要有明确规则、可解释的损失率。我一般固定用四条按顺序执行每一步都打印剩余样本量。规则判定条件处理方式典型损失重复提交同一提交标识多次出现按提交时间保留首条3%8%秒答作答时长 60 秒删除5%15%直线作答5 道以上量表题答案方差为 0删除2%5%逻辑矛盾年级与年龄、生活费与消费额明显冲突删除1% 以内阈值不是死的。60 秒这个数要看题量25 题的问卷正常人读题加点击至少 100 秒所以 60 秒已经是宽松下限题量更大时可以直接提到 90 秒。直线作答的判定要排除那些只有两三道量表题的问卷否则误杀率会很高。缺失值分两类处理性别、年级这类背景变量缺失的整条删除因为它们是分组变量单品类的偏好题缺失则按题剔除不要对整表dropna()否则四百份样本可能只剩两百多份。3.2 多选拆分与金额分箱的口径设计多选题是口径事故高发区。一份平台偏好题的答案长这样淘宝|京东|拼多多。拆分之后统计占比分母到底是「人数」还是「选项被勾选的总次数」直接决定数字对不上。正确口径是选择率 勾选该项的人数 ÷ 有效样本数这样各项加起来会超过 100%符合多选题的常识。月均消费金额这种连续变量报告里通常要分箱100 元以内、100300、300500、5001000、1000 元以上。分箱边界必须在写代码之前定死并写进报告不要中途改——改了就得重跑全部交叉表和图表。3.3 清洗脚本从 answers.csv 到 clean.csvimport pandas as pd import numpy as np raw pd.read_csv(answers.csv, encodingutf-8-sig, dtypestr) n0 len(raw) # 1 去重同一 openid 只留最早一条 raw[submit_time] pd.to_datetime(raw[submit_time], errorscoerce) raw raw.sort_values(submit_time).drop_duplicates(subset[openid], keepfirst) n1 len(raw) # 2 时长过滤转换失败按无效处理 raw[duration] pd.to_numeric(raw[q12_duration], errorscoerce) clean raw[raw[duration] 60].copy() n2 len(clean) # 3 直线作答量标题之间的方差为 0 即视为无效 scale_cols [c for c in clean.columns if c.startswith(q8) or c.startswith(q9)] clean[scale_cols] clean[scale_cols].apply(pd.to_numeric, errorscoerce) clean clean[clean[scale_cols].var(axis1) 0] n3 len(clean) # 4 金额分箱rightFalse 表示左闭右开避免 300 落在两个区间里 clean[monthly] pd.to_numeric(clean[q3_monthly], errorscoerce) bins [0, 100, 300, 500, 1000, np.inf] labels [100以内, 100-300, 300-500, 500-1000, 1000以上] clean[monthly_bin] pd.cut(clean[monthly], binsbins, labelslabels, rightFalse) clean.to_csv(clean.csv, indexFalse, encodingutf-8-sig) print(f原始 {n0} → 去重 {n1} → 时长 {n2} → 直线 {n3}有效率 {n3/n0:.1%})最后那行打印是整个脚本里最值钱的输出。有效率低于 70% 时报告里的样本量描述要重新斟酌结论的推广范围也要收紧。rightFalse这个参数容易被忽略默认rightTrue会让 300 同时落进两个区间交叉表里出现重复计数排查起来非常费时间。4. 统计与可视化交叉表、卡方检验和中文图表4.1 频数与交叉表先把分布算清楚单变量频数用value_counts(normalizeTrue)拿到占比但注意多选列不能直接这么用——它统计的是整段字符串不是拆分后的选项。多选要先拆分再算选择率。# 多选先炸开再按人数算选择率分母是有效样本数而不是选项总次数 multi clean[q5_platform].str.split(|).explode() platform_rate multi.value_counts() / len(clean) print(platform_rate.sort_values(ascendingFalse).round(3)) # 交叉表年级 × 月消费区间normalizeindex 让每行加起来是 100% ct pd.crosstab(clean[q2_grade], clean[monthly_bin], normalizeindex) print((ct * 100).round(1))normalizeindex按行归一适合看「每个年级内部的消费结构」换成columns按列归一看的是「每个消费区间里各年级的占比」。同一张交叉表两种归一方式结论完全不同报告里配图时必须注明用的是哪一种。4.2 卡方检验判断差异是否显著「大三学生的月均消费明显高于大一」这种结论不能靠看图说话。用卡方独立性检验给出 p 值再配一句人话解释。from scipy.stats import chi2_contingency ct pd.crosstab(clean[q2_grade], clean[monthly_bin]) chi2, p, dof, expected chi2_contingency(ct) print(f卡方值{chi2:.2f} 自由度{dof} p{p:.4f}) # 期望频数检查超过 20% 的单元格期望值小于 5 时检验结果不可靠 bad (expected 5).mean() print(f期望频数小于 5 的单元格占比: {bad:.1%})expected这个返回值大多数人直接丢掉了但它是判断检验能不能用的唯一依据。经验规则是期望频数小于 5 的单元格超过 20%或任何一个单元格小于 1就得合并类别比如把「1000 元以上」并进「5001000」再用卡方。2×2 表在小样本下可以直接改用scipy.stats.fisher_exact它不依赖大样本近似。4.3 matplotlib 中文图表与批量出图中文方块是必踩的坑根源是默认字体不含中文字形。另外脚本里必须用Agg后端否则在无图形界面的服务器上会直接报错。import matplotlib matplotlib.use(Agg) # 无显示环境下必须指定避免弹窗报错 import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # Linux 换成 Noto Sans CJK SC plt.rcParams[axes.unicode_minus] False # 负号不显示成方块的开关 def save_bar(series, title, path, dpi200): series: value_counts 结果; path: 输出 png 路径 fig, ax plt.subplots(figsize(6, 4)) series.plot(kindbar, axax, color#4472C4) ax.set_title(title, fontsize12) ax.set_ylabel(人数) for i, v in enumerate(series.values): # 柱顶标数值省得读者估高度 ax.text(i, v, str(v), hacenter, vabottom, fontsize9) fig.tight_layout() fig.savefig(path, dpidpi) plt.close(fig) # 不关闭会在批量出图时吃满内存 save_bar(clean[q5_platform].str.split(|).explode().value_counts(), 常用购物平台分布, fig_platform.png)dpi200是为 Word 准备的——插入文档后按 100% 缩放200 dpi 的图边缘依然清晰96 dpi 的图放大就糊。figsize决定宽高比控制在 3:2 左右最适配正文页宽。plt.close(fig)一定要写批量生成十几张图时缺这一行内存会一路涨到进程被杀。5. 用 python-docx 把分析结果写进 .doc 报告5.1 模板文档法样式交给 Word内容交给代码python-docx 只能写.docx读不了也写不了老式.doc。交付时要求.doc的话做法是先出.docx再用 Word 另存为.doc或者用 LibreOffice 命令行转换。别指望用库直接生成二进制.doc那条路性价比极低。内容组织上有两种写法纯代码从空文档开始搭或者先手工做一个template.docx把页眉、标题样式、字号、行距调好代码只往里填内容。第二种在课程报告场景下更省事因为格式要求往往写得很细宋体小四、1.5 倍行距、章标题黑体三号在 Word 里调样式比在代码里调快得多。from docx import Document from docx.shared import Pt, Inches from docx.oxml.ns import qn doc Document(template.docx) # 用模板继承样式避免每段单独设字体 # 中文字体要单独设 eastAsia只设 font.name 对中文不生效 style doc.styles[Normal] style.font.name Times New Roman style.font.size Pt(12) rpr style.element.get_or_add_rPr() rpr.get_or_add_rFonts().set(qn(w:eastAsia), 宋体) doc.add_heading(二、数据与分析, level1) doc.add_paragraph(f本次调查共回收答卷 {n0} 份经清洗后有效样本 {n3} 份 f有效率 {n3/n0:.1%}。样本构成如下表所示。)qn(w:eastAsia)是整段代码里最容易漏的一行。只设font.name英文和数字会变成你指定的字体中文仍然是默认字体打印出来一眼能看出不统一。5.2 插入表格、图片与图表题注正文里最占篇幅的是表格。数据框转 Word 表格要手动控制行列注意不要直接把索引塞进去。def add_df_table(doc, df, index_name项目, float_fmt{:.1%}): 把 DataFrame 写成 Word 表格首行表头首列当行标签 t doc.add_table(rows1, colslen(df.columns) 1) t.style Table Grid # 中文字体环境下比 Light Grid 系列稳 hdr t.rows[0].cells hdr[0].text index_name for j, col in enumerate(df.columns, start1): hdr[j].text str(col) for idx, row in df.iterrows(): cells t.add_row().cells cells[0].text str(idx) for j, v in enumerate(row.values, start1): cells[j].text float_fmt.format(v) if isinstance(v, float) else str(v) return t doc.add_picture(fig_platform.png, widthInches(5.5)) # 页宽的 5.5 英寸左右 doc.add_paragraph(图 1 大学生常用购物平台选择率).alignment 1 # 1居中 doc.save(report.docx)表格样式名在不同 Word 版本里差别很大Table Grid兼容性最好Light Grid Accent 1这类强调色样式在部分模板里不存在会抛KeyError。图片宽度用相对值Inches(5.5)而不是像素A4 默认页边距下正文宽度约 6.3 英寸5.5 英寸留出居中余量。题注紧跟在图片段落后编号手工写死即可——用 Word 的域自动编号虽然规范但代码插入的域在未打开文档前不显示反而不如手写可控。5.3 从 .docx 到 .doc格式转换与一键重跑交付.doc用命令行转换最省事soffice --headless --convert-to doc --outdir ./dist report.docx转换后务必打开检查图注位置和表格边框命令行转换对复杂样式的还原度不是 100%。如果目标机器上装着 Word直接另存为.doc更稳。一键重跑的本质是把整条链路串成一个脚本读clean.csv→ 算指标字典 → 出图 → 渲染 Word。数据更新后只跑这一条命令报告全量重生成。关键约束是图表里的数字和正文里的数字必须来自同一个指标字典绝不允许正文手写、图表现算——这是报告前后数字打架的头号原因。6. 排错与交付前校验让报告里的数字经得起追问6.1 高频报错与处理路径现象常见原因处理方式图表中文显示成方块字体不含中文字形换SimHei/Noto Sans CJK SC同步关掉axes.unicode_minusKeyError: Light Grid Accent 1模板里没有该样式改用Table Grid或在模板里手工建样式卡方结果异常显著期望频数过小合并类别或对 2×2 表用 Fisher 精确检验插入图片模糊dpi 过低保存图片时dpi200以上宽度用英寸控制正文百分比与图表不一致两处口径不同统一由一个指标字典渲染禁止手写数字图片模糊这个问题的判断标准很简单把 Word 缩放调到 150%看柱状图边缘有没有锯齿。有锯齿就说明源图分辨率不够重新出图比重排文档快得多。6.2 交付前的三组自检第一组是数值自检单选题的各类占比之和应为 100%浮点误差允许 ±0.1 个百分点多选题的选择率之和应该大于 100%各组交叉表的样本量之和应等于有效样本量。这三个等式各写一行断言跑通再交付。# 自检占比合计、样本量守恒 rate_sum clean[q2_grade].value_counts(normalizeTrue).sum() assert abs(rate_sum - 1) 0.001, f单选占比异常: {rate_sum} ct pd.crosstab(clean[q2_grade], clean[monthly_bin]) assert ct.values.sum() (clean[monthly_bin].notna() clean[q2_grade].notna()).sum(), 交叉表丢样本第二组是文档自检把生成的report.docx用 python-docx 读回来检查段落数、表格数、图片数是否与预期一致。这一步能抓住「某张图忘了插」「循环少跑一轮」这类静默失败——它们不会报错只会让报告缺一块。第三组是口径自检报告里出现的每个百分比回溯它属于全样本还是子样本。最常见的事故是「男生中 62% 使用拼多多」被写成「62% 的大学生使用拼多多」前者分母是男生数后者是全样本差了整整一倍。把这三组检查固化进脚本末尾再配上一份指标字典——每个指标写清字段来源、计算公式、分母定义、对应图表编号——数据换一批时需要改的只有字典里的映射关系正文一个数字都不用碰。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门