第5章封面

前面 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 数组最大的区别:数据有了名字

Series与DataFrame结构

📋 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() 是探索数据的第一反应。