我们平时做数据这一行的,基本都绕不开 Excel 和 Pandas 这两样。Pandas 这个词,在数据科学这条路上几乎就是基本功的代名词。我见过太多朋友,一开始处理几万行 Excel 数据还能对付,等到数据量上了几十万、几百万,文件打开慢,公式卡死,透视表拖不动,整个人都崩溃了。而 Pandas 解决的就是这个问题:它让你用几行 Python 代码,轻松搞定百万行数据的读取、清洗、分析和导出。
这篇文章是《Python数据科学实战之路》系列的第3章,我会从 Excel 处理场景切入,带你把 Pandas 的数据结构、核心操作、以及冲击百万级数据时必须注意的性能陷阱全部过一遍。适合刚开始学 Pandas 的零基础朋友,也适合已经在用但总觉得效率不够、想系统补一补性能优化思路的同学们。读完你会发现,从 Excel 到 Pandas 不是一次推翻重来,而是把表格思维迁移到代码里,很多概念其实都有对应关系。
1. 为什么做数据的人,早晚得换掉 Excel 这一套
先说一个我真实的感受:我最早接触数据也是 Excel 重度用户,VLOOKUP、透视表、条件格式这些都玩得飞起。但第一次接到一个包含 120 万行订单记录的需求时,我拿 Excel 打开那个文件,风扇狂转,等了三分钟才响应,期间还崩了一次。从那时候开始我就清楚,Excel 适合的是“人机交互式”的数据操作,而真正的数据分析,得靠代码来跑。
1.1 Excel 的隐形天花板
Excel 的行数上限是 1048576 行,也就是大约一百万行。这听起来挺大,但在业务数据里其实很容易触及。更重要的是,Excel 的交互式操作每一步都要渲染界面,一旦数据量大了,高亮缓存区、公式重算、图表刷新都会拖慢速度,这跟你电脑配置关系不大,是软件架构决定的。
另外还有几个实际痛点。Excel 打开大文件、按列筛选、做透视表时容易无响应;文件里的公式和格式在真正做数据清洗时反而是累赘——比如一列数字里混进了文本格式、日期格式不统一、空值被合并单元格搞乱了,这些用 Excel 手工清理非常痛苦,而且没法复现。你永远无法精确地告诉别人“我这次数据到底清到了什么程度”。
1.2 Pandas 到底是做什么的
Pandas 本质是一个基于 Python 的表格处理库,它把数据抽象成两张表。一张叫 DataFrame,你可以理解成是“内存里的超级 Excel 工作表”,另一个叫 Series,是单列数据。所有对表格的增删改查、分组、合并、转换,Pandas 都有对应的函数,而且全流程可代码化、可复现、可自动化。
这个“可复现”是关键。你用 Excel 做分析,操作过程难以被记录和回放;用 Pandas,你写的每一条数据清洗命令都是流水账,往前翻代码就知道每一步做了什么,换台电脑也能原样跑出来。这对团队协作、交接项目、以及对外说清楚数据处理逻辑来说,价值非常高。
1.3 Excel 与 Pandas 的快速对照
为了让你觉得不陌生,我列一个对照表,把 Excel 里的常用操作对应到 Pandas 函数上。
| 你在 Excel 里做的事情 | Pandas 里的对应操作 |
|---|---|
| 筛选行 | df[df['列名'] > 数值] |
| VLOOKUP 匹配 | df.merge() |
| 数据透视表 | pd.pivot_table() |
| 公式计算新列 | df['新列'] = 其他列进行运算 |
| 排序 | df.sort_values() |
| 删除重复值 | df.drop_duplicates() |
| 分组求和 | df.groupby('列名').sum() |
| 查找替换 | df.replace() 或 df['列'].map() |
如果你会透视表,就一定能理解 groupby 和 pivot_table 的逻辑,它们本质上是对分类维度做聚合运算。有了这张对照表,从 Excel 迁移过来会少很多挫败感。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先搞懂 Pandas 的两大核心结构,这步走稳了后面不慌
很多新手一上来就喜欢背函数,背了一堆记不住,用起来还是乱。其实 Pandas 的底层就两个对象:Series 和 DataFrame,所有操作都是围绕它们展开的,这两个概念吃透了,后面学什么函数都顺手。
2.1 Series:带标签的一维数组
Series 你可以想象成一个带索引的 Python 列表,或者 Excel 里一列数据。它有两部分:索引(index)和值(values)。默认索引是 0 到 n-1 的整数,但你也可以自定义索引,比如姓名、日期、字母。
python复制import pandas as pd
s = pd.Series([85, 92, 78], index=['张三', '李四', '王五'])
print(s)
print(s['李四'])
输出结果就是带中文索引的一列数据,数据标签是序号,数据内容是具体分数。这里要注意,Series 的索引和行位置是两码事,s[0] 取的是位置上的第一个元素,s['李四'] 取的是索引对应的元素。理解了这个区别,你就不会在切片的时候踩坑。
2.2 DataFrame:由多个 Series 拼成的二维表格
DataFrame 就是多个 Series 按列方向拼起来的表格对象。每列都可以是不同的类型,比如一列是整数、一列是字符串、一列是日期,这在 Excel 里叫“表”,在 Pandas 里就叫 DataFrame。
python复制import pandas as pd
data = {
'姓名': ['张三', '李四', '王五'],
'城市': ['北京', '上海', '广州'],
'销售额': [2500, 3400, 4100]
}
df = pd.DataFrame(data)
print(df)
df['销售额'] 取出来是一个 Series,df[['姓名', '城市']] 取出来还是一个 DataFrame,这两者在使用方法上是有差别的。在实际项目中,我见很多人取单列时习惯用 df.销售额(类似访问属性的写法),但这只有在列名恰好是合法变量名时才有效,如果列名带空格、带中文括号,就会报错。稳妥的办法永远是 df['列名'] 这种下标写法。
2.3 三大视图方法:head、info、describe
拿到任何一份新数据,第一步绝对不是处理,而是“看”。我习惯用三个方法快速摸清数据的底细。
df.head() 显示前 5 行,让你大致看看表长什么样。df.info() 给出每一列的名称、非空值数量、数据类型,这个最有用,能快速发现哪些列有空值、哪些列类型不对。df.describe() 是对数值列做统计摘要,包括计数、均值、标准差、最小值、四分位数、最大值,这一步可以最快发现有没有离谱的异常值。
python复制# 读取数据后,先这样走一圈
df.head()
df.info()
df.describe()
如果你连数据有多少列、每列什么类型都没确认,就直接上手清洗,大概率会在中途被类型报错打乱节奏。
3. 从 Excel 到 Pandas:读写文件的细节,很多坑藏在这里
Pandas 最让人心动的能力之一,是用 read_excel 和 read_csv 直接读取文件。但越简单的接口,底下坑越多,尤其是从 Excel 文件切换到大数据量场景的时候,读取方式直接决定速度。
3.1 读取 Excel 文件的正确姿势
Excel 文件用 pd.read_excel() 读取。新手最容易忽略的参数是 sheet_name,如果一个工作簿里有很多 sheet,你不指定的话默认只读第一个。我通常这样处理:
python复制df = pd.read_excel('销售数据.xlsx', sheet_name='订单明细', header=0)
header=0 表示第一行是列名,如果你的表前几行是标题或说明文字,这里就要调整,比如 header=2 表示从第三行开始读。还有一个常见的需求是保留原 Excel 的格式,比如某列的数字是 00123,如果你在 Excel 里存的是文本格式,在 Pandas 里默认读出来可能会变成数字 123。为了避免这种问题,可以加参数指定该列的类型:
python复制df = pd.read_excel('销售数据.xlsx', dtype={'订单号': str})
这样订单号就不会丢掉前导零了。
3.2 CSV 才是大数据量的首选
说句实在话,数据量一旦到了几十万行以上,我不推荐再用 Excel 格式作为中间存储了,Excel 读写速度慢,而且文件体积大。更通用的做法是转成 CSV 来读。
CSV 是纯文本格式,不包含格式、公式、图表,Pandas 读起来非常快。读 CSV 的时候有一个参数特别重要,叫 encoding,否则遇到中文乱码没处说理。最常用的是 utf-8,但有些来自旧 Windows 环境的文件是 gbk 编码的,这种情况下你要么手动指定 encoding='gbk',要么用 encoding='utf-8', engine='python' 配合容错参数,但更省心的还是先试 encoding='gbk'。
python复制df = pd.read_csv('销售记录.csv', encoding='gbk', parse_dates=['下单时间'])
parse_dates 参数也很关键,把日期列直接解析成 Pandas 的 datetime 类型,后面做时间筛选和月份聚合就非常方便。如果你不指定,日期列会被当成普通字符串,排序和分组时就会出现按字典序排的问题,比如“2024-01-31”排在“2024-02-01”前面,逻辑就错了。
3.3 大文件读取的两个提速思路
等你真正面临百万行级别的 CSV 文件时,pd.read_csv() 一次全读进内存可能直接导致内存溢出。这时候有两个思路。
第一个是分块读取,设置 chunksize,比如每次读 10 万行,分批处理,最后再合并结果:
python复制chunk_list = []
for chunk in pd.read_csv('big_data.csv', chunksize=100000):
chunk['年份'] = chunk['订单日期'].dt.year
chunk_list.append(chunk)
df = pd.concat(chunk_list)
第二个是只读取需要的列,用 usecols 参数指定列名列表。很多时候你的原始文件有 30 列,但真正分析只需要其中 5 列,全部读进来既慢又费内存,只读目标列能省一大截性能。
4. 十大核心操作实战:从清洗到聚合,一步步啃下数据
拿到数据之后,真正的工作才刚刚开始。我把日常处理数据最常用到的操作整理成了一套固定流程:先处理缺失值、再处理重复值、然后改类型、做筛选、最后分组聚合。这套流程跑顺了,能应对市面上绝大多数数据分析需求。
4.1 缺失值处理:别轻易 dropna
缺失值是最常见的脏数据类型,Excel 里就是空单元格,Pandas 里显示为 NaN。处理缺失值有两个方向:删掉和填充。删掉用 df.dropna(),默认是只要有任一列为空就删除整行,这通常太粗暴了,我更推荐指定列进行删除。
而填充用 df.fillna(),很多时候业务上缺失是有含义的。比如“客户备注”为空,不代表异常,可能只是没填。而数值型列如果为空,你可以用均值、中位数或前后值填充,这就要看你的业务逻辑了。
python复制# 只删除订单号为空的行
df = df.dropna(subset=['订单号'])
# 用 0 填充销量缺失值
df['销量'] = df['销量'].fillna(0)
千万注意,Pandas 判断缺失值要用 pd.isna(),不要用 df['列名'] == None,因为 CSV 里的空字符串 '' 和 NaN 是两回事,== None 常常判断不出来。
4.2 重复值处理:drop_duplicates 的三个参数
重复数据的清洗也不容小觑。drop_duplicates() 默认是整行所有列都相同才判定重复,但在实际业务中,我们往往只看某几个关键列,比如“订单号 + 商品编号”相同就判定重复了,这时候要指定 subset 参数。
python复制df = df.drop_duplicates(subset=['订单号', '商品编号'])
另外还有一个 keep 参数,默认 keep='first' 保留第一条重复记录,你也可以设置 keep='last' 保留最后一条。如果你想要的是发现重复而不是删除重复,可以先用 df.duplicated(subset=['列'], keep=False) 看看哪些行是重复的,确认之后再动手删。
4.3 数据筛选:loc 和布尔索引
筛选是 Pandas 日常操作里频率最高的。基础写法是 df[df['销售额'] > 3000],这个思路一定要理解:df['销售额'] > 3000 会生成一个布尔 Series,里面全是 True/False,然后用这个布尔 Series 去过滤 DataFrame 的行,留下来的就是符合条件的行。
多条件筛选用 &(与)和 |(或),注意括号不能省,比如筛选销售额大于 3000 且城市为上海:
python复制df_filtered = df[(df['销售额'] > 3000) & (df['城市'] == '上海')]
如果你既要筛行又要选列,更规范的做法是 .loc:
python复制df_selected = df.loc[df['销售额'] > 3000, ['姓名', '城市']]
逗号左边是行条件,右边是要的列。.loc 是按标签索引,.iloc 是按位置索引,这两个是 Pandas 进阶的必经之路,越早统一使用习惯越好。
4.4 用 groupby 替代透视表,完成分组聚合
分组聚合是数据清洗向数据分析过渡的关键一步。我见过很多人还在手动一层层筛选、再粘贴求和,效率太低。用 groupby 一行就能搞定。
python复制result = df.groupby('城市', as_index=False)['销售额'].agg(['sum', 'mean', 'count'])
这个操作的意思是“按城市分组,然后对销售额列求和、求均值、计数”。as_index=False 表示把“城市”这列保持为普通列,而不是变成索引,这样更方便导出 Excel,更符合大众看表格的习惯。agg 里的列表可以随意加,比如最大值、最小值、中位数。
如果你想做更复杂的透视,可以用 pd.pivot_table():
python复制pivot = pd.pivot_table(df, values='销售额', index='城市', columns='商品类别', aggfunc='sum')
这和 Excel 透视表的操作是一样的,index 相当于行标签,columns 相当于列标签,values 是要聚合的数值,aggfunc 决定聚合方式。
4.5 列的新增、删除与重命名
建新列最直接的方式就是赋值运算。比如计算销售额的 10% 作为提成:
python复制df['提成'] = df['销售额'] * 0.1
如果新列依赖于多列,比如客单价等于销售额除以销量,也可以直接一对多计算:
python复制df['客单价'] = df['销售额'] / df['销量']
删除列用 df.drop(columns=['提成']),如果你不想改原表,可以加 inplace=False,这是 Pandas 的默认行为,返回一个新 DataFrame;如果加 inplace=True,则直接改原表。新手经常搞混,我的建议是尽量别用 inplace=True,把结果重新赋值给变量,更清晰。
重命名列用 df.rename(columns={'旧名': '新名'}),它也是一种“生成新表而不是改原表”的操作,适合在清洗完之后统一整理列名,让下游分析更顺手。
5. 百万级数据分析的性能之路:从慢到快的三步优化
处理 1 万行数据,随便怎么写都很快;处理 100 万行,性能问题就开始冒头了。Pandas 在百万级别并不是天生就慢,但用错了方法会慢几十倍。我总结了三个最常见的性能困境,以及对应的解法。
5.1 告别 for 循环,用向量化操作
很多从 Excel 转过来的人,习惯用 Python 原生 for 循环逐行处理数据。你如果写过 for i in range(len(df)) 然后逐行赋值,面对百万行数据会非常痛苦。Pandas 的底层是 NumPy,它支持向量化运算,也就是说整列数据可以一次性执行数学运算,完全不需要逐行迭代。
python复制# 慢
df['提成'] = 0
for i in range(len(df)):
df.loc[i, '提成'] = df.loc[i, '销售额'] * 0.1
# 快
df['提成'] = df['销售额'] * 0.1
这两行代码功能一样,但速度差距是数量级的。向量化的核心逻辑在于,Pandas 会把整列当做一个整体,交给底层的 C 语言和 NumPy 数组批量计算,而不是在 Python 解释器里逐行跑。
5.2 数据类型优化:把 object 变成 category
当你的数据量大了以后,内存占用也是一个令人头疼的问题。我见过一个表格里有“城市”列,单元格里全是北京、上海、广州这几个分类值,但 Pandas 默认把它识别为 object 类型,也就是字符串,内存占用极高。
这种重复度很高的文本列,最适合转成 category(分类)类型。分类类型在底层只存整数编号,并维护一个映射表,因此内存占用会大幅下降:
python复制df['城市'] = df['城市'].astype('category')
实测下来,对一个重复编码很高的列,转 category 后内存可以降到原来的几分之一。这个优化在百万行数据上效果极其显著。另外数值列如果不需要高精度,可以顺手转成 int32 或者 float32,而不是默认的 int64 和 float64,也能缓解内存压力。
5.3 分块处理与并行思路
当文件大到单次读取内存快撑不住时,分块处理配合多进程就是最实用的思路。分块读取首次要做的就是先读一遍表头,确认列名和数据格式,再计算预计分块数。
处理的时候,可以用 pd.concat 把分块结果汇总起来。不过要注意,分块不等同于所有场景都能简单合并,比如你要做排序和去重,最好还是先在每一块内清理,再按主键去重,最后再做全量分析。
除了分块,针对某些重的计算任务,用 multiprocessing 把不同区间的数据分给不同 CPU 核心并行处理,也能大幅提速。但 Python 里的多进程和 Pandas 结合有一些坑,建议新手先把分块和向量化用好,再考虑多进程。
6. 一份案例走向实战:从百万行订单里提取核心指标
本来觉得理论说了一大堆,不如直接拿一份模拟数据从头到尾过一遍。假设你现在拿到一份包含 80 万行订单记录的 CSV 文件,有这些列:订单号、下单日期、客户城市、商品类别、销售额、销量。你要在最短时间内算出“各城市的月度销售额汇总”,并输出成 Excel。
6.1 第一步:读取并初步探查
python复制import pandas as pd
df = pd.read_csv('orders.csv', parse_dates=['下单日期'])
print(df.shape)
print(df.info())
print(df.head())
这里 print(df.shape) 输出行数和列数,让你心里有个底。如果发现下单日期被读成 object,说明 parse_dates 没生效,可以先检查列名是否完全一致,包括大小写和空格。
6.2 第二步:统一格式、清洗异常
读取之后,先把文本列尾部空格去掉。常见名目在真实数据里经常会有“上海 ”和“上海”这样的脏数据,不统一会被算成两个城市。
python复制df['客户城市'] = df['客户城市'].str.strip()
df['商品类别'] = df['商品类别'].str.strip()
顺便把销量和销售额转成数值类型。如果这列有非数字的脏值,pd.to_numeric 会报错,但你可以加 errors='coerce' 让它变成 NaN,之后再统一填充或删除。
python复制df['销量'] = pd.to_numeric(df['销量'], errors='coerce')
df['销售额'] = pd.to_numeric(df['销售额'], errors='coerce')
df = df.dropna(subset=['销售额', '销量'])
这里有个细节,很多人会把 astype('float') 直接拿来转类型,但如果数据里有特殊字符或空字符串,会直接抛异常。相比之下 pd.to_numeric(..., errors='coerce') 温和得多,它会乖乖把错误值变成缺失值,你再处理缺失值,流程就顺畅很多。
6.3 第三步:新增月份列并分组聚合
既然要做月度汇总,第一步就是从下单日期里提取月份。dt.to_period('M') 是很实用的写法,它会把日期统一转成 2024-01 这种月度周期格式,比手动拼接年和月更规范。
python复制df['月份'] = df['下单日期'].dt.to_period('M')
monthly = df.groupby(['月份', '客户城市'], as_index=False)['销售额'].sum()
如果需要同时看销量和销售额,agg 可以跟上多个聚合需求:
python复制monthly = df.groupby(['月份', '客户城市'], as_index=False).agg(总销量=('销量', 'sum'), 总销售额=('销售额', 'sum'))
6.4 第四步:输出结果为 Excel 或 CSV
最后把结果导出,方便给别人看。to_excel 和 to_csv 的真实使用场景不同:Excel 适合给人预览、带格式,CSV 适合再加工、口径更轻。
python复制monthly.to_excel('各城市月度销售汇总.xlsx', index=False)
monthly.to_csv('各城市月度销售汇总.csv', index=False, encoding='utf-8-sig')
这里特别提醒一个坑:如果你导出的 CSV 用 Excel 直接打开,中文很可能是乱码,因为 UTF-8 无 BOM 时 Excel 不认。解决方式是在 to_csv 里指定 encoding='utf-8-sig',这样 Excel 打开就正常显示中文了。
7. 高频报错与排查技巧速查
写过一段时间 Pandas,谁都会碰到报错。我把海量新手最容易踩的报错原因总结成一份速查表,搞明白为什么错,比背 100 个函数更有效。
7.1 最常见的 KeyError 与 SettingWithCopyWarning
KeyError: '列名' 表示你要访问的列在 DataFrame 里不存在。原因往往是列名拼写不一致、列名有隐藏空格、或者读取时 header 设置错误导致列名不对。排查方法很简单,先 print(df.columns.tolist()),把列名列表打出来,肉眼检查。
SettingWithCopyWarning 出现时,通常是因为你试图在一个从 DataFrame 切片出来的子集上赋值,Pandas 不确定你是想改子集还是改原表。例如:
python复制sub = df[df['城市'] == '上海']
sub['新列'] = 0
这种写法容易给出警告。修改方法是改成 .loc 方案,直接对原表操作:
python复制df.loc[df['城市'] == '上海', '新列'] = 0
7.2 数据类型相关的错乱
当你用 df['销量'].mean() 时报错,或者结果明显不对时,十有八九是销量列被识别成了字符串。可以先用 df.dtypes 检查类型,再通过 pd.to_numeric 或 astype 修正。
如果你在 merge 两张表时发现明明主键都一样,但就是匹配不上,很大概率也是两表主键的类型不一致——一边是字符串,一边是整数。解决方法是先把两边主键统一成同一种类型,再去 merge。
7.3 内存不足时的应急处理
进程崩溃或 Jupyter 页面卡死,很多时候不是 Pandas 的问题,而是内存被耗尽。应急办法有三板斧:
第一,用 usecols 只读需要的列,减小数据量;第二,把 object 列改成 category;第三,用分块读取避免一次性全量加载。如果这三招还不行,再考虑把内存中不用的变量删掉,用 del 配合 gc.collect() 手动触发垃圾回收。
写在最后的小技巧
学到这儿,整个从 Excel 到 Pandas 再到百万级数据的路径就完整了。我在实际项目里最想说的一件事是:不要觉得用 Pandas 就很高级,它只是替代繁琐 Excel 操作的工具。Pandas 的代码,追求的是“清晰、可复现、可查错”,而不是一味地炫技。
最后给刚上手的朋友分享一个小经验:不用急着背函数,遇到某个操作不会做,直接在搜索引擎里搜“pandas 按列求和”、“pandas 筛选日期范围”,找到一份可靠的文档,然后自己动手模拟一份小数据,跑通一遍,再套到真实数据上。这样你的熟练度增长是最快的。
还有一点:数据处理永远不要只跑一次就宣布完工。同一份数据,往往要在不同口径下验证几遍。我通常会把聚合结果导出一份 Excel,用透视表手工核对几行关键数字,确认没有因为筛选条件写错导致结果偏差,再做高层的二级分析。前期核对多花十分钟,后期汇报就能少改很多轮。
