Pandas数据分析实战:从数据清洗到聚合的完整代码指南
1. 项目概述为什么说Pandas是数据分析的“瑞士军刀”如果你刚开始接触Python数据分析或者已经用了一段时间但总觉得代码写得不够利索那么今天聊的这个话题对你来说可能就是那层窗户纸。我们经常在各种教程里看到“Pandas是数据分析的利器”这种说法但落到具体操作上面对一个Excel表格或者数据库导出的CSV文件你是不是还在用df.head()看一眼然后就开始写一堆自己都觉得重复的循环和判断我刚开始用Pandas那会儿也这样总觉得它功能强大但用起来不顺手直到后来在几个实际项目里反复折腾才慢慢总结出一套真正高频、实用的基础代码模式。这些代码不是什么高深的算法但恰恰是处理日常数据时能让你效率提升80%的“硬通货”。它们覆盖了数据读取、清洗、探索、转换到输出的全流程就像一把瑞士军刀虽然每项功能单独看都不复杂组合起来却能解决绝大多数常见问题。这篇文章我就把这些年积累下来的、最常用的Pandas基础分析代码整理出来。目标很明确让你看完之后能立刻复制这些代码块到自己的Jupyter Notebook或脚本里替换掉文件路径和列名就能跑起来看到结果。我们不会涉及太复杂的合并或时间序列预测只聚焦于那些你几乎每天都会遇到的、实实在在的数据操作场景。2. 核心操作流程与设计思路2.1 从数据加载到初步审视建立分析基线任何数据分析的第一步都是把数据“请进来”并看个大概。这一步的目标是快速建立对数据的整体印象包括它的规模、结构、大概有什么字段以及是否存在明显的格式问题。2.1.1 灵活的数据读取与关键参数很多人习惯用pd.read_csv但往往只传一个文件路径。其实这里面有很多参数能帮你省去后续大量的清洗工作。import pandas as pd # 基础读取 df pd.read_csv(your_data.csv) # 带实用参数的读取强烈推荐 df pd.read_csv(your_data.csv, encodingutf-8, # 指定编码防止中文乱码 sep,, # 明确分隔符如果是制表符就用\t header0, # 指定第0行作为列名 skiprows[1], # 跳过第1行例如可能是副标题 na_values[NA, NULL, --, ] # 将特定字符串识别为缺失值 )注意encoding参数是中文数据处理中的“头号杀手”。如果遇到UnicodeDecodeError可以尝试gbk、gb2312或latin1。更稳妥的做法是用chardet库先检测文件编码。读取之后不要急着处理先用几个“快照”函数了解全貌# 1. 查看数据形状行数列数 print(f数据集形状: {df.shape}) # 输出 (10000, 15) 表示1万行15列 # 2. 预览头部和尾部数据 print(df.head()) # 默认前5行 print(df.tail(3)) # 查看最后3行有助于发现数据收集末期的格式问题 # 3. 查看列名、数据类型和非空计数 print(df.info())df.info()的输出非常宝贵它能一次性告诉你每列的数据类型int64float64object以及非空值的数量。如果某列的非空计数远小于总行数说明缺失严重需要重点关注。2.1.2 描述性统计用数字感知数据分布对于数值型数据describe()函数是你的第一把尺子。# 生成描述性统计 desc_stats df.describe() print(desc_stats)默认情况下它会计算计数count、均值mean、标准差std、最小值min、四分位数25% 50% 75%和最大值max。但这里有个技巧describe()默认只针对数值列。如果你的数据里有日期或分类编码的整数它们也会被计算这可能不是你想要的。更精细的控制如下# 包含所有列的描述对于对象类型列会输出计数、唯一值数、最高频值及其频次 print(df.describe(includeall)) # 仅针对数值列 print(df.describe(include[np.number])) # 仅针对对象列通常是字符串 print(df.describe(include[object]))2.2 数据清洗与预处理打造高质量分析原料原始数据几乎总是“脏”的。清洗的目标是将数据转化为一致、可靠的格式。这部分工作通常占据数据分析80%的时间但好的代码模式能极大压缩这个比例。2.2.1 处理缺失值策略比删除更重要直接删除缺失值df.dropna()是最简单粗暴的方法但可能导致大量信息丢失。我的经验是先评估再处理。# 1. 探查缺失情况 missing_summary df.isnull().sum() # 每列缺失值总数 missing_percentage (df.isnull().sum() / len(df)) * 100 # 每列缺失值百分比 # 将缺失情况整理成表格更直观 missing_df pd.DataFrame({ 缺失数量: missing_summary, 缺失百分比%: missing_percentage }) print(missing_df[missing_df[缺失数量] 0]) # 只显示有缺失的列 # 2. 根据策略处理缺失值 # 策略一删除缺失行适用于缺失很少或该行其他列也无价值 df_dropped df.dropna() # 删除任何包含NaN的行 df_dropped_col df.dropna(subset[重要列名]) # 仅删除‘重要列名’缺失的行 # 策略二填充缺失值更常用 # 用固定值填充 df_filled_zero df.fillna(0) # 适用于数值型如收入缺失填0 df_filled_unknown df.fillna(Unknown) # 适用于分类型文本 # 用统计值填充更合理 df[数值列].fillna(df[数值列].median(), inplaceTrue) # 用中位数填充对异常值不敏感 df[数值列].fillna(df[数值列].mean(), inplaceTrue) # 用均值填充 df[类别列].fillna(df[类别列].mode()[0], inplaceTrue) # 用众数出现最多的值填充 # 策略三向前或向后填充适用于时间序列 df[时间序列列].fillna(methodffill, inplaceTrue) # 用前一个有效值填充 df[时间序列列].fillna(methodbfill, inplaceTrue) # 用后一个有效值填充2.2.2 处理重复值去重的艺术重复值会扭曲统计结果尤其是计数和求和。# 1. 检查重复行基于所有列 duplicate_rows df[df.duplicated()] print(f完全重复的行数: {len(duplicate_rows)}) # 2. 检查基于关键列的重复例如用户ID、订单号 duplicate_on_key df[df.duplicated(subset[用户ID, 订单日期], keepFalse)] # keepFalse会标记出所有重复项方便你查看所有重复记录 # 3. 删除重复值 # 删除所有列完全相同的行保留第一次出现的记录 df_unique df.drop_duplicates() # 基于关键列去重保留最后出现的记录 df_unique_last df.drop_duplicates(subset[用户ID], keeplast) # 基于关键列去重且不保留任何重复记录全删除风险高慎用 df_unique_none df.drop_duplicates(subset[用户ID], keepFalse)2.2.3 数据类型转换与格式化自动推断的数据类型有时不准需要手动校正。# 1. 转换数值类型 df[字符串数字列] pd.to_numeric(df[字符串数字列], errorscoerce) # errorscoerce会将无法转换的值设为NaN而不是报错 # 2. 转换日期类型这是最容易出错的环节之一 df[日期字符串列] pd.to_datetime(df[日期字符串列], format%Y-%m-%d, errorscoerce) # 明确指定格式能大幅提升转换速度和准确性。常见格式符%Y年%m月%d日%H时%M分%S秒 # 3. 转换分类类型节省内存提高分组性能 df[性别列] df[性别列].astype(category) # 4. 查看转换结果 print(df.dtypes)2.3 数据筛选、排序与基本变换清洗完后我们开始对数据进行“切片和切块”提取感兴趣的部分。2.3.1 条件筛选像查数据库一样查DataFrame这是最核心的操作之一熟练使用布尔索引能解决大半的查询需求。# 单条件筛选 high_sales df[df[销售额] 10000] # 多条件“与”筛选使用 high_sales_beijing df[(df[销售额] 10000) (df[城市] 北京)] # 多条件“或”筛选使用 | target_cities df[(df[城市] 上海) | (df[城市] 广州)] # 使用 isin() 进行多值匹配更简洁 target_cities_elegant df[df[城市].isin([上海, 广州, 深圳])] # 字符串模糊筛选包含 name_contains_wang df[df[客户姓名].str.contains(王, naFalse)] # naFalse 很重要避免包含NaN的行报错 # 字符串开头/结尾筛选 name_starts_with_zhao df[df[客户姓名].str.startswith(赵, naFalse)]2.3.2 排序让数据按你的规则排列# 单列升序排序 df_sorted df.sort_values(by销售额) # 单列降序排序 df_sorted_desc df.sort_values(by销售额, ascendingFalse) # 多列排序先按城市升序城市相同再按销售额降序 df_sorted_multi df.sort_values(by[城市, 销售额], ascending[True, False])2.3.3 创建新列与简单变换数据分析中经常需要基于现有列计算衍生指标。# 1. 直接算术运算 df[利润] df[销售额] - df[成本] df[利润率] df[利润] / df[销售额] # 2. 使用 apply 函数进行更复杂的行级运算 def categorize_sales(x): if x 10000: return 高 elif x 5000: return 中 else: return 低 df[销售等级] df[销售额].apply(categorize_sales) # 3. 使用 lambda 表达式简单函数 df[销售额千元] df[销售额].apply(lambda x: x / 1000) # 4. 向量化操作性能最优推荐 df[折扣后价格] df[原价] * (1 - df[折扣率])2.4 数据聚合与分组分析发现深层模式这是Pandas的精华所在groupby操作让你能轻松进行“分类汇总”。2.4.1 基础分组聚合# 按单个列分组对另一列进行聚合如每个城市的平均销售额 city_sales df.groupby(城市)[销售额].mean().reset_index() # .reset_index() 将分组键城市从索引变回普通列方便后续处理 # 按多个列分组如每个城市每个产品的销售总额 city_product_sales df.groupby([城市, 产品类别])[销售额].sum().reset_index() # 同时对多列进行多种聚合如计算每个城市的销售额总和、客户数、平均订单额 agg_result df.groupby(城市).agg({ 销售额: sum, 客户ID: nunique, # 计算唯一客户数 订单ID: count, # 计算订单总数 利润: mean }).reset_index() # 可以重命名聚合后的列 agg_result.columns [城市, 总销售额, 独立客户数, 订单数, 平均利润]2.4.2 分组后的排序与筛选聚合结果本身也是一个DataFrame可以继续操作。# 找出销售额最高的10个城市 top10_cities city_sales.sort_values(by销售额, ascendingFalse).head(10) # 找出平均利润低于0亏损的产品类别 loss_making_products df.groupby(产品类别)[利润].mean().reset_index() loss_making_products loss_making_products[loss_making_products[利润] 0]2.5 数据合并与连接整合多源信息实际分析中数据往往分布在多个表格里。2.5.1 基于键的合并类似SQL JOIN# 假设有两个DataFrame: orders订单和 customers客户 # orders 有列OrderID, CustomerID, Amount # customers 有列CustomerID, Name, City # 内连接inner join只保留两个表都有的CustomerID merged_inner pd.merge(orders, customers, onCustomerID, howinner) # 左连接left join以orders表为主保留所有订单没有客户信息的填NaN merged_left pd.merge(orders, customers, onCustomerID, howleft) # 外连接outer join保留所有记录两边都没有匹配的填NaN merged_outer pd.merge(orders, customers, onCustomerID, howouter) # 当键名不同时 merged_diff_key pd.merge(orders, customers, left_onCustID, right_onCustomerID, howinner)2.5.2 轴向拼接堆叠数据# 上下拼接增加行要求列结构相同 df_combined pd.concat([df1, df2, df3], axis0, ignore_indexTrue) # ignore_indexTrue 会重建索引避免重复的索引号 # 左右拼接增加列要求行索引对齐或行数相同 df_combined_cols pd.concat([df1, df2], axis1)3. 核心环节实现一个完整的数据分析小案例光说不练假把式。我们用一个模拟的电商销售数据把上面的代码串起来走一个完整的分析流程。假设我们有一个sales_data.csv文件包含order_idcustomer_idproductcategorysalescostorder_datecity这几列。3.1 场景设定与目标老板想知道整体销售表现如何总额、平均额、趋势哪个城市、哪个产品类别贡献最大有没有亏损的品类列出销售额最高的10个订单详情。3.2 端到端代码实现import pandas as pd import numpy as np # 1. 加载与审视数据 print( 步骤1: 数据加载与初步审视 ) df pd.read_csv(sales_data.csv, encodingutf-8) print(f数据形状: {df.shape}) print(\n前5行数据:) print(df.head()) print(\n数据概览 (info):) print(df.info()) print(\n描述性统计:) print(df.describe()) # 2. 数据清洗 print(\n 步骤2: 数据清洗 ) # 检查缺失 missing df.isnull().sum() print(f缺失值统计:\n{missing[missing 0]}) # 假设sales列有少量缺失用中位数填充 if df[sales].isnull().sum() 0: median_sales df[sales].median() df[sales].fillna(median_sales, inplaceTrue) print(f已用中位数 {median_sales} 填充sales列缺失值。) # 检查并删除完全重复的行 initial_rows len(df) df.drop_duplicates(inplaceTrue) final_rows len(df) print(f删除完全重复行: {initial_rows - final_rows} 行) # 转换日期 df[order_date] pd.to_datetime(df[order_date], errorscoerce) # 3. 数据转换计算衍生列 print(\n 步骤3: 计算衍生指标 ) df[profit] df[sales] - df[cost] df[profit_margin] df[profit] / df[sales] df[profit_margin].replace([np.inf, -np.inf], np.nan, inplaceTrue) # 处理除零错误导致的无穷大 # 4. 整体销售表现分析 print(\n 步骤4: 整体销售表现 ) total_sales df[sales].sum() avg_sales df[sales].mean() total_profit df[profit].sum() print(f总销售额: {total_sales:,.2f}) print(f平均订单销售额: {avg_sales:,.2f}) print(f总利润: {total_profit:,.2f}) # 5. 城市与品类分析 print(\n 步骤5: 按城市和品类分析 ) # 按城市聚合 city_analysis df.groupby(city).agg({ sales: sum, profit: sum, order_id: count }).rename(columns{sales:total_sales, profit:total_profit, order_id:order_count}) city_analysis[avg_sales_per_order] city_analysis[total_sales] / city_analysis[order_count] print(城市销售排行:) print(city_analysis.sort_values(bytotal_sales, ascendingFalse).head()) # 按产品类别聚合 category_analysis df.groupby(category).agg({ sales: sum, profit: sum, profit_margin: mean }).rename(columns{sales:total_sales, profit:total_profit, profit_margin:avg_margin}) print(\n产品类别表现:) print(category_analysis.sort_values(bytotal_sales, ascendingFalse)) # 6. 识别亏损品类 print(\n 步骤6: 识别亏损或低利润品类 ) loss_categories category_analysis[category_analysis[total_profit] 0] if not loss_categories.empty: print(以下品类总利润为负或零:) print(loss_categories) else: print(未发现总利润为负的品类。) low_margin_categories category_analysis[category_analysis[avg_margin] 0.1] # 利润率低于10% if not low_margin_categories.empty: print(\n以下品类平均利润率低于10%:) print(low_margin_categories[[total_sales, avg_margin]]) # 7. 高价值订单分析 print(\n 步骤7: 销售额最高的10笔订单 ) top10_orders df.nlargest(10, sales)[[order_id, customer_id, product, city, sales, profit, order_date]] print(top10_orders) # 8. 简单可视化可选需安装matplotlib try: import matplotlib.pyplot as plt # 绘制各城市总销售额柱状图 plt.figure(figsize(10,6)) city_analysis_sorted city_analysis.sort_values(bytotal_sales, ascendingFalse).head(10) plt.bar(city_analysis_sorted.index, city_analysis_sorted[total_sales]) plt.title(Top 10 Cities by Total Sales) plt.xlabel(City) plt.ylabel(Total Sales) plt.xticks(rotation45) plt.tight_layout() plt.show() except ImportError: print(\n(如需可视化请安装matplotlib库))这个脚本提供了一个完整的分析框架。你可以把它保存为.py文件运行或者在Jupyter Notebook中分步执行。每一步的输出都会打印在控制台让你清晰地看到分析结果是如何一步步产生的。4. 高频问题排查与性能优化技巧即使掌握了上面的代码在实际操作中你还是会遇到各种“坑”。下面是我总结的一些常见问题和解决办法。4.1 常见报错与解决思路问题/报错信息可能原因解决方案KeyError: ‘column_name’列名拼写错误、列名包含空格、使用了错误的变量名。1. 用df.columns打印所有列名仔细核对。2. 列名有空格时使用df[‘column name’]或df.rename(columns{‘old‘: ’new‘})重命名。SettingWithCopyWarning对DataFrame切片后的副本进行赋值Pandas不确定你是想修改原始数据还是副本。明确你的意图1.想修改原始数据使用.loc如df.loc[df[‘A‘] 0, ‘B‘] 1。2.想操作副本先显式拷贝df_copy df[df[‘A‘] 0].copy()再修改df_copy。MemoryError数据量太大超出内存。1. 指定数据类型df pd.read_csv(‘file.csv‘, dtype{‘col1‘: ’int32‘})。2. 只读取需要的列usecols[‘col1‘, ’col2‘]。3. 分块读取chunksize10000。4. 使用df.info(memory_usage‘deep‘)查看内存占用。UnicodeDecodeError文件编码与读取时指定的编码不匹配。1. 尝试常见编码encoding‘gbk‘’gb2312‘’latin1‘。2. 用chardet库检测import chardet; with open(‘file.csv‘, ’rb‘) as f: result chardet.detect(f.read()); print(result)。ValueError: time data … does not match format日期字符串格式与format参数不匹配。1. 先用df[‘date_col‘].head()查看原始格式。2. 如果不确定格式可先尝试pd.to_datetime(df[‘date_col‘], errors‘coerce‘)让Pandas自动推断但速度慢。3. 参考Python的strftime格式代码定义正确的格式字符串。分组聚合结果异常如求和为NaN分组列中存在NaN值NaN会被视为一个独立的分组。在分组前处理缺失值df df.dropna(subset[‘group_col‘])或df[‘group_col‘].fillna(‘Missing‘, inplaceTrue)。apply函数运行极慢对大数据集使用apply尤其是里面调用了复杂函数或循环。1. 优先使用Pandas内置的向量化函数如.str..dt. 算术运算。2. 如果必须用apply尝试使用swifter库并行加速。3. 考虑使用numpy的向量化操作。4.2 提升代码效率的实战技巧选择正确的数据类型这是提升性能和降低内存占用最有效的方法。将字符串列转换为category类型如果唯一值少于总值的50%将int64转为int32或int16如果数值范围允许。df[city] df[city].astype(category) df[small_int_col] df[small_int_col].astype(int32)避免链式索引df[df[A] 0][B] 1这种写法会触发SettingWithCopyWarning且可能无效。始终使用.loc进行条件赋值。# 正确做法 df.loc[df[A] 0, B] 1使用query()方法进行复杂筛选当筛选条件非常复杂时query()方法语法更清晰。# 传统写法 filtered df[(df[sales] 1000) (df[city].isin([北京,上海])) (df[profit] 0)] # 使用query filtered df.query(sales 1000 and city in [北京,上海] and profit 0)利用eval()进行高性能表达式求值对于大型DataFrame的复杂数值运算pd.eval()可以显著提升速度。# 传统写法较慢 df[result] df[A] df[B] * df[C] # 使用eval较快 df[result] pd.eval(df.A df.B * df.C)迭代数据是最后的选择iterrows()和itertuples()都很慢。如果逻辑无法向量化考虑使用apply如果数据极大考虑分块处理或使用Dask库。4.3 数据导出保存你的分析成果分析完成后别忘了把结果保存下来。# 1. 保存为CSV最通用 df_processed.to_csv(processed_data.csv, indexFalse, encodingutf-8-sig) # indexFalse 不保存行索引utf-8-sig 能让Excel正确打开中文 # 2. 保存为Excel带多个工作表 with pd.ExcelWriter(analysis_report.xlsx) as writer: df_summary.to_excel(writer, sheet_nameSummary, indexFalse) city_analysis.to_excel(writer, sheet_nameBy_City, indexFalse) category_analysis.to_excel(writer, sheet_nameBy_Category, indexFalse) # 3. 保存为Pickle保留DataFrame所有信息包括数据类型用于Python内部传递 df.to_pickle(data.pkl) # 读取df pd.read_pickle(data.pkl)把这些代码片段组合起来形成你自己的分析工具箱。真正的熟练不是背下这些代码而是在不同的数据场景下能像搭积木一样快速组合它们直指问题的核心。最开始可以多参考这个清单做上几个项目后你就会发现自己已经形成了肌肉记忆数据分析的效率自然就上来了。