为什么数据分析绕不开 pandas
拿到一份 CSV 或者数据库导出的表,很多人的第一反应是用 Excel 打开、拖拖拽拽做个透视表。数据量小的时候这完全没问题,但一旦行数超过十万、或者要把同一段分析重复跑十几遍,Excel 就开始卡顿甚至崩溃。pandas 就是 Python 生态里替代这一切的工具:用代码描述你的分析过程,可复现、可批量、可版本管理。
它的名字来自"面板数据"(panel data),核心能力是让你像操作表格一样操作数据:选列、过滤、排序、分组、汇总、合并,每一步都有对应的一行代码,而不是鼠标点来点去。更重要的是,它和 NumPy、Matplotlib、scikit-learn 无缝衔接,是从"数据处理"走向"机器学习"的必经之路。
写 pandas 的核心心法:把每一步操作都当成一条声明式指令——"我要哪几列、按什么条件过滤、按什么分组、聚合出什么指标",而不是写循环逐行处理。
- DataFrame带行列标签的二维表格,是 pandas 最核心的数据结构。
- 数据清洗处理缺失值、重复行、类型错误,是分析前的必经工序。
- groupby拆分、应用、合并三步法,是分组聚合的标准套路。
- 透视表一行代码做出 Excel 数据透视表的效果,还能多层嵌套。
Series 与 DataFrame:pandas 的两块积木
pandas 只有两个核心数据结构。Series 是带标签的一维数组,相当于 Excel 里的一列;DataFrame 是带行列标签的二维表格,相当于整个工作表。你可以把 DataFrame 想象成一堆 Series 按列拼起来,每一列有自己的名字和数据类型。
import pandas as pd
# 从字典创建一个 DataFrame
df = pd.DataFrame({
"name": ["张三", "李四", "王五", "赵六"],
"age": [28, 34, 22, 41],
"city": ["北京", "上海", "广州", "北京"],
"salary": [15000, 22000, 9000, 30000],
})
print(df.head()) # 看前 5 行
print(df.dtypes) # 每列的数据类型
print(df.shape) # (4, 4) 行数列数
操作 DataFrame 最常用的三种方式:
- 选列:
df["salary"]返回一个 Series;df[["name", "salary"]]返回一个新 DataFrame。 - 过滤行:
df[df["age"] > 30],条件表达式放在方括号里,pandas 会自动选出满足条件的行。 - 新增列:
df["annual"] = df["salary"] * 12,直接赋值就会加一列。
注意 pandas 的布尔索引用 & 和 |,不是 Python 的 and 和 or,而且每个条件必须用括号包起来。这是新手最常见的语法错误。
数据读取与初步探查
真实项目里,数据很少是手写字典来的,大多来自 CSV、Excel 或数据库。pandas 提供了 read_csv、read_excel、read_sql 等一系列读取函数,一行就能把外部文件变成 DataFrame。
# 读取 CSV,指定编码和分隔符
df = pd.read_csv("orders.csv", encoding="utf-8", parse_dates=["order_date"])
# 读取 Excel,指定 sheet
df = pd.read_excel("report.xlsx", sheet_name="销售明细")
# 初步探查三板斧
print(df.head(3)) # 前 3 行长什么样
print(df.info()) # 每列非空数量、类型、内存占用
print(df.describe()) # 数值列的 count / mean / std / min / max / 分位数
拿到一份陌生数据,养成先跑这三板斧的习惯:head 看长什么样,info 看有没有缺失和类型问题,describe 看数值分布是否合理。比如订单金额列的最小值是负数、或者某列 90% 都是空值,这些问题在这一步就会暴露出来,而不是分析到一半才发现。
| 探查方法 | 看什么 | 常见发现 |
|---|---|---|
| df.head(n) | 前 n 行数据样例 | 列名是否正确、有没有错位 |
| df.info() | 每列非空数与类型 | 缺失率、被识别成 object 的数值列 |
| df.describe() | 数值列统计分布 | 异常值、量级是否合理 |
| df["col"].value_counts() | 某列取值频次 | 分类是否有拼写不一致 |
数据清洗:缺失值、重复值与类型转换
真实数据从来不干净。电商导出的表里,可能有几行缺了用户 ID、有重复订单、日期列被识别成了字符串。分析之前必须先清洗,否则后面算出来的指标全是错的。
# 1. 缺失值处理
df.isna().sum() # 每列缺失多少个
df = df.dropna(subset=["user_id"]) # 关键字段缺失的行直接丢
df["city"] = df["city"].fillna("未知") # 分类缺失填充默认值
# 2. 重复值处理
df.duplicated().sum() # 有多少重复行
df = df.drop_duplicates() # 全列重复才删
df = df.drop_duplicates(subset=["order_id"]) # 按订单号去重
# 3. 类型转换
df["price"] = df["price"].astype(float) # 字符串转数值
df["order_date"] = pd.to_datetime(df["order_date"]) # 转日期
清洗的原则是先理解、再处理:缺失值不是越多越坏,得先看缺失是不是有规律——比如"用户没填手机号"本身就是一个信号。删行和填充都要有理由,不能见到空值就一刀切。
- 删行:关键字段缺失、且占比很小(<5%)时用。
- 填充:数值列用中位数(抗极端值),分类列用众数或"未知"。
- 保留标记:重要的缺失可以加一列布尔值"是否缺失",告诉模型这一行本来就缺。
分组聚合:groupby 三步走
分组聚合是数据分析里用得最多的操作:"按城市统计平均工资"、"按月看订单总量"、"按用户算消费频次"——这些都是 groupby 的事。它的套路是固定的三步:拆分(split)→ 应用(apply)→ 合并(combine)。
# 按城市分组,算每个城市的平均工资和人数
result = df.groupby("city")["salary"].agg(["mean", "count"])
print(result)
# 按多列分组:先按城市、再按年龄档
df["age_group"] = pd.cut(df["age"], bins=[0, 25, 35, 50], labels=["青年", "中年", "资深"])
result2 = df.groupby(["city", "age_group"])["salary"].mean().unstack()
print(result2)
agg 可以一次传多个聚合函数,也可以对不同列用不同的聚合方式:
agg_result = df.groupby("city").agg(
avg_salary=("salary", "mean"),
total_salary=("salary", "sum"),
user_count=("name", "count"),
max_age=("age", "max"),
)
print(agg_result)
这种命名聚合(named aggregation)是 pandas 0.25 之后的写法,可读性远好于字典传参。unstack() 则把多层行索引展开成列,结果从长表变成宽表,直接就能画热力图或者导出 Excel。
合并、连接与透视表
真实分析很少在一张表里完成。订单表、用户表、商品表通常分开存,需要的时候再拼到一起。pandas 的 merge 对应 SQL 的 JOIN,concat 对应 UNION,pivot_table 对应 Excel 透视表。
# 合并:订单表 left join 用户表
orders = pd.DataFrame({
"order_id": [1, 2, 3, 4],
"user_id": [101, 102, 101, 103],
"amount": [99, 250, 150, 80],
})
users = pd.DataFrame({
"user_id": [101, 102, 103],
"city": ["北京", "上海", "北京"],
})
merged = orders.merge(users, on="user_id", how="left")
print(merged)
# 透视表:行是城市,列是是否复购,值是订单数
pivot = pd.pivot_table(
merged,
values="order_id",
index="city",
columns="amount",
aggfunc="count",
fill_value=0,
)
print(pivot)
merge 的 how 参数和 SQL 完全对应:left 保留左表全部行、inner 只保留两边都有的、outer 取并集。做关联分析时一定要先确认主表是哪张、用什么键连,连完再用 shape 对比行数变化——如果行数突然暴涨,大概率是连接键不唯一导致了笛卡尔积。
性能优化与常见误区
数据量到百万行级别时,一些写法会明显变慢。记住几条原则:
- 永远不要写 for 循环逐行处理。pandas 的列操作是向量化的,底层用 NumPy 批量执行,速度比 Python 循环快几十倍。需要逐行逻辑时,优先用
map、apply配合向量化表达式,或者pd.cut、np.where这类专用函数。 - 链式赋值会报警告。
df[df["age"] > 30]["salary"] = 0这种写法可能改不到原表,正确做法是用.loc:df.loc[df["age"] > 30, "salary"] = 0。 - 类别列用 category 类型。城市、性别这种取值有限的列,转成 category 后内存能省一大截,分组速度也更快。
- 只加载需要的列。
read_csv(usecols=["name", "salary"])比全量读快很多,尤其是列特别多的宽表。
# 正确的条件赋值:用 .loc 一步到位
df.loc[df["salary"] < 10000, "level"] = "初级"
df.loc[(df["salary"] >= 10000) & (df["salary"] < 25000), "level"] = "中级"
df.loc[df["salary"] >= 25000, "level"] = "高级"
# 类别类型节省内存
df["city"] = df["city"].astype("category")
最后一个常见误区是把 pandas 当 Excel 用:数据量小的时候感觉不到,但养成"每一步都写代码、每一步都可复现"的习惯之后,分析过程就是一份天然的文档。下次别人问"这个数字怎么算出来的",你只需要把脚本发过去,而不是去翻那十几个版本的 Excel。
pandas 的学习曲线不是线性的:前两小时会觉得"怎么这么多函数名记不住",但一旦建立了"选列、过滤、分组、聚合"这套思维模型,剩下的只是查文档查表。