数据工具链:NumPy / Pandas / 可视化
目标:拿到一份陌生 CSV,能在 2 小时内产出可行动的结论。
这是 AI 从业者使用频率最高的技能。模型可以换,数据处理每天都要做。
三件套的分工
| 工具 | 负责 | 心智模型 |
|---|---|---|
| NumPy | 数值数组、向量化计算 | 「一整块数字」 |
| Pandas | 表格、索引、分组聚合、清洗 | 「带行标列标的 Excel」 |
| Matplotlib / Seaborn | 可视化 | 「用图说服人」 |
原则:能用向量化就别写 for 循环,差 10–100 倍性能。
一、NumPy:向量化思维
python
import numpy as np
# 创建
a = np.array([1, 2, 3])
z = np.zeros((2, 3)); o = np.ones(5); r = np.random.randn(3, 4)
seq = np.arange(0, 10, 2); lin = np.linspace(0, 1, 5)
# 形状操作
x = np.random.randn(32, 128, 768) # (batch, seq, dim)
print(x.reshape(32, -1).shape) # (32, 98304) -1 自动推断
print(x.transpose(0, 2, 1).shape) # (32, 768, 128)
print(x[0].shape, x[:, 0].shape) # (128,768) (32,768) 切片
# 统计(指定 axis 是关键)
print(x.mean(axis=0).shape) # (128,768) 对 batch 求均值
print(x.mean(axis=(0, 1)).shape) # (768,) 压平 batch 与 seq
# 向量化:一次算完,比 for 快几十倍
data = np.random.randn(1000000)
print((data ** 2).mean()) # 全量平方再平均
# 广播
X = np.random.randn(100, 5)
mu, sd = X.mean(0), X.std(0) # (5,) (5,)
X_norm = (X - mu) / (sd + 1e-8) # (100,5),自动广播三个最常用的习惯
axis想清楚:axis=0是「跨行压缩」,结果是列数+ 1e-8:除法前永远加个极小值,避免除零产出nanreshape(-1, ...):不确定维度就交给-1
二、Pandas:80% 的数据工作
加载与初看
python
import pandas as pd
df = pd.read_csv("data.csv")
# 大文件/中文编码问题:pd.read_csv("data.csv", encoding="utf-8-sig")
# 只要部分列:pd.read_csv("data.csv", usecols=["id","price","label"])
print(df.shape) # (行数, 列数)
print(df.head(3)) # 前 3 行
print(df.info()) # 类型 + 缺失情况 —— 第一眼必看
print(df.describe()) # 数值列统计
print(df["label"].value_counts()) # 类别分布 —— 第二眼必看加载后 30 秒内必须看的三件事:shape、info()(有没缺失)、value_counts()(类别是否失衡)。
选择与过滤
python
df["price"] # 一列 → Series
df[["id", "price"]] # 多列 → DataFrame
df.loc[0, "price"] # 按标签
df.iloc[0, 2] # 按位置
df[df["price"] > 100] # 布尔过滤
df[(df["price"] > 100) & (df["city"] == "上海")] # 与用 &,或 |,每个条件加括号
df.query("price > 100 and city == '上海'") # 更可读的写法清洗:真实工作的 70%
python
# 1. 缺失值:先量化再决策
df.isna().mean().sort_values(ascending=False) # 每列缺失率
df["age"] = df["age"].fillna(df["age"].median()) # 数值:中位数(比均值抗异常)
df["city"] = df["city"].fillna("未知") # 类别:单独一类,别丢信息
df = df.dropna(subset=["label"]) # 标签缺失的样本必须删
# 2. 类型转换
df["price"] = pd.to_numeric(df["price"], errors="coerce") # 脏值变 NaN
df["date"] = pd.to_datetime(df["date"], errors="coerce")
# 3. 去重
df = df.drop_duplicates(subset=["user_id", "date"])
# 4. 类别编码
df["city_code"] = df["city"].astype("category").cat.codes # 省内存
# 或用 one-hot(类别少时)
df = pd.get_dummies(df, columns=["city"], prefix="city")缺失值三种处理,选错会毁掉模型
| 方式 | 何时用 | 风险 |
|---|---|---|
| 删除行 | 缺失率极低(<1%) | 有偏采样 |
| 填充统计量 | 数值型、缺失随机 | 低估方差 |
| 填充「未知」 | 类别型 | 无风险,推荐 |
| 填充目标均值 | ❌ 绝对不要 | 数据泄漏 |
分组聚合:数据分析的核心动作
python
# 单维度
print(df.groupby("city")["price"].agg(["count", "mean", "median", "max"]))
# 多维度 + 多指标
summary = df.groupby(["city", "category"]).agg(
orders=("order_id", "count"),
revenue=("price", "sum"),
avg_price=("price", "mean"),
).reset_index()
# 透视表(交叉分析)
print(pd.pivot_table(df, index="city", columns="category",
values="price", aggfunc="mean", fill_value=0))
# 时间序列
df["month"] = df["date"].dt.to_period("M")
print(df.groupby("month")["price"].sum())合并
python
# 左连接:以左表为主
merged = orders.merge(users, on="user_id", how="left")
# 合并前必查:主键是否唯一,否则行数会翻倍
print(users["user_id"].duplicated().sum())三、可视化:让结论一眼可见
python
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams["font.sans-serif"] = ["PingFang SC", "Microsoft YaHei", "SimHei"]
plt.rcParams["axes.unicode_minus"] = False # 负号显示
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# ① 数值分布
sns.histplot(df["price"], bins=40, kde=True, ax=axes[0])
axes[0].set_title("价格分布")
# ② 类别对比
sns.boxplot(data=df, x="city", y="price", ax=axes[1])
axes[1].set_title("各城市价格对比")
# ③ 相关性
sns.heatmap(df.select_dtypes("number").corr(),
annot=True, fmt=".2f", cmap="RdBu_r", center=0, ax=axes[2])
axes[2].set_title("特征相关性")
plt.tight_layout()
plt.savefig("eda.png", dpi=150)按目的选图
| 你想说明 | 用什么图 |
|---|---|
| 单变量分布 | 直方图、KDE、箱线图 |
| 类别间比较 | 箱线图、条形图、小提琴图 |
| 两个变量的关系 | 散点图(量大用 hexbin / 抽样) |
| 相关性全貌 | 热力图 |
| 时间趋势 | 折线图 |
| 占比构成 | 堆叠条形图(少用饼图) |
| 失衡程度 | 频次条形图(对数 y 轴) |
四、一个完整的最小 EDA 流程
python
def quick_eda(path: str, target: str | None = None) -> pd.DataFrame:
df = pd.read_csv(path)
print(f"形状: {df.shape}")
report = pd.DataFrame({
"dtype": df.dtypes.astype(str),
"missing%": (df.isna().mean() * 100).round(2),
"n_unique": df.nunique(),
})
print("\n--- 列画像 ---")
print(report.sort_values("missing%", ascending=False).head(20))
if target and target in df.columns:
print(f"\n--- 目标 {target} 分布 ---")
print(df[target].value_counts(normalize=True).round(3))
if df[target].nunique() < 10:
print(f"失衡比: {df[target].value_counts().max() / df[target].value_counts().min():.2f}")
num = df.select_dtypes("number")
if num.shape[1] > 1:
print("\n--- 与目标相关性 Top5 ---")
if target in num.columns:
print(num.corr()[target].abs().sort_values(ascending=False).head(5))
return report
quick_eda("titanic.csv", target="Survived")五、性能与常见坑
| 场景 | 做法 |
|---|---|
| 文件很大(>1GB) | dtype 指定类型、usecols 只读需要列、分块 chunksize |
| 内存不够 | pd.read_csv(..., low_memory=False) 排除类型推断问题,或改 Parquet |
| 循环太慢 | 换成 groupby / apply / 向量化 |
SettingWithCopyWarning | 用 .loc 赋值:df.loc[df.a > 1, "b"] = 0 |
| 合并后行数暴涨 | 检查主键重复 |
| 中文乱码 | encoding="utf-8-sig" |
通过标准
下一步
→ L2 机器学习:把干净的数据变成会预测的模型