1. 案例背景:数据整理是图表生成的前提
不少初学者拿到一份数据后,第一反应就是直接画图,结果要么报错,要么图表内容与实际业务对不上。原因并不在绘图代码本身,而在于数据没有经过整理。原始数据通常存在日期格式不统一、字段名混乱、分类值带空格、存在缺失值或重复记录等问题,直接喂给绘图函数会出现柱状图高度异常、折线图横轴乱序、饼图占比合计不等于 100% 等现象。
数据整理(Data Wrangling)指的是把杂乱数据转换为结构化、规范、便于分析和可视化的表格过程,主要包括读取数据、检查数据质量、清洗缺失值和重复值、修正数据类型、重命名字段、分组聚合等环节。图表生成(Chart Generation)则是基于整理后的数据,用柱状图、折线图、饼图、箱线图等图形展示规律和结论。两者是前后衔接的关系:数据整理决定图表能否画出来,也决定图表传递的信息是否可信。
本文以一个销售订单数据集为例,完整演示从 CSV 读取、数据清洗、字段加工、分组统计到生成 4 张不同图表的全过程。案例面向 Python 数据分析初学者,也适用于有基础但希望规范操作流程的开发者。学完你能够独立完成一份“原始表 → 干净表 → 统计表 → 可视化图表”的分析链路。
2. 环境准备与数据集设计
2.1 运行环境与依赖安装
本案例基于 Python 实现,核心依赖为 pandas 和 matplotlib,可选依赖为 seaborn。建议使用 Python 3.8 及以上版本,pandas 与 matplotlib 的版本以当前常见稳定版本为准,不同小版本之间的 API 差异不大。如果本地还没有安装依赖,可以在终端中执行以下命令:
pip install pandas matplotlib安装完成后,可以通过以下方式验证环境:
import pandas as pd import matplotlib.pyplot as plt print("pandas version:", pd.__version__) print("matplotlib version:", matplotlib.__version__)执行后能够输出版本号,说明环境就绪。需要说明的是,不同操作系统、不同 Python 版本下的版本号会有所差异,只要 pandas 版本在 1.0 以上,本文代码的绝大多数用法都可以正常执行。
2.2 模拟数据文件准备
为了演示方便,我们不去刻意寻找真实企业数据,而是先写一个小脚本生成一份带有一系列“脏数据”特征的销售订单表。这份表覆盖了常见的数据整理场景,包括缺失值、重复记录、日期格式不一致、分类值前后带空格等情况。
在项目目录下新建generate_data.py,代码内容如下:
# 文件路径:generate_data.py import random import pandas as pd from datetime import datetime, timedelta random.seed(42) categories = { "手机数码": ["智能手机", "蓝牙耳机", "智能手表", "移动电源"], "家用电器": ["电饭煲", "空气炸锅", "吸尘器", "电风扇"], "服饰鞋包": ["T恤", "运动鞋", "双肩包", "牛仔裤"], "食品生鲜": ["坚果礼盒", "进口牛奶", "咖啡豆", "新鲜水果"], } records = [] start_date = datetime(2024, 1, 1) order_id = 1001 for _ in range(120): day_offset = random.randint(0, 181) order_date = start_date + timedelta(days=day_offset) category = random.choice(list(categories.keys())) product = random.choice(categories[category]) quantity = random.randint(1, 10) price = random.choice([99, 199, 299, 499, 699, 1299, 1999, 3999]) amount = quantity * price # 模拟少量脏数据 if order_id % 37 == 0: quantity = None if order_id % 41 == 0: amount = None if order_id % 29 == 0: category = " " + category + " " records.append([ "ORD" + str(order_id), order_date.strftime("%Y-%m-%d"), category, product, quantity, price, amount, ]) order_id += 1 # 插入两条完全重复的记录 records.append(records[0]) records.append(records[1]) df = pd.DataFrame(records, columns=[ "订单编号", "日期", "商品类别", "商品名称", "销量", "单价", "销售额" ]) df.to_csv("sales_orders.csv", index=False, encoding="utf-8-sig") print("数据已生成,共", len(df), "条记录")运行该脚本会在当前目录生成sales_orders.csv文件。文件字段说明如下:
| 字段 | 含义 | 示例 |
|---|---|---|
| 订单编号 | 订单唯一标识 | ORD1001 |
| 日期 | 下单日期 | 2024-01-13 |
| 商品类别 | 商品所属大类 | 手机数码 |
| 商品名称 | 具体商品名 | 智能手机 |
| 销量 | 该订单的商品数量 | 2 |
| 单价 | 单个商品价格 | 1999 |
| 销售额 | 订单总金额 | 3998 |
生成的数据中故意包含了缺失值、重复记录、类别字段两侧空白、日期字段仍为字符串等问题,正好用于演示数据整理。你也可以用自己的业务数据替换sales_orders.csv,整理思路保持不变。
2.3 项目目录结构
为了方便管理和后续扩展,建议把脚本、数据和产物分开存放。案例目录结构参考如下:
data_analysis_case/ ├── generate_data.py # 生成模拟数据 ├── sales_orders.csv # 生成的原始数据 ├── data_process.py # 数据整理脚本 ├── chart_analysis.py # 图表生成脚本 └── output/ ├── sales_clean.csv # 清洗后的数据 ├── category_amount_bar.png ├── monthly_trend_line.png ├── category_quantity_pie.png └── price_boxplot.pngoutput目录需要手动创建,或者由脚本通过os.makedirs自动创建。后续操作中,我们按“先整理、后绘图”的顺序逐步完成。
3. 数据整理核心操作拆解
3.1 读取数据与初步检查
读取 CSV 文件并快速了解数据规模、字段类型和缺失情况,是一切整理工作的起点。
# 文件路径:data_process.py(第 1 部分) import pandas as pd df = pd.read_csv("sales_orders.csv", encoding="utf-8-sig") print("数据形状:", df.shape) print("\n前 5 行:") print(df.head()) print("\n字段信息:") print(df.info()) print("\n描述性统计:") print(df.describe())运行结果可以得到以下关键信息:
- 共有多少行、多少列,判断数据量级。
- 每条字段是否为正确的数据类型,比如销量应为整数却可能是浮点数。
- 是否存在缺失值,
info()输出中Non-Null Count会明确显示每个字段非空数量。 describe()只对数值列计算统计量,可用于观察销售额、单价的量级和分布。
这里有一个常见误区:read_csv默认把日期读成字符串,把缺失值读成NaN,重复记录不会自动删除。因此,读取之后必须根据业务需求做后续清洗。
3.2 缺失值与重复值处理
缺失值和重复值并不是直接删除就万事大吉。删除前要想清楚:这一列缺失是否代表业务含义?重复记录是录入重复还是本来就可能出现的多条订单?
本案例的模拟规则是:部分订单缺失销量或销售额,属于录入不完整;第 0 行和第 1 行被完全复制了一遍,属于重复记录。处理方式如下:
# 文件路径:data_process.py(第 2 部分) print("清洗前重复行数:", df.duplicated().sum()) df = df.drop_duplicates() print("清洗后行数:", len(df)) print("缺失值统计:") print(df.isnull().sum()) # 有缺失的销量和销售额按业务常理用中位数填充 df["销量"] = df["销量"].fillna(df["销量"].median()) df["销售额"] = df["销售额"].fillna(df["销售额"].median()) # 重新计算销售额,保证 销量 × 单价 = 销售额 的一致性 df["销售额"] = df["销量"] * df["单价"] # 确认没有缺失 print("处理后的缺失值数量:", df["销量"].isnull().sum() + df["销售额"].isnull().sum())drop_duplicates()默认对所有列进行完全匹配判断,如果想仅依据订单编号去重,可以传入subset=["订单编号"]。本案例的重复记录是完全相同的两行,因此默认参数即可。
缺失值的填充策略需要结合实际业务。销售额缺失时,最稳妥的做法不是简单填充,而是利用“销售额 = 销量 × 单价”的公式重新计算。填充后再统一重算一遍,可以保证字段间的逻辑关系自洽,避免后续画趋势图时出现与业务常识矛盾的数据。
3.3 数据清洗与类型转换
商品类别字段在生成时人为加入了空格,比如" 手机数码 ",如果不处理,分组时"手机数码"和" 手机数码 "会被看成两个不同的类别,导致统计结果分裂。日期字段是字符串,直接按年月聚合时也需要转换为 pandas 的日期时间类型。
# 文件路径:data_process.py(第 3 部分) # 分类字段去掉两侧空格 df["商品类别"] = df["商品类别"].str.strip() # 日期字符串转 datetime df["日期"] = pd.to_datetime(df["日期"]) # 新增“月份”字段,方便按月统计 df["月份"] = df["日期"].dt.to_period("M").astype(str) # 商品名称也做一次清洗 df["商品名称"] = df["商品名称"].str.strip() # 修正数值字段类型为整数 df["销量"] = df["销量"].astype(int) df["销售额"] = df["销售额"].astype(int) print(df.head()) print("\n处理后字段信息:") print(df.info())这段代码做了四件事:
- 用
str.strip()去除字符串两侧空格,避免同义不同值导致的分组错误。 - 用
pd.to_datetime()把日期列转为标准日期类型,这样后续可以按年、月、日灵活聚合。 - 提取出
月份字段,其值为2024-01这样的格式,比直接使用日期分组更直观。 - 把销量、销售额从浮点数转为整数,保证图表坐标轴刻度干净。
这里强调一点:字符串类型的数字和数值类型的数字在排序、求和时表现完全不同。如果发现字段类型是object但内容看起来是数字,需要先排查是否存在"1,299"、"¥199"这类带格式的字符串,否则直接astype(int)会抛异常。
3.4 分组聚合生成统计口径
图表表达的是统计结果,不是原始明细。我们需要按业务问题确定“维度”和“度量”,比如“各品类销售额”“每月销售额变化”“各品类销量占比”。pandas 的groupby是完成这类统计最常用的方法。
# 文件路径:data_process.py(第 4 部分) # 各品类销售额汇总 category_amount = ( df.groupby("商品类别")["销售额"] .sum() .reset_index() .sort_values("销售额", ascending=False) ) print("各品类销售额:") print(category_amount) # 各品类销量汇总 category_quantity = ( df.groupby("商品类别")["销量"] .sum() .reset_index() ) print("\n各品类销量:") print(category_quantity) # 每月销售额趋势 monthly_trend = ( df.groupby("月份")["销售额"] .sum() .reset_index() ) print("\n每月销售额:") print(monthly_trend) # 保存清洗后的数据 df.to_csv("output/sales_clean.csv", index=False, encoding="utf-8-sig")groupby后的结果是一个带有分组索引的 Series 或 DataFrame,直接用于绘图时索引反而容易引起困惑,因此这里先用reset_index()把它转成普通表格。sort_values按销售额降序排列,后续柱状图横轴就会自动按金额大小展示,可读性更好。
从数据整理的角度看,清洗后的sales_clean.csv就是一份“干净数据表”,而category_amount、monthly_trend则是“统计结果表”。建议在分析项目中明确区分原始数据、清洗数据和统计数据的产物,避免反复修改造成流程混乱。
4. 图表生成实战
4.1 中文字体与画布基础设置
matplotlib 默认字体对中文支持并不友好,直接绘制中文标题时经常出现方块“口口口”,需要显式指定中文字体。Windows 环境下常用SimHei或Microsoft YaHei,macOS 可以用Arial Unicode MS。不同机器可用字体不同,最稳妥的设置方式如下:
# 文件路径:chart_analysis.py(第 1 部分) import matplotlib.pyplot as plt import pandas as pd plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "Arial Unicode MS"] plt.rcParams["axes.unicode_minus"] = False # 解决负号显示为方块的问题 df_clean = pd.read_csv("output/sales_clean.csv") df_clean["日期"] = pd.to_datetime(df_clean["日期"])第一行设置字体列表,matplotlib 会按顺序尝试使用;第二行设置坐标轴负号正常显示。如果你发现字体仍然不对,可以在matplotlib.font_manager中查询本机可用字体后替换名称。这里的df_clean直接复用上一节保存的清洗结果文件,保证绘图数据与清洗数据完全一致。
4.2 各品类销售额柱状图
柱状图适合展示分类变量的数量差异。这里统计口径是“各商品类别的累计销售额”,它是一个分组求和的结果。
# 文件路径:chart_analysis.py(第 2 部分) category_amount = ( df_clean.groupby("商品类别")["销售额"] .sum() .reset_index() .sort_values("销售额", ascending=True) ) plt.figure(figsize=(8, 5)) bars = plt.bar( category_amount["商品类别"], category_amount["销售额"], color=["#4C72B0", "#55A868", "#C44E52", "#8172B2"], ) # 在柱状图上添加数据标签 for bar in bars: height = bar.get_height() plt.text( bar.get_x() + bar.get_width() / 2, height + 500, f"{height:,.0f}", ha="center", va="bottom", ) plt.title("2024 年上半年各品类销售额对比", fontsize=14) plt.xlabel("商品类别") plt.ylabel("销售额(元)") plt.grid(axis="y", linestyle="--", alpha=0.6) plt.tight_layout() plt.savefig("output/category_amount_bar.png", dpi=150) plt.show()这里有一个细节:排序时我们使用了ascending=True,这样绘制出来的柱状图是按金额从小到大排列,反过来ascending=False则是从大到小。具体使用哪种排序取决于你想让最大品类出现在右端还是左端。
数据标签通过plt.text添加,坐标取自柱体的 x 中心位置和高度,height + 500的作用是让文字略微高过柱顶,避免文字被柱子遮挡。grid(axis="y")只画水平网格线,视觉上更干净,alpha=0.6则降低网格线亮度。
4.3 月度销售趋势折线图
折线图适合展示时间序列变化,金字塔形的核心是横轴必须是按时间顺序排列的。如果直接对原始日期分组,横轴可能因日期格式问题出现乱序,因此我们在数据整理阶段已经生成了月份字段。
# 文件路径:chart_analysis.py(第 3 部分) monthly_trend = ( df_clean.groupby("月份")["销售额"] .sum() .reset_index() ) plt.figure(figsize=(10, 5)) plt.plot( monthly_trend["月份"], monthly_trend["销售额"], marker="o", linewidth=2, color="#4C72B0", ) # 突出最高点 max_idx = monthly_trend["销售额"].idxmax() plt.annotate( f"峰值:{monthly_trend['销售额'][max_idx]:,} 元", xy=(monthly_trend["月份"][max_idx], monthly_trend["销售额"][max_idx]), xytext=(monthly_trend["月份"][max_idx - 1], monthly_trend["销售额"][max_idx] + 5000), arrowprops=dict(arrowstyle="->", color="gray"), ) plt.title("2024 年 1-6 月销售额趋势", fontsize=14) plt.xlabel("月份") plt.ylabel("销售额(元)") plt.grid(linestyle="--", alpha=0.5) plt.tight_layout() plt.savefig("output/monthly_trend_line.png", dpi=150) plt.show()marker="o"在每个数据点位置显示圆形标记,方便观察具体数值点;annotate用来在最高峰位置添加箭头注释,让读者一眼看到趋势中的关键节点。如果在真实场景中月份数量很多,可以增加plt.xticks(rotation=45)旋转横轴标签,防止文字重叠。
4.4 销量占比饼图与价格分布箱线图
饼图展示构成比例,适合分类数量较少的场景。箱线图则展示数值分布,可以看到不同品类的价格区间和中位数水平。
# 文件路径:chart_analysis.py(第 4 部分) category_quantity = ( df_clean.groupby("商品类别")["销量"] .sum() .reset_index() ) plt.figure(figsize=(7, 7)) plt.pie( category_quantity["销量"], labels=category_quantity["商品类别"], autopct="%.1f%%", startangle=90, colors=["#4C72B0", "#55A868", "#C44E52", "#8172B2"], ) plt.title("各品类销量占比", fontsize=14) plt.tight_layout() plt.savefig("output/category_quantity_pie.png", dpi=150) plt.show() # 不同品类单价分布箱线图 plt.figure(figsize=(8, 5)) df_clean.boxplot(column="单价", by="商品类别", grid=False) plt.title("各品类商品单价分布") plt.suptitle("") plt.xlabel("商品类别") plt.ylabel("单价(元)") plt.tight_layout() plt.savefig("output/price_boxplot.png", dpi=150) plt.show()饼图使用autopct="%.1f%%"在扇形内部显示保留一位小数的百分比,startangle=90让第一块扇形从 12 点方向开始。箱线图这里直接调用了 DataFrame 内置的boxplot方法,by="商品类别"会自动按分类分组生成多个箱体。plt.suptitle("")是为了去掉 pandas 自动添加的Box Plot grouped by 商品类别外层标题,避免标题重复。
关于箱线图要说明的是,如果品类的单价跨度差异很大,直接观察各类别箱体位置和上下须是比均值更稳健的方法,因为均值容易被极端值拉动。
5. 完整流程一键脚本
5.1 合并文件路径
实际项目中通常不希望每次都手动运行两个脚本,更推荐把数据整理和图表生成合并为一个主脚本,并且由脚本自动创建output目录。下面把前面所有功能整合成一个完整脚本。文件路径仍然放在项目根目录下。
5.2 完整代码
# 文件路径:run_analysis.py import os import pandas as pd import matplotlib.pyplot as plt os.makedirs("output", exist_ok=True) plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "Arial Unicode MS"] plt.rcParams["axes.unicode_minus"] = False # ---------- 1. 读取数据 ---------- df = pd.read_csv("sales_orders.csv", encoding="utf-8-sig") print("原始数据形状:", df.shape) # ---------- 2. 数据清洗 ---------- df = df.drop_duplicates() df["销量"] = df["销量"].fillna(df["销量"].median()) df["销售额"] = df["销售额"].fillna(df["销售额"].median()) df["销售额"] = df["销量"] * df["单价"] df["商品类别"] = df["商品类别"].str.strip() df["商品名称"] = df["商品名称"].str.strip() df["日期"] = pd.to_datetime(df["日期"]) df["月份"] = df["日期"].dt.to_period("M").astype(str) df["销量"] = df["销量"].astype(int) df["销售额"] = df["销售额"].astype(int) print("清洗后数据形状:", df.shape) print("缺失值:", df.isnull().sum().sum()) df.to_csv("output/sales_clean.csv", index=False, encoding="utf-8-sig") # ---------- 3. 统计口径 ---------- category_amount = ( df.groupby("商品类别")["销售额"] .sum() .reset_index() .sort_values("销售额", ascending=True) ) monthly_trend = df.groupby("月份")["销售额"].sum().reset_index() category_quantity = df.groupby("商品类别")["销量"].sum().reset_index() # ---------- 4. 柱状图:各品类销售额 ---------- plt.figure(figsize=(8, 5)) bars = plt.bar( category_amount["商品类别"], category_amount["销售额"], color=["#4C72B0", "#55A868", "#C44E52", "#8172B2"], ) for bar in bars: height = bar.get_height() plt.text( bar.get_x() + bar.get_width() / 2, height + 500, f"{height:,.0f}", ha="center", va="bottom", ) plt.title("各品类销售额对比", fontsize=14) plt.xlabel("商品类别") plt.ylabel("销售额(元)") plt.grid(axis="y", linestyle="--", alpha=0.6) plt.tight_layout() plt.savefig("output/category_amount_bar.png", dpi=150) plt.close() # ---------- 5. 折线图:月度趋势 ---------- plt.figure(figsize=(10, 5)) plt.plot( monthly_trend["月份"], monthly_trend["销售额"], marker="o", linewidth=2, color="#4C72B0", ) max_idx = monthly_trend["销售额"].idxmax() plt.annotate( f"峰值:{monthly_trend['销售额'][max_idx]:,} 元", xy=(monthly_trend["月份"][max_idx], monthly_trend["销售额"][max_idx]), xytext=(monthly_trend["月份"][max_idx - 1], monthly_trend["销售额"][max_idx] + 5000), arrowprops=dict(arrowstyle="->", color="gray"), ) plt.title("月度销售额趋势", fontsize=14) plt.xlabel("月份") plt.ylabel("销售额(元)") plt.grid(linestyle="--", alpha=0.5) plt.tight_layout() plt.savefig("output/monthly_trend_line.png", dpi=150) plt.close() # ---------- 6. 饼图:各品类销量占比 ---------- plt.figure(figsize=(7, 7)) plt.pie( category_quantity["销量"], labels=category_quantity["商品类别"], autopct="%.1f%%", startangle=90, colors=["#4C72B0", "#55A868", "#C44E52", "#8172B2"], ) plt.title("各品类销量占比", fontsize=14) plt.tight_layout() plt.savefig("output/category_quantity_pie.png", dpi=150) plt.close() # ---------- 7. 箱线图:单价分布 ---------- plt.figure(figsize=(8, 5)) df.boxplot(column="单价", by="商品类别", grid=False) plt.title("各品类商品单价分布") plt.suptitle("") plt.xlabel("商品类别") plt.ylabel("单价(元)") plt.tight_layout() plt.savefig("output/price_boxplot.png", dpi=150) plt.close() print("分析完成,图表已保存到 output 目录。")注意脚本中保存每张图后立即调用plt.close(),这是为了避免多个 Figure 对象在内存中堆积。在低频脚本中影响不明显,但如果未来改造成循环绘制几十张图表,不关闭画布会出现内存占用持续增长的问题。
5.3 运行结果与产物说明
在项目目录下依次运行:
python generate_data.py python run_analysis.py第二个脚本执行完成后,控制台会输出清洗前后的数据形状和缺失值数量,output目录下出现以下文件:
| 文件 | 说明 |
|---|---|
| sales_clean.csv | 清洗后的明细数据 |
| category_amount_bar.png | 各品类销售额柱状图 |
| monthly_trend_line.png | 月度销售额趋势折线图 |
| category_quantity_pie.png | 各品类销量占比饼图 |
| price_boxplot.png | 各品类单价分布箱线图 |
你可以打开这些图片检查:柱状图最长柱对应销售额第一的品类;折线图能看出销售额最高的月份;饼图所有百分比之和应为 100%;箱线图能够观察不同品类的价格中位数和离群点。任何一处异常都应回溯到数据整理环节,而不是在绘图参数上盲目调整。
6. 常见问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
运行read_csv后中文乱码 | 文件编码与encoding参数不匹配 | 确认 CSV 保存编码,统一使用utf-8-sig或gbk再读取 |
| 柱状图横轴顺序混乱 | 未对分组结果做排序,或类别字段含有空格 | 检查groupby后的值,先strip()再去重排序 |
| 图表中文显示为方块 | matplotlib 未配置中文字体 | 设置plt.rcParams["font.sans-serif"]为本机可用中文字体 |
| 负数显示为方块 | 未关闭 unicode 负数支持 | 设置plt.rcParams["axes.unicode_minus"] = False |
pd.to_datetime报错 | 日期列中存在无法解析的格式或时间戳 | 先查看唯一值df["日期"].unique(),再针对性修正格式 |
箱线图出现很多离群点 | 真实数据中单价差异较大,并非代码错误 | 确认业务上离群点是否有意义,必要时单独分析 |
| 生成的 PNG 图片文字模糊 | dpi参数过低 | 保存时设置dpi=150或更高,满足论文和报告需要 |
astype(int)报 ValueError | 数据中存在无法转成整数的字符串或缺失值 | 先fillna,再检查脏字符如逗号、货币符号,最后转类型 |
需要特别说明的是,上述表格中的多数问题在“数据整理阶段”就可以预防。很多图表绘制异常,本质上是上游数据质量没有处理干净。遇到报错时,建议先打印数据样本,再逐字段判断数据类型和取值,最后才去修改绘图代码。
7. 最佳实践与工程建议
7.1 数据整理阶段
第一,永远保留原始数据。清洗过程中的修改应输出到新文件或新变量,例如本文的sales_clean.csv,不要在原始 CSV 上覆盖,这样便于回溯问题。
第二,清洗逻辑尽量拆成步骤化的函数或注释分段。数据整理往往需要反复调整,步骤清晰后,新增清洗规则不会影响已有逻辑。
第三,统计口径必须有记录。比如“销售额”是指订单金额还是实付金额?“月份”是按下单日期还是支付日期拆分?这些口径决定图表的业务含义,统一口径比调整绘图参数重要得多。
第四,处理真实生产数据时,凡是涉及删除行或填充值,都应该先行打印统计量并确认影响范围。若数据量大,可以用抽样数据测试清洗逻辑,再全量执行。
7.2 图表生成阶段
图表不是越花哨越好。柱状图用不同的颜色区分类别即可,饼图不要超过 6 个扇区,折线图如果有多条线一定要提供图例。保存图片时建议统一设置dpi,日常分析 150 够用,准备发表或报告可以提高到 300。
另外,所有图表都要有完整的标题、轴标签和数据标签。读图的人不一定了解数据背景,如果你的横轴只写一个“月份”,纵轴只写一个“销售额”,那么图表的含义是不完整的。数据标签适量添加,密集时可以使用空心圆点和色块代替逐点文字。
7.3 从实验脚本到生产脚本
本文的脚本适合学习和小规模分析,但放到生产环境还需要关注以下几点:
- 路径参数化:不要硬编码
"sales_orders.csv",改为通过命令行参数或配置文件传入。 - 日志记录:用
logging替换print,记录脚本每次运行的时间、数据量、清洗前后对比。 - 异常处理:对数据库连接、文件读取、类型转换增加
try-except,避免单条脏数据导致整个流程中断。 - 扩展性:如果图表数量增加,可以把每一张图封装成函数,输入统计表,输出图片文件,便于单元测试。
例如,将柱状图封装为函数后,新增品类时无需修改绘图逻辑,只需修改输入统计表。这样的结构在真实项目中能显著降低维护成本。
8. 下一步可以延伸的方向
运行完本文的案例,你已经掌握了一条基本的数据分析链路:从原始数据、清洗整理、统计聚合到可视化的完整流程。这是很多数据分析和商业报告脚本的通用骨架。
接下来的进阶方向可以根据自己的需求选择:一是把统计结果导出为 Excel 多工作表报告,使用pd.ExcelWriter实现;二是把图表嵌入 HTML 周报,使用savefig保存后由邮件附件发送;三是使用 seaborn 增加热力图、成对图等更复杂的可视化;四是把 pandas 的query、pivot_table和apply应用到更多样的脏数据场景。
建议你直接修改生成数据脚本中的参数,比如增加商品类别、修改日期范围、加入更多缺失值,然后重新运行整个流程,观察图表如何变化。只有数据“脏”过,才知道整理步骤每一步的价值。数据整理和图表生成是互相成就的,前者保证正确,后者放大价值,两者都值得在项目中持续打磨。
如果这篇教程对你有所帮助,建议收藏备用,方便下次处理类似分析任务时直接对照参考。