ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

pandas实战指南:从数据读取、清洗到分组透视的全流程

pandas实战指南:从数据读取、清洗到分组透视的全流程

说实话,在数据分析这个岗位上干了好几年,装过删过的工具换了一茬又一茬,但有一个库一直没敢卸——pandas。不是因为它功能最花哨,恰恰相反:它足够稳定、足够通用,日常需求里十有七八,打开它就能就地解决。网上讲pandas的内容铺天盖地,但动不动就从底层原理讲到源码分析,真靠那套思路去应对日常工作,大部分人早就被吓跑了。所以这次我想换个角度,把“日常工作中80%场景会用到的功能”梳理成一套能直接上手的组合拳:读取、类型检查、清洗、分组、透视、导出,外加最常见的坑和我的排查习惯。适合正在从Excel或SQL往Python过渡的人,也适合那些已经装了pandas、但每次写代码还得现搜写法的人。

1. 内容整体设计与思路拆解

1.1 日常工作里的那80%到底是什么

先说个很多人都有的误解:pandas功能太庞大,学不完怎么办?真相是,日常工作中你根本不需要学完。我回想了一下自己在日报、周报、临时取数、探索性分析这几类任务里反复做的事情,其实高度收敛:

  • 把数据读进来(CSV、Excel、SQL查出来的表)
  • 看看每列是什么类型、有没有空值、长什么样
  • 把数据洗干净(去重、补空、改格式、修文本)
  • 按某个维度分组统计、求占比、求排名
  • 把结果导出成新的表格给别人用,或者画张图看趋势

就这么点事。pandas的API虽然有几千个,但日常高频用的满打满算不超过二十个。问题从来不是“pandas功能太多”,而是很多人被文档里那些偏门功能分散了注意力,真正常用的几个反而没练熟。

1.2 为什么是pandas而不是Excel、SQL

我经常被问到:Excel做数据透视表也挺方便,SQL写几个聚合也很直接,为什么偏偏要在中间插一层pandas?我的答案很实在:因为现实中的临时分析,往往落在Excel太吃力、SQL又不够灵活的缝隙里。

举几个具体场景:从数据库导出的线上订单明细,跑一次要好几百万行,Excel打开就卡死了;需要把两张表做模糊匹配或按区间关联,SQL写起来又臭又长;或者数据文件散落在十几个CSV里,每次都要合并、清洗、补标准字段,用Excel手动操作能把自己烦死。这些场景恰恰是pandas最顺手的地方。它跟Excel一样是“所见即所得的格子思维”,又有编程语言的自动化能力,等于是给表格加了脚本引擎。

1.3 方案选型背后的取舍逻辑

pandas的底层是按列存储、按行访问的DataFrame结构,这跟Excel的表格模型非常接近,Tableau也好、Power BI也罢,背后处理数据的核心逻辑无非也是这批东西。所以只要你的数据量还停在单机内存能扛住的级别(我个人经验是几百万行以内),pandas几乎是最均衡的选择:生态成熟、文档全、遇到问题随便一搜都是案例。

反过来也说一句:如果你的数据量大到几十个G,频繁跑批,那确实应该考虑上Spark或者数据库的物化视图之类,不该硬在pandas里硬扛。选型这种事,永远是为场景服务的。

2. 核心细节解析与实操要点

2.1 读数据的第一步:类型推断和编码

日常最容易翻车的环节不在数据处理本身,而在“读文件”这一步。最典型的两个问题:中文乱码和类型被读错。

乱码多半是编码问题。默认情况下,Windows环境导出的CSV常用GBK编码,而pandas在多数环境里默认按UTF-8读取,于是中文全变“锟斤拷”。我的习惯是,读取Excel文件时不用太担心编码,但凡是CSV或TXT,第一件事先确认编码。不确定的时候,可以先打开文件看一眼,或者用最简单的方式先读个几十行试试,别一上来就整个文件读进来。

类型被读错这个问题更隐蔽。举个例子,订单明细里的“订单号”通常是纯数字,但长度已经超过普通整数范围,或者带了前导零,pandas就默认把它读成整数或浮点,导致后续匹配出现问题。预防的方法很笨但很有效:先读小样本,打印dtypes,确认每列的类型是对的,再全量读取。

import pandas as pd # 强烈建议:先小批量看结构 df_preview = pd.read_csv("order_detail.csv", nrows=100) print(df_preview.dtypes) print(df_preview.head()) # 再正式读取,并为关键列指定类型 df = pd.read_csv( "order_detail.csv", dtype={ "order_id": "string", "customer_id": "string", "amount": "float64", }, parse_dates=["order_date"], encoding="utf-8" )

这里有个很实用的经验:parse_dates参数别省。日期列如果不主动解析,pandas会把它当普通object存着,后续排序、按日月汇总都会出各种莫名其妙的问题。读的时候顺手转好,比事后统一处理要省事得多。

2.2 清洗数据:缺失值、重复值、文本

清洗数据是整个分析流程里最没成就感、但又最耗时的一环。行业里有句话叫“垃圾进,垃圾出”,清洗没到位,后面分析做得再漂亮也是空中楼阁。

先说缺失值。刚接触pandas的人最爱干的一件事就是一上来dropna(),把有缺失的行全删掉。这个习惯很危险。因为很多时候“空”本身就是信息。比如用户表的“会员等级”为空,可能仅仅表示这个用户没有开通会员,不代表数据有问题。正确做法是先按列统计缺失数量,再看每一列的缺失有没有业务含义。

# 按列统计缺失数量 print(df.isna().sum())

对于真正的缺失值,我的处理原则是:能补的尽量补,不能补的少删。比如时间序列里某一分钟的数值缺失,用ffill()把上一个值填进来,比直接删掉那一行更合理;数值型字段缺了,可以用该列的中位数或平均数填充,避免影响后续聚合。

再说重复值。日常数据里重复往往不是整行完全一样,而是主键字段重复。比如同一个订单ID出现了两次,哪怕其他字段略有差异,这种“脏重复”才是要去掉的。drop_duplicates()里有个subset参数,可以用它只按几个关键列判断重复,而不是要求整行一模一样。

df_clean = df.drop_duplicates(subset=["order_id"], keep="first")

最后说文本清洗。很多数据是从各种表单里导出来的,字段前面后面带着空格、全角数字、中英文标点混用。这些肉眼看着没什么,分组匹配的时候全是坑。“张三”和“ 张三”在Excel里长得差不多,在Python里是两个完全不同的字符串。我用得最多的就三招:str.strip()去掉首尾空格,str.replace()统一替换,str.lower()统一大小写。

df["user_name"] = df["user_name"].str.strip() df["city"] = df["city"].str.replace("北京市", "北京", regex=False)

regex=False这个参数是个好东西,很多人在replace里因为没关正则匹配,遇到特殊字符时报错,实际上大多数场景你想做的只是字面替换。

2.3 分组聚合和透视:从宽表到洞察

清洗完之后,真正出活的部分就来了。日常分析里最常用的是groupby配合agg,它解决的核心问题是“按某个维度分组,看各个组的汇总指标”。

初次用groupby的人常遇到一个困惑:为什么分组后打印出来,分组字段跑到索引里去了?这确实会干扰后续操作,比如你想再把这个结果导出成Excel,或者继续跟别的表关联,就会发现分组字段变成了索引,处理起来很别扭。我通常习惯配合reset_index()使用,或者直接设置as_index=False。

daily_stats = df.groupby("order_date", as_index=False).agg( order_cnt=("order_id", "count"), gmv=("amount", "sum"), avg_amount=("amount", "mean") )

这里有三个小细节值得展开。第一,count()统计的是非空行数,所以如果你要数订单量,必须选一个确定非空的字段,比如订单号;第二,sum()是对数值累加,如果字段类型是字符串,会直接报错,所以分组前先确认dtype没错;第三,agg可以同时指定多个汇总口径,不用每次重复groupby,性能上也有优势。

除了groupby,还有一位老将——pivot_table(透视表)。它和Excel里的数据透视表逻辑几乎一模一样,适合做“维度交叉”的分析场景。我想着,其实“宽表窄表互转”和这类交叉汇总,恰恰是Excel用户最熟、最想迁移到Python里的能力。

pivot = pd.pivot_table( df, values="amount", index="category", columns="status", aggfunc="sum", fill_value=0 )

这个例子生成了一张表:行是商品类目,列是订单状态,值是总金额,没有任何NaN。用fill_value=0把没有记录的空位补成0,比默认的NaN直观得多。

3. 实操过程与核心环节实现

3.1 一套可以直接抄的销售数据分析流程

空讲概念没太大意思,我直接上一个完整的实操案例,大家可以照抄照改。模拟一个场景:某电商平台有一份30万行的销售明细表,字段包括订单号、用户ID、下单日期、商品类目、订单金额、订单状态,任务是产出一份经营日报,包含每日GMV趋势、品类占比、用户复购分布。

第一步还是读数据。我习惯先把dtypes、shape、head都打出来,心里有个底。

import pandas as pd df = pd.read_csv( "sales_detail.csv", dtype={"order_id": "string", "user_id": "string", "amount": "float64"}, parse_dates=["order_date"], encoding="utf-8" ) print(df.shape) print(df.dtypes) print(df.head())

读完之后,开始做体检。看哪些列有空值,看金额分布有没有离谱的极值,看看状态字段都有哪些取值。

# 缺失值检查 print(df.isna().sum()) # 描述性统计 print(df[["amount"]].describe()) # 看状态字段取值 print(df["status"].value_counts())

经验之谈:这里打印出来的结果可能直接告诉你问题所在,比如状态字段里突然出现一个“待发货”以外的“已退款”,甚至还有“ 已完成”带个空格,这种脏数据在后续分组时会悄悄变成两个独立的组,不仔细看报表根本发现不了。

3.2 按日趋势与品类占比

清洗干净后,先做每日GMV趋势,这是日报的灵魂。用groupby加agg,一天搞定。

daily = df.groupby("order_date", as_index=False).agg( order_cnt=("order_id", "count"), gmv=("amount", "sum") ) # 确保日期按顺序排 daily = daily.sort_values("order_date") # 画成折线图 daily.plot(x="order_date", y="gmv", ylabel="GMV")

这里我吃过一个亏:如果不先sort_values,直接用原始顺序画图,日期乱序会让趋势线跳来跳去,图形上感觉完全不可信。排序这种看似多余的动作,很重要。

再看品类占比。一行value_counts(normalize=True)就能出结果,相当于按类目统计订单数,然后转成百分比。

cat_share = df["category"].value_counts(normalize=True) * 100 print(cat_share)

如果希望看到“每个类目贡献了多少金额”而不是“多少订单”,就需要再绕一层:

cat_gmv = df.groupby("category")["amount"].sum().sort_values(ascending=False) cat_gmv_share = (cat_gmv / cat_gmv.sum() * 100).round(2) print(cat_gmv_share)

重点不是代码怎么背,而是分析口径要想清楚:是按订单数占比,还是按金额占比?两个口径可能指向完全不同的业务结论。日报里我会同时放这两个数字,避免管理者只看单一维度被带偏。

3.3 用户维度分析

做完时间序列和品类结构,再把视角切到用户身上。最基础的是统计每个用户的消费次数,再看消费次数的频次分布。

user_freq = df.groupby("user_id")["order_id"].count().rename("order_cnt") print(user_freq.value_counts().sort_index())

value_counts()在这里干了一件聪明的活:先把每个用户对应的订单数算出来,然后再统计“买1次的有多少人、买2次的有多少人”。一个复购分析就出来了。要是业务里还关心新老用户划分,可以按用户最早下单日期打标签,这也是分析日报里很常用的一招。

用户维度的分析,关键是别把粒度搞混。groupby("user_id")的结果已经粒度到用户了,后面再聚合的次数分布是第二层聚合,此时脑子里要清楚:第一个结果表的每一行是“一个用户”,第二张表的每一行是“一个消费档位”。

3.4 结果导出

分析结果最终都要出成Excel或CSV给别人看。导出这一步有个极易踩的坑:中文乱码。CSV文件在Excel里打开时,默认按系统编码解析,而pandas默认的to_csv编码是UTF-8,Excel不一定能正确识别。解决方案是用utf-8-sig编码,它会在文件头加一个BOM标记,Excel一看就明白了。

daily.to_csv("daily_report.csv", index=False, encoding="utf-8-sig")

如果你有多张结果表,想一次性发到一个Excel的不同Sheet里,可以用ExcelWriter:

with pd.ExcelWriter("report.xlsx", engine="openpyxl") as writer: daily.to_excel(writer, sheet_name="每日GMV", index=False) cat_gmv.to_excel(writer, sheet_name="品类占比", index=False)

index=False挂在嘴边,因为让索引跑到Excel里,会给同事增加无意义的负担。我见过太多导出结果里带着第一列索引的报表了,讲真,特别不专业。

4. 常见问题与排查技巧实录

4.1 高频报错与对应解法

我把自己这些年踩过的坑集中列了一张简表,全是真实项目中反复出现的,不是冷门知识。

现象原因解决办法
中文CSV读出来乱码编码不匹配指定encoding="utf-8"或"gbk"
Excel打开导出CSV乱码缺少BOM头to_csv(..., encoding="utf-8-sig")
groupby结果没法继续操作分组字段变成索引加reset_index()或as_index=False
某列数字不能求和类型其实是object用pd.to_numeric(..., errors="coerce")转换
日期排序乱序日期还没转成datetime用pd.to_datetime()统一
出现SettingWithCopyWarning链式操作碰了副本及时copy(),或统一用loc赋值
整列出现“NaN”字符串数据里的脏值先replace后再转数值类型

挑几个展开说。SettingWithCopyWarning是新手最容易吓到的警告。它的本质是:你在一个DataFrame的切片副本上试图做修改,pandas不确定你改的是原数据,还是临时副本,于是提出警告。我解决这个问题的习惯是:每做一步“修改性操作”之前,先明确df = df.copy(),切断引用关系,后续怎么操作都不心虚。

pd.to_numeric处理“数字被读成了文本”这个场景很常用。Excel里有的数字前面带个单引号或者空格,导出来以后在pandas里就变成object类型,直接sum()必然报错。errors="coerce"会让无法转换的值变成NaN,先用它完成类型转换,再回头看有哪些脏值,比直接砍掉行更稳妥。

4.2 排查思路:先数据体检,再代码调试

我的排查习惯可能跟很多人相反:代码报错之后,我并不会盯着报错信息查半天,而是先去打印数据本身。因为绝大多数“报错”都不是代码语法问题,而是数据没长成你期待的样子。

一个典型的排查流程是这样的:

print(df.head()) print(df.dtypes) print(df.isna().sum()) print(df.describe())

这四行打出来,80%的数据问题已经一目了然。比如你本来以为“amount”是数字,结果现在显示object,那根本不用看后面的报错,问题就在类型上;比如你以为“order_date”已经统一成datetime,结果打印出来还是字符串,后面排序乱掉就不是偶然。

真要等到代码报错再回头查数据,往往已经绕了很大一圈。所以在清洗阶段多打印、早发现,付出一点点时间成本,后面不知道能省多少事。

4.3 一个必须养成的习惯:把清洗函数收起来

最后分享一个我个人的习惯。在做过几十个项目之后,我慢慢发现日常清洗的动作其实是高度重复的:去空格、转日期、统一编码、去重。后来我把这些动作整理成了几个小函数,放到一个叫clean.py的脚本里。每次接到新数据,先import clean,然后一行一行调用,效果立竿见影。

比如我会写一个统一日期列的处理函数:

def unify_datetime(df, col): return pd.to_datetime(df[col], format="%Y-%m-%d %H:%M:%S", errors="coerce")

再比如一个快速去重函数:

def dedup(df, subset): return df.drop_duplicates(subset=subset, keep="first")

这些函数的价值不在于代码本身有多高级,而在于它们形成了一个“套路模板”。每次打开新数据,我的头脑里有一个固定的处理顺序,不需要每次从零开始想。人其实非常不擅长记忆大量程序化步骤,把常用的东西固化下来,是提升效率最划算的方式。

我个人在实际操作中最大的体会是:pandas提供的每个方法,单独看都不难,难的是建立起一种“先看数据结构、再决定清洗动作、最后才写分析逻辑”的流程感。工具永远是工具,真正值钱的不是背下来的API名单,而是当一份乱糟糟的表格递到你面前时,你知道该从哪一步下手、每一步的目的到底是什么。

再分享一条一直保留的小技巧:每次拿到新数据,第一件事绝对不是跑模型,而是问自己三个问题——这份数据有多少行多少列、每一列有没有空值、主键字段是否有重复。回答完这三个问题,你对数据的了解已经超过很多直接把数据拖进可视化工具的人了。剩下的事情,pandas会帮你的。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进