前面 4 章都是铺垫,本书真正的重头戏从第 5 章开始。pandas 的两个核心数据结构——Series(一维)和 DataFrame(二维)——是你今后每天打交道的主角。这一章覆盖它们的全部日常操作。
🐼 Series:带标签的一维数组
import pandas as pd
obj = pd.Series([4, 7, -5, 3])
# 0 4
# 1 7
# 2 -5
# 3 3
# dtype: int64
obj.values # array([ 4, 7, -5, 3])
obj.index # RangeIndex(start=0, stop=4, step=1)
# 自定义标签——Series 的灵魂
obj2 = pd.Series([4, 7, -5, 3], index=["d", "b", "a", "c"])
obj2["a"] # -5
obj2[["c", "a", "d"]] # 多标签取值,保持给定顺序
obj2[obj2 > 0] # 布尔筛选
obj2 * 2 # 向量化(NumPy 思维直接适用)
# 用字典创建:键变索引
sdata = {"Ohio": 35000, "Texas": 71000, "Utah": 65000}
pd.Series(sdata)
注意左边的标签列——它叫 index,是 Series/DataFrame 与 NumPy 数组最大的区别:数据有了名字。
📋 DataFrame:带索引的表格
data = {"state": ["Ohio", "Ohio", "Nevada", "Nevada"],
"year": [2000, 2001, 2000, 2001],
"pop": [1.5, 1.7, 2.4, 2.9]}
df = pd.DataFrame(data)
df.head() # 看前 5 行(默认)
df.columns # Index(['state','year','pop'])
df["state"] # 取一列 → Series
df.state # 等价(列名合法时)
df.loc[1] # 取一行 → Series
df["eastern"] = df.state == "Ohio" # 加新列
del df["eastern"] # 删列
嵌套字典创建时,外层键变列、内层键变行;df.index.name = "..." 和 df.columns.name 可以给轴起名。
🔒 Index 对象:不可变的标签集合
表格的行列标签存放在 Index 对象里,它有三个关键性质:不可变(改不了,防止误伤其他引用者)、有序集合(支持 & 交集、| 并集、^ 对称差)、可被多个表共享。
🔁 reindex:按新索引重排
obj = pd.Series([4.5, 7.2, -5.3], index=["d", "b", "a"])
obj.reindex(["a", "b", "c", "d"], method="ffill")
# c 原来没有 → 用前值填充(ffill = 前向填充)
reindex 不改数据,只按新标签重排 + 缺口填补。时间序列插值、对齐两张表的行全靠它。
🗑️ drop 与 🎯 loc / iloc
df.drop(["Nevada", "Ohio"]) # 按行删(返回新对象)
df.drop("pop", axis=1) # 按列删
# loc:标签定位;iloc:整数位置定位 —— 精确定位双雄
df.loc[1, "pop"] # 行标签 1、列 pop
df.loc[:2, ["year", "pop"]] # 切片含端点 2!
df.iloc[2, [1, 0]] # 第2行、第1和0列
df.iloc[:, :3] # 前 3 列
⚠️ 最著名的坑在这里:pandas 切片,整数用 iloc 风格不含尾、标签用 loc 风格含尾。所以书强烈建议:定位永远用 loc/iloc,别用裸 [] 链式取值,又慢又容易出 SettingWithCopyWarning。
布尔筛选就是 NumPy 套路:df[df["pop"] > 2]、df[(df.year == 2000) & (df.state == "Ohio")]。
➕ 算术与索引对齐
两个对象运算时,pandas 自动按标签对齐,对不上的位置填 NaN:
s1 = pd.Series([7.3, -2.5], index=["a", "c"])
s2 = pd.Series([-2.1, 3.6], index=["a", "d"])
s1 + s2
# a 5.2 ← 两边都有
# c NaN ← s2 没有 c
# d NaN ← s1 没有 d
不想产生 NaN?用算术方法 + fill_value:
s1.add(s2, fill_value=0) # 缺的按 0 参与 → c:-2.5, d:3.6
DataFrame 与 Series 相减默认按行广播(沿列方向),数学上像「每行减去一个向量」;想按列广播就写 df.sub(series, axis=0)。
🧮 apply / map:函数搬进表格
frame = pd.DataFrame(rng.standard_normal((4, 3)), columns=list("bde"))
f = lambda x: x.max() - x.min()
frame.apply(f) # 每列算一次(默认按行方向压)
frame.apply(f, axis=1) # 每行算一次
def top(df, n=2, col="b"):
return df.nlargest(n, col)
frame.apply(top, n=1) # 函数带参数也没问题
fmt = lambda x: f"{x:.2f}"
frame.map(fmt) # 逐元素处理(Series 用 .map,DataFrame 用 .map)
📏 排序与排名
frame.sort_index(axis=1, ascending=False) # 按索引排
frame.sort_values("b") # 按值排(NaN 默认沉底)
frame.rank(method="first") # 排名:first=并列按出现顺序
# 其他并列策略:average(默认)、min、max、dense
📊 描述统计三件套
df.sum() # 默认按列(NaN 自动跳过,skipna=True)
df.mean(axis=1) # 按行
df.idxmax() # 最大值所在的行标签
df.describe() # count/mean/std/min/25%/50%/75%/max 一次齐活
相关系数与协方差:s1.corr(s2)、s1.cov(s2),DataFrame 一键出矩阵 df.corr()。最后是分类统计三姐妹:
obj.unique() # 去重值
obj.value_counts() # 频数表——EDA 第一命令
obj.isin(["b", "c"]) # 成员判断
pd.unique(obj) # 顶层函数版,略快
📌 本章小结:Series=数据+标签,DataFrame=共享索引的多列 Series;Index 不可变;loc 按标签(含尾)iloc 按位置(不含尾);算术自动对齐、
fill_value堵 NaN;apply按轴、map逐元素;value_counts()是探索数据的第一反应。