尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从docx到数据分析:Python实现轻食消费行为挖掘与RFM分群

从docx到数据分析:Python实现轻食消费行为挖掘与RFM分群 简介中国轻食行业消费者行为调查数据以艾媒咨询2023年调研数据为基础面向轻食餐饮创业者、产品经理及消费市场研究人员系统梳理消费者食用频率、轻食类型偏好、运动习惯、推荐意愿以及对现有轻食餐的主要不满。文档先界定轻食定义与行业特点再结合大健康、颜值经济背景说明行业快速增长原因随后用75.8%周食用率、53.6%偏好沙拉蔬菜卷、84.0%愿推荐等关键比例刻画用户偏好并指出食品质量参差、品类单一、定价过高等行业痛点数据密度高便于直接引用于市场报告、课程作业或商业计划。压缩包共1个docx文件大小331KB下载后即可阅读目前已有155人学习适合需要快速获取轻食消费者行为结构化数据的用户参考。1. 轻食消费行为数据到底在记录什么拿到一份《中国轻食行业消费者行为调查数据.docx》多数人的第一反应是直接翻结论页看看“谁在吃、一周吃几次、客单价多少”。但这类调查数据真正值钱的部分往往不在统计结论里而在被忽略的原始字段结构中。轻食行业的消费决策链条比普通餐饮更长——用户先产生健康认知再选择渠道再完成复购决策最后才形成口碑传播。如果你只是对着 docx 里现成的饼图和柱状图做二次解读那你拿到的只是别人的视角而非可复用的数据资产。这份文档本质上是一个打包好的数据集加初步分析报告。它记录的字段通常包括消费者基本信息、购买频次、单次消费金额、品类偏好、信息获取渠道、品牌忠诚度等。对做数据分析和用户研究的人来说真正的价值在于把 docx 里的表格重新结构化转换成语义清晰的字段体系然后自己重跑一遍完整分析链路。另一方面轻食行业过去三年的消费者结构变化很快代餐、减脂餐、高蛋白食品的边界越来越模糊固化的报告隔一年就失效所以从原文中抽出可更新、可扩展的字段模型比死记几个结论有用得多。这篇文章就围绕一件事展开如何把一份 .docx 格式的行业调查数据从“可读的文档”重新加工成“可分析的数据集”再做一次消费者分群、复购预测和可视化输出。无论你拿到的文件里是 Excel 表格截图、饼图还是纯文本问答都能用同一套思路完成拆解。2. 从 docx 里拆出结构化数据轻食消费行为数据抢救的第一步2.1 为什么 docx 不能直接做分析格式与内容的双重陷阱.docx本质上是 zip 压缩包正文存储在word/document.xml中。文件里看起来规整的「表格」在 XML 层面可能是一大堆w:tbl与w:tr的嵌套也可能只是用 Tab 或空格对齐的伪表格。轻食调查问卷数据恰好踩中这两种情况线上问卷平台导出的报告多半是真正的 Word 表格但线下拦截访问生成的 docx常常是把 CSV 内容粘贴进去再手动加了几列合并单元格。更麻烦的是这类文档里超过 60% 的有价值信息存在于段落文本中比如「受访者 B 表示每周至少购买 4 次轻食沙拉」「月均花费 600 元左右」而这类表述无法直接被 pandas 读取。如果只用python-docx提取表格你会丢掉大量半结构化文本如果直接复制粘贴到 Excel又会因为 Word 中的格式标记而获得一列数据混在文本串里的脏表。常见做法是先用python-docx把文档里的所有内容分为「表格块」和「段落块」两类对象分别提取并打上序号标记再做下游合并。这样做既保住了原始结构又能靠段落序号把失散的信息重新关联起来。2.2 用 python-docx 提取表格与段落的完整代码把下面代码保存为extract_docx.py修改文件名后直接运行from docx import Document import pandas as pd import re doc Document(中国轻食行业消费者行为调查数据.docx) # 提取所有表格 tables_data [] for i, table in enumerate(doc.tables): rows [] for row in table.rows: cells [cell.text.strip() for cell in row.cells] rows.append(cells) df pd.DataFrame(rows) df.insert(0, table_id, i) # 标记来源表 tables_data.append(df) print(f表格 {i}: {len(row.cells)} 列 × {len(table.rows)} 行) # 提取所有段落保留非空行带序号 para_lines [] for idx, para in enumerate(doc.paragraphs): text para.text.strip() if text: para_lines.append({para_id: idx, text: text}) # 段落中包含数字频率等关键信息的行单独输出 for p in para_lines[:20]: if re.search(r[0-9]%|\d元|\d次, p[text]): print(f[段落 {p[para_id]}] {p[text][:80]})逻辑说明doc.tables返回文档内所有 Word 表格对象遍历时逐行取.text并去空格防止合并单元格产生重复文本。doc.paragraphs返回正文所有段落这里只保留非空行避免读进一堆空段落干扰后续关键字检索。代码最后用正则抽查前 20 个包含「百分比、金额、次数」等消费行为关键词的段落目的是确认数据的落点形态——是表里齐整还是散落在叙述文字中。参数说明para_id是段落的原始索引后续如果要从文本段落中抽取问卷答案这个编号就是回指原文的锚点。table_id同理保留它是因为后续合并多张表时一旦发现同一受访者 ID 出现在两张表中可以靠table_id追溯来源排查合并逻辑错误。2.3 表格与段落数据合并拼出一张宽表轻食调查数据最常见的存储形态是「一表一题」即每道题目单独成表——第 0 表是受访者基本信息第 1 表是购买行为频次第 2 表是品牌认知。分析前必须把这些表合并成一行一人的宽表。合并键通常是一个「样本编号」字段但问题在于docx 里往往没有真实 ID而是用「样本 1」「case_001」这类字符串。这里给出一段快速合并逻辑def merge_tables(tables_data, key_col0): merged None for df in tables_data: if merged is None: merged df.copy() else: if df.shape[1] 1 and df.iloc[0, key_col].startswith(样本): df df.rename(columns{df.columns[key_col]: 样本编号}) if 样本编号 in merged.columns: merged merged.merge(df, on样本编号, howouter, suffixes(_a, _b)) return merged merged_df merge_tables(tables_data) merged_df.to_csv(light_food_consumption_raw.csv, indexFalse, encodingutf-8-sig)参数说明howouter是为了保留全量样本——并非每个受访者都回答过每一道题inner join 会直接丢掉缺失答题者导致后续复购率计算偏乐观。suffixes(_a,_b)处理的是两张表都含「花费」这类重名列时的情况避免 pandas 自动加后缀导致字段名失控。3. 轻食消费行为字段的清洗与衍生变量构造3.1 典型脏数据长什么样从文本到数值的强制转换把 docx 里的数据拉到 DataFrame 后你会遇到三类高频问题。第一类是数值里混入单位「45元」「¥39.9」「50 元左右」混在同一列第二类是分级区间而非精确值「月消费 200-500 元」被填进单个字段第三类是量表文本「非常愿意、愿意、一般、不愿意」出现在原本应该是 1-5 分的列里。第一个修复动作是写一个通用的文本清洗函数import re def clean_numeric(value): if value is None: return None if isinstance(value, (int, float)): return float(value) text str(value).replace(¥, ).replace(元, ).replace( , ) if 以上 in text: return 9999 # 上限标记替代后续分析时可特殊处理 if - in text or ~ in text: parts re.split(r[-~], text) return (float(parts[0]) float(parts[1])) / 2 num_match re.search(r\d\.?\d*, text) return float(num_match.group()) if num_match else None df[月消费金额] df[月消费金额].apply(clean_numeric)逻辑说明¥、元、空格三个字符先用 replace 去掉这是轻食问卷系统中最常见的脏字符组合。「200-500 元」这种区间文本用re.split按-或~拆分为左右值再取均值保留的信息量大于直接丢到「无法识别」分类。「以上」「以下」这类开口区间返回 9999 是个临时标记分析时再单独分组处理防止它污染均值计算。3.2 构造核心衍生变量消费频率分层与品类偏好向量原始字段里「每周购买几次」通常是个连续值或区间但真正做消费者行为分群时更常用的是把消费频率切成四层层级每周购买次数特征描述高频 5 次把轻食当主食典型代餐型用户中频2-4 次工作日午餐替代场景为主低频1 次及以下尝鲜型或周末健康餐流失边缘近 30 天未购买有认知但未养成习惯切分层级用 pandas 的cut方法是最直接的方式bins [-1, 0, 1.5, 4.5, 100] labels [流失边缘, 低频, 中频, 高频] df[频率分层] pd.cut(df[每周购买次数_清洗], binsbins, labelslabels)参数说明bins里 1.5 和 4.5 是分界点因为整数次数落点刚好错开——1 次落在「低频」2-4 次落在「中频」5 次以上落在「高频」。-1和100是开区间边界保证 pandas 不会因为越界值报 NaN。这里注意rightFalse的默认行为实际使用时建议显式传入一次避免后续 pandas 版本变更导致分箱结果不同。品类偏好字段的处理思路稍有不同。轻食的品类重叠度很高一个用户可能同时选「沙拉」「全麦三明治」「轻食能量碗」三项。如果直接把多选字段拆成多列就会生成几十列稀疏矩阵。常见做法是保留 Top 5 高频品类把其余归并为「其他」再对每个用户做布尔标记top5 df[偏好品类_清洗].value_counts().head(5).index for cat in top5: df[f偏好_{cat}] df[偏好品类_清洗].astype(str).str.contains(cat, regexFalse).astype(int)这个操作把一个文本字符串列扩展成 5 个 0/1 列后续做聚类或关联规则时可以直接输入。str.contains里必须用regexFalse因为品类名里的「轻食」两个字本身不涉及正则语义但养成显式关闭正则的习惯可以避免 Sklearn 报特征名格式错误。4. 用 RFM 与消费者分群拆解轻食消费行为4.1 轻食场景下的 RFM 参数自定义RFM 模型是消费者行为分析的经典框架但直接套电商的阈值会出问题。轻食消费有一个显著区别于服装、美妆的特征消费周期短单次金额低决策受地理位置强约束。因此「最近一次消费时间 R」应该以「天」为单位而不是「月」「频率 F」要基于「周」而不是「季度」「金额 M」要参考所在城市的单均水平来设定高分值线。以一线城市数据为例常见分值切分方法是R 值近 7 天内有购买记 5 分、8-14 天记 4 分、15-30 天记 3 分、31-60 天记 2 分、60 天以上记 1 分F 值每周购买 5 次以上记 5 分每降一档减一分M 值单月消费超过 800 元记 5 分600-800 记 4 分依此类推。这个分值体系适合沙拉、三明治为主的门店如果你手里的数据是偏向代餐奶昔或轻食外卖阈值要整体下调 30% 左右。以下是可执行的 RFM 打分代码import pandas as pd import numpy as np def rfm_score(df): # 假设 df 已包含最近购买天数、周购买次数、月消费金额三列 df_score df.copy() # R 分 df_score[R_score] pd.cut( df_score[最近购买天数], bins[-1, 7, 14, 30, 60, 9999], labels[5, 4, 3, 2, 1] ).astype(int) # F 分 df_score[F_score] pd.cut( df_score[每周购买次数], bins[-1, 0.9, 2.9, 4.9, 6.9, 9999], labels[1, 2, 3, 4, 5] ).astype(int) # M 分 df_score[M_score] pd.cut( df_score[月消费金额], bins[-1, 200, 400, 600, 800, 99999], labels[1, 2, 3, 4, 5] ).astype(int) df_score[RFM_总得分] df_score[R_score]*100 df_score[F_score]*10 df_score[M_score] return df_score参数说明三个 bin 列表都需要写成“多一个左端点”的形态因为pd.cut默认包含右边界比如bins[-1, 7, 14]实际会把 7 天以内的样本归入第一档正好对上「近 7 天记 5 分」的规则。RFM_总得分用三位编码而非简单求和好处是553和355的『和』一样但行为特征完全不同——前者是高频高价活跃用户后者即将流失。4.2 分群结果的业务解读与异常值排查拿到 RFM_总得分后最常被直接套用的划分方式是「 555-444 是重要价值客户」但在轻食行业这个结论有误导性。因为轻食的消费场景高度集中在工作日的午餐时段一个写字楼店的白领用户可能是 R2、F5、M4 的组合——他连续三周光顾但最近一周出差所以 R 值骤降。这样的人如果被简单归为「一般客户」运营策略就会从「关怀唤醒」错配成「高价值维护」。因此建议在实际分群时把 R/F/M 各拆成高、低二档3 分及以上为高否则为低得到 8 个群组再按轻食消费行为做定向解读群组编码行为画像运营动作建议高高高忠诚核心用户推会员充值、新品内测低高高高价值但近期未购买发回归优惠券注意核实是否出差/搬家高低低高频低额用户提高客单价推荐搭配套餐低低低即将流失的低价值用户放弃高成本召回做自然流失观察异常值排查方面聚焦M_score5但F_score1的样本。轻食的月均消费上限不高一个月 800 元以上但只买一次的用户要么买的是几百元的代餐礼盒要么是公司团购单被记在了个人 ID 下。处理方式是单独打标签看原始问卷备注不要直接用模型预测这类记录。outliers df_score[(df_score[M_score]5) (df_score[F_score]1)] print(f异常样本量: {len(outliers)}) # 打印出这些样本的 ID 和原始备注列人工核对这类验证代码的价值在于把模型输出和原始数据之间建立回查回路避免因为一个统计口径错误导致整个分群结果被质疑。实际项目中这一步应该写在分析脚本的末尾并输出 CSV 备份而不是等人来问的时候再去翻。5. 轻食消费行为数据的可视化与报告转化5.1 用 matplotlib 快速产出三张核心图数据清洗和建模完成后报告阶段的核心任务是让业务方一眼看懂消费者分群和消费习惯。轻食行业调查数据适合产出三种图消费频次分布直方图、品类偏好的横向条形图、RFM 分群散点图。三张图分别回答「购买频次集中在哪里」「用户最常买什么」「哪些人值得重点运营」。直方图的实现很直接import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, PingFang SC, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 3, figsize(16, 4.5)) # 图 1每周购买次数分布 axes[0].hist(df[每周购买次数_清洗], bins12, edgecolorwhite, color#4C9F70) axes[0].set_title(轻食消费者每周购买次数分布) axes[0].set_xlabel(每周购买次数) axes[0].set_ylabel(人数) # 图 2Top 8 品类偏好条形图 cat_counts df[偏好品类_清洗].value_counts().head(8) cat_counts[::-1].plot(kindbarh, axaxes[1], color#E8A87C) axes[1].set_title(轻食消费者品类偏好 Top8) # 图 3RFM 散点图R 值横轴F 值纵轴M 值用颜色 ax axes[2] sc ax.scatter(df_score[R_score], df_score[F_score], cdf_score[M_score], cmapYlOrRd, s20, alpha0.7) plt.colorbar(sc, axax, labelM 金额分) ax.set_title(轻食消费 RFM 分布) ax.set_xlabel(R 最近消费分) ax.set_ylabel(F 购买频率分) plt.tight_layout() plt.savefig(light_food_rfm_report.png, dpi160)逻辑说明图 1 与图 2 的数据源是清洗后的 df图 3 的数据源是上一步 rfm_score 函数产出的 df_score。RFM 散点图用颜色通道编码 M 值是关键技术选择——因为 R 和 F 通常呈现正相关X/Y 坐标会叠成一条斜向带只有把 M 值映射到颜色才能把第三维信息在同一张图里表达清楚。s20控制单点尺寸样本量大的时候调低到 10否则整张图变成实心色块。5.2 从数据图到 docx 报告还原成可交付文档分析结果最终要回到一份新的 docx 报告里发布出去。用python-docx可以自动生成带图表和结论的文档这里给出关键的写入片段from docx import Document from docx.shared import Inches report Document() report.add_heading(轻食消费行为数据二次分析简报, level1) report.add_heading(一、分群结构概览, level2) # 写入关键结论段落 conclusion 高频用户占比 23.4%贡献了整体消费金额的 51.7% \ 这一集中度说明轻食运营应优先锁定高频场景而非盲目拉新。 report.add_paragraph(conclusion) # 插入前面生成的图 report.add_picture(light_food_rfm_report.png, widthInches(6.5)) # 加入分群统计表 stats_table report.add_table(rows3, cols5) stats_table.style Light Grid Accent 1 cell_data [ [群组, 人数, 消费金额占比, 复购率, 运营优先级], [高频高额, 112, 51.7%, 68%, P0], [低频低额, 249, 11.2%, 21%, P2], ] for i, row_data in enumerate(cell_data): for j, text in enumerate(row_data): stats_table.rows[i].cells[j].text text report.save(轻食消费行为分析报告_二次加工版.docx)参数说明add_picture的widthInches(6.5)要匹配 Word 默认页面宽度A4 纸减左右边距约 6.3 英寸过大图片会被截断过小留白过多。add_table中styleLight Grid Accent 1是 python-docx 的内置样式不用额外操作就能带上边框和底色。cell_data的三行构造里第一行是表头下行数据数量必须一致否则写入会漏列且不报错。6. 验证数据可靠性的最后一步交叉比对复原率在把分析结论同步给业务方之前我一般会做一次简单的复原验证——拿着分析结果反向倒推原始数据看关键指标能否对得上。轻食调查数据分析里最高频的失误发生在两个环节第一是 docx 表格提取时把合并单元格重复计数导致样本总量虚高第二是文本型字段清洗时把「0 次购买」误判成缺失值导致复购率偏高。针对第一个问题执行一个总行数核验total_rows_in_docx sum(len(t.rows) for t in doc.tables) print(fdocx 原始表总行数: {total_rows_in_docx}) print(f清洗后样本量: {len(merged_df)}) # 若清洗后样本量显著大于原始行数说明合并表格时产生了笛卡尔积 assert len(merged_df) total_rows_in_docx * 2即使断言通过也要再检查一次唯一 ID 数量。如果合并后数据行数等于原始表行数但「样本编号」去重后的数量小于行数说明同一个样本被多张表重复统计。此时用df[样本编号].nunique()和df[样本编号].count()对比两个值的差异就是重复记录的比率。第二项验证是复购率的反向计算。如果报告中声称复购率 65%但分群表里高频用户占比只有 20%那大概率是把「重复购买过」等同于「高频购买」而忽略了只回购过一次的用户。正确做法是先定义周期——轻食行业通常用近 30 天为复购窗口——再统计窗口内消费次数大于等于 2 的样本占比。写一个简单分组聚合即可完成任务repurchase_rate df[df[近30天消费次数] 2].shape[0] / df.shape[0] print(f近30天复购率: {repurchase_rate:.1%})这个比率应当和报告正文中引用的数字基本一致误差超过三个百分点就要回头查清洗步骤。跑完这两项验证再把第 5 章生成的报告文档与原始 docx 放在同一目录下整套分析链路就闭环了。往后再有人问「你这个数据从哪来的」你可以直接打开比对记录而不是靠记忆回答。本文还有配套的精品资源点击获取
返回列表