Python 与数据栈
这一课你将能做到
- 用 numpy 做向量化统计(均值、布尔筛选),不写 for 循环
- 用 pandas 加载做题记录表,完成筛选、新增列、groupby 聚合,算出每道题/每个知识点/每个用户的正确率
- 用 matplotlib 画出正确率柱状图和用时分布直方图,导出成图片
为什么要学
机器学习的工作流里,建模只占两成,剩下八成是「把数据变成模型能吃的形状」。numpy 是所有 Python 数值库的底层(sklearn、pandas、PyTorch 的数据结构都向它看齐),pandas 是表格数据的瑞士军刀(本站的做题记录、题目表、用户表全靠它),matplotlib 是你观察数据的第一双眼睛。
这一课不做模型,只练「数据操作肌肉」——后面每一课的代码都默认你已经会这些。
先建立图像
numpy pandas matplotlib
ndarray → DataFrame → 图表
一列数字 一张带表名的表 柱状/折线/直方/散点
↓ ↓ ↓
算得快 行列都能筛选 肉眼找规律
(底层C实现) (能按名字取列) (趋势/异常一目了然)
本站数据流:
原始做题记录 → pandas 清洗聚合 → 特征表(矩阵) → 模型
跟着做
步骤 1:环境与 numpy 向量化
python -m venv .venv && .venv\Scripts\activate
pip install numpy pandas matplotlib
import numpy as np
scores = np.array([1, 0, 1, 1, 0, 1]) # 6 次作答,1=对
print(scores.mean()) # 0.667 正确率
durations = np.array([45, 120, 60, 40, 200, 55])
print(durations.mean(), durations.std()) # 平均用时和波动
mask = durations < 90 # 布尔掩码
print(scores[mask].mean()) # 只统计 90 秒内作答的正确率
注意 scores[mask]:没有 for 循环,一行完成「筛选+统计」,这叫向量化,比 Python 循环快一个数量级。
步骤 2:pandas 构造做题记录表
import pandas as pd
records = pd.DataFrame({
"user": ["u1", "u1", "u1", "u2", "u2", "u3", "u3", "u3", "u3", "u2"],
"question": ["q1", "q1", "q2", "q1", "q2", "q2", "q3", "q3", "q1", "q3"],
"topic": ["循环", "循环", "集合", "循环", "集合", "集合", "多线程", "多线程", "循环", "多线程"],
"correct": [1, 1, 0, 1, 1, 0, 0, 1, 0, 0],
"seconds": [42, 51, 130, 38, 66, 145, 210, 160, 98, 175],
})
print(records.head()) # 前几行长什么样
print(records.describe()) # 数值列的统计摘要
DataFrame 就是「带名字的 Excel 表」:records["seconds"] 取一列(Series),records[records["correct"] == 0] 筛行。
步骤 3:筛选与新增列
# 筛出多线程的所有作答
thread = records[records["topic"] == "多线程"]
print(thread["correct"].mean()) # 多线程正确率
# 新增列:是否超时(超过 2 分钟算卡壳)
records["is_slow"] = records["seconds"] > 120
print(records[["question", "seconds", "is_slow"]])
步骤 4:groupby 聚合——题目难度和用户画像的雏形
# 每道题的正确率 → 难度评分的雏形(正确率越低越难)
difficulty = records.groupby("question")["correct"].mean()
print(difficulty.sort_values()) # 正确率最低的题排最前
# 每个知识点:正确率、平均用时、作答次数,一次算完
topic_stat = records.groupby("topic").agg(
accuracy=("correct", "mean"),
avg_seconds=("seconds", "mean"),
attempts=("correct", "size"),
)
print(topic_stat)
groupby 的逻辑是「拆分-应用-合并」:按 topic 拆成小表 → 各自算均值 → 拼回一张汇总表。这是特征工程里最高频的操作。
步骤 5:matplotlib 出图
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"] # Windows 中文字体
plt.rcParams["axes.unicode_minus"] = False
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
difficulty.plot(kind="bar", ax=axes[0], title="题目正确率(越低越难)")
records["seconds"].plot(kind="hist", bins=6, ax=axes[1], title="作答用时分布")
plt.tight_layout()
plt.savefig("stats.png", dpi=120)
plt.show()
两张图直接告诉你:哪道题最需要修(正确率异常低)、用户卡壳集中在什么时间段(用时直方图的长尾)。
讲清楚要点
- 向量化是 pandas/numpy 的灵魂:
df["correct"].mean()比逐行 for 循环快得多也更不容易错。看到自己写了for i in range(len(df)),九成是写法有问题。 - Series 和 DataFrame 是两级结构:单列是 Series(带索引的数组),整表是 DataFrame(多个 Series 共用索引)。搞混这两级是初学者报错的重灾区。
- groupby 是特征工程的发动机:「该题全站正确率」「该用户历史正确率」「该知识点平均用时」全都是 groupby 出来的,ml-practice 综合项目会大量使用。
- 图表选型:类别对比用柱状图,数值分布用直方图,两变量关系用散点图,时间趋势用折线图。选错图,数据会撒谎。
describe()先看一眼再动手:均值、分位数、最大值扫一遍,缺失和异常往往当场现形。
你可能会懵的地方
- loc 和 iloc 有什么区别?
loc按标签(行名/列名),iloc按整数位置。df.loc[0, "seconds"]是「行标签为 0、seconds 列」,df.iloc[0, 4]是「第 0 行第 4 列」。筛选后索引不连续时两者差异最明显。 - NaN 是什么?和 None 一样吗?
NaN(Not a Number)是浮点世界的缺失值标记,
df.isna()统一识别 NaN 和 None。它有个坑:NaN == NaN是 False,判断缺失必须用isna()。 SettingWithCopyWarning是怎么回事? 链式筛选后赋值可能作用在副本上,改动不生效。正确姿势是单次定位:df.loc[mask, "col"] = value,或者先df = df.copy()再改。- Jupyter 和脚本怎么选?
探索数据、画图、试特征用 Jupyter(即时反馈);可复用的清洗/训练流程写成
.py脚本进版本库。上线的东西永远别从 notebook 里直接拷。 - 数据量大 pandas 卡怎么办?
先确认真的卡(10 万行 pandas 毫无压力)。真到了千万级,用
pd.read_csv(chunksize=...)分块处理,或者改用 Polars/DuckDB——但那是后话。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
for i in range(len(df)) 逐行算正确率 | 慢且易错 | df.groupby("question")["correct"].mean() |
df[col] == None 判断缺失 | 永远 False | df[col].isna() |
链式写法 df[a][b] = 1 赋值 | 可能改在副本上 | df.loc[mask, col] = 1 |
| 中文标签没设字体直接 plt | 图上全是方框 | 先设 font.sans-serif(见步骤 5) |
| 修改了切片以为改了原表 | SettingWithCopyWarning | 用 .loc 单次定位或显式 .copy() |
卡点急救
- matplotlib 中文全是方框:Windows 设
SimHei,macOS 设Arial Unicode MS/PingFang SC;还乱就检查axes.unicode_minus = False。 - 读 CSV 中文乱码:Excel 导出的文件用
pd.read_csv(path, encoding="utf-8-sig")或"gbk"试一遍。 - pip 安装慢/超时:换国内镜像
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple numpy pandas matplotlib。
面试常问
- pandas 的 groupby 原理是什么?
拆分-应用-合并(Split-Apply-Combine):按 key 把表拆成组,对每组应用聚合函数,再把结果合并成新表。配合
agg一次算多个指标,是特征工程的核心操作。 - 为什么 numpy 比 Python 列表快? numpy 底层是 C 实现的连续内存同类型数组,支持 SIMD 向量指令和广播;Python 列表是对象指针数组,每次运算都有解释器开销和类型检查。
- 如何用 pandas 找出异常值?
describe()看分位数轮廓;用quantile的 IQR 法(低于 Q1-1.5IQR 或高于 Q3+1.5IQR)或 3σ 法筛出离群行,再结合业务判断是删、是截断还是单独分箱。
自测三问
- 不用 for 循环,怎么算「每道题的正确率」?
df.loc和df.iloc的区别是什么?- 想看「作答用时有没有长尾」,该画什么图?
小结一句话
numpy 管数组、pandas 管表格、matplotlib 管出图,groupby 聚合是特征工程的发动机——这三件套熟练了,建模前的八成工作就顺了。
延伸
- pandas 官方文档
- 下一课:ml-preprocess 数据预处理——缺失值、编码、标准化,以及最要命的「数据泄漏」。