Pandas 入门教程 —— 从 Excel 思维到 DataFrame 思维
本章目标
- 理解 Series 和 DataFrame 的数据结构
- 掌握数据清洗的标准流程(ETL)
- 学会用 groupby+agg 替代 Excel 透视表
- 理解链式操作与管道编程风格
1. Pandas 的核心数据结构
Series —— 带索引的一维数组
import pandas as pd
s = pd.Series([10, 20, 30, 40], index=["a", "b", "c", "d"])
print(s)
# a 10
# b 20
# c 30
# d 40
# Series 同时具备 dict 和 ndarray 的特性
print(s["b"]) # 像 dict
print(s[s > 20]) # 像 ndarray 布尔索引
print(s.values) # 底层 NumPy 数组
DataFrame —— 带行列标签的二维表
df = pd.DataFrame({
"name": ["Alice", "Bob"],
"age": [25, 30],
"salary": [50000, 60000],
})
# 每列是一个 Series
# 行索引 (index) 默认 0, 1, 2...
# 列索引 (columns) 是字典的 key
2. 数据清洗标准流程(EDA Pipeline)
# 第1步:加载
df = pd.read_csv("raw_data.csv")
# 第2步:表面检查
print(df.shape)
print(df.head())
print(df.info())
print(df.describe())
print(df.isnull().sum())
# 第3步:缺失值处理
df["age"].fillna(df["age"].median(), inplace=True) # 数值:中位数
df["city"].fillna("Unknown", inplace=True) # 分类:特定值
# 第4步:重复值检查
df.drop_duplicates(subset=["email"], keep="first", inplace=True)
# 第5步:类型转换
df["date"] = pd.to_datetime(df["date"])
df["category"] = df["category"].astype("category")
# 第6步:异常值处理
q1 = df["price"].quantile(0.25)
q3 = df["price"].quantile(0.75)
iqr = q3 - q1
df = df[(df["price"] >= q1 - 1.5 * iqr) & (df["price"] <= q3 + 1.5 * iqr)]
# 第7步:保存
df.to_parquet("clean_data.parquet")
3. groupby 深度指南
groupby 三部曲:split → apply → combine
原始数据 Split Apply Combine
┌──┬──┬──┐ ┌──┬──┬──┐
│A│X│10│ │A│X│10│ → mean() → A: 20
│B│Y│30│ → │A│X│30│ → sum() → B: 105
│A│Z│20│ │B│Y│30│
│B│Y│75│ │B│Z│75│
└──┴──┴──┘ └──┴──┴──┘
agg vs transform vs apply
df = pd.DataFrame({
"team": ["A", "A", "B", "B"],
"score": [10, 20, 30, 40],
})
# agg: 聚合为一行(shape 缩小)
print(df.groupby("team").agg(avg_score=("score", "mean")))
# avg_score
# team
# A 15.0
# B 35.0
# transform: 保持原形状(广播回来)
df["team_avg"] = df.groupby("team")["score"].transform("mean")
# team score team_avg
# 0 A 10 15.0
# 1 A 20 15.0
# 2 B 30 35.0
# 3 B 40 35.0
# apply: 最灵活,但最慢
def top_score(group):
return group.nlargest(1, "score")
print(df.groupby("team").apply(top_score))
4. 链式操作与管道
方法链(Method Chaining)
# ❌ 传统风格 —— 中间变量地狱
df1 = df[df["age"] > 25]
df2 = df1.groupby("city")
df3 = df2["salary"].mean()
df4 = df3.sort_values(ascending=False)
result = df4.head(5)
# ✅ 链式风格 —— 清晰的数据流
result = (
df
.query("age > 25")
.groupby("city")["salary"]
.mean()
.sort_values(ascending=False)
.head(5)
)
pipe —— 自定义函数链入管道
def remove_outliers(df, column):
q1, q3 = df[column].quantile([0.25, 0.75])
iqr = q3 - q1
return df[df[column].between(q1 - 1.5*iqr, q3 + 1.5*iqr)]
def add_features(df):
df["income_per_age"] = df["salary"] / df["age"]
return df
clean_df = (
df
.pipe(remove_outliers, "salary")
.pipe(add_features)
.dropna()
)
5. Pandas 与 SQL 对照表
| SQL |
Pandas |
SELECT col1, col2 |
df[["col1", "col2"]] |
WHERE condition |
df[df["col"] > 10] 或 df.query() |
GROUP BY |
df.groupby("col") |
HAVING |
.filter(lambda g: g["val"].sum() > 100) |
ORDER BY |
df.sort_values("col") |
LIMIT n |
df.head(n) |
JOIN ... ON |
pd.merge(left, right, on="key") |
UNION ALL |
pd.concat([df1, df2]) |
COUNT(DISTINCT) |
df["col"].nunique() |
CASE WHEN |
np.select() 或 pd.cut() |
思考题
- 什么情况下
df.groupby().apply() 比 agg() 更好?性能上有何代价?
pd.cut 和 pd.qcut 的区别是什么?各自适用什么场景?
- 为什么
pd.concat([df1, df2]) 有时会导致索引重复?如何修复?
- Pandas 的
inplace=True 参数有什么优缺点?为什么很多开发者建议避免使用?