Python · 3 分钟阅读
Pandas 基础操作
2026 年视角:Pandas 仍是数据分析师日常的主力(数据清洗、Excel/CSV 交互、ETL)。
pyarrow后端默认开启、Polars 在性能敏感场景是强力替代、2.x 引入的可空 dtype(Int64、string)已稳定。本篇用 2.2+ 风格演示。
准备
pip install "pandas[pyarrow]" openpyxl
import pandas as pd
print(pd.__version__) # 2.2+
pd.options.future_info # 2.x 引入,提醒你哪些 future warnings
核心对象
# Series:带标签的一维数组
s = pd.Series([1, 2, 3], index=["a", "b", "c"])
# DataFrame:表格
df = pd.DataFrame(
{
"name": ["Alice", "Bob", "Carol"],
"age": [30, 25, 40],
"city": ["BJ", "SH", "GZ"],
}
)
读 / 写文件
| 格式 | 读 | 写 |
|---|---|---|
| CSV | pd.read_csv(...) |
df.to_csv(..., index=False) |
| Excel | pd.read_excel(...) |
df.to_excel(..., index=False) |
| Parquet | pd.read_parquet(...) |
df.to_parquet(...) |
| JSON | pd.read_json(...) |
df.to_json(..., orient="records") |
| SQL | pd.read_sql(...) |
df.to_sql(...) |
# 常用参数
df = pd.read_csv(
"data.csv",
dtype_backend="pyarrow", # 列类型交给 pyarrow
parse_dates=["created_at"],
na_values=["-", "N/A"],
)
df.to_parquet("data.parquet", engine="pyarrow")
大量数据落地用 Parquet:列式压缩、保留类型、比 CSV 小一个数量级。
速览 DataFrame
df.head(5) # 前 5 行
df.tail(3) # 后 3 行
df.info() # 字段 / 类型 / 缺失
df.describe() # 数值列统计
df.shape # (行数, 列数)
df.dtypes # 每列类型
df.columns # 列名
df.index # 索引
选择数据
列
df["name"] # 单列(Series)
df[["name", "age"]] # 多列(DataFrame)
行:loc / iloc
df.loc[0] # 按标签(默认整数索引=位置)
df.loc[0:2, ["name", "age"]] # 行 + 列
df.loc[df["age"] > 30, "name"] # 条件 + 单列
df.iloc[0] # 按位置
df.iloc[0:3, 0:2] # 前 3 行前 2 列
条件过滤
df[df["age"] > 30]
df.query("age > 30 and city == 'BJ'")
df.query("age > @threshold") # 用 @ 引用外部变量
修改
# 新列
df["is_adult"] = df["age"] >= 18
# 改类型
df["age"] = df["age"].astype("Int64") # 2.x 可空整数
# 改名
df = df.rename(columns={"name": "full_name"})
# 排序
df.sort_values("age", ascending=False)
astype("Int64")(大写 I)是可空整型;int64(小写)不允许NaN。
缺失值
df.isna() # 布尔矩阵
df.isna().sum() # 每列缺失数
df.dropna() # 丢缺失行
df.fillna(0) # 用 0 填
df.fillna({"age": df["age"].median(), "city": "UNKNOWN"})
聚合
df.groupby("city")["age"].mean()
df.groupby("city").agg(
avg_age=("age", "mean"),
n=("age", "count"),
)
df.value_counts("city") # 频次表
df["age"].agg(["min", "max", "mean", "std"])
合并
# 纵向
pd.concat([df1, df2], ignore_index=True)
# 横向(SQL 风格 join)
pd.merge(df1, df2, on="id", how="left") # left/right/inner/outer
应用函数
# 列上:lambda 或函数
df["age2"] = df["age"].apply(lambda x: x * 2)
# 行上:axis=1
df["info"] = df.apply(lambda r: f"{r['name']} ({r['age']})", axis=1)
# 更快的矢量化
import numpy as np
df["age2"] = np.where(df["age"] > 30, df["age"], 0)
apply是 Python 级循环,慢;能用 numpy 矢量化就别用。
实战:从 CSV 到 Parquet
import pandas as pd
df = pd.read_csv("orders.csv", dtype_backend="pyarrow")
print(df.shape, df.dtypes.to_dict())
# 清洗
df = df.dropna(subset=["order_id"])
df["amount"] = df["amount"].astype("Float64")
df = df.query("amount >= 0")
# 落地
df.to_parquet("orders.parquet", engine="pyarrow", index=False)
一些经验
- 优先用
pyarrow后端(dtype_backend="pyarrow"),字符串和数值更省内存 - 一次过 100 万行先用
info(memory_usage="deep")看内存 - 大数据 + 性能敏感:考虑 Polars,API 接近但快一截
inplace=True在 2.x 里被逐步弃用,直接用赋值返回SettingWithCopyWarning出现 = 链式赋值不明确,写法上df = df.assign(...)或显式df.loc[...]