3
Pandas:Excel 的 Python 版
Pandas DataFrame
Pandas 处理的是带标签的表格——就像 Excel 里的一张工作表:一行一条记录,一列一个字段。读 CSV、清洗脏数据、按某列分组、算平均值、画个图——这些事在 Excel 里要点半天,Pandas 一行代码。它是数据科学的主力工具。
两种核心结构:Series 和 DataFrame
import pandas as pd
# Series:一维带标签的数组(像一列)
s = pd.Series([90, 85, 78], index=["小明", "小红", "小刚"])
print(s["小明"]) # 90
# DataFrame:二维表格(像一张工作表)
df = pd.DataFrame({
"姓名": ["小明", "小红", "小刚"],
"年龄": [18, 20, 19],
"成绩": [90, 85, 78]
})
print(df)
# 姓名 年龄 成绩
# 0 小明 18 90
# 1 小红 20 85
# 2 小刚 19 78
读 CSV & 查看数据
# 读 CSV(最常用)
df = pd.read_csv("sales.csv")
# 快速查看
df.head() # 前 5 行
df.tail() # 后 5 行
df.info() # 每列类型、非空数量
df.describe() # 数值列的统计:count/mean/std/min/max/分位数
print(df.columns) # 列名列表
print(df.shape) # (行数, 列数)
论EDA 五步检查清单
拿到新数据,按这个顺序走一遍,别上来就建模:① shape 看有多少行列;② info() 看类型对不对、有没有缺失;③ describe() 看数值分布;④ isnull().sum() 看缺失分布;⑤ 画几张分布图。很多"模型不行"其实是脏数据没看清。
选择、筛选、排序
# 选一列(返回 Series)
df["成绩"]
# 选多列
df[["姓名", "成绩"]]
# 条件筛选:成绩 >= 80 的人
good = df[df["成绩"] >= 80]
# 多条件:年龄 >= 19 且成绩 >= 80
df[(df["年龄"] >= 19) & (df["成绩"] >= 80)]
# 注意:每个条件要包括号,& 是与,| 是或
# 排序
df.sort_values("成绩", ascending=False) # 按成绩降序
# 增删列
df["是否及格"] = df["成绩"] >= 60
df = df.drop(columns=["是否及格"])
apply / map:对每列每行加工
# apply:对一列(或一行)应用函数
# 成绩分等级:90+ 优,80+ 良,否则中
def grade(score):
if score >= 90:
return "优"
elif score >= 80:
return "良"
else:
return "中"
df["等级"] = df["成绩"].apply(grade)
# map:用字典映射替换(小数据快)
df["性别"] = df["性别"].map({"M": "男", "F": "女"})
# 字符串访问器 .str:Series 上的字符串方法
df["邮箱"].str.lower()
df["姓名"].str.startswith("张")
df["电话"].str.extract(r"(\d{3})-(\d{4})") # 正则抽取
时间序列:to_datetime & resample
# 把字符串列转成真正的日期类型
df["日期"] = pd.to_datetime(df["日期"])
# 按日期过滤
df[df["日期"] >= "2026-01-01"]
# 取出年、月、星期
df["年"] = df["日期"].dt.year
df["月"] = df["日期"].dt.month
df["星期"] = df["日期"].dt.dayofweek # 0=周一
# resample:把日数据聚合成月/周
df = df.set_index("日期")
monthly = df.resample("M")["销售额"].sum() # 每月总销售额
缺失值与重复值
# 看哪些是缺失值
df.isnull().sum() # 每列有几个 NaN
# 删掉有缺失的行
df_clean = df.dropna()
# 用某个值填充(比如平均值)
df["成绩"] = df["成绩"].fillna(df["成绩"].mean())
# 去重
df = df.drop_duplicates()
异常值检测:IQR 方法
# 箱线图原理:Q1=25分位,Q3=75分位,IQR=Q3-Q1
# 超出 [Q1-1.5*IQR, Q3+1.5*IQR] 的就是异常值
q1 = df["工资"].quantile(0.25)
q3 = df["工资"].quantile(0.75)
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
# 过滤掉异常值
df = df[(df["工资"] >= lower) & (df["工资"] <= upper)]
分组聚合 groupby
Excel 里的"数据透视表",Pandas 一行搞定。
# 假设 df 有"部门"和"工资"两列
# 按部门分组,算每组的平均工资
result = df.groupby("部门")["工资"].mean()
# 一次算多个统计量
stats = df.groupby("部门")["工资"].agg(["mean", "max", "min", "count"])
# 透视表
pivot = pd.pivot_table(df, values="销售额",
index="月份", columns="产品", aggfunc="sum")
合并:merge / concat
# 像 SQL 的 JOIN:按某个键把两张表拼起来
users = pd.DataFrame({"uid": [1, 2, 3], "name": ["小明", "小红", "小刚"]})
orders = pd.DataFrame({"uid": [1, 1, 2], "amount": [100, 200, 50]})
merged = pd.merge(users, orders, on="uid", how="inner")
print(merged)
# uid name amount
# 0 1 小明 100
# 1 1 小明 200
# 2 2 小红 50
# concat:上下或左右拼
all_df = pd.concat([df1, df2], axis=0) # 上下拼
导出
df.to_csv("out.csv", index=False, encoding="utf-8-sig")
df.to_excel("out.xlsx", index=False)
# utf-8-sig 让 Excel 打开不乱码
类别型数据 category:省内存
一列里只有有限几个值(性别、城市、血型),存成 category 比 object 省内存,groupby 也更快。
df["城市"] = df["城市"].astype("category")
# 100 万行的"城市"列,object 可能占 80MB,category 只要几 MB
时间序列:DatetimeIndex / resample / rolling / shift
论时间序列四板斧
to_datetime 把字符串变时间;set_index 把时间列设成行索引;resample 按时间窗口聚合(日→周→月→年);rolling 算滑动窗口统计(移动平均);shift 把数据往前/往后挪一格,算"同比/环比"。这是金融、运营数据分析的日常。
import pandas as pd
import numpy as np
# 构造示例:100 天的销售额
dates = pd.date_range("2026-01-01", periods=100, freq="D")
df = pd.DataFrame({
"sales": np.random.randn(100).cumsum() + 100,
"visitors": np.random.randint(50, 200, size=100)
}, index=dates)
print(df.head())
# sales visitors
# 2026-01-01 99.583795 120
# 2026-01-02 99.849708 150
# ...
# 1. resample:按周(W)、按月(ME)聚合
weekly = df.resample("W").sales.sum() # 每周总销售额
monthly_mean = df.resample("ME").sales.mean() # 每月平均销售额
print(monthly_mean)
# 2026-01-31 100.6
# 2026-02-28 102.3
# Freq: ME, Name: sales, dtype: float64
# 2. rolling:7 天滑动平均(移动均线)
df["sales_ma7"] = df["sales"].rolling(window=7).mean()
# 前 6 个值是 NaN(窗口凑不齐)
# 3. shift:环比——今天比昨天涨了多少
df["sales_lag1"] = df["sales"].shift(1) # 昨天的销售额
df["pct_change"] = df["sales"].pct_change() # 环比涨幅(一行搞定)
print(df[["sales", "sales_lag1", "pct_change"]].head())
# 4. 时间筛选:取某段时间
df.loc["2026-02-01":"2026-02-15"] # 2 月前 15 天
df.loc["2026-03"] # 整个 3 月
# 5. dt 访问器:从时间戳提取各种字段
df["year"] = df.index.year
df["month"] = df.index.month
df["weekday"] = df.index.dayofweek # 0=周一
df["quarter"] = df.index.quarter # 1~4 季度
分组聚合进阶:agg / transform / apply
# 假设 df 有"部门"和"工资"两列
# 1. agg:一次算多个统计量
stats = df.groupby("部门")["工资"].agg([
("平均", "mean"),
("最高", "max"),
("最低", "min"),
("人数", "count"),
("标准差", "std")
])
print(stats)
# 平均 最高 最低 人数 标准差
# 部门
# 研发 25000 35000 18000 10 5200.5
# 产品 22000 28000 16000 8 3800.2
# 不同列用不同聚合函数
df.groupby("部门").agg({
"工资": ["mean", "max"],
"年龄": "mean",
"入职日期": "min" # 最早入职日期
})
# 2. transform:把聚合结果"广播"回每一行(和原表同形状)
# 经典用途:在组内做标准化
df["工资_组内标准化"] = df.groupby("部门")["工资"].transform("mean")
# 每行都等于它所在部门的平均工资(列长度和 df 一样)
# 组内 Z-score:(工资 - 部门均值) / 部门标准差
def zscore(x):
return (x - x.mean()) / x.std()
df["工资_组内zscore"] = df.groupby("部门")["工资"].transform(zscore)
# 3. apply:对每个组应用一个自定义函数(最慢,最灵活)
def top2_salary(g):
return g.nlargest(2) # 每组工资最高的 2 个人
df.groupby("部门").apply(top2_salary)
# 4. 过滤组:只要人数大于 5 的部门
big_dept = df.groupby("部门").filter(lambda g: g["工资"].count() > 5)
多表合并:merge / join / concat / append
import pandas as pd
users = pd.DataFrame({
"uid": [1, 2, 3, 4],
"name": ["小明", "小红", "小刚", "小李"]
})
orders = pd.DataFrame({
"uid": [1, 1, 2, 5],
"amount": [100, 200, 50, 999]
})
# 1. merge:像 SQL JOIN,on 指定连接键
inner = pd.merge(users, orders, on="uid", how="inner")
print(inner)
# uid name amount
# 0 1 小明 100
# 1 1 小明 200
# 2 2 小红 50
left = pd.merge(users, orders, on="uid", how="left")
# 保留 users 全部行;没有订单的人 amount 是 NaN
outer = pd.merge(users, orders, on="uid", how="outer")
# 两边都保留,找不到的地方填 NaN
# 连接键列名不一样时
pd.merge(users, orders, left_on="uid", right_on="user_id", how="inner")
# 2. concat:上下堆(axis=0)或左右拼(axis=1)
df1 = pd.DataFrame({"a": [1,2], "b": [3,4]})
df2 = pd.DataFrame({"a": [5,6], "b": [7,8]})
all_rows = pd.concat([df1, df2], axis=0, ignore_index=True)
# a b
# 0 1 3
# 1 2 4
# 2 5 7
# 3 6 8
# 3. join:按索引合并(不常用,一般用 merge)
df1.join(df2, lsuffix="_l", rsuffix="_r")
Pandas 章节面试题
面试 · Pandas
Q1. apply / transform / agg 三者有什么区别?
查看答案
agg 返回聚合后的统计量(行数变少);transform 返回与原表相同形状(把聚合值广播回每行);apply 最灵活,可以返回任意形状,但最慢。
Q2. Pandas 怎么优化大表性能?
查看答案
① 类别列转 category;② 数值列选更小的 dtype(int32 代替 int64);③ 用 pd.read_csv(usecols=[...]) 只读需要的列;④ 用 query() / eval() 替代布尔索引;⑤ 千万行以上用 PyArrow 后端或 DuckDB。
Q3. merge 的 how 参数 inner / left / outer / right 区别?
查看答案
inner 只保留两边都匹配的行;left 保留左表全部;right 保留右表全部;outer 保留两边全部(SQL 里分别对应 INNER / LEFT OUTER / RIGHT OUTER / FULL OUTER JOIN)。
Q4. SettingWithCopyWarning 怎么修?
查看答案
用 .loc[行条件, 列名] = 值 替代链式索引 df[条件][列] = 值。链式索引可能先返回副本,改副本不影响原表。
Q5. 怎么算"环比增长率"?
查看答案
df["col"].pct_change() 一行搞定;或者 df["col"] / df["col"].shift(1) - 1。同比用 .shift(12)(月数据)或 .shift(4)(周数据)。