1. 从“脏数据”到“干净数据”电商数据清洗实战大家好我是老张一个在数据圈摸爬滚打了十来年的老手。今天我们不聊那些高大上的概念就实实在在地用一份真实的电商数据集手把手带你走一遍从数据清洗到商业洞察的全过程。你可能会觉得数据清洗不就是处理缺失值和重复值吗但真实世界的数据可比教科书里的例子“脏”多了。我敢说一个数据分析项目80%的时间都花在了和数据“搏斗”上。今天我们就来打这场硬仗。我手头有一份模拟真实电商业务的数据集里面包含了用户行为、订单详情和商品信息。刚拿到手的时候它长什么样呢我来给你描述一下日期格式五花八门有“2023-01-01”也有“2023/1/1”甚至还有“Jan-1-2023”用户ID里混着奇怪的字符商品价格有的写着“99.9”有的写着“99.9元”还有的干脆是空值订单状态有“已支付”、“已完成”、“已发货”还有拼写错误的“以支付”。看到这样的数据新手可能头都大了但对我们来说这恰恰是宝藏的原始形态。清洗数据就是把这些粗糙的矿石打磨成可以精炼的原料。1.1 数据加载与初步“体检”第一步当然是把它读进Pandas里看看。这里我强烈建议不要一上来就用pd.read_csv默认参数多花几秒钟设置一下能省去后面很多麻烦。import pandas as pd import numpy as np # 加载数据注意编码和日期解析 df pd.read_csv(ecommerce_data.csv, encodingutf-8, parse_dates[order_date, user_reg_date]) print(数据形状行列:, df.shape) print(\n--- 前5行数据预览 ---) print(df.head()) print(\n--- 数据基本信息 ---) print(df.info()) print(\n--- 数值型字段统计摘要 ---) print(df.describe())运行这几行代码你就能对数据有个全局的“体检报告”。df.info()会告诉你每一列有多少非空值以及数据类型。这里我踩过坑有时候Pandas会把明明是数字的列识别成object字符串原因可能是里面混了中文单位或者空格。df.describe()则专注于数值列你能一眼看出最大值、最小值、平均值有时候一个9999999的异常价格就这么暴露了。1.2 处理缺失值不是简单删除那么简单看到缺失值NaN很多人的第一反应是df.dropna()。但在商业数据里直接删除可能会损失大量有价值的信息。我们需要像侦探一样分析缺失的原因。# 1. 查看缺失情况 missing_summary df.isnull().sum().sort_values(ascendingFalse) missing_percentage (df.isnull().sum() / len(df) * 100).sort_values(ascendingFalse) missing_df pd.DataFrame({缺失数量: missing_summary, 缺失百分比%: missing_percentage}) print(各字段缺失情况统计) print(missing_df[missing_df[缺失数量] 0]) # 2. 针对不同列采取不同策略 # 对于“商品评论”这类文本字段缺失可能代表用户未评论填充为“无” df[product_review].fillna(无, inplaceTrue) # 对于“用户年龄”这类数值字段如果缺失不多可以用中位数填充比均值更抗异常值干扰 if df[user_age].isnull().sum() len(df)*0.05: # 缺失率小于5% df[user_age].fillna(df[user_age].median(), inplaceTrue) else: # 缺失太多考虑新增一个“是否年龄缺失”的布尔特征这本身可能就是信息 df[is_age_missing] df[user_age].isnull() # 然后再用中位数填充或者根据其他特征如用户等级分组填充 df[user_age].fillna(df.groupby(user_level)[user_age].transform(median), inplaceTrue) # 对于关键ID如“订单ID”原则上不应缺失如果缺失则整行删除 df.dropna(subset[order_id], inplaceTrue)这里的关键是分而治之。对于“配送地址”缺失我们可以联系业务方看是否是“自提”订单对于“优惠券金额”缺失填充为0可能比删除更合理因为大部分订单本来就没用优惠券。记住填充方法没有绝对的对错只有是否贴合业务场景。1.3 处理异常值与格式统一异常值不一定是错误但需要我们特别关注。格式混乱则是影响分析的“慢性毒药”。# 1. 异常值检测基于业务逻辑 # 假设商品单价不可能低于0.1元或高于100000元 price_valid (df[product_price] 0.1) (df[product_price] 100000) df df[price_valid].copy() # 使用.copy()避免后续操作出现警告 # 或者更温和地将其设为缺失值再处理 # df.loc[~price_valid, product_price] np.nan # df[product_price].fillna(df[product_price].median(), inplaceTrue) # 2. 文本格式清洗 # 去除用户昵称、地址等字段的首尾空格 df[user_name] df[user_name].str.strip() # 统一商品分类的大小写 df[product_category] df[product_category].str.title() # 每个单词首字母大写 # 3. 日期格式统一虽然在read_csv时用了parse_dates但可能还有漏网之鱼 # 如果还有列为object的日期可以强制转换 df[payment_date] pd.to_datetime(df[payment_date], errorscoerce) # errorscoerce将转换失败的设为NaT处理完这些你的数据已经“顺眼”多了。但这还不够我们还需要处理重复值。真正的重复行所有字段都相同很少见更多是“业务意义上的重复”比如同一个用户短时间内产生的两条完全一样的订单记录这很可能是系统错误。# 检查并删除完全重复的行 initial_rows len(df) df.drop_duplicates(inplaceTrue) print(f删除了 {initial_rows - len(df)} 条完全重复的记录。) # 检查关键业务ID是否重复如订单ID应唯一 if df[order_id].duplicated().any(): print(警告发现重复的订单ID需要进一步排查业务逻辑。)数据清洗到这里算是完成了第一阶段。我们得到了一份相对干净、规整的DataFrame。但这只是开始就像做饭前洗好了菜接下来才是切配和烹饪让数据真正产生价值。2. 探索性数据分析用Pandas“看透”你的业务数据清洗干净了就像拿到了一张清晰的地图。探索性数据分析就是让你拿着这张地图先不设定具体目的地到处走走看看发现地形地貌、资源分布。在Pandas里我们不用复杂的算法就用基本的统计和可视化来回答一些最直接的业务问题我们的用户是谁他们喜欢买什么什么时候买得多哪里是业绩的“粮仓”2.1 用户画像初探谁在买我们的东西用户是业务的根本。我们先从用户维度切入。# 1. 用户基础统计 print(f总用户数{df[user_id].nunique()}) print(f总订单数{len(df)}) print(f平均每个用户订单数订单频次{len(df) / df[user_id].nunique():.2f}) # 2. 用户地域分布 user_region_dist df.groupby(user_region)[user_id].nunique().sort_values(ascendingFalse) print(\n--- 用户地域分布按用户数---) print(user_region_dist.head(10)) # 看前十大用户来源地 # 可视化 import matplotlib.pyplot as plt plt.figure(figsize(10,6)) user_region_dist.head(10).plot(kindbarh, colorskyblue) # 用水平条形图更清晰 plt.title(Top 10 用户来源地区) plt.xlabel(用户数量) plt.tight_layout() plt.show() # 3. 用户消费能力分层基于订单金额 # 计算每个用户的累计消费 user_total_spent df.groupby(user_id)[order_amount].sum().sort_values(ascendingFalse) # 划分用户层级例如高价值Top 20%、中价值、低价值 high_value_threshold user_total_spent.quantile(0.8) print(f\n高价值用户消费前20%门槛{high_value_threshold:.2f} 元) high_value_users user_total_spent[user_total_spent high_value_threshold] print(f高价值用户数量{len(high_value_users)}贡献总销售额占比{(high_value_users.sum() / user_total_spent.sum()):.2%})通过这几步你马上就能得到一些关键洞察比如可能你会发现80%的销售额来自20%的用户这就是经典的二八法则。或者发现某个地区的用户数增长迅猛但客单价偏低这或许就是下一个需要重点运营的市场。2.2 商品分析什么好卖什么赚钱接下来我们把目光投向商品。老板最常问的两个问题就是“啥卖得最好”和“啥最赚钱”这两个问题的答案可能完全不同。# 1. 热销商品按销量 top_products_by_quantity df.groupby(product_name)[quantity].sum().sort_values(ascendingFalse).head(10) print(--- 销量Top 10 商品 ---) print(top_products_by_quantity) # 2. 畅销商品按销售额 top_products_by_sales df.groupby(product_name)[order_amount].sum().sort_values(ascendingFalse).head(10) print(\n--- 销售额Top 10 商品 ---) print(top_products_by_sales) # 3. 高利润商品按利润额 # 假设数据中有‘profit’列或可通过‘order_amount’和‘cost’计算 df[profit] df[order_amount] - df[product_cost] * df[quantity] # 简单计算毛利润 top_products_by_profit df.groupby(product_name)[profit].sum().sort_values(ascendingFalse).head(10) print(\n--- 利润额Top 10 商品 ---) print(top_products_by_profit) # 4. 对比分析创建一个对比表格 summary_df pd.DataFrame({ 销量排名: top_products_by_quantity.rank(methodmin, ascendingFalse).astype(int), 销售额排名: top_products_by_sales.rank(methodmin, ascendingFalse).astype(int), 利润额排名: top_products_by_profit.rank(methodmin, ascendingFalse).astype(int) }).head(10) print(\n--- 商品综合表现对比排名数字越小越好---) print(summary_df)这个对比表格非常有用。你可能会发现某款商品销量冲进了前五但利润排名却在二十开外这说明它可能是在“赔本赚吆喝”用于引流。而另一款商品销量一般但利润排名很高属于“闷声发大财”的利润奶牛应该给予更多库存和曝光支持。2.3 时间序列分析生意有什么规律电商业务有很强的周期性。分析时间趋势能帮助我们预测未来、备货、策划营销活动。# 1. 将订单日期设为索引方便重采样 df_time df.set_index(order_date) # 2. 按天、周、月聚合销售额和订单量 daily_sales df_time[order_amount].resample(D).sum() weekly_orders df_time[order_id].resample(W).count() # 按周统计订单数 monthly_profit df_time[profit].resample(M).sum() # 3. 绘制趋势图 fig, axes plt.subplots(3, 1, figsize(14, 10)) daily_sales.plot(axaxes[0], title日销售额趋势, colorgreen, linewidth1) axes[0].set_ylabel(销售额元) weekly_orders.plot(axaxes[1], title周订单量趋势, colororange, markero) axes[1].set_ylabel(订单数) monthly_profit.plot(axaxes[2], title月利润趋势, colorred, kindbar) axes[2].set_ylabel(利润元) plt.tight_layout() plt.show() # 4. 分析周内效应星期几卖得好 df[order_weekday] df[order_date].dt.day_name() # 获取星期名称 weekday_sales df.groupby(order_weekday)[order_amount].sum() # 按星期顺序排序 weekday_order [Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday] weekday_sales weekday_sales.reindex(weekday_order) weekday_sales.plot(kindbar, title不同星期几的销售额, colorpurple) plt.ylabel(销售额元) plt.tight_layout() plt.show()从时间序列图里你可能会发现明显的周末效应、季度性高峰如618、双11甚至是每天下午3点有个小高峰。这些规律就是指导你安排客服人力、投放广告、进行秒杀活动的黄金依据。3. 深度洞察与特征工程从“是什么”到“为什么”探索性分析告诉我们“是什么”比如“A商品卖得好”。但商业决策更需要知道“为什么”以及“接下来怎么办”。这就需要我们进行更深入的分析和特征工程挖掘数据背后的关联。3.1 用户行为关联分析买了A的人还会买什么这就是经典的购物篮分析可以帮助我们做商品推荐和关联促销。# 1. 首先我们需要将数据整理成“每个订单购买了哪些商品”的格式 # 假设原始数据是订单明细一行代表一个商品在一个订单里的记录 basket df.groupby([order_id, product_name])[quantity].sum().unstack(fill_value0) # 这里得到的basket是一个DataFrame行是订单列是商品值是购买数量 print(basket.head()) # 2. 简化我们只关心“是否购买”将数量大于0的转为1 basket_sets basket.applymap(lambda x: 1 if x 0 else 0) # 3. 计算商品之间的共同出现频率简单版 # 这里我们可以计算两两商品同时出现在一个订单中的次数 from itertools import combinations co_occurrence {} product_list basket_sets.columns for prod_a, prod_b in combinations(product_list, 2): # 计算同时包含商品A和商品B的订单数 co_count ((basket_sets[prod_a] 1) (basket_sets[prod_b] 1)).sum() if co_count 10: # 设置一个最小阈值避免偶然性 co_occurrence[(prod_a, prod_b)] co_count # 4. 找出最常一起购买的商品组合 sorted_co sorted(co_occurrence.items(), keylambda x: x[1], reverseTrue)[:10] print(\n--- 最常一起购买的商品组合Top 10 ---) for (prod_a, prod_b), count in sorted_co: print(f{prod_a} {prod_b}: 共同出现在 {count} 个订单中)这个分析结果可以直接指导运营把高频共现的商品放在同一个专题页、打包成套餐、或者设置交叉优惠券能有效提升客单价。3.2 构建用户价值标签RFM模型实战RFM最近一次消费Recency消费频率Frequency消费金额Monetary是衡量用户价值最经典的模型。我们用Pandas可以轻松实现。# 假设当前分析日期是数据集里最新的日期 current_date df[order_date].max() # 1. 计算每个用户的R、F、M值 rfm df.groupby(user_id).agg({ order_date: lambda x: (current_date - x.max()).days, # Recency: 最近一次消费距今天数 order_id: count, # Frequency: 订单总数 order_amount: sum # Monetary: 总消费金额 }).rename(columns{order_date: Recency, order_id: Frequency, order_amount: Monetary}) print(rfm.head()) # 2. 对R、F、M进行打分这里采用简单的四分位数分箱 # Recency越小越好所以打分逻辑相反 rfm[R_Score] pd.qcut(rfm[Recency], q4, labels[4, 3, 2, 1]) # 1分最差4分最好 rfm[F_Score] pd.qcut(rfm[Frequency], q4, labels[1, 2, 3, 4]) rfm[M_Score] pd.qcut(rfm[Monetary], q4, labels[1, 2, 3, 4]) # 3. 组合RFM分数 rfm[RFM_Group] rfm[R_Score].astype(str) rfm[F_Score].astype(str) rfm[M_Score].astype(str) # 4. 定义经典用户分层简化版 def rfm_segment(row): if row[R_Score] 3 and row[F_Score] 3 and row[M_Score] 3: return 重要价值用户 elif row[R_Score] 3 and row[F_Score] 3 and row[M_Score] 3: return 重要发展用户 elif row[R_Score] 3 and row[F_Score] 3 and row[M_Score] 3: return 重要保持用户 elif row[R_Score] 3 and row[F_Score] 3 and row[M_Score] 3: return 重要挽留用户 else: return 一般用户 rfm[User_Segment] rfm.apply(rfm_segment, axis1) # 5. 查看分层结果 segment_counts rfm[User_Segment].value_counts() print(\n--- 用户RFM分层结果 ---) print(segment_counts) segment_counts.plot(kindpie, autopct%1.1f%%, figsize(8,8)) plt.title(用户价值分层占比) plt.ylabel() plt.show()有了这个分层运营策略就清晰了对“重要价值用户”提供VIP服务和专属优惠保持其忠诚度对“重要挽留用户”要主动触达发放大额优惠券尝试挽回对“重要发展用户”则鼓励其提高购买频率。3.3 营销活动效果评估你的钱花得值吗假设数据集中有标记每次订单是否属于某次营销活动如‘promotion_id’。我们可以评估活动的拉新、促活、增收效果。# 1. 对比活动期间与非活动期间的指标 df[is_promotion] df[promotion_id].notnull() # 假设有促销活动的订单会记录活动ID promotion_stats df.groupby(is_promotion).agg({ order_id: count, user_id: nunique, order_amount: [sum, mean], profit: sum }) promotion_stats.columns [订单数, 下单用户数, 总销售额, 客单价, 总利润] print(--- 营销活动效果对比 ---) print(promotion_stats) # 2. 计算活动的增量效果简化估算 # 假设活动前同期如上一周的日均数据作为基线 baseline_avg_order 1000 # 这里应是计算出的真实基线值 baseline_avg_user 200 promotion_days 7 incremental_orders promotion_stats.loc[True, 订单数] - (baseline_avg_order * promotion_days) incremental_sales promotion_stats.loc[True, 总销售额] - (baseline_avg_order * promotion_days * promotion_stats.loc[False, 客单价]) print(f\n估算活动带来的增量订单{incremental_orders:.0f}) print(f估算活动带来的增量销售额{incremental_sales:.2f}元) # 3. 分析不同活动对不同用户群体的吸引力 if User_Segment in df.columns: # 如果前面已经合并了用户分层信息 promotion_segment df[df[is_promotion]].groupby(User_Segment)[order_id].count() print(\n--- 参与活动的用户分层 ---) print(promotion_segment)通过这样的分析你就能用数据告诉市场部这次活动总共带来了多少新客户刺激了多少老客户复购投入产出比大概是多少。哪些类型的用户对活动最敏感下次可以针对性地投放。4. 高效技巧与性能优化让Pandas飞起来当数据量变大或者分析步骤变复杂时你会感觉Pandas有点“慢”。这不是Pandas的错可能是我们的使用方法需要优化。这里分享几个我实战中总结的提速技巧。4.1 选择正确的数据类型省内存就是省时间Pandas默认的数据类型可能不是最省空间的。尤其是对于分类数据如城市、产品类别使用category类型可以极大提升速度和节省内存。# 查看当前数据类型和内存使用 print(df.info(memory_usagedeep)) # 转换对象列为分类类型 categorical_columns [user_region, product_category, order_status] for col in categorical_columns: if col in df.columns: df[col] df[col].astype(category) # 转换整数列为更小的类型如果值范围允许 # 例如用户年龄范围0-120用int8就够了 if df[user_age].max() 128 and df[user_age].min() -128: df[user_age] df[user_age].astype(int8) # 再次查看内存优化效果 print(\n优化后的内存使用) print(df.info(memory_usagedeep))我处理过一个几千万行的数据集通过优化数据类型内存占用从8GB降到了不到2GB后续所有操作都快了好几倍。4.2 避免循环使用向量化操作和内置函数这是Pandas性能优化的核心原则。能不用for循环就不用。# 慢的方式使用iterrows() # for index, row in df.iterrows(): # df.loc[index, new_col] row[amount] * 0.1 # 快的方式向量化运算 df[discount_10pct] df[order_amount] * 0.1 # 更复杂的条件判断使用np.where或applyapply也比循环快 df[price_level] np.where(df[product_price] 1000, 高价, np.where(df[product_price] 100, 中价, 低价)) # 或者使用Pandas的cut函数进行分箱 df[price_bin] pd.cut(df[product_price], bins[0, 50, 200, 1000, float(inf)], labels[超低价, 低价, 中价, 高价]) # 分组聚合时尽量使用内置的agg函数而不是在组内循环 result df.groupby(product_category).agg({ order_amount: [sum, mean, count], profit: sum }).round(2)4.3 处理超大文件分块读取与处理当CSV文件大到内存装不下时不要慌我们可以分块处理。# 方法1分块读取逐块处理并筛选 chunk_size 100000 # 每次读10万行 filtered_chunks [] for chunk in pd.read_csv(huge_ecommerce_data.csv, chunksizechunk_size, encodingutf-8): # 在每一块上执行数据清洗和筛选 chunk_clean chunk[chunk[order_amount] 0] # 例如过滤掉金额为0的异常订单 chunk_clean[order_date] pd.to_datetime(chunk_clean[order_date], errorscoerce) filtered_chunks.append(chunk_clean) # 将所有处理好的块合并 df_clean pd.concat(filtered_chunks, ignore_indexTrue) print(f处理后的数据大小{df_clean.shape}) # 方法2如果只是需要聚合统计可以边读边聚合不保存所有数据 total_sales 0 user_set set() for chunk in pd.read_csv(huge_ecommerce_data.csv, chunksizechunk_size, usecols[user_id, order_amount]): total_sales chunk[order_amount].sum() user_set.update(chunk[user_id].unique()) print(f总销售额{total_sales}) print(f总用户数{len(user_set)})4.4 利用.eval()和.query()进行高效筛选对于复杂的布尔条件筛选使用.query()方法不仅写法简洁而且效率往往更高尤其是结合numexpr引擎时。# 传统写法 # df_complex df[(df[order_amount] 100) (df[user_region].isin([北京,上海])) (df[order_date] 2023-06-01)] # 使用.query()更清晰有时更快 df_complex df.query(order_amount 100 and user_region in [北京, 上海] and order_date 2023-06-01) # 对于涉及多个列的计算.eval()可以加速 df[total_value] df.eval(order_amount * quantity - discount)这些技巧不是一蹴而就的都是在处理真实项目、被数据“折磨”的过程中积累下来的。记住数据分析不是炫技最终目的是为了解决问题。当你用清晰的图表和扎实的数据向业务方讲明白“为什么这个品类要增加备货”、“为什么那个促销活动应该停止”时Pandas就从你手中的一个工具变成了驱动业务增长的引擎。这份电商数据集的分析之旅到此告一段落但其中用到的方法论——从清洗、探索、挖掘到优化可以套用到绝大多数数据分析场景中。多练多思考业务你就能越来越得心应手。