pandas 速查表
最后更新
创建 DataFrame
手动构建的小型 DataFrame,适合用来写示例、测试和查找表。
| 操作 | 代码 |
|---|---|
| 导入 pandas 和 NumPy | import pandas as pd
import numpy as np |
| 由列组成的字典创建 | df = pd.DataFrame({"name": ["Ada", "Ben"], "age": [36, 28]}) |
| 由行组成的列表创建 | pd.DataFrame([{"name": "Ada", "age": 36}, {"name": "Ben", "age": 28}]) |
| 由 NumPy 数组创建 | pd.DataFrame(np.arange(6).reshape(3, 2), columns=["x", "y"]) |
| 带标签的 Series | s = pd.Series([3, 5, 2], index=["a", "b", "c"]) |
| 日期范围 | pd.date_range("2026-01-01", periods=7, freq="D") |
读写文件
一行代码把文件读进 DataFrame,一行代码再写回去。
| 操作 | 代码 |
|---|---|
| 读取 CSV 文件 | df = pd.read_csv("sales.csv") |
| 只读部分列,并解析为日期 | pd.read_csv("sales.csv", usecols=["date", "city", "sales"], parse_dates=["date"]) |
| 把某一列作为索引 | pd.read_csv("sales.csv", index_col="id") |
| 制表符或其他分隔符 | pd.read_csv("sales.tsv", sep="\t") |
| 把其他标记也当作缺失值 | pd.read_csv("sales.csv", na_values=["n/a", "-"]) |
| 只读取前面若干行 | pd.read_csv("sales.csv", nrows=1000) |
| 读取 Excel 工作表(需要 openpyxl) | pd.read_excel("report.xlsx", sheet_name="2026") |
| 读取 JSON 记录 | pd.read_json("records.json") |
| 从 SQL 数据库读取 | pd.read_sql("SELECT * FROM orders", conn) |
| 写入 CSV,不带索引 | df.to_csv("clean.csv", index=False) |
| 写入 Excel 或 Parquet | df.to_excel("clean.xlsx", index=False), df.to_parquet("clean.parquet") |
查看数据
拿到新数据集后最先要用的几个调用。
| 操作 | 代码 |
|---|---|
| 开头和末尾几行 | df.head(), df.tail(3) |
| 行数和列数 | df.shape # (6, 7) |
| 列类型和非空值数量 | df.info() |
| 数值列的统计摘要 | df.describe() |
| 所有列的摘要 | df.describe(include="all") |
| 列名和列类型 | df.columns, df.dtypes |
| 统计每个值出现的次数 | df["city"].value_counts() |
| 每个值所占的比例 | df["city"].value_counts(normalize=True) |
| 不同的值及其个数 | df["city"].unique(), df["city"].nunique() |
| 每列的缺失值数量 | df.isna().sum() |
| 随机抽取几行 | df.sample(3, random_state=0) |
| 数值列之间的相关系数 | df.corr(numeric_only=True) |
选择列和行
loc 按标签选择,切片包含终点;iloc 按位置选择,切片不含终点。
| 操作 | 代码 |
|---|---|
| 选一列,返回 Series | df["age"] |
| 选多列,返回 DataFrame | df[["name", "age"]] |
| 按标签选择行和列 | df.loc[0:2, ["name", "age"]] |
| 按位置选择行和列 | df.iloc[0:2, 0:3] |
| 取单个值 | df.at[0, "age"], df.iat[0, 3] |
| 最后一行 | df.iloc[-1] |
| 选出某种类型的列 | df.select_dtypes(include="number") |
| 文本列(pandas 3) | df.select_dtypes(include="str") |
| 按列名匹配选择 | df.filter(like="sal"), df.filter(regex="^(age|salary)$") |
| 按某一列查找行 | df.set_index("name").loc["Ada", "salary"] |
| 恢复成 0、1、2 的索引 | df.reset_index(drop=True) |
| 删除列或行 | df.drop(columns=["email"]), df.drop(index=[0, 1]) |
筛选行
每个条件都用括号括起来,再用 &(与)、|(或)和 ~(非)组合。
| 操作 | 代码 |
|---|---|
| 满足条件的行 | df[df["age"] > 30] |
| 同时满足两个条件 | df[(df["age"] > 30) & (df["city"] == "Oslo")] |
| 满足任意一个条件 | df[(df["city"] == "Oslo") | (df["salary"] > 6000)] |
| 值在列表中 | df[df["city"].isin(["Oslo", "Rome"])] |
| 值不在列表中 | df[~df["city"].isin(["Oslo", "Rome"])] |
| 介于两个值之间(含两端) | df[df["age"].between(30, 40)] |
| 用查询字符串筛选 | df.query("age > 30 and city == 'Oslo'") |
| 在查询中使用变量 | min_age = 30
df.query("age > @min_age") |
| 文本包含某个字符串 | df[df["name"].str.contains("e", case=False)] |
| 缺失或非缺失的值 | df[df["age"].isna()], df[df["age"].notna()] |
| 筛选行的同时选择列 | df.loc[df["age"] > 30, ["name", "salary"]] |
| 最大或最小的 n 行 | df.nlargest(3, "salary"), df.nsmallest(2, "age") |
| 某个值重复出现的行 | df[df.duplicated(subset=["city"], keep=False)] |
排序与排名
| 操作 | 代码 |
|---|---|
| 按某一列排序 | df.sort_values("salary") |
| 从大到小 | df.sort_values("salary", ascending=False) |
| 按多列排序 | df.sort_values(["city", "salary"], ascending=[True, False]) |
| 缺失值排在前面 | df.sort_values("age", na_position="first") |
| 按索引排序 | df.sort_index() |
| 排名,最大值为第 1 名 | df["salary"].rank(ascending=False) |
添加和修改列
对整列操作速度很快。只有在没有内置方法可用时,才考虑 apply。
| 操作 | 代码 |
|---|---|
| 由其他列计算新列 | df["bonus"] = df["salary"] * 0.1 |
| 在链式调用中添加列 | df.assign(bonus=lambda d: d["salary"] * 0.1) |
| 根据条件取值 | df["level"] = np.where(df["age"] >= 40, "senior", "junior") |
| 根据多个条件取值 | df["band"] = np.select([df["age"] < 30, df["age"] < 45], ["young", "mid"], default="senior") |
| 修改满足条件的值 | df.loc[df["city"] == "Lima", "salary"] = 4000 |
| 用字典映射值 | df["country"] = df["city"].map({"Oslo": "NO", "Rome": "IT", "Lima": "PE"}) |
| 对一列应用函数 | df["salary"].apply(lambda x: round(x, -3)) |
| 对每个单元格应用函数 | df[["age", "salary"]].map(lambda x: x / 1000) |
| 重命名列 | df.rename(columns={"salary": "pay"}) |
| 修改列的类型 | df["salary"].astype("float64") |
| 文本转数字,无效值变为 NaN | pd.to_numeric(s, errors="coerce") |
| 把数值分到带标签的区间 | pd.cut(df["age"], bins=[0, 30, 45, 120], labels=["young", "mid", "senior"]) |
| 把数值分成数量相等的组 | pd.qcut(df["salary"], q=4, labels=False) |
| 逐行循环(很慢,最后的选择) | for row in df.itertuples():
print(row.name, row.salary) |
缺失值与数据清洗
缺失的数字显示为 NaN,缺失的日期显示为 NaT。下面每个调用都会返回新对象,记得把结果赋值回去。
| 操作 | 代码 |
|---|---|
| 每列缺失值的比例 | df.isna().mean() |
| 删除含有缺失值的行 | df.dropna() |
| 删除关键列缺失的行 | df.dropna(subset=["age"]) |
| 按列填充不同的值 | df.fillna({"age": df["age"].median()}) |
| 用上一行或下一行填充 | df["age"].ffill(), df["age"].bfill() |
| 用插值填补空缺 | df["age"].interpolate() |
| 把标记转换为缺失值 | df.replace(["n/a", "-", ""], np.nan) |
| 删除重复行 | df.drop_duplicates() |
| 每个键只保留最后一行 | df.drop_duplicates(subset=["city"], keep="last") |
| 整理列名 | df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_") |
| 限制极端值 | df["salary"].clip(lower=4000, upper=6000) |
| 重复文本使用 category 类型 | df["city"] = df["city"].astype("category") |
文本列(.str)
在 pandas 3 中,文本列默认使用 str 数据类型。.str 方法作用于整列,缺失值保持缺失。
| 操作 | 代码 |
|---|---|
| 转大写、小写或首字母大写 | df["name"].str.upper(), df["name"].str.lower(), df["name"].str.title() |
| 去掉首尾空格 | df["name"].str.strip() |
| 替换文本 | df["email"].str.replace("mail.com", "example.com") |
| 用正则表达式替换 | df["email"].str.replace(r"@.*", "", regex=True) |
| 包含、以某字符开头 | df["name"].str.contains("a"), df["name"].str.startswith("E") |
| 每个值的长度 | df["name"].str.len() |
| 前几个字符 | df["name"].str[:2] |
| 拆分成多列 | df["email"].str.split("@", expand=True) |
| 用正则分组提取 | df["email"].str.extract(r"@(\w+)") |
| 拼接文本列 | df["name"] + " (" + df["city"] + ")" |
| 数字前面补零 | df["salary"].astype("str").str.zfill(6) |
| 新的文本数据类型 | df["name"].dtype # str |
日期与时间
.dt 访问器用于日期时间列。有序的日期索引(下面的 ts)支持按文本切片和重采样。
| 操作 | 代码 |
|---|---|
| 把一列解析为日期时间 | df["date"] = pd.to_datetime(df["date"]) |
| 按指定格式解析 | pd.to_datetime(["03/01/2026", "15/01/2026"], format="%d/%m/%Y") |
| 年、月、星期名称 | df["date"].dt.year, df["date"].dt.month, df["date"].dt.day_name() |
| 按月份归并日期 | df["date"].dt.to_period("M") |
| 两个日期之间相差的天数 | (pd.Timestamp("2026-12-31") - df["date"]).dt.days |
| 加上天数或月数 | df["date"] + pd.Timedelta(days=30), df["date"] + pd.DateOffset(months=1) |
| 某个日期范围内的行 | df[df["date"].between("2021-01-01", "2022-12-31")] |
| 日期索引,按文本切片 | ts = df.set_index("date").sort_index()
ts.loc["2021":"2022"] |
| 按月求和 | ts["salary"].resample("ME").sum() |
按周求均值;"QE" 表示按季度,"YE" 表示按年 | ts["salary"].resample("W").mean() |
| 滚动平均 | ts["salary"].rolling(3).mean() |
| 与上一行相比的变化 | ts["salary"].diff(), ts["salary"].pct_change() |
| 值整体下移一行 | ts["salary"].shift(1) |
| 累计求和 | ts["salary"].cumsum() |
| 设置和转换时区 | ts.index.tz_localize("UTC").tz_convert("Europe/Oslo") |
分组与聚合
groupby 按键把行拆分成组,对每组应用函数,再把结果合并起来。
| 操作 | 代码 |
|---|---|
| 每组的均值 | df.groupby("city")["salary"].mean() |
| 一次计算多个统计量 | df.groupby("city")["salary"].agg(["mean", "max", "count"]) |
| 为结果列命名 | df.groupby("city").agg(avg_pay=("salary", "mean"), people=("name", "count")) |
| 按两列分组 | df.groupby(["city", "team"])["salary"].sum() |
| 把分组键保留为列 | df.groupby("city", as_index=False)["salary"].mean() |
| 每组的行数 | df.groupby("city").size() |
| 把分组结果写到每一行 | df["city_avg"] = df.groupby("city")["salary"].transform("mean") |
| 只保留部分分组 | df.groupby("city").filter(lambda g: len(g) >= 2) |
| 每组中最大值所在的行 | df.loc[df.groupby("city")["salary"].idxmax()] |
| 每组的前 n 行 | df.sort_values("salary", ascending=False).groupby("city").head(2) |
| 给每组内的行编号 | df.groupby("city").cumcount() |
| 遍历各个分组 | for city, group in df.groupby("city"):
print(city, len(group)) |
| 在一条链中完成筛选、分组和排序 | (df.query("age > 30")
.groupby("city", as_index=False)
.agg(avg_pay=("salary", "mean"))
.sort_values("avg_pay", ascending=False)) |
透视与重塑
长格式数据每行是一条观测;宽格式数据把一个变量分散到多列中。
| 操作 | 代码 |
|---|---|
| 数据透视表 | df.pivot_table(index="city", columns="team", values="salary", aggfunc="mean", fill_value=0) |
| 带合计的数据透视表 | df.pivot_table(index="city", values="salary", aggfunc="sum", margins=True) |
| 统计组合出现的次数 | pd.crosstab(df["city"], df["team"]) |
| 每行内的占比 | pd.crosstab(df["city"], df["team"], normalize="index") |
| 宽格式转长格式 | long = df.melt(id_vars="name", value_vars=["age", "salary"], var_name="field", value_name="value") |
| 长格式转宽格式 | long.pivot(index="name", columns="field", values="value") |
| 把内层索引转为列 | df.groupby(["city", "team"])["salary"].sum().unstack(fill_value=0) |
| 列表中的每一项展开成一行 | pd.DataFrame({"id": [1, 2], "tags": [["a", "b"], ["c"]]}).explode("tags") |
| 对一列做独热编码 | pd.get_dummies(df["team"], prefix="team") |
| 行列互换 | df.T |
合并 DataFrame
concat 把多个 DataFrame 堆叠起来,merge 则像 SQL 一样按键列连接。
| 操作 | 代码 |
|---|---|
| 按行堆叠 | pd.concat([df_2025, df_2026], ignore_index=True) |
| 左右并排拼接 | pd.concat([left, right], axis=1) |
| 按键列连接 | orders.merge(customers, on="customer_id", how="left") |
| 连接方式 | how="inner", "left", "right", "outer" |
| 键列名称不同 | orders.merge(users, left_on="customer_id", right_on="id") |
| 校验连接并标记每行的来源 | orders.merge(customers, on="customer_id", how="left", validate="many_to_one", indicator=True) |
| 按索引连接 | left.join(right, how="inner") |
| 在另一张表中找不到匹配的行 | orders[~orders["customer_id"].isin(customers["customer_id"])] |
用 pandas 绘图
pandas 底层用 Matplotlib 绘图。每个绘图调用都会返回 Matplotlib 的 ax,你可以继续调整样式。
| 操作 | 代码 |
|---|---|
| Series 折线图 | ts["salary"].plot() |
| 用两列画折线图 | df.plot(x="date", y="salary") |
| 汇总结果的柱状图 | df.groupby("city")["salary"].mean().plot.bar() |
| 排好序的水平条形图 | df["city"].value_counts().sort_values().plot.barh() |
| 直方图 | df["salary"].plot.hist(bins=20) |
| 散点图 | df.plot.scatter(x="age", y="salary") |
| 按组绘制箱线图 | df.boxplot(column="salary", by="city") |
| 标题和坐标轴标签 | ax = df.plot(x="date", y="salary", title="Salary")
ax.set_ylabel("EUR") |
| 显示或保存图表 | plt.show(), ax.figure.savefig("salary.png", dpi=150) |
pandas 3:旧写法与现在的写法
从旧教程复制来的代码在 pandas 3 中经常报错或产生警告,请改用 pandas 3 一列中的写法。
| 操作 | pandas 3 | 旧写法 |
|---|---|---|
| 对每个单元格应用函数 | df.map(f) | df.applymap(f) |
| 用上一行填充 | df.ffill() | df.fillna(method="ffill") |
| 添加行 | pd.concat([df, new_rows]) | df.append(new_rows) |
| 按月末重采样 | ts["salary"].resample("ME").sum() | resample("M") |
| 按小时生成日期 | pd.date_range("2026-01-01", periods=24, freq="h") | freq="H" |
| 设置单个值 | df.loc[0, "age"] = 37 | df["age"][0] = 37 |
| 修改筛选出的子集 | high = df[df["salary"] > 5000]
high["salary"] = 0 | high = df[df["salary"] > 5000].copy() |
| 判断是否为文本列 | df["name"].dtype == "str" | df["name"].dtype == object |
数据分析师每天都会用到的 pandas 代码,按任务分组:创建和加载数据、查看数据、选择和筛选行、添加列、清洗缺失值、处理文本和日期、分组聚合、重塑、合并以及绘图。每一行都是一段可以直接粘贴的短代码,再配一句话说明它的作用。
这里的内容全部基于 Coddy 运行的 pandas 3 编写:文本列使用新的 str 数据类型,写时复制(Copy-on-Write)始终开启,applymap 和 fillna(method=...) 等已删除的 API 只出现在旧写法对照表里。示例使用一个名为 df 的 DataFrame,包含 name、city、team、age、salary、date 和 email 这几列。还不熟悉 Python 本身?先看 Python 速查表,试试 Python 在线编辑器,再把 NumPy、Matplotlib 和 scikit-learn 速查表放在手边一起用。
pandas 速查表常见问题
这份 pandas 速查表免费吗?
免费。这份 pandas 速查表完全免费,无需注册。把它加入书签,或者在浏览器里打印成 PDF,需要查 groupby、merge 或数据透视表的写法时随时回来看。
这份速查表对应哪个 pandas 版本?
str 数据类型,写时复制始终开启,pandas 3 中已删除的调用(applymap、fillna(method=...)、append,以及 M 和 H 这两个频率别名)只出现在最后一张表里,旁边写明了现在的写法。除了依赖 str 数据类型或写时复制的那几行,几乎所有代码片段在 pandas 2.2 上也能运行。loc 和 iloc 有什么区别?
loc 按标签选择,也就是索引值和列名,像 df.loc[0:2] 这样的切片包含终点。iloc 像 Python 列表一样按整数位置选择,所以 df.iloc[0:2] 返回前两行,不含终点。把 loc 和条件一起用,可以一步完成筛选行和选择列:df.loc[df["age"] > 30, ["name"]]。怎么按多个条件筛选 DataFrame?
& 表示与、| 表示或、~ 表示非:df[(df["age"] > 30) & (df["city"] == "Oslo")]。Python 的 and 和 or 不能用于整列。条件很长时,df.query("age > 30 and city == 'Oslo'") 读起来更清楚。merge、join 和 concat 有什么区别?
merge 像 SQL 连接一样按键列合并两个 DataFrame,可选 how="inner"、"left"、"right" 或 "outer"。join 是按索引合并的快捷方式。concat 完全不匹配键:它把多个 DataFrame 上下堆叠,或者用 axis=1 左右并排。为什么赋值后 DataFrame 没有变化?
df["age"][0] = 37 这样的链式赋值修改的是一个临时对象,df 本身不会变。改成一次 loc 调用:df.loc[0, "age"] = 37。dropna 和 fillna 等方法也会返回新对象,记得把结果赋值回去。