乐于分享
好东西不私藏

0 基础学习机器学习Day 5:用 Excel 类比理解 Pandas:从 csv 读写到 groupby 全流程

0 基础学习机器学习Day 5:用 Excel 类比理解 Pandas:从 csv 读写到 groupby 全流程

Pandas 是 Python 处理表格数据的工具包。日常工作中遇到的"打开 csv → 筛选几行 → 算每组平均"这类任务, Excel 能做,但样本量到十万行就卡。 Pandas 处理千万行表格仍是秒级。

本篇覆盖 ML 数据预处理 90% 的 Pandas 操作。读者读完应能独立完成"读 csv → 清洗 → 分组聚合 → 输出图表数据"全流程。

参考来源:Pandas 官方文档[1]、Wes McKinney 《 Python for Data Analysis 》第 3 版[2]、Pandas Cookbook[3]

一、 Series 与 DataFrame

用 Excel 类比理解: DataFrame 就是一张 Excel 表,行是记录、列是字段; Series 是单独抽出来的一列。和 Excel 的区别是: Pandas 用 Python 对象表示,可以批量操作数十万行,且支持复杂的条件筛选和分组。

维度
Series
DataFrame
维度数
1 (一列)
2 (整张表)
索引
单层 Index
行 Index + 列 Index
创建
pd.Series([1,2,3])pd.DataFrame({'a':[1,2], 'b':[3,4]})
ML 对应
单个特征列
完整特征矩阵
import pandas as pdimport numpy as nps = pd.Series([1.23.45.67.8], name="temperature")print(s.index.tolist())        # [0, 1, 2, 3]  ← 左侧的行号print(s.values)                # array([1.2, 3.4, 5.6, 7.8])  ← 数据本身print(s.name)                  # temperature  ← 列名df = pd.DataFrame({    "date": ["2026-06-01""2026-06-02""2026-06-03"],    "temp": [28.530.126.7],    "sales": [152287134],})print(df.shape)                # (3, 3)  ← 3 行 3 列print(df.columns.tolist())     # ['date', 'temp', 'sales']  ← 列名清单print(df.dtypes.tolist())

注解: Pandas 的 object dtype 表示 Python 对象(通常是字符串)。机器学习模型只吃数字,所以后续要把 object 列转为 category 或数值。

二、读写文件

用日常操作类比:读 csv 就是用 Excel 打开文件,写 csv 就是另存为。差别在于 Pandas 不需要 GUI ,可以脚本化批量处理。

df = pd.read_csv("data.csv", encoding="utf-8")df = pd.read_csv("data.csv", sep="\t", na_values=["""NA""N/A"])  # TSV,自定义空值df = pd.read_csv("data.csv", parse_dates=["date"], index_col="date")  # 把 date 列解析为时间,并设为索引df = pd.read_csv("data.csv", usecols=["temp""sales"], nrows=1000)   # 只读两列、前 1000 行for chunk in pd.read_csv("huge.csv", chunksize=10000):    process(chunk)  # 每次处理 1 万行df.to_csv("out.csv", index=False, encoding="utf-8-sig")  # 不写行索引(避免多一列)df.to_parquet("out.parquet")                              # Parquet 格式,比 csv 小 5-10 倍、读取快df.to_excel("out.xlsx", sheet_name="data")                # Excel,需要 openpyxldf = pd.read_excel("data.xlsx", sheet_name="Sheet1")df = pd.read_json("data.json")df = pd.read_html("https://example.com/table.html")[0]    # 网页表格

注解: ML 项目中间数据优先用 parquet 。它保留 dtype ( csv 会把所有数据当文本),体积小,读取快。

三、索引: loc vs iloc

最常踩坑的两个 API。一句话区分: loc 用"名字"取, iloc 用"位置"取。

索引方式
接受类型
闭区间
类比
loc
标签(行列名)
左右都闭
按"姓名"叫人
iloc
整数位置
左闭右开
按"第几个"叫人
df = pd.DataFrame(    np.random.randint(0, 100, (5, 4)),    columns=list("ABCD"),    index=["r1""r2""r3""r4""r5"],)df.loc["r2""B"]              # 单个标量df.loc["r1":"r3", ["A""C"]]  # 标签切片(闭区间,含 r3)df.loc[df["A"] > 50, "B"]      # 布尔筛选 + 列名df.iloc[1, 2]                  # 第 2 行第 3 列(从 0 数)df.iloc[0:3, [0, 2]]           # 位置切片(左闭右开,不含第 4 行)df.iloc[-1]                    # 最后一行

注解:处理原始数据时用 loc (按列名明确),切分 batch 时用 iloc (按位置)。

df[df["A"]>50]["B"]=999# 不生效!会警告df.loc[df["A"]>50,"B"]=999# 正确写法

四、筛选与排序

用 SQL/Excel 类比:筛选就是 SQL 的 WHERE ,排序就是 Excel 的"按列排序"。

df[df["A"] > 50]                         # 单条件:A 列大于 50df[(df["A"] > 50) & (df["B"] < 30)]      # 且(注意括号必须)df[(df["A"] > 50) | (df["B"] < 30)]      # 或df[df["C"].isin([10, 20, 30])]           # 取值在集合中(SQL 的 IN)df[df["C"].between(10, 50)]              # 区间筛选(10 ≤ C ≤ 50)df[~df["A"].isna()]                      # A 列非空df.sort_values("A", ascending=False)             # 按 A 降序df.sort_values(["A""B"], ascending=[True, False])  # 多列排序:A 升 B 降df.sort_index()                                  # 按行索引排序df.drop_duplicates()                    # 整行去重df.drop_duplicates(subset=["A"], keep="last")  # 按列 A 去重,保留最后一条df["C"].unique()                        # 该列唯一值df["C"].value_counts()                  # 该列值计数(按出现次数排序)

注解:& 和 | 在 Pandas 中必须用括号包裹每个条件,否则优先级报错。and/or 关键字不能用于 Series 。

五、 groupby 分组聚合

用 Excel 数据透视表类比: groupby 就是数据透视表的代码版。先按某列拆分(如按"班级"),每组求和/求平均(如班级总分、班级平均分),最后合并成结果表。

该策略由 Hadley Wickham 在 2011 年 Journal of Statistical Software 第 40 卷第 1 期论文 The Split-Apply-Combine Strategy for Data Analysis[4] 中形式化。三步对应 Pandas 实现:

阶段
中文
Pandas 操作
类比 Excel
Split
拆分
df.groupby(key)
数据透视表的"行"
Apply
应用
.agg(func)
 / .apply(func)
值字段设置
Combine
合并
自动按 key 合并
生成透视表
df = pd.DataFrame({    "category": ["A""B""A""B""A"],    "value": [1020304050],})df.groupby("category")["value"].sum()df.groupby("category")["value"].agg(["sum""mean""count"])df.groupby("category").agg({    "value": ["sum""mean"],    "count""size",})df.groupby("category")["value"].apply(lambda x: x.max() - x.min())df.groupby(["year""month"])["sales"].sum()

注解: groupby 后默认分组键成为 Index 。加 as_index=False 保留为普通列:

df.groupby("category",as_index=False)["value"].sum()

六、 merge 与 concat

用 SQL/HR 类比: merge 就是 SQL 的 JOIN ,按某个 key 把两张表拼起来。例如员工表 + 部门表按 dept_id 合并,得到带部门名的完整员工表。

left = pd.DataFrame({"id": [123], "name": ["a""b""c"]})   # 员工表right = pd.DataFrame({"id": [124], "score": [809070]})    # 评分表pd.merge(left, right, on="id", how="inner")   # 内连接:只保留两边都有的 id(1,2)pd.merge(left, right, on="id", how="left")    # 左连接:保留左表全部(1,2,3),缺的填 NaNpd.merge(left, right, on="id", how="outer")   # 外连接:并集(1,2,3,4),缺的填 NaNpd.merge(left, right, left_on="id", right_on="user_id")pd.concat([df1, df2], axis=0)   # 纵向(行拼接,类似 UNION ALL)pd.concat([df1, df2], axis=1)   # 横向(列拼接)df1.set_index("id").join(df2.set_index("id"), how="inner")

四种 how 参数对比:

参数
SQL 对应
行为
结果行数
how="inner"
INNER JOIN
取交集
≤ min(左,右)
how="left"
LEFT JOIN
保留左表全部
= len(左)
how="right"
RIGHT JOIN
保留右表全部
= len(右)
how="outer"
FULL JOIN
取并集
≥ max(左,右)

注解: merge 后行数可能变化, concat 后行数或列数严格等于 sum 。

七、 pivot 与 melt

用宽表/长表类比:宽表是一行一个样本、各列是不同指标(适合人看);长表是一条记录一行(适合机器存)。 pivot 把长转宽(生成报告用), melt 把宽转长(数据清洗用)。两者互逆。

long_df = pd.DataFrame({    "date": ["2026-01-01"]*3 + ["2026-01-02"]*3,    "city": ["BJ""SH""GZ"]*2,    "temp": [58153613],})wide = long_df.pivot(index="date", columns="city"values="temp")long_again = wide.reset_index().melt(    id_vars="date", var_name="city", value_name="temp")pd.pivot_table(    long_df,    index="date",    columns="city",    values="temp",    aggfunc="mean",   # 默认 mean)

注解:pivot 要求 index+columns 唯一,重复时报错;pivot_table 自动聚合重复值。日常工作中数据有重复就用 pivot_table 。

八、缺失值处理

用 Excel 类比:缺失值就是 Excel 的空单元格。 Pandas 用 NaN ( Not a Number )表示。处理前先看缺失比例,再决定 drop (删除)还是 fill (填充)。

df.isna().sum()                    # 每列缺失数df.isna().mean()                   # 每列缺失比例(0-1)df.dropna()                        # 删除含缺失的行df.dropna(subset=["A""B"])       # 仅当 A 或 B 列空时才删df.dropna(thresh=3, axis=1)        # 删除非空值少于 3 的列df.fillna(0)                       # 全部填 0df["A"].fillna(df["A"].mean())     # 用均值填 A 列df["A"].fillna(df["A"].median())   # 中位数(对异常值更稳健)df["A"].fillna(method="ffill")     # 前向填充(时间序列常用:用昨天的值填今天)df["A"].interpolate()              # 线性插值(用相邻值的平均填)

注解: ML 中缺失值处理顺序——先看缺失比例(< 5% 可丢,> 50% 该列考虑丢,中间用 fill ),再分析缺失是否随机( MCAR/MAR/MNAR ),最后选 drop/fill/标记。直接 fillna(0) 在数值特征上会引入偏差。

九、时间序列

典型场景:股票日线数据、电商日销量、传感器每秒采样。这些数据有时间索引,常用滑动统计、重采样。

dates = pd.date_range("2026-01-01", periods=10, freq="D")  # 10 天的日期序列ts = pd.Series(np.random.randn(10), index=dates)ts.resample("W").mean()           # 周均值ts.resample("M").sum()            # 月求和ts.rolling(window=3).mean()       # 3 日滑动均值ts.rolling(window=7).std()        # 7 日滑动标准差(波动率)ts.diff()                         # 一阶差分(今天的值 - 昨天的值)ts.pct_change()                   # 百分比变化(涨跌幅)ts_utc = ts.tz_localize("UTC")ts_bj = ts_utc.tz_convert("Asia/Shanghai")

注解:时间序列预测任务(销量、股价)的标准特征工程——滞后值、滑动统计、差分。

十、性能优化

何时关心性能:数据量到百万行级, Pandas 默认写法会慢。下面是几个常用加速技巧。

场景
慢写法
快写法
加速比
行遍历
for idx, row in df.iterrows()df.apply(func, axis=1)
 或向量化
10-50×
列变换
for 循环 + .loc
df['c'] = df['a'] + df['b']
100×
字符串处理
df['s'].apply(lambda x: x.lower())df['s'].str.lower()
5-10×
类型节省内存
int64 / object
downcast
 / astype('category')
内存减 50-80%
多条件查询
多次 df[df[...]]
df.query("A > 50 and B < 30")
略快
分类编码
df['cat'].map({...})df['cat'].astype('category').cat.codes
df = pd.read_csv("data.csv")print(f"默认内存: {df.memory_usage(deep=True).sum() / 1e6:.1f} MB")for col in df.select_dtypes(include="int64").columns:    df[col] = pd.to_numeric(df[col], downcast="integer")for col in df.select_dtypes(include="float64").columns:    df[col] = pd.to_numeric(df[col], downcast="float")for col in df.select_dtypes(include="object").columns:    if df[col].nunique() / len(df) < 0.5:    # 唯一值比例 <50%        df[col] = df[col].astype("category")print(f"优化后内存: {df.memory_usage(deep=True).sum() / 1e6:.1f} MB")

注解:百万行 csv 经类型优化通常节省 50-80% 内存。

十一、电影评分数据案例

完整场景:模拟 100 个用户对 50 部电影评分( 1-5 星), 2000 条评分记录。计算:评分最高的用户、热门高分电影、用户偏好类型。

import pandas as pdimport numpy as nprng = np.random.default_rng(42)n_users, n_movies = 10050users = [f"u{i:03d}" for i in range(n_users)]movies = [f"m{i:03d}" for i in range(n_movies)]genres = ["动作""喜剧""剧情""科幻""爱情""动画"]records = []for _ in range(2000):    u = rng.choice(users)    m = rng.choice(movies)    s = int(rng.integers(16))    records.append({"user": u, "movie": m, "score": s})df = pd.DataFrame(records)df["genre"] = df["movie"].map(    {m: rng.choice(genres) for m in movies})top_users = df.groupby("user")["score"].mean().sort_values(ascending=False).head(5)print("评分最高的 5 个用户:")print(top_users)movie_stats = df.groupby("movie")["score"].agg(["count""mean""std"])print(f"\n电影评分统计前 3:\n{movie_stats.head(3)}")pivot = df.pivot_table(    index="user", columns="genre", values="score", aggfunc="mean")print(f"\n透视表 shape: {pivot.shape}")  # (100, 6)popular = movie_stats[movie_stats["count"] > 30].sort_values("mean", ascending=False)print(f"\n热门高分电影前 5:\n{popular.head()}")

注解:此例覆盖创建 → groupby 聚合 → pivot_table 透视 → 多条件筛选,是 ML 推荐系统数据预处理的标准范式。


参考链接

[1] Pandas 官方文档: https://pandas.pydata.org/docs/

[2] Wes McKinney 《 Python for Data Analysis 》第 3 版: https://wesmckinney.com/book/

[3] Pandas Cookbook: https://pandas.pydata.org/docs/user_guide/cookbook.html

[4] The Split-Apply-Combine Strategy for Data Analysis: https://www.jstatsoft.org/v40/i01/