1. 这个分析项目到底在解决什么问题
先交代一下背景。我接到的任务是分析某电商平台一整年的销售数据,原始数据是几万条订单记录,包含订单号、用户ID、商品类目、成交金额、下单时间、支付方式这些字段。客户的核心诉求有三层:第一,搞清楚整体销售走势和节奏;第二,找到贡献主要营收的商品结构和用户分层;第三,能从数据里挖出几条可以直接指导运营动作的结论,而不是给一堆图表让运营自己猜。
说实话,这类需求在电商数据分析里非常典型。很多初学者拿到数据第一反应就是"跑个折线图看看趋势",但实际做下来会发现,真正有价值的部分往往藏在数据清洗和特征工程的细节里。比如订单表里经常出现重复支付的记录、退款订单没标记、同一用户在不同渠道下的单需要去重,这些问题如果不处理干净,后面所有统计结果都是错的。
我用的工具链很常规:Python 3.10 + pandas 2.0 + NumPy 1.24 + Matplotlib 3.7 + Seaborn 0.12,外加 Jupyter Notebook 做交互式探索。没有上 Spark 或者 Dask 这类分布式框架,原因很简单,数据量是几万条级别,pandas 处理绰绰有余,杀鸡不用牛刀。但如果你是处理千万级以上的订单数据,或者需要做实时指标计算,那确实要考虑换工具,这个后面我会单独讲。
整个项目我拆成了五个阶段:数据探索与清洗、销售趋势分析、商品品类结构分析、用户价值分层、结论与建议输出。每一步都有对应的产出物和验收标准,这样做的好处是每个阶段都能自查,不会到最后发现数据出了问题还要倒回去重做。
2. 数据清洗中那些容易翻车的细节
2.1 从拿到原始数据到能分析的DataFrame
第一步永远是眼睛先看,不要急着写代码。我先用 Excel 打开原始 CSV 文件,看了前几百行,确认字段格式、缺失值情况、有没有乱码。这个动作看似原始,但能帮我对数据质量有个直观判断,比直接用df.info()更真实。
然后才进入 pandas 流程:
import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 设置中文显示,这一步在Mac/Linux/Windows上略有差异 plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'WenQuanYi Micro Hei'] plt.rcParams['axes.unicode_minus'] = False df = pd.read_csv('sales_data.csv', encoding='utf-8') print(df.shape) print(df.info()) print(df.head())这里有一个特别容易踩的坑:CSV 文件的编码。我这次拿到的文件是 UTF-8 编码,但很多电商系统导出的 CSV 其实是 GBK 编码,直接用pd.read_csv('sales_data.csv')会报UnicodeDecodeError,或者读出来全是乱码。解决办法是在read_csv里指定encoding='gbk'或者encoding='gb18030',后者兼容性更好。
2.2 重复订单、退款订单和异常金额的处理
接下来是数据清洗的核心环节。我梳理了四类必须处理的问题:
第一,重复记录。同一笔订单因为系统重试或者用户多次提交,可能在表里出现多行。我按照订单号分组统计出现次数,找出重复项,然后保留第一条。
# 检查重复订单号 dup_count = df.duplicated(subset='order_id', keep=False).sum() print(f'重复订单记录数: {dup_count}') # 删除重复记录,保留第一条 df = df.drop_duplicates(subset='order_id', keep='first')第二,退款订单。电商数据里退款订单很正常,但如果不剔除,销售额会被高估。原始数据里有一个order_status字段,取值包括 "已完成"、"已退款"、"待发货" 等。我把已退款和待发货的订单单独拎出来,分析的时候分成"有效订单"和"全量订单"两个口径。
# 标记退款订单 df['is_refund'] = df['order_status'].apply(lambda x: 1 if x == '已退款' else 0) # 有效订单数据集 df_valid = df[df['is_refund'] == 0].copy() # 对比两个口径的总销售额 total_all = df['amount'].sum() total_valid = df_valid['amount'].sum() print(f'全量销售额: {total_all:.2f}, 有效销售额: {total_valid:.2f}')这里我多说一句,很多分析报告只给一个销售额数字,不说统计口径,这是很不专业的做法。不同口径下得出的结论可能相差很大,所以我在后面所有图表里都统一用有效订单数据,但在汇报时会标注清楚。
第三,异常金额。我遇到过订单金额为负数、金额为 0 但有成交记录、金额超过 10 万的离谱数据。这些一般是测试订单、系统 bug 或者恶意刷单。处理策略是:先看占比,如果占比很小(<1%)就直接剔除;如果占比大,就需要跟业务方确认,不能自己拍脑袋删。
# 剔除异常金额 df = df[(df['amount'] > 0) & (df['amount'] < 100000)]第四,用户 ID 缺失或者格式不一致。有些订单的 user_id 是 NaN,有些是字符串数字混在一起。我统一转成字符串,缺失的直接标记为"unknown_user",后续做用户分析时单独处理。
df['user_id'] = df['user_id'].astype(str).str.strip() df['user_id'] = df['user_id'].fillna('unknown_user')2.3 时间字段的标准化:暗藏玄机的细节
时间字段是本次分析里最容易被忽视、但影响最大的一个环节。原始数据的order_time是字符串格式,类似 "2024-05-12 14:23:56",但其中有几条记录是 "2024/5/12 14:23",还有几条是 "2024-05-12",格式不统一。必须统一转成 pandas 的 datetime 类型:
df['order_time'] = pd.to_datetime(df['order_time'], errors='coerce')这里有个关键参数errors='coerce',遇到无法解析的格式会变成 NaT 而不是直接报错,方便我们定位异常数据。转换后要检查有多少条 NaT,然后决定是丢弃还是手动修正。
时间字段标准化之后,我额外提取了四个衍生字段:年、月、日、星期几。
df['year'] = df['order_time'].dt.year df['month'] = df['order_time'].dt.month df['day'] = df['order_time'].dt.day df['weekday'] = df['order_time'].dt.weekday # 0=周一, 6=周日 df['hour'] = df['order_time'].dt.hour这些衍生字段在后面做趋势分析、星期规律分析和时段分析时会反复用到。尤其是weekday和hour,很多初学同学想不到要提取,结果后面想分析"周末销售是否更好"、"晚上几点下单最多"的时候,又要回头重新处理数据,浪费时间。
2.4 商品类目字段的归类合并
商品类目在原始数据里非常"脏"。同样的商品,有的记录叫"手机",有的叫"智能手机",有的叫"手机通讯"。不统一的话,按类目分组统计时会出现十几个相似类目,图表根本没法看。
我用了一个简单的规则脚本做归一化:先列出所有唯一类目值,然后按关键词映射到顶层类目。
category_map = { '手机': '手机数码', '智能手机': '手机数码', '手机通讯': '手机数码', '笔记本电脑': '电脑办公', '台式机': '电脑办公', '平板': '电脑办公', '上衣': '服饰鞋包', '裤子': '服饰鞋包', '运动鞋': '服饰鞋包', # ... 更多映射 } df['category_group'] = df['category'].map(category_map).fillna('其他')这一步做完,数据清洗才算真正结束。整个过程大概占了整个项目 40% 的时间,这完全正常。很多新手急着画图,结果输出的图表自己都不敢信,就是因为在清洗环节偷了懒。
3. 销售趋势分析:看清大局才能谈细节
3.1 按月销售趋势与"金九银十"验证
数据清洗完成后,第一件事是看整体盘子有多大。
total_revenue = df_valid['amount'].sum() total_orders = df_valid['order_id'].nunique() total_users = df_valid['user_id'].nunique() avg_order_value = total_revenue / total_orders print(f'总销售额: {total_revenue:.2f}') print(f'总订单数: {total_orders}') print(f'总用户数: {total_users}') print(f'客单价: {avg_order_value:.2f}')然后按月汇总销售额和订单量:
df_valid['year_month'] = df_valid['order_time'].dt.to_period('M').astype(str) monthly = df_valid.groupby('year_month').agg( revenue=('amount', 'sum'), order_count=('order_id', 'count') ).reset_index()画折线图,两条线分别表示月销售额和月订单量。我得到的结论是:全年销售呈现明显的季节性波动,9-11月是全年高峰期,12月有明显回落,2月因为春节因素处于低谷。这个规律跟电商行业的"金九银十"说法基本吻合——9月、10月有开学季和国庆促销,11月有双十一大促,三个月的合计销售额占了全年的 45% 左右。
但这里我要提醒一点:看到趋势图上的峰值,不要急着下结论说"这个月做得好"。一定得去查一下这个月是不是有大促活动、是不是有节日、是不是有重点商品上新。数据本身不会告诉你背后的业务原因,需要结合运营日历去解读。我当时就发现 6 月有一个小高峰,后来查了运营记录才知道是年中大促叠加新品发布。
3.2 星期规律与每日时段热力分析
除了月度趋势,我还做了两件很多分析报告里不会深挖的事:星期规律和时段规律。
星期规律是订单量在周一至周日的分布。我原本预想周末会更高,结果实际数据显示周二和周三反而是下单高峰,周末略低。后来分析原因,发现这个电商平台的用户群体以企业采购为主,工作日集中下单,周末反而少。这就是为什么不能凭直觉做业务的判断,必须看数据。
weekday_order = df_valid.groupby('weekday').agg( order_count=('order_id', 'count'), revenue=('amount', 'sum') ).reset_index() weekday_order['weekday_name'] = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']时段规律我用的是小时维度,按"小时-星期"做交叉表,再用 Seaborn 的热力图展示。展示出来之后非常直观,能明显看到工作日上午 10-11 点、下午 3-4 点有两个下单高峰。这个发现对后续运营投放很有价值——广告预算应该集中在这些时间段,客服排班也可以据此调整。
pivot_table = df_valid.pivot_table( index='weekday', columns='hour', values='order_id', aggfunc='count', fill_value=0 ) plt.figure(figsize=(16, 8)) sns.heatmap(pivot_table, cmap='YlOrRd', annot=False) plt.title('星期-小时订单量热力图') plt.show()3.3 趋势分析的几张关键图
我做了四张核心趋势图,分别是:月度销售额折线图、月度订单量柱状图、星期订单量柱状图、星期-小时热力图。这四张图构成了宏观趋势分析的基础。
做图表时有几个注意事项分享给大家:
- 图表标题、坐标轴标签、图例必须齐全,中文显示要提前设置好字体,否则导出图片时中文会变成方框。
- 颜色不要用默认的彩虹色,建议统一用一套色系,比如蓝色系或者企业VI色系,这样图表放在一起比较协调。
- 图表尺寸要设置合理,
plt.figure(figsize=(12, 6))比默认尺寸更适合屏幕阅读。 - 导出的图片要设置
dpi=150以上,否则放到 PPT 或文档里会模糊。
plt.figure(figsize=(12, 6)) plt.plot(monthly['year_month'], monthly['revenue'], marker='o', linewidth=2) plt.title('月度销售额趋势') plt.xlabel('月份') plt.ylabel('销售额(元)') plt.xticks(rotation=45) plt.grid(True, alpha=0.3) plt.tight_layout() plt.savefig('monthly_revenue.png', dpi=150) plt.show()4. 商品品类结构分析:找出真正赚钱的品类
4.1 销售额构成与增速的双维分析
光看总盘子不够,还得看结构。电商运营最关心的问题是:哪些品类贡献了主要营收?哪些品类增速快、有潜力?
销售额构成分析很简单,用groupby按品类分组,计算销售额、订单量、客单价、占比。
category_stats = df_valid.groupby('category_group').agg( revenue=('amount', 'sum'), order_count=('order_id', 'count'), avg_amount=('amount', 'mean') ).reset_index() category_stats['revenue_share'] = category_stats['revenue'] / category_stats['revenue'].sum() category_stats = category_stats.sort_values('revenue', ascending=False)我得到的结果是:手机数码类贡献了总营收的 38%,是绝对的营收主力;电脑办公类占比 22%,排在第二位;服饰鞋包类占比 15%;美妆个护类占比 10%;剩下的是食品生鲜、家居家装等其他品类。
但这里有个问题:占比高的品类不一定值得重点投放,因为可能已经处于存量市场。所以我还算了各品类的月均销售额增速,用销售额构成和增速构成一张二维散点图,横轴是销售占比,纵轴是近半年增速,气泡大小代表订单量。落在右上角的是"明星品类",落在左上角的是"潜力品类",落在右下角的是"现金牛品类",落在左下角的是"瘦狗品类"。这个思路借鉴了波士顿矩阵,但做成了数据驱动的版本。
4.2 客单价的品类差异与运营启示
不同品类的客单价差异非常大。手机数码客单价接近 3000 元,服饰鞋包客单价只有 150 元左右。这意味着它们的运营策略完全不同:高客单价品类要注重信任背书、售后保障、分期付款等降低决策门槛的手段;低客单价品类要注重组合销售、满减优惠、冲动消费刺激。
category_stats['avg_amount'] = category_stats['revenue'] / category_stats['order_count'] category_stats = category_stats.sort_values('avg_amount', ascending=False) print(category_stats[['category_group', 'avg_amount']])这个分析看似简单,但对业务指导意义很大。我当时写报告的时候就专门强调了:手机数码虽然营收占比高,但复购率低,用户购买周期长,不能靠它做用户活跃;服饰鞋包虽然客单价低,但复购频次高,是可以用来拉动用户粘性的品类。运营资源应该往哪个品类倾斜,数据已经给出了方向。
4.3 Top10 热销商品与连带购买分析
我进一步按商品维度做了 Top10 热销排行,输出热销商品的销售额、销量、毛利估算(如果能拿到成本字段的话)。这里有个小小的提醒:热销商品不要只看销售额,还要看销量和毛利率。有些商品销售额高但毛利极低,是引流款;有些商品销售额不高但毛利极高,是利润款。两种商品的价值不同,运营策略也不同。
连带购买分析也是电商分析的一个隐藏亮点。我通过订单明细数据,用关联规则找出了"经常同时出现在一个订单里"的商品组合。比如手机膜和手机壳同时出现的概率很高,笔记本电脑和电脑包、无线鼠标的关联度也很高。这个发现可以直接指导商品推荐的设置,以及详情页的交叉推荐模块。
当然,关联规则分析(比如 Apriori 算法)在这个数据量级下不算难,但如果数据量巨大,需要考虑用 FP-Growth 等更高效的算法。我在这个项目里直接用 pandas 做简单的两两组合计数,已经能满足需求:
from itertools import combinations from collections import Counter # 每个订单的商品组合 order_items = df.groupby('order_id')['product_name'].apply(list) combo_counter = Counter() for items in order_items: if len(items) >= 2: for combo in combinations(sorted(set(items)), 2): combo_counter[combo] += 1 top_combos = combo_counter.most_common(10)5. 用户价值分层:RFM模型的电商实战
5.1 RFM计算过程与分层规则设计
用户分析是电商数据分析的重头戏。我这次采用 RFM 模型:Recency(最近一次购买距今的天数)、Frequency(购买频率)、Monetary(消费金额)。这个模型不新鲜,但非常实用,而且实现难度不高。
计算过程直接用 pandas 的groupby和agg:
import datetime as dt # 确定分析截止日期 current_date = df_valid['order_time'].max() + pd.Timedelta(days=1) rfm = df_valid.groupby('user_id').agg( recency=('order_time', lambda x: (current_date - x.max()).days), frequency=('order_id', 'count'), monetary=('amount', 'sum') ).reset_index()这里有个细节值得注意:current_date我取的是数据里的最大下单时间再加一天,而不是取系统当前日期。原因很简单,这是一份历史数据,用系统当前日期会导致所有用户的 recency 都偏大,失去比较意义。
得到 R、F、M 三个指标后,需要一个分层规则。常见做法是分别计算三个指标的中位数,高于中位数记 1,低于或等于记 0,然后组合成 8 个用户类别。
rfm['R_rank'] = (rfm['recency'] <= rfm['recency'].median()).astype(int) rfm['F_rank'] = (rfm['frequency'] >= rfm['frequency'].median()).astype(int) rfm['M_rank'] = (rfm['monetary'] >= rfm['monetary'].median()).astype(int) def rfm_label(row): if row['R_rank'] == 1 and row['F_rank'] == 1 and row['M_rank'] == 1: return '重要价值用户' elif row['R_rank'] == 1 and row['F_rank'] == 0 and row['M_rank'] == 1: return '重要发展用户' elif row['R_rank'] == 0 and row['F_rank'] == 1 and row['M_rank'] == 1: return '重要保持用户' elif row['R_rank'] == 0 and row['F_rank'] == 0 and row['M_rank'] == 1: return '重要挽留用户' elif row['R_rank'] == 1 and row['F_rank'] == 1 and row['M_rank'] == 0: return '一般价值用户' elif row['R_rank'] == 1 and row['F_rank'] == 0 and row['M_rank'] == 0: return '一般发展用户' elif row['R_rank'] == 0 and row['F_rank'] == 1 and row['M_rank'] == 0: return '一般保持用户' else: return '一般挽留用户' rfm['user_segment'] = rfm.apply(rfm_label, axis=1)这里的分层命名参考了经典 RFM 模型,但实际业务中可以按自己的习惯调整。比如把"重要价值用户"叫作"高价值核心用户","一般挽留用户"叫作"流失风险用户",让业务团队更容易理解。
5.2 用户分层结果与针对性运营建议
分层之后统计每个类别的用户数和贡献金额占比:
segment_stats = rfm.groupby('user_segment').agg( user_count=('user_id', 'count'), total_monetary=('monetary', 'sum') ).reset_index() segment_stats['user_ratio'] = segment_stats['user_count'] / segment_stats['user_count'].sum() segment_stats['monetary_ratio'] = segment_stats['total_monetary'] / segment_stats['total_monetary'].sum()我得到的结果非常有说服力:重要价值用户只占用户总量的 12%,但贡献了总营收的 56%。这就是典型的"二八法则"在电商数据里的呈现。同时,一般挽留用户和重要挽留用户加起来占比接近 30%,这部分用户有流失风险,需要重点关注。
基于分层结果,我给运营团队提了几个具体建议:
- 对重要价值用户:提供专属客服、新品优先体验、积分加倍等权益,维持他们的忠诚度。
- 对重要发展用户:最近有购买且金额不低,但频次低,重点推送高频品类的优惠券,提升购买频次。
- 对重要保持用户:购买频次和金额都很高,但最近没有购买,需要唤醒,比如发送"好久不见"类型的定向优惠券。
- 对重要挽留用户:历史上贡献过较大金额,但最近没买、频次也低了,需要用大额限时优惠做最后的挽留。
- 对一般挽留用户:价值不高且濒临流失,不建议投入过多营销资源。
这些建议不是空话,每一条都能对应到具体的运营动作,这比单纯堆砌"要提升用户粘性"这种正确的废话有价值得多。
5.3 新老用户构成与复购率分析
除了 RFM,我还做了新老用户分析。通过用户第一次下单时间判断是否为新用户,然后统计每个月的用户构成。
df_valid['first_order_time'] = df_valid.groupby('user_id')['order_time'].transform('min') df_valid['is_new_user'] = (df_valid['first_order_time'] == df_valid['order_time']).astype(int)复购率计算逻辑是:统计每个用户的下单次数,下单次数为 1 的是新用户,大于 1 的是复购用户,复购率 = 复购用户数 / 总下单用户数。我那次算出的整体复购率只有 23% 左右,说明这个平台的用户粘性一般,需要通过会员体系、积分体系、定期促销等手段提升复购。
6. 数据可视化与报告输出:图表怎么组合才有效
6.1 图表选型与组合原则
数据分析的价值最终要通过可视化输出,但图表不是越多越好。我遵循的原则是:一张图只回答一个问题。整体趋势用折线图,结构比较用饼图或堆叠柱状图,对比排序用条形图,分布情况用直方图或箱线图,关系分析用散点图。
这次项目里我最终输出的图表组合是:
| 分析模块 | 图表类型 | 回答的问题 |
|---|---|---|
| 月度销售趋势 | 折线图 | 全年销售节奏如何,哪些月份是高峰 |
| 星期规律 | 柱状图 | 一周中哪天订单最多 |
| 时间热力 | 热力图 | 每天的哪个时段订单集中 |
| 品类结构 | 饼图/条形图 | 哪些品类贡献主要营收 |
| 品类客单价对比 | 条形图 | 不同品类的消费水平差异 |
| 用户分层 | 饼图 | 各类用户的构成比例 |
| 用户价值分布 | 散点图 | 用户的消费金额与购买频次关系 |
| Top10商品 | 条形图 | 哪些商品是主力爆款 |
6.2 一组实用的可视化代码示例
下面给出一段生成"品类销售额占比"的完整代码,适合直接套用:
plt.figure(figsize=(10, 8)) colors = sns.color_palette('Blues_r', len(category_stats)) explode = [0.03] * len(category_stats) plt.pie( category_stats['revenue'], labels=category_stats['category_group'], autopct='%.1f%%', startangle=90, colors=colors, explode=explode ) plt.title('各品类销售额占比') plt.axis('equal') plt.tight_layout() plt.savefig('category_pie.png', dpi=150, bbox_inches='tight') plt.show()做热力图的时候注意一点,sns.heatmap的annot参数如果设为 True,会在每个格子里显示数值,但当格子数量很多时会非常拥挤。我一般只在数据量小的时候开annot,像星期-小时这种 7x24 的矩阵,关闭annot反而更清晰。
6.3 报告输出:结论先行、数据佐证
报告输出的核心原则是"结论先行":每一页 PPT 的标题就是一个结论,然后放佐证的图表,最后给具体建议。不要先贴一张大图让读者自己解读,那是自嗨不是汇报。
我的报告结构是:
- 核心结论摘要(一页纸,写最重要的3-5个结论)
- 整体销售趋势
- 品类结构分析
- 用户价值分析
- 运营建议清单
每一部分都用"结论 → 图表 → 数据佐证 → 建议"的格式。这样的报告,老板和业务团队都能快速抓住重点,不用从头到尾自己啃数据。
另外,如果读者需要把分析结果做成交互式看板,可以考虑用 Streamlit 或者 Dash 快速搭建。pandas 负责数据计算,Plotly 负责交互图表,Streamlit 负责页面框架,三件套很快就能出一个像模像样的数据看板。我后来在这个项目基础上做了个简版看板,用 Streamlit 实现下拉框筛选品类、动态展示月度趋势,整个过程不到两个小时。对电商数据的日常监控来说,这个方案比每次重新跑一遍 Jupyter Notebook 高效得多。
7. 整个项目踩过的坑与避坑清单
7.1 排查链路完整复盘:从错误结果倒推到元凶
这个项目最有意思的部分,其实是排查一个"错误的结论"。我在初步分析月度趋势时,发现 3 月份的销售额高得离谱,几乎是其他月份的三倍。当时第一反应是"3月是不是有大促?",但查了运营日历发现并没有。
于是我开始排查。第一步,复查 3 月份的数据量,发现订单数确实多出了几千条。第二步,按小时拆分 3 月份的订单,发现大量订单集中在同一天凌晨 2 点到 5 点,而且金额都接近 9999 元。第三步,抽查这些订单的用户 ID,发现都是同一批测试账号。第四步,查原始数据源头,确认这是运营在做系统压力测试时产生的测试数据,当时没有从线上环境清理掉。
处理方案是:剔除这些测试账号的订单,重新计算月度汇总。剔除后,3 月份的销售额回落到正常水平,整体趋势变得合理。
这个排查过程给我最大的启发是:数据异常不要急着删,先查清楚原因再决定怎么处理。删错了数据,结论就失真了;不删,结论也被污染。最好的做法是先保留异常标记,分析时按字段过滤,而不是直接在原表上删除。
7.2 遇到的常见报错与解决方法
我把这次项目中遇到的几个高频报错整理成了一张表,方便读者对照排查:
| 报错信息 | 原因 | 解决方法 |
|---|---|---|
UnicodeDecodeError | CSV编码不是UTF-8 | 尝试encoding='gbk'或encoding='gb18030' |
SettingWithCopyWarning | 对DataFrame切片后再赋值 | 用.copy()创建显式副本 |
ValueError: cannot convert float NaN to integer | 包含NaN的列转整数 | 先fillna()再转换 |
KeyError: 'xxx' | 列名不存在或拼写错误 | 用df.columns检查列名 |
AttributeError: 'NoneType' object has no attribute 'xxx' | 变量为None | 检查前面的操作是否返回空结果 |
NameError: name 'plt' is not defined | 没导入matplotlib | 执行import matplotlib.pyplot as plt |
其中SettingWithCopyWarning对新手来说最常见。比如:
df_valid = df[df['is_refund'] == 0] df_valid['year_month'] = df_valid['order_time'].dt.to_period('M') # 这里会警告原因是df_valid是df的一个视图而非副本,对它做赋值操作会触发警告。解决办法是在切片时加.copy():
df_valid = df[df['is_refund'] == 0].copy() df_valid['year_month'] = df_valid['order_time'].dt.to_period('M')7.3 关于工具链升级的一些看法
如果你处理的数据量大很多,比如千万级订单量,pandas 单机内存可能会不够用。那要怎么办?我建议按四个档位升级:
- 第一档:优化 pandas 代码,用
category数据类型压缩内存,用vectorized操作替代循环,用chunksize分块读取。 - 第二档:用 Polars,Rust 写的 DataFrame 库,处理速度是 pandas 的数倍,内存占用更小,API 风格跟 pandas 很接近,迁移成本不大。唯一的痛点是中文文档和生态不如 pandas 丰富。
- 第三档:用 Dask 或者 Spark,支持分布式计算,适合真正的大数据场景。但学习成本明显上升,运维也要考虑,不建议小项目硬上。
- 第四档:上数仓体系,用 SQL 做ETL和汇总,用BI工具(如 PowerBI、Tableau)做可视化,Python 只负责复杂建模。
我这个项目里,pandas 完全够用。做电商数据分析,大部分工作量其实在"理解业务 + 清洗数据 + 提炼结论",计算引擎只是工具,工具选型取决于数据规模和团队能力,没必要一味追求大而全。
7.4 能直接抄的经验清单
最后分享几条我在这个项目里沉淀下来的实操经验,每一条都是从踩坑里总结出来的:
- 拿到数据先看
df.describe()和df.isnull().sum(),这两个命令能让你在 10 秒内了解数据的基本健康状况。 - 所有金额字段最好单位统一,要么都是元,要么都是分,混用会导致分析结果差了百倍。
- 做月度趋势时,注意月份的字符串排序问题。
'10月'排在'2月'前面,用 sort 会出乱序。解决办法是先把月份转成整数再排序。 - 写 CSV 导出时用
index=False,否则会多出一列无意义的索引。 - 报告里的每个数字都要能追溯。保留分析代码和中间结果文件,不然过两周有人问"这个数字怎么来的",你可能自己都说不清楚。
另外一个很常见的坑:不要用matplotlib的默认样式直接出图,先用sns.set_theme(style='whitegrid')或者类似配置,图的质量会提升一个档次。这在做汇报时影响很大,同样一份数据,图好不好看,决定了老板愿不愿意认真看。