假设你新开了一家小饭馆,每天上午供应商都会送来三样东西:一箱贴着潦草字迹的进货单、一沓收银小票、一本顾客留言本。晚上打烊后,你想知道“这个月香蕉一共进花了多少钱”“苹果和橙子哪个卖得好”,却只能把纸翻得哗哗响。要是有张“魔法工作台”就好了——把单据往上一倒,它们自动摊成整齐的表格;想挑出某一类,手指一划;想让同类归堆,拖一下;想把两张单子拼成一张总表,按一下。Pandas 就是 Python 世界里的这张魔法工作台。它是数据分析师、算法工程师整理表格数据的标配工具:把散落在 CSV、Excel 里的原始数据读进来,清洗干净,筛选出目标部分,做各种计算和统计,再从文件导出。这篇文章会用“做饭”的视角带你走完全流程,全程不需要数学功底和编程基础,跟着直觉走就行。
对 AI 从业者的价值:训练模型前,80% 的时间都花在整理数据上。Pandas 是数据清洗、特征工程与 ETL 的事实标准,掌握它,你的 AI 项目就拥有了一条高效的数据流水线——从原始文件到可用特征,快人一步。
目录
可视化演示
展示Pandas DataFrame的筛选和分组聚合操作,筛选按条件过滤行,分组聚合按键归类并计算统计值。
本节配有交互动效(进入视口后自动加载,约 3KB)。
筛选操作 按条件过滤行,保留满足条件的记录 布尔索引df[df[col]>val] df[condition] → filtered
Pandas初识:厨房里的智能工作台
新店开张第一天,你站在后厨里最发愁的不是灶台,而是那张孤零零的木桌——食材筐乱堆,青椒和茄子混在一起,称重用的小票塞在土豆袋底下。你咬咬牙换了一张带隔断的不锈钢工作台:每一格贴上标签,蔬菜归蔬菜,肉类归肉类,要找什么一目了然。Pandas 就是 Python 厨房里的这张工作台,它给你两种收纳容器:Series 是单个隔断,DataFrame 是整张台面。
Series 是一维的、带标签的数组:就像一排放好的隔断,每个隔断上有名字(索引)和内容(数据)。DataFrame 是二维的表格,行和列都有名字,长得像 Excel 工作表,但天生为自动化而生——你给它数据,它记住每一列的“菜名”和每一行的“编号”,之后的所有操作都靠这些标签完成。更重要的是,DataFrame 的每一列本质上就是一个 Series,这解释了为什么 Pandas 的所有操作都如此统一。
安装 Pandas 只需 pip install pandas,代码里统一 import pandas as pd。从字典创建 DataFrame 最直观:字典的键变成列名,值是列表,一一对齐成列。df.shape 返回 (行数,列数),df.index 是行的编号,df.columns 是列名的集合。这些概念和后面所有操作都直接挂钩,值得像记住工作台隔断的位置一样记住它们。
现在台面整理好了:每一个隔断贴着标签,每一行都编了号。可菜还躺在供应商的箱子里,并没有真的被搬上桌——下一节,我们把单据和食材搬进厨房。
- DataFrame(整张台面)DataFrame 是二维表格结构,类比厨房里贴好标签的智能工作台。
- 行是食材(一条记录)每一行 $x_{i1}, x_{i2}, \ldots, x_{im}$ 是一条完整记录。
- 列是隔断(一个特征)每一列 $x_{1j}, x_{2j}, \ldots, x_{nj}$ 是一个属性,同一列的数据类型通常一致。
- 索引是编号下标 $i$ 和 $j$ 分别标记行与列的位置,后续 loc/iloc 都靠它们定位。
数据读写:把单据搬上台面
每天清晨,供应商的货车准时停在门口,卸下来的货形态各异:有打印的 Excel 进货单,有发票系统导出的 CSV 纯文本,还有老收银机吐出的 xlsx 报表。你不可能一份份手抄进工作台——好在 Pandas 台面自带两条“传送带”:read_csv 和 read_excel。把文件往传送带上一放,自动变成台面上整齐的表格。
反向同样有传送带:to_csv 和 to_excel 把处理好的 DataFrame 写回成文件,这叫“落盘”。数据从磁盘到内存再回磁盘,是数据分析的基本循环。期间还可以调节传送带参数:encoding='utf-8' 或 'gbk' 防止中文乱码,sep=',' 指定分隔符,index_col=0 指定哪一列当作行的编号,nrows=100 先只读 100 行快速瞧瞧货色。
这套读写能力正是 ETL 的起点和终点。ETL 是数据领域的标准流水线:Extract 抽取(从文件或数据库读入)、Transform 转换(清洗与计算)、Load 装载(写回文件或数仓)。Pandas 用两行函数就完成了 E 和 L,中间的 T 就是后面几节的主角。实际工作中,分析师每天到岗的第一件事,往往就是 read_csv 把昨天的数据抽出来。
单据全部搬上台面了。一百份食材摊在面前,你要做鱼香肉丝,只需要其中的里脊肉、冬笋和木耳——先把该拿的精准拿出来。
- File(原始单据)磁盘上的 CSV 或 Excel 文件,类比供应商送来的单据。
- 传送带 read_csv
read_csv(path)把文件读成内存里的 DataFrame,边读边自动识别列名。 - 加工台处理内存里的 DataFrame 被清洗、筛选、聚合(见后续章节)。
- 输出落盘
to_csv(out)把结果写回磁盘,下次可直接再读。
索引筛选:精准点名拿食材
做鱼香肉丝,你不可能把整张台面倒进锅里。你得根据菜谱点名:“里脊肉,过来;冬笋,过来;木耳,也过来。” Pandas 的索引与筛选就是这套“点名”动作,快得只要一瞬间。
Pandas 提供两套点名口令:按位置数格子用 iloc(比如第 2 行第 1 列),按标签念名字用 loc。df['销量'] 取整列,df.loc[2] 取某一行。最常用的是布尔筛选:df[df['销量'] > 100]——这相当于拿着一支“合格手电筒”照每一行,照到满足条件的就放行,最后聚成一张新表格。
细节决定成败:布尔筛选返回的是符合条件的子集(新表),原表不动;多个条件用 &(且)与 |(或)拼接,且每个条件都要加括号,比如 df[(df['销量'] > 100) & (df['类别'] == '水果')]。loc 还支持切片 df.loc[2:5, ['销量', '价格']],一次把行和列都圈定,是取数最常用的组合拳。
短短一行代码,你从一百份食材里挑出了今天要用的十二份。可这里的苹果皮上还沾着泥,有两张单子重了——下一节,把食材洗干净。
- r(一盘食材)$r$ 表示表格中的一行,即一条记录。
- r ∈ DataFrame(遍历全表)$r \in \text{DataFrame}$ 表示逐行检查台面上每一份食材。
- 条件竖线(筛选标准)$\mid$ 后面是“留下它的理由”,此处为销量大于 100。
- 大括号 selected(收集篮)结果是一个只装合格记录的新集合,对应筛选后的新 DataFrame。
数据清洗:摘烂叶去坏菜
供应商的货从来不会完美:有的苹果被压烂了没标产地,有的单据重复打印了两张,有的重量一栏填的是“约三斤”这种没法计算的文字。直接下锅,一整锅菜都毁了。数据清洗就是摘烂叶、挑坏果、贴对标签的过程,是数据分析里最不起眼却最花时间的一步。
缺失值有两个处理动作:dropna() 直接把有缺失的行或列扔掉,fillna(0) 用指定值填空。重复值用 drop_duplicates() 一次去重。类型不对用 astype() 转换,比如把参差不齐的字符串 "5.0" 转成 float 才能参与计算。列名不直观就用 rename() 换成业务语言,比如把 sale_amt 改成 "销售金额"。
经验法则:先 df.isna().sum() 数一数每列缺了多少,算出缺失率。缺失率很低(比如 1%),直接 dropna 删掉不心疼;缺失率很高但又很重要,就要考虑是否值得填均值或中位数,或者干脆保留“是否缺失”作为一个新特征——对机器学习来说,“缺失”本身常是有信息的信号。
摘完烂叶、贴好标签,台面上的食材干净整齐。现在终于可以动刀了——配菜、切丝、下锅,这就是下一节的变换与计算。
- N_missing(坏果数量)某列缺失值的个数,用
isna().sum()数出来。 - N_total(整筐食材数)该列的总行数,用
len(df)获得。 - 商与百分号(坏果率)两者相除再乘 100%,得到缺失比例。
- 决策(删还是填)缺失率低就删行,缺失率高且列重要就考虑填补或保留为特征。
变换计算:配菜与下锅
厨师常有即兴发挥:今天的菜单上要写“每桌金额 = 销量 × 单价”,这道菜台面上没有现成的,你得现场配。Pandas 的厉害之处在于,新增一列就像在台面上加一个新隔断,而且是一次性给整列算好——不写 for 循环,不逐行数,像传送带一样刷一下全部完成。
最简单的新列就是四则运算:df['金额'] = df['销量'] * df['价格'],整列同时运算,这叫矢量化。遇到没法用四则运算表达的逻辑,用 apply:df['评语'] = df['销量'].apply(评判函数),它像一条自动传送带,把每个值依次送进函数加工再送回原位。sort_values 则让数据按某列排好队,常用运算 sum/mean/max/min 直接作用在列上。
性能心法:矢量化运算底层是 C 语言批量执行,比 Python for 循环快几十到几百倍,所以能写 df['a'] * 2 就别写 apply,能用 apply 就别写循环。apply 的适用场景是“逻辑无法用四则运算表达”,比如文本截取、规则打分、日期解析——它是救火队员,不是默认主力。
菜备好了,也下了锅。可你要对账:这个月苹果进了多少斤?香蕉总共花了多少钱?一道菜一道菜数是数不过来的。下一节教你“按类装盘”——分组聚合。
- amount_i(这道菜的总价)新列第 $i$ 行的值,是我们要算的结果。
- price_i 与 quantity_i(两样原料)第 $i$ 行的单价和数量,来自已有列。
- 乘号(组合方式)整列对应元素逐个相乘,行与行一一配对。
- i 下标(行号)$i = 1, 2, \ldots, n$ 表示每一行都应用同一规则,无需手动 for 循环。
分组聚合:按类装盘统计
打烊后盘点是每个店主的心头痛:三十种食材,不能一根根数。聪明做法是先按类别归堆:苹果一堆、香蕉一堆、橙子一堆,然后每种称一次总重。Pandas 的 groupby 就是干这个的,它把“归类”和“称重”两个动作焊在了一起。
核心写法一行搞定:df.groupby('类别')['销量'].sum()。拆开看是三个动作:split 分裂(按“类别”把数据切开)、apply 应用(每组调用 sum)、combine 合并(把各组的汇总拼回成一张新表),这就是数据库里经典到不行的 Split-Apply-Combine 模式。想多指标一起算用 agg:.agg(['sum', 'mean', 'max']) 一次得到总和、平均、最大值。透视表 pivot_table 是分组聚合的变体,把“组”从行搬到列上,摆盘更好看,适合做交叉统计。
细节:分组后索引变成了分组键,想恢复正常列用 reset_index();透视表默认聚合函数是 mean,可指定 aggfunc='sum'。分组聚合是数据分析和特征工程中出现频率最高的操作之一——几乎所有“按 XX 统计 YY”的需求都能用它解决。
每种食材的进销存都心里有数了。收工前还有最后一步:销售明细和进货价目表,得拼成一张总表,才能算出利润。这就是下一节的合并。
- G_j(第 j 盘食材)第 $j$ 个分组,即同类别行构成的集合。
- i ∈ G_j(盘里的每一份)$i$ 遍历该组中的每个值 $x_i$。
- 求和符号(称重)把组内所有值加起来,对应 sum()。
- 1/|G_j|(除以数量得平均)$\lvert G_j \rvert$ 是组内行数,相除得到均值,对应 mean()。
合并实战:拼表上菜,端出报表
晚上对账,你手上有两张纸:一张是今天的每笔销售(写着商品名和售价),另一张是进货价目表(写着商品名和进价)。你需要的“毛利”藏在两张纸的配合里:得把相同商品的两行信息拼到同一行。这就是合并——像把两块积木对准榫卯,咔哒一声拼在一起。
Pandas 里最常用的是 pd.merge:pd.merge(销售表, 价目表, on='商品') 按共同列“商品”连接;how='left' 左连接保留左表所有行,how='inner' 只保留两边都匹配的行。pd.concat 则是另一类合并——把结构相同的表像摞盘子一样纵向堆叠,常见于拼接多个月份的销售数据。join 是索引层面的 merge,用行索引做连接键。
到这里,数据厨房的完整流水线就通了:Extract——read_csv 读入销售单与价目表;Transform——清洗、筛选、分组、合并拼出宽表;Load——to_csv 输出最终报表。这也是特征工程的标准姿势:把用户表、订单表、商品表多表 merge 成宽表,再构造统计特征,喂给模型。数据工程师和算法工程师每天都在这条流水线上工作。
现在你站在自己小饭馆的后厨,看着台面上井井有条的表格:原料已入库、烂叶已摘净、菜品已分组、两本账已合二为一。Pandas 这台“智能厨房工作台”,也正式成了你的看家本领——从原始文件到分析报表,一条龙搞定。
- A 与 B(两张单据)两个待合并的 DataFrame,如销售表与价目表。
- key(共同的钥匙)连接键,即两表都有的列,如“商品”。
- ⋈(榫卯拼接)数据库中的“自然连接”符号,把匹配的行横向拼在一起。
- C(最终报表)合并后的新 DataFrame,一行同时包含两表的信息。
AI 应用场景
数据分析师输出销售日报
阶段:数据准备与清洗(ETL 的 Extract / Transform / Load)
解决的问题:传统做法是人力把各门店 CSV 复制进 Excel、逐列核对、手写 SUMIF,一天最多做一次还容易错;AI 时代的报表质量完全取决于上游数据质量。用 Pandas 把读入、去重、缺失值填补、按门店 groupby 汇总、to_csv 导出压成一段脚本,两分钟出结果,且可重复运行。
具体案例:某连锁奶茶店的数据分析师每天凌晨用同一份脚本跑前一日销售:read_csv 读门店导出 → drop_duplicates 去重 → groupby('门店')['金额'].sum() → merge 门店信息 → to_csv 输出“门店日报.csv”,再挂上企业微信定时推送。
机器学习特征工程
阶段:特征工程(Transform)
解决的问题:原始数据散落在用户表、订单表、商品表,模型需要一张“宽表”;手工拼接既慢又容易产生行数爆炸。Pandas 的 merge / groupby 是构造聚合特征的标配组合拳。
具体案例:算法工程师做用户购买预测时,先用 user_id 把订单表与用户表 left join,再用 groupby('user_id').agg({'订单金额': 'sum', '订单数': 'count'}) 造出“历史客单价、购买频次”等特征,最后 fillna 处理新用户缺失,输出宽表交给 LightGBM 训练。
数据仓库 ETL 管道
阶段:ETL 数据集成(Extract-Transform-Load)
解决的问题:多源异构数据难以统一,数仓需要每天定时把业务系统导出的文件清洗后归档;没有 Pandas 就得写大量 Java 或手工 SQL,维护成本高。
具体案例:数据工程师每天定时读取销售库与库存库导出的 CSV,统一列名(rename)、转类型(astype)、去重,按月份 concat 堆叠成历史大表,落到数仓 ODS 层;异常数据单独 dropna 并写日志留痕。
Python 演示
import pandas as pd # 阶段1: Extract - 模拟供应商送来的销售单据sales_data = { "商品": ["苹果", "香蕉", "苹果", "橙子", "香蕉", "苹果"], "销量": [30, 20, 15, 10, 25, 18], "价格": [5, 3, 5, 4, 3, 5],}df_sales = pd.DataFrame(sales_data) # 阶段2: Transform - 计算金额列df_sales["金额"] = df_sales["销量"] * df_sales["价格"] # 检查缺失值并去重print(df_sales.isna().sum())df_clean = df_sales.drop_duplicates() # 按商品分组聚合summary = df_clean.groupby("商品").agg({"销量": "sum", "金额": "sum"}) # 合并进货价目表price = pd.DataFrame({"商品": ["苹果", "香蕉", "橙子"], "进价": [2, 1, 2]})result = pd.merge(summary.reset_index(), price, on="商品") # 计算毛利并排序result["毛利"] = result["金额"] - result["销量"] * result["进价"]result = result.sort_values("毛利", ascending=False) # 阶段3: Load - 导出日报result.to_csv("sales_report.csv", index=False, encoding="utf-8-sig")print(result)
df_sales = pd.DataFrame(sales_data)把字典类型的原始单据铺成工作台上的表格,键自动变列名。df_sales["金额"] = df_sales["销量"] * df_sales["价格"]整列直接相乘生成新列,这就是比 for 循环快得多的“矢量化运算”。print(df_sales.isna().sum())逐列数空缺值,像先检查坏果子数量再决定怎么处理。df_clean = df_sales.drop_duplicates()扔掉重复的采购记录。summary = df_clean.groupby("商品").agg({"销量": "sum", "金额": "sum"})按商品归堆,对销量和金额分别求和,即分组聚合。result = pd.merge(summary.reset_index(), price, on="商品")用“商品”这一共同钥匙,把销售汇总与进价表榫卯拼接。result["毛利"] = result["金额"] - result["销量"] * result["进价"]用合并后的宽表一步算出毛利。result.to_csv("sales_report.csv", index=False, encoding="utf-8-sig")把结果写回磁盘文件,Excel 打开不乱码。
常见误区
误区我 `pd.read_csv('销售.csv')` 一运行就报 UnicodeDecodeError,肯定是我把文件弄坏了,得重新找供应商要一份。
正解文件大概率没坏,坏的是“编码标签”。CSV 是纯文本,可能用 gbk 或 utf-8 保存。就像供应商送来的单据是用你不认识的方言写的,换个“编码翻译器”即可:.read_csv(path, encoding='gbk') 或 'utf-8'。报错信息里通常会提示它期望的编码。
为何常见:文本文件在磁盘上都是字节,Excel 保存 CSV 用 gbk 还是 utf-8 取决于系统区域,初学者不知道“编码”这个概念。
误区我执行了 `df[df['销量']>100]`,然后打印 df 发现还和原来一样,Pandas 是不是有 bug?
正解Pandas 默认“生成新表、不动原表”,就像菜谱抄一份再改,而不是把原菜谱撕了。所以筛选结果要接住:df2 = df[...]。若真想原地改,部分方法有 inplace=True 参数,但更推荐用赋值的方式。
为何常见:在 Excel 里筛选后“看见的表格变了”,人们容易把“显示”和“修改”混为一谈。
误区我写 for 循环一行行算总额,感觉很直接很可控,应该比 Pandas 的“魔法”快吧?
正解恰恰相反。Pandas 的矢量化运算由底层 C 语言批量执行,df['金额'] = df['销量']*df['价格'] 一次算整列;Python for 循环每条都要解释执行,慢几十到几百倍。就像用传送带一次送一筐菜 vs 一颗颗手拿。
为何常见:有编程基础的人惯性写循环,不知道矢量化才是数据分析性能的核心。
误区数据有空缺就 `fillna(0)`,反正填上了总比空着强,代码还短。
正解填 0 会改变统计意义。比如“用户月消费”缺失时填 0,等于伪造了“没消费”的事实,平均值会被严重拉低。正确姿势是先 isna().sum() 看缺失率:低则 dropna,高则结合业务填均值、中位数、上一值,或单独保留为特征。
为何常见:“填空”符合直觉,而且代码最短;没有意识到缺失本身可能携带信息。
误区我两个表都是 100 行,merge 完变 320 行,肯定是我代码参数错了。
正解如果连接键在某个表里重复,join 会把重复键的每一行都匹配出来,产生一对多膨胀,这符合数据库连接语义。像订单表和商品表连接,一个商品被多个订单引用,商品信息就会“复制”到每个订单行。处理办法:先对明细表 drop_duplicates,或先 groupby 聚合再 merge。
为何常见:没接触过关系型数据库的 join 语义,习惯用“行数守恒”来理解操作。
面试题
Q1. Pandas 中用于表示一维带标签数组的结构是?
- A.DataFrame
- B.Series← 正确答案
- C.ndarray
- D.list
答案:B。Series 是一维的、带索引标签的数组,可以理解为工作台上的一个“收纳隔断”,隔断上的标签就是索引;DataFrame 是二维表格,由多个 Series 拼成。ndarray 是 NumPy 的数组,没有标签;list 是 Python 原生列表,也没有索引标签。
Q2. 读取 CSV 文件应使用哪个函数?
- A.pd.read_excel
- B.pd.read_csv← 正确答案
- C.pd.load
- D.pd.open
答案:B。read_csv 是读取 CSV 的标准函数;read_excel 用于读取 Excel。Pandas 没有 load 或 open 这样的顶层读取函数,它们来自其他库或内置函数。
Q3. 关于 df.loc 与 df.iloc,下列说法正确的是?
- A.两者都按行号取数
- B.loc 按标签取数,iloc 按位置取数← 正确答案
- C.iloc 按标签取数,loc 按位置取数
- D.两者没有任何区别
答案:B。loc 是“念名字”(按索引标签),iloc 是“数格子”(按行号位置)。比如 df.loc[2] 取索引为 2 的行,df.iloc[2] 取第 2 行(从 0 数起)。两者都可以取行列,但语义不同,混淆会导致取错数据。
Q4. 下列哪些操作属于 Pandas 缺失值处理?
- A.dropna()← 正确答案
- B.fillna(0)← 正确答案
- C.drop_duplicates()
- D.astype()
答案:AB。dropna() 删除含缺失值的行或列,fillna(0) 用指定值填补空缺,二者是缺失值处理的两条思路。drop_duplicates() 是重复值处理,astype() 是类型转换,它们解决的是其他清洗问题。
Q5. df.groupby('类别')['销量'].sum() 的含义是?
- A.按销量分组,再对类别求和
- B.按类别分组,再对销量求和← 正确答案
- C.对全表所有列求和
- D.按类别对销量排序
答案:B。这条语句按“类别”把数据切开成若干组(split),然后对每一组的“销量”列执行 sum(apply),最后拼回汇总表(combine)。A 把分组键说反了,C 忽略了分组,D 混淆了排序与聚合。
Q6. 想同时得到每组销量的总和与平均值,应该用?
- A..agg(['sum', 'mean'])← 正确答案
- B..sum()
- C..max()
- D..value_counts()
答案:A。agg 可以一次传入多个聚合函数,得到包含 sum 和 mean 的汇总表,相当于一次称重同时记录总重和平均重。sum 只求和,max 只求最大值,value_counts 用于统计频次而非聚合数值。
Q7. 执行 pd.merge(A, B, on='key', how='left'),关于结果行数最准确的说法是?
- A.一定等于 A 的行数
- B.等于 A 与 B 行数之和
- C.不小于 A 的行数← 正确答案
- D.等于 B 的行数
答案:C。左连接保留左表 A 的所有行;但如果 B 的 key 有重复,A 中某行会与 B 的多行都匹配,行数随之增加,所以“不小于 A 的行数”最准确。A 选项忽略了 key 重复导致的一对多膨胀,这正是新手常踩的坑。
Q8. 关于 Pandas 数据清洗,下列说法正确的有?
- A.drop_duplicates 可以删除重复行← 正确答案
- B.对所有缺失值一律 fillna(0) 是安全的
- C.astype 可以用于列的类型转换← 正确答案
- D.rename 可以重命名列← 正确答案
答案:ACD。drop_duplicates 去重、astype 转类型、rename 改列名都是标准清洗手段。B 错在“一律填 0”——缺失值填 0 会扭曲均值等统计量,需结合缺失率和业务场景决定删、填还是保留为特征。
Q9. 关于 pivot_table 与 groupby 的关系,下列说法正确的是?
- A.pivot_table 本质是“分组聚合 + 行列重排”,能输出更宽的布局← 正确答案
- B.pivot_table 不能执行聚合运算
- C.pivot_table 与 groupby 完全等价,没有区别
- D.pivot_table 只能处理数值列
答案:A。pivot_table 是“分组聚合 + 重排”的组合拳,把分组键同时放到行和列上做交叉统计,默认聚合函数是 mean;groupby 输出的是紧凑的长表。B 错在 pivot_table 能做聚合(如 sum/mean/count),C 忽略了布局差异,D 错在它也支持类别型列做计数。
Q10. 关于布尔筛选 df[df['销量'] > 100],下列说法正确的有?
- A.返回满足条件行组成的新 DataFrame← 正确答案
- B.会原地修改原 DataFrame
- C.多个条件用 & 连接,且每个条件要加括号← 正确答案
- D.条件列必须是数值型
答案:AC。布尔筛选返回一个满足条件的全新 DataFrame,不会改动原表,所以 A 对 B 错;多个条件必须用 & 或 | 连接且每个条件加括号,C 对;筛选条件不限于数值——df[df['商品'] == '苹果'] 这种字符串等值筛选同样常见,D 错。