Kaggle电商数据集实战用Pandas搞定90%的数据清洗难题附完整代码刚接触Kaggle上的电商数据集很多人会被那份看似规整的CSV文件迷惑。打开一看Description字段里混杂着大小写和奇怪的标点StockCode里藏着字母Quantity冷不丁冒出个负值还有将近四分之一的CustomerID神秘失踪。这感觉就像收到一份包装精美的礼物拆开却发现里面零件散落一地还缺了几个关键螺丝。数据清洗这个听起来有些枯燥的“脏活累活”恰恰是决定你后续分析是建在沙滩上还是岩石上的关键一步。今天我们不谈那些高大上的算法就扎扎实实地用Pandas这把瑞士军刀把Kaggle上经典的“E-Commerce Data”数据集从头到脚清理一遍。我会带你绕过我踩过的坑分享那些文档里不会写的实战技巧让你真正掌握处理真实世界混乱数据的核心能力。1. 数据初探打开潘多拉魔盒前的必备检查在动手清洗任何数据之前莽撞地直接修改列或删除行是最大的忌讳。你得先像个侦探一样对数据的全貌、结构和潜在问题有一个系统的了解。对于这个电商数据集我们的第一次“体检”必须全面。首先用pd.read_csv加载数据时就有几个细节决定了后续工作的难易程度。这个数据集包含非ASCII字符比如商品描述中的特殊符号直接使用默认的utf-8编码会报错。正确的做法是指定encodingISO-8859-1。另外为了避免Pandas自动将某些数字字符串如以0开头的StockCode误判为整数初次导入时将所有列视为字符串dtypestr是个稳妥的策略等看清全貌后再进行类型转换。import pandas as pd import numpy as np # 加载数据注意编码和初始数据类型 df pd.read_csv(E-Commerce Data.csv, encodingISO-8859-1, dtypestr) print(f数据集形状: {df.shape}) print(df.head())执行上面几行代码你就能看到数据的前几行。但head()只能给你一个模糊的印象真正的洞察来自info()和describe()。# 1. 查看数据整体信息列名、非空值数量、数据类型 print(df.info()) # 2. 查看数值型列的统计摘要虽然目前是object类型但Pandas的describe会尝试转换 print(df.describe())运行df.info()你会立刻发现两个警报Description和CustomerID存在大量缺失值。而df.describe()的输出更令人吃惊——Quantity数量和UnitPrice单价的最小值居然是负数最大值又大得离谱80995件商品38970的单价。这显然不是正常的交易数据。提示在数据清洗的早期养成将原始数据备份的习惯。例如df_raw df.copy()这样无论后续操作如何你始终有一份原始数据可供回溯。仅仅几分钟的初探我们就已经锁定了几个主攻方向缺失值、异常值负值和极大值、错误的数据类型以及后续需要深入处理的文本混乱问题。这张“问题清单”就是我们接下来的行动路线图。2. 缺失值处理不仅仅是简单删除面对缺失值很多新手的第一反应是df.dropna()一键删除。这在某些情况下可行但在这个数据集中我们需要更精细的策略。CustomerID缺失率高达25%直接删除会损失大量样本而Description缺失较少但它是重要的商品信息。2.1 量化与定位缺失首先我们需要精确地知道每个列缺失了多少。# 计算每列的缺失值数量和比例 missing_count df.isnull().sum() missing_percentage (missing_count / len(df)) * 100 # 创建一个清晰的表格来展示 missing_df pd.DataFrame({ 缺失数量: missing_count, 缺失比例(%): missing_percentage.round(2) }) print(missing_df.sort_values(缺失比例(%), ascendingFalse))列名缺失数量缺失比例(%)CustomerID13508024.93Description14540.27Country00.00.........这个表格清晰地告诉我们CustomerID是缺失的重灾区。盲目删除不可取我们需要探究缺失的模式。这些CustomerID为空的记录是否集中在某些特定的StockCode如赠品、运费或InvoiceNo如以‘C’开头的取消订单中# 检查CustomerID缺失的记录其InvoiceNo是否多为取消订单 missing_customer_df df[df[CustomerID].isnull()] canceled_in_missing missing_customer_df[InvoiceNo].str.startswith(C).mean() print(f在CustomerID缺失的记录中属于取消订单(C开头)的比例: {canceled_in_missing:.2%}) # 检查Description缺失的记录是否有其他特征 missing_desc_df df[df[Description].isnull()] print(fDescription缺失的记录对应的常见StockCode:\n{missing_desc_df[StockCode].value_counts().head()})如果发现缺失CustomerID的记录大部分是取消订单或系统操作如“POST”代表邮费那么我们可以为这些记录创建一个特殊的客户标识如“ANONYMOUS”或“SYSTEM”而不是直接删除这样可以保留这些交易行为用于整体销售分析。2.2 针对性填补策略基于分析我们可以制定策略CustomerID对于非取消订单的缺失若业务上允许可考虑用“未知客户”填充对于明确是取消订单或系统费用的可归类为“系统交易”。这里我们演示一种方法将缺失CustomerID且非取消订单的记录填充为-1一个明确的标识同时保留“取消订单”这一信息供后续分析。# 首先标记取消订单 df[Is_Canceled] df[InvoiceNo].astype(str).str.startswith(C) # 处理CustomerID缺失非取消订单的填充为-1取消订单的填充为-999代表系统取消 df.loc[df[CustomerID].isnull() ~df[Is_Canceled], CustomerID] -1 df.loc[df[CustomerID].isnull() df[Is_Canceled], CustomerID] -999 # 转换CustomerID为数值类型注意原来有字符串现在引入了负整数标识 df[CustomerID] pd.to_numeric(df[CustomerID], errorscoerce)Description商品描述缺失较少我们可以尝试用StockCode进行匹配填补。先构建一个StockCode到最常见Description的映射字典。# 创建StockCode到Description的映射取非缺失描述中出现频率最高的 desc_map df.dropna(subset[Description]).groupby(StockCode)[Description].agg(lambda x: x.mode()[0] if not x.mode().empty else UNKNOWN).to_dict() # 用映射填补缺失的Description df[Description] df.apply( lambda row: desc_map.get(row[StockCode], UNKNOWN) if pd.isnull(row[Description]) else row[Description], axis1 )处理完缺失值后再次运行df.isnull().sum()你应该会看到除了我们特意保留为NaN的极少数无法匹配的情况大部分缺失问题已得到解决。这种基于业务逻辑的填补远比粗暴删除更能保留数据的信息量和真实性。3. 异常值检测与清洗揪出数据中的“坏蛋”电商数据中的异常值往往不是随机错误而是蕴含着特殊的业务含义如取消订单、退货、赠品、系统调整。我们的目标不是消灭它们而是识别、理解并妥善处理。3.1 数值型异常负值与天文数字Quantity和UnitPrice的负值是最显眼的异常。我们先将其分离出来仔细审视。# 找出Quantity或UnitPrice为负的所有记录 negative_records df[(df[Quantity].astype(float) 0) | (df[UnitPrice].astype(float) 0)] print(f负值记录总数: {len(negative_records)}) print(negative_records[[InvoiceNo, StockCode, Description, Quantity, UnitPrice, Is_Canceled]].head(10))查看这些记录你很可能发现它们大多与Is_Canceled为True发票号以‘C’开头相关。这是合理的取消订单或退货时系统会记录一笔负的数量和价格来冲销原交易。因此不能简单地删除所有负值记录否则会破坏财务数据的完整性。正确的做法是将正负交易关联起来分析或者在后期的分析中根据分析目标决定是否包含取消订单。例如在计算净销售额时需要包含这些负值在分析客户购买行为时可能需要过滤掉取消的订单。对于极大值如Quantity为80995这很可能是数据录入错误、批发订单或系统错误。我们可以通过分位数来识别极端值。# 将Quantity和UnitPrice转换为数值类型 df[Quantity] pd.to_numeric(df[Quantity], errorscoerce) df[UnitPrice] pd.to_numeric(df[UnitPrice], errorscoerce) # 计算99.9%分位数识别极端大的值 Q_high df[Quantity].quantile(0.999) P_high df[UnitPrice].quantile(0.999) print(fQuantity的99.9%分位数: {Q_high}) print(fUnitPrice的99.9%分位数: {P_high}) # 查看超过该分位数的记录 extreme_qty df[df[Quantity] Q_high] extreme_price df[df[UnitPrice] P_high] print(fQuantity极端值记录数: {len(extreme_qty)}) print(fUnitPrice极端值记录数: {len(extreme_price)})对于这些极端值需要结合StockCode和Description判断。如果StockCode是“BANK CHARGES”、“POST”等非商品代码或是Description中包含“AMAZON FEE”、“ADJUSTMENT”等字样则可能是合理的系统记录。否则可以视为异常考虑用中位数或分位数进行截断Winsorization或者直接标记并排除在后续的特定分析之外。3.2 文本型异常StockCode里的“暗号”StockCode本应是商品编号但数据中混入了“POST”、“D”、“BANK CHARGES”等字母代码。这些通常代表邮费、折扣、银行手续费等非商品项。我们需要识别并分类处理它们。# 使用正则表达式找出包含字母的StockCode special_stock_codes df[df[StockCode].str.contains(r^[A-Za-z], naFalse)][StockCode].unique() print(特殊的非纯数字StockCode:) print(special_stock_codes) # 为这些特殊代码创建一个分类标签 df[StockCode_Type] NORMAL # 默认是正常商品 df.loc[df[StockCode].isin([POST, DOT, C2, M]), StockCode_Type] POSTAGE/DELIVERY df.loc[df[StockCode] D, StockCode_Type] DISCOUNT df.loc[df[StockCode] BANK CHARGES, StockCode_Type] BANK_FEE df.loc[df[StockCode].isin([PADS, CRUK]), StockCode_Type] MISC_FEE # 查看分类统计 print(df[StockCode_Type].value_counts())通过添加StockCode_Type这一列我们不仅清洗了数据还丰富了数据的信息维度。在后续分析中我们可以轻松地过滤掉非商品交易或者单独分析各类费用的构成。4. 文本数据标准化让Description字段从混乱到清晰Description商品描述字段是文本清洗的典型战场。它由人工输入充满了不一致大小写混用、多余空格、标点符号随意、同一商品有多种表述。不处理它会影响基于文本的分析如商品分类、搜索的准确性。4.1 基础清洗大小写、空格与标点第一步是执行标准的文本规范化操作。我们使用Pandas的字符串方法链式操作高效完成这些任务。# 1. 转换为小写 (确保一致性) df[Description_Cleaned] df[Description].str.lower() # 2. 移除字符串两端的空格 df[Description_Cleaned] df[Description_Cleaned].str.strip() # 3. 将多个连续空格替换为单个空格 df[Description_Cleaned] df[Description_Cleaned].str.replace(r\s, , regexTrue) # 4. 处理常见的标点符号和特殊字符根据实际情况调整 # 例如移除尾随的句点但保留单词中的连字符 df[Description_Cleaned] df[Description_Cleaned].str.replace(r\.$, , regexTrue) # 移除末尾的句号 df[Description_Cleaned] df[Description_Cleaned].str.replace(r[^\w\s-], , regexTrue) # 移除非单词、非空格、非连字符的字符 print(清洗前后对比示例:) print(df[[Description, Description_Cleaned]].head(10))4.2 处理同义词与缩写基础清洗后更深层次的问题是同一商品的不同表达。例如“white hanging heart t-light holder”和“white hanging heart tealight holder”可能指的是同一商品。这需要领域知识或外部映射表。一个实用的方法是利用StockCode进行分组检查同一代码下的描述变体。# 找出同一个StockCode下有多少种不同的描述 desc_variation df.groupby(StockCode)[Description_Cleaned].nunique().sort_values(ascendingFalse) high_variation_codes desc_variation[desc_variation 1].index.tolist() print(f有{len(high_variation_codes)}个StockCode对应多于1种描述。) # 查看一个例子 example_code high_variation_codes[0] print(f\nStockCode {example_code} 的所有描述变体:) print(df[df[StockCode]example_code][Description_Cleaned].unique())对于变体较少的情况可以手动创建映射规则进行替换。对于大型数据集可以考虑使用文本相似度算法如TF-IDF结合余弦相似度自动聚类相似的描述。这里我们演示一个简单的基于关键字的替换规则# 定义一个简单的同义词替换字典 synonym_map { tealight: t-light, christmas : xmas , # 注意空格避免替换单词中间部分 colour: color, holder: hold, # ... 可根据数据实际情况不断扩充 } def replace_synonyms(text): if isinstance(text, str): for wrong, right in synonym_map.items(): text text.replace(wrong, right) return text df[Description_Cleaned] df[Description_Cleaned].apply(replace_synonyms)经过这些步骤Description_Cleaned字段就变得干净、一致多了为后续的文本分析或作为机器学习特征打下了坚实基础。5. 日期与类型转换为分析铺平道路原始数据中的InvoiceDate是字符串并且包含日期和时间。Quantity和UnitPrice目前是object类型。我们需要将它们转换为合适的类型以便进行时间序列分析和数值计算。5.1 智能解析日期时间Pandas的pd.to_datetime函数非常强大能自动解析多种日期格式。但为了确保万无一失特别是处理跨国数据时明确指定格式或让Pandas自动推断是更好的选择。# 方法1让Pandas自动推断格式通常很有效 df[InvoiceDate] pd.to_datetime(df[InvoiceDate], infer_datetime_formatTrue, errorscoerce) # 方法2如果知道确切格式可以指定以提升性能和准确性 # df[InvoiceDate] pd.to_datetime(df[InvoiceDate], format%m/%d/%Y %H:%M, errorscoerce) print(f转换后InvoiceDate的数据类型: {df[InvoiceDate].dtype}) print(f日期范围: {df[InvoiceDate].min()} 到 {df[InvoiceDate].max()}) # 从日期时间中提取有用的特征 df[InvoiceYearMonth] df[InvoiceDate].dt.to_period(M) # 年月周期便于按月度聚合 df[InvoiceHour] df[InvoiceDate].dt.hour # 交易小时分析购买时段 df[InvoiceDayOfWeek] df[InvoiceDate].dt.dayofweek # 星期几 (0周一) df[InvoiceDayOfWeekName] df[InvoiceDate].dt.day_name() # 星期几名称注意errorscoerce参数会将无法解析的日期转换为NaTNot a Time。转换后务必检查是否有新的缺失值产生print(df[InvoiceDate].isnull().sum())。5.2 数值类型转换与验证转换Quantity和UnitPrice为数值类型后我们还需要进行合理性验证。例如单价为0的记录可能是赠品或数据错误需要单独审视。# 转换数值列 (已在异常值处理部分转换此处再次确认) df[Quantity] pd.to_numeric(df[Quantity], errorscoerce) df[UnitPrice] pd.to_numeric(df[UnitPrice], errorscoerce) # 计算总销售额并检查是否存在因类型转换失败导致的NaN df[TotalSales] df[Quantity] * df[UnitPrice] print(f总销售额为NaN的记录数: {df[TotalSales].isnull().sum()}) # 查看单价为0或极低的记录可能是赠品 free_or_cheap df[df[UnitPrice] 0.01] print(f\n单价低于0.01的记录数: {len(free_or_cheap)}) if len(free_or_cheap) 0: print(free_or_cheap[[StockCode, Description_Cleaned, Quantity, UnitPrice]].head())至此核心的数据清洗工作基本完成。我们得到的是一个缺失值已妥善处理、异常值已被识别和分类、文本清晰一致、数据类型准确可用的干净数据集。你可以通过df.info()和df.head()来感受一下前后的巨大差异。这份干净的数据已经可以放心地交给下游的探索性数据分析EDA、客户分群RFM模型或机器学习模型了。记住数据清洗没有唯一的标准答案关键是理解你的业务目标让清洗逻辑服务于分析目的。每次清洗决策都最好记录在代码注释或文档中形成可追溯、可复现的数据处理流水线。