资讯详情

Python数据可视化实战:新零售销售数据清洗与pyecharts绘图教案

📅 2026/10/11 15:28:14 | 华诺云谱 👁 阅读
Python数据可视化实战:新零售销售数据清洗与pyecharts绘图教案
简介《Python数据可视化实战》第7章新零售智能销售数据可视化实战配套教案面向大数据类专业的教师与学生围绕某公司智能销售设备数据讲解从理解工程背景、读取清洗与规约数据到借助pyecharts绘制交互式图形并撰写分析报告的完整流程。教案详细列出材料清单、教学目标与基本要求并设计了引导性、探究性与拓展性三类问题来驱动课堂思考重点难点部分点明数据处理与可视化绘制教学过程则分别给出理论讲授步骤与实验操作项目覆盖销售分析图、库存分析图、用户分析图制作以及按消费情况为顾客画像还涉及折线图、饼图、柱状图等多种图表的应用场景。全文以PDF格式呈现文件大小仅120KB资源包只含这1个PDF便于下载后打印或离线查阅。教案同时提供教材与参考书目等延伸资料可辅助系统化备课。已有3537人学习过这份教案适合教师备课、学生自学或翻转课堂使用能帮助快速梳理章节脉络、明确教学重点并落实关键实操方法。1. 新零售智能销售数据可视化教案这条链路能帮你解决什么问题这份《Python数据可视化实战》第7章的新零售智能销售数据可视化教案真正值钱的地方不在理论而在它把一条完整的分析链路——读取销售流水、清洗异常、规约聚合、用 pyecharts 绘图、撰写工程分析报告——全部串成了一个可以照着复现的样本。案例数据是某公司在广东省投放的新零售智能销售设备产生的真实订单流水不是处理干净的比赛数据带缺失、带异常恰好用来练数据清洗和规约。教案的落点很明确不只教你会画图还要你从一张原始销售表走到一份能说明问题的分析报告。它把销售分析、库存分析、用户分析三块串到同一个工程里比孤立地学某个图表函数要接近真实工作状态。适合三类人大数据专业学生想交一份合格的课程设计刚接触 Python 可视化的工程师想找真实场景练手讲师想省掉设计案例数据的精力。这份资源可以直接回答你“拿到一堆乱糟糟的销售数据之后下一步该干嘛”这个实际问题。2. 先别急着画图销售数据读取、清洗、规约的三个关键点教案的第一部分内容是“了解工程背景与目标”第二步才是读取数据。实际动手时我建议跳过教材里冗长的背景介绍直接进入“数据长什么样”的侦察环节。下面这套处理流程后来被我直接搬进了企业级数据可视化的日常项目里几乎原样复用。2.1 读取CSV 和 JSON 进来之前先确认三件事新零售智能销售设备回传的数据通常是一张订单流水表字段大致包括设备编号、商品大类、商品名称、订单号、销售金额、销售时间、是否促销标记。不同厂商的导出格式不一样常见的是 CSV 或 JSON。我的习惯是先用三行代码做侦察import pandas as pd # 侦察用先看形状和类型再决定怎么清洗 df pd.read_csv(sales_data.csv, encodingutf-8) print(df.shape) print(df.dtypes) print(df.head())df.shape直接告诉你数据量df.dtypes告诉你每一列的类型。不要只看head()因为head()看不出类型问题而类型问题恰恰是后面画图翻车的第一大来源。比如order_id被读成浮点order_date是字符串这些问题在dtypes里一眼就能发现。确认完字段之后我会把日期解析和列类型一次性放进读取函数里# 日期提前解析订单号强制按文本读 df pd.read_csv( sales_data.csv, encodingutf-8, parse_dates[order_date], # 时间列进内存就转 datetime64 dtype{order_id: string, sales_amount: float64}, )参数说明parse_dates把时间列直接转成datetime64后面画折线图不会再出现横轴乱序dtype强制订单号按文本读避免长数字被读成科学计数法。如果数据源是 JSON写成pd.read_json(sales_data.json, encodingutf-8)即可嵌套结构再配合pd.json_normalize展开。提示CSV 中文乱码时不要盲目换库先试encodinggbk或encodinggb18030这两个编码在国产终端设备导出的文件里出现频率最高。2.2 清洗缺失值、负金额、重复订单的处理顺序教案里明确要求掌握“清洗数据”和“规约数据”但没有展开清洗到什么程度算合格。真实销售设备数据里我高频遇到三类脏数据设备断网导致部分字段为空退款订单被记录成正金额而实际上该冲销同一笔交易因网络重试被上报多次。我的清洗顺序固定如下# 顺序先删关键空值再过滤负数最后按业务唯一键去重 df df.dropna(subset[order_id, sales_amount]) df df[df[sales_amount] 0] df df.drop_duplicates(subset[order_id, order_date, device_id], keepfirst)这段代码的逻辑是先删掉关键字段为空的行再过滤金额小于等于 0 的记录最后按业务唯一键去重。顺序不能反如果先去重缺失行和负金额行可能把正常记录顶掉。subset指定参与判断的列三列组合模拟业务唯一键keepfirst表示重复时保留第一条适合同一订单重复上报的情况。清洗之后我再跑一个描述性统计# 看金额分布最大值和分位数之间差距过大就是异常信号 print(df[sales_amount].describe())describe输出里最值得看的是最大值和 75% 分位。如果 75% 分位是 200最大值是 8000要么存在团购订单要么有异常值。教案在这里没有继续展开我的做法是把这类极端值单独过滤出来看来源而不是直接删除。把“为什么删”写进分析报告比只给一个删除结果更显专业。2.3 规约把细粒度流水聚合成可分析的宽表规约的目的是把细粒度的交易流水压缩成能支持图形分析的形式。教案里的第一张图是“生鲜类和一般商品每天销售金额折线图”所以第一步是按日期聚合# 同一日期同一商品大类聚合成一行 daily_sales df.groupby([order_date, category]).agg( total_amount(sales_amount, sum), # 日销售金额合计 order_cnt(order_id, nunique), # 不重复订单数 ).reset_index() print(daily_sales.head())这段代码的逻辑是把同一天同一商品大类的多笔交易折叠成一行。agg里的写法是 pandas 命名聚合左边是新列名右边是“源列, 聚合函数”。nunique统计不重复订单数比count更能反映真实单量因为同一订单可能被拆成多行。如果要做教案里的“周环比增长率”规约粒度要改成周# 增加周标签列后续按周聚合 df[week] df[order_date].dt.to_period(W).astype(str) weekly df.groupby([week, is_promo]).agg( total_amount(sales_amount, sum) ).reset_index() # 周环比必须在按周排序后再算否则结果会乱 weekly weekly.sort_values([week, is_promo]) weekly[growth] weekly.groupby(is_promo)[total_amount].pct_change() * 100dt.to_period(W)会把日期归到以周一为起始的 ISO 周。这里容易犯的错是把week当普通字符串直接用忘记画图前还要sort_values(week)。另外pct_change()第一周的结果必然是 NaN教案没提但实验里几乎人人都能踩到。3. 用 pyecharts 画三类业务图四张图的配置细节与参数边界这一章是教案的实验核心。pyecharts 的 API 风格统一但每类图的配置点都不一样。按业务目标拆开讲参数边界更容易看清楚。3.1 折线图生鲜类与一般商品的每日销售趋势教案要求绘制“生鲜类商品和一般商品每天销售金额的折线图”。我的做法是先把数据拆成两条序列再核对长度最后绘图from pyecharts.charts import Line from pyecharts import options as opts # 分别拆出生鲜类和一般商品的销售序列 fresh_data daily_sales[daily_sales[category] 生鲜类][total_amount].tolist() normal_data daily_sales[daily_sales[category] 一般商品][total_amount].tolist() x_axis daily_sales[order_date].astype(str).tolist() assert len(fresh_data) len(x_axis) len(normal_data) line ( Line() .add_xaxis(x_axis) .add_yaxis(生鲜类, fresh_data, is_smoothTrue) .add_yaxis(一般商品, normal_data, is_smoothTrue) .set_global_opts( title_optsopts.TitleOpts(title生鲜类与一般商品每日销售金额趋势), tooltip_optsopts.TooltipOpts(triggeraxis), xaxis_optsopts.AxisOpts(type_category, name日期), yaxis_optsopts.AxisOpts(type_value, name销售金额元), datazoom_opts[opts.DataZoomOpts(range_start0, range_end100)], ) ) line.render(sales_trend.html)参数说明add_yaxis的is_smoothTrue让曲线更接近业务趋势的视觉习惯triggeraxis让悬停时两条曲线一起显示数值对比更方便xaxis_opts的type_category表示横轴是等距刻度不按真实时间间隔压缩节假日的空档datazoom_opts提供底部的缩放条数据跨度超过 60 天基本必加。assert那行是防御性代码pyecharts 在长度不匹配时不报错只会画出错位的图用断言可以把异常提前暴露出来。3.2 柱状图促销与非促销商品的周环比增长率怎么算第二张图的业务目标是对比促销和非促销商品的销售表现。教案要求画“周环比增长率柱状图”但这里有个隐藏陷阱促销商品的销售基数通常只有非促销的几分之一直接并排比较增长率会得出“促销没用”的错误结论因为小基数上很容易产生高增长率。我的做法是柱状图下方保留绝对值作为注释或者至少在报告里写明基线。增长率计算要用到刚才说的pct_change()陷阱正确写法# 先按促销分组再按周排序最后算环比 weekly_sorted weekly.sort_values([is_promo, week]) weekly_sorted[growth] ( weekly_sorted.groupby(is_promo)[total_amount].pct_change() * 100 ) weekly_sorted[growth] weekly_sorted[growth].fillna(0) weeks weekly_sorted[week].unique().tolist() # 拆出两组增长率序列顺序必须和 x 轴一一对应 promo_growth weekly_sorted[weekly_sorted[is_promo] 1][growth].tolist() normal_growth weekly_sorted[weekly_sorted[is_promo] 0][growth].tolist()sort_values([is_promo, week])必须放在pct_change之前否则分组内不是按时间顺序计算环比结果会乱套。fillna(0)把第一周的 NaN 置为 0表示“无参照”而不是“增长 0%”这两者在报告里要区分清楚。画柱状图时给每个柱子加格式化标签from pyecharts.charts import Bar bar ( Bar() .add_xaxis(weeks) .add_yaxis(促销商品, promo_growth, label_optsopts.LabelOpts(formatter{c}%)) .add_yaxis(非促销商品, normal_growth, label_optsopts.LabelOpts(formatter{c}%)) .set_global_opts( title_optsopts.TitleOpts(title促销与非促销商品销售金额周环比增长率), yaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(formatter{value} %)), ) ) bar.render(weekly_growth.html)formatter{c}%让柱顶直接显示百分比axislabel_opts控制 y 轴刻度的百分号。周数多的时候我给 x 轴加一个axislabel_optsopts.LabelOpts(rotate45)避免标签横排重叠。3.3 饼图库存结构占比图绘制前先做归并教案的库存分析图是“按月绘制各大类商品销售金额的占比饼图”。饼图最怕一类一标签类别超过 8 个以后基本没法读。我的习惯是先按金额降序排序保留前 8 类其余合并成“其他”再进Piefrom pyecharts.charts import Pie # 排序后按占比归并饼图才能读 sorted_data month_data.sort_values(total_amount, ascendingFalse) data_pair [list(x) for x in zip(sorted_data[category], sorted_data[total_amount])] pie ( Pie() .add( series_name销售金额占比, data_pairdata_pair, radius[40%, 70%], # 环形图中间留白可放核心指标 center[50%, 50%], label_optsopts.LabelOpts(formatter{b}: {d}%), ) .set_global_opts( title_optsopts.TitleOpts(title当月各大类商品销售金额占比), legend_optsopts.LegendOpts(orientvertical, pos_leftleft), ) ) pie.render(category_pie.html)radius[40%, 70%]得到的是环形图中间留白可以放核心指标比实心饼图专业formatter{b}: {d}%的{b}是分类名{d}是占比pos_leftleft的竖排图例比默认底部图例省空间。如果某个月份类别确实很多可以给Pie.add传is_avoid_label_overlapTrue缓解标签重叠但小扇区的信息密度问题依然存在归并才是一劳永逸的解法。3.4 顾客画像Top 10 顾客不是终点指标组合才是教案里“为累计消费前 10 的顾客画像”一句话看着简单落到代码要先把顾客维度指标算出来。顾客画像至少要有三个指标累计消费、订单数、客单价。# 按顾客聚合累计消费订单数客单价三个维度才算画像 customer_stats ( df.groupby(customer_id) .agg( total_consume(sales_amount, sum), # 累计消费 order_cnt(order_id, nunique), # 下单次数 avg_price(sales_amount, mean), # 客单价 ) .sort_values(total_consume, ascendingFalse) .head(10) )这三个字段组合在一起才叫“画像”单独一个累计消费只能叫“排行榜”。画图时我常用横向条形图因为顾客 ID 较长from pyecharts.charts import Bar bar ( Bar() .add_xaxis(customer_stats[customer_id].astype(str).tolist()) .add_yaxis( 累计消费金额, customer_stats[total_consume].round(2).tolist(), label_optsopts.LabelOpts(formatter{c} 元), ) .reversal_axis() # 横放条形图防止 ID 被截断 ) bar.render(top10_customers.html)reversal_axis()把条形图横放避免 ID 被截断。教案只给了“顾客画像”的结果要求没规定图形类型这正是实验课值得让学生自己选图的部分。如果后续想深入把订单频次和客单价放进散点图可以直接观察高价值顾客集中在哪个区间这已经接近 RFM 模型的入门用法。4. pyecharts 可视化常见问题排查五个让我翻过车的细节这一章的内容是我把教案案例完整跑过一遍之后的踩坑记录。每一条都按现象、原因、解决三部分写方便对照排查。4.1 图表在 Jupyter Notebook 里白屏现象代码不报错执行完也没有异常但 Notebook 单元格里只有一块空白。原因pyecharts 默认从公共 CDN 加载echarts.js文件在内网或网络受限环境里 JS 加载失败图表区域就是白屏。这个问题在教案配套实验环境里出现概率极高。解决改用本地静态资源。先下载一份 pyecharts-assets 到本地再在代码开头指定资源地址# 指定本地静态资源地址避免 CDN 加载失败导致白屏 from pyecharts.globals import CurrentConfig CurrentConfig.ONLINE_HOST http://localhost:8000/assets/如果不想搭本地静态服务另一个更快的办法是放弃 Notebook 内联显示直接chart.render(chart.html)再用浏览器打开 HTML。浏览器能访问的 CDN 范围通常比 Notebook 沙箱环境大这个差异很难从代码层面看出来。4.2 标题和轴标签的中文显示成方块现象图能渲染出来但标题、图例、轴标签里的中文全部变成“豆腐块”。原因要么是读取数据时编码不对中文字符到内存里已经乱码要么是渲染环境缺少中文字体或字体栈配置不当。解决先区分是数据乱码还是渲染乱码。数据乱码用pd.read_csv(..., encodinggbk)或encodinggb18030重新读入渲染乱码通常在TitleOpts(title销售金额趋势)里把 title 设置为普通中文字符串即可。按我的经验九成情况是读取编码问题。排查方法是在清洗前后各打印一次df[category].unique()如果分类列里出现乱码符号基本可以断定是编码选错了。4.3 折线图横轴时间乱序现象折线图能画出来但横轴出现“2023-02-05”排在“2023-01-31”前面或 2 月排在 1 月前面。原因日期列被当成字符串sort_values按字典序排序所以“01”系列永远排在“02”系列之前月份超过 12 个月时跳变特别明显。解决在任何groupby之前把日期列转成datetime画图前再转回字符串# 时间序列图的保底操作先转 datetime排序后再转回字符串 df[order_date] pd.to_datetime(df[order_date]) daily_sales daily_sales.sort_values(order_date) x_axis daily_sales[order_date].astype(str).tolist()这条我建议当成时间序列图的保底操作写进自己的代码模板里不做第二次想。4.4 饼图标签互相重叠现象饼图只有 6 个分类但百分比标签叠成一团完全没法看。原因Pie默认给每个扇区都显示标签比例小的扇区角度本身就很窄标签自然挤在一起。解决先归并再画图。把占比低的分类合并为“其他”再配合is_avoid_label_overlapTrue让 pyecharts 自动调整位置# 低于总金额 3% 的分类合并成“其他”饼图才可读 threshold month_data[total_amount].sum() * 0.03 big month_data[month_data[total_amount] threshold] small month_data[month_data[total_amount] threshold] other pd.DataFrame({category: [其他], total_amount: [small[total_amount].sum()]}) merged pd.concat([big, other], ignore_indexTrue)threshold按当月总金额的 3% 计算低于 3% 的项目在饼图里既看不清也说明不了问题合并后图表的信息准确度反而更高。4.5 周环比增长率第一周是 NaN现象柱状图横轴第一个柱子高度为 0或者柱子数量比周数少一根。原因pct_change()依赖上周数据第一周没有参照返回 NaN。pyecharts 遇到 NaN 会跳过对应数据点导致柱子缺失。解决计算后显式处理# 计算周环比后先处理 NaN再交给 pyecharts 绘图 weekly[growth] weekly.groupby(is_promo)[total_amount].pct_change() * 100 weekly[growth] weekly[growth].fillna(0)如果业务上第一周不参与展示就在画图前dropna()如果要保留fillna(0)只是占位报告里必须写清“第一周无参照”。这两种处理代表不同的业务口径教案没有讲实验报告里却最容易被提问。5. 从图表到分析报告洞察从哪来、结论怎么写、图怎么嵌进去教案把报告要求拆成“分析思路、分析结果、总结和建议”这三块是教学评价时的打分点。落到实际工程里我建议按四段式组织比教案划分法多一个“数据处理说明”。5.1 报告骨架背景、方法、结论、建议四段式教案要求我在报告里写什么篇幅建议分析思路数据来源、清洗与规约规则、为什么选这几张图350 字分析结果每张图对应的关键数字与对比结论700 字总结全量数据上的主要规律控制在三条以内300 字建议针对总结的可执行动作不要写空话250 字我在“数据处理说明”里最在意的一行字是原始记录 12.8 万条清洗后保留 10.6 万条删除 2.2 万条异常。把这个数字写出来比任何图表都有说服力它证明分析者真的处理过数据而不是拿现成的干净数据集跑了个模板。这个习惯源于一次被答辩老师追问“你清洗了什么”的血泪教训。5.2 从图里提炼业务发现的三个原则第一先看结构再看波动。销售趋势图里生鲜类如果呈现稳定的周末峰值这是结构规律某一天突然跳水那是波动。报告应该先写规律再解释波动否则容易被极端值带偏。第二对比必须有基线。促销商品和非促销商品的周环比增长率不能直接比因为基数完全不同。我写报告时会补一句“促销商品周均销售额约为非促销的 23%”作为基线再谈增长率。“促销增长率更高”和“促销带来了多少绝对增量”是两个完全不同的问题。第三结论必须绑定业务动作。“生鲜类销售占比最高”只是数据写成“建议在晚高峰前对生鲜类补货并扩大陈列”才变成可执行的结论。在新零售这类强互联网属性的业务场景里分析报告最终回答的不是“图好不好看”而是“下一步运营动作是什么”。新人写到第三条原则时通常已经能从“记录现象”过渡到“提出动作”。5.3 把 HTML 交互图嵌进 Word/PPTpyecharts 输出的是 HTML课程作业和部门汇报通常要求 Word/PPT这里天然存在格式矛盾。我的处理方式有三种一是 PPT 里放静态截图把 HTML 链接作为附注二是用浏览器打印成 PDF 再插入适合正式报告三是把 HTML 部署到内网静态目录报告里给网址。没有最完美的方案但要避免交付一个打不开的.html文件。如果需要快速把交互图转成图片可以用 playwright 无头浏览器截图# 用无头浏览器把 pyecharts 输出的 HTML 截成 PNG python -m playwright screenshot --full-page sales_trend.html sales_trend.png--full-page参数会截取完整页面高度适合长宽比较大的折线图。首次使用要安装浏览器内核装完截图效果和浏览器预览完全一致。如果只是临时用直接在浏览器里按 F12 打开设备工具栏把视口设为 1600x900 再截图也能得到够用的结果。6. 进阶用法把整套流程封装成一个可复用的销售报表生成器教案案例跑通之后我发现前面所有步骤都是重复劳动读 CSV、清洗、按周期聚合、画折线、渲染 HTML。与其每次复制粘贴不如封装成一个函数参数化控制周期和输出目录。from pathlib import Path import pandas as pd from pyecharts.charts import Line from pyecharts import options as opts def build_sales_report(data_path: str, granularity: str D, out_dir: str output): # 输出目录不存在就自动创建 Path(out_dir).mkdir(exist_okTrue) # 读取、清洗、规约三步统一处理 df pd.read_csv(data_path, encodingutf-8, parse_dates[order_date]) df df.dropna(subset[order_id, sales_amount]) df df[df[sales_amount] 0] # 按日或按周生成周期标签 if granularity W: df[period] df[order_date].dt.to_period(W).astype(str) else: df[period] df[order_date].astype(str) daily (df.groupby([period, category])[sales_amount] .sum().reset_index()) daily daily.sort_values(period) # 生成折线图并输出 line ( Line() .add_xaxis(daily[period].astype(str).tolist()) .add_yaxis(销售金额, daily[sales_amount].tolist()) .set_global_opts(title_optsopts.TitleOpts(title销售金额趋势)) ) out_path Path(out_dir) / sales_trend.html line.render(str(out_path)) return out_path参数说明granularityD按日聚合改成W自动切到周聚合日报告和周报告共用同一套代码out_dir把生成的 HTML 统一放进一个目录方便后续部署。这段代码的价值不在于绘图精致而在于它把最容易出问题的读取编码、时间转换、排序逻辑全部挡在函数入口。验证方式很简单把教案的样例数据丢进去检查返回的 HTML 文件存在且大小不是 0再打开浏览器确认标题和横轴顺序正确。从那以后我每次带新人跑这份教案都强制要求先过一遍这个函数再谈自定义图表和美化因为前期数据坑被统一处理掉之后后面出错的概率会小很多。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑