Skip to content

第 01 章 · 环境与数据准备 ​

这一章要解决什么:把数据包跑起来,确认"我和书上是同一份数据"。

最容易踩的坑:用系统 Python 装依赖,撞上 externally-managed-environment; 或者跳过校验和那一步 —— 后面数字对不上时,你会怀疑代码,其实是数据不同。

本章你需要完成的代码:两个小脚本(第 1.6 节,自己建)

1.1 先把包解开,看清里面有什么 ​

下载:feature-engineering-pipeline-code.zip(约 2.6 MB)

解开后是这样一个结构:

text
code/
├── README.md                  ← 包的总说明(建议先读一遍)
├── requirements.txt           ← 依赖清单
├── check_env.py               ← 环境自检(装完先跑这个)
└── data/battery/
    ├── cycle_raw.csv          逐循环时序(69,591 行 × 9 列)
    ├── soh_features.csv       窗口表(21,239 行 × 9 列)
    ├── soh_features_v2.csv    时序特征表(21,239 行 × 27 列)★ 主表
    ├── cells.csv              设备级真值(60 行 × 5 列)
    └── README.md              字段说明(**建议第 03 章之前读一遍**)

★ 包里没有 demos/,也没有练习答案 —— 这是刻意的。

特征工程的每一步都很"轻"(`shift(1).rolling(20).mean()` 一行就完了)
    ↓
但口径错一个字符,结果就全错,而且【不报错】
    ↓
所以只有你自己敲过、错过、查过,才会真正记住那些口径
    ↓
书里每一章都给了【完整的真实输出】,你敲完可以逐行对照

所以每一章的"本章你需要完成的代码"都是你要自己创建的文件。

1.2 建立环境(三步) ​

打开终端,进入 code/ 目录:

bash
cd code

① 建一个虚拟环境

bash
python -m venv .venv

② 激活它

bash
source .venv/bin/activate        # macOS / Linux
.venv\Scripts\activate           # Windows

激活成功的标志:命令行前面多了 (.venv):

text
(.venv) $

③ 装依赖

bash
pip install -r requirements.txt

★ 确认你用的是虚拟环境里的 Python

bash
python -c "import sys; print(sys.executable)"

期望输出(路径里应该包含 .venv):

text
/Users/你/code/.venv/bin/python

如果输出的是 /usr/bin/python3 或类似路径 → 虚拟环境没激活成功,回到第 ② 步。

点开:`externally-managed-environment` 报错怎么处理

报错长这样:

text
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try brew install xyz,
    or use a virtual environment instead.

这是什么:新版 pip 的保护机制。它发现你想往系统 Python里装包, 而这可能破坏操作系统的工具(很多系统工具依赖 Python)。

✅ 正确做法:

bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

❌ 不要这么做(网上很多答案会教你这样):

bash
pip install --break-system-packages -r requirements.txt
pip install --user -r requirements.txt
sudo pip install -r requirements.txt

为什么不要:

--break-system-packages  →  直接关掉保护,真的可能弄坏系统工具
--user                   →  装到用户目录,但会和别的项目互相干扰
sudo pip                 →  用 root 权限污染系统 Python,最危险
    ↓
【三个都会在今天省 1 分钟,在未来某天花你 1 小时】

★ 一个判断标准:

看到 externally-managed-environment,答案永远是"建虚拟环境", 而不是"绕过这个保护"。

★ 如果你已经用 sudo 装过了怎么办

一般不会立刻出问题,但建议:
   ① 别再用 sudo pip
   ② 后续都用虚拟环境(本书的依赖不会和系统 Python 冲突)
   ③ 如果系统工具开始报错,用包管理器重装它

1.3 跑自检:确认环境 + 数据都对 ​

bash
python check_env.py

真实输出:

text
✅ Python 版本:3.13.13  (/path/to/code/.venv/bin/python)
✅ numpy         2.5.3
✅ pandas        3.0.6
✅ matplotlib    3.11.2
✅ scikit-learn  1.9.1
⚠️ xgboost        未安装(可选)
   不影响学习:书里凡用到提升的地方都用 sklearn 自带的 HistGradientBoostingRegressor,两条路都能跑通。
------------------------------------------------------------
✅ 数据文件:/path/to/code/data/battery
✅ cycle_raw.csv             3676 KB   69,591 行
✅ soh_features.csv          1221 KB   21,239 行
✅ soh_features_v2.csv       3617 KB   21,239 行
✅ cells.csv                    2 KB       60 行
------------------------------------------------------------
✅ 全部通过。下一步:从第 03 章开始,把代码敲进 demos/ 里跑起来。

★ 自检做了三件事

① 检查 Python 版本(要 ≥ 3.10)
② 检查四个必需的库(并打印版本)
③ 核对四个数据文件的 SHA-256

关于 xgboost 那个 ⚠️:

它是【可选】的。书里凡用到梯度提升的地方都给了两条路:
   · 装了 xgboost        → 用它
   · 没装(默认)        → 用 sklearn 自带的 HistGradientBoostingRegressor
两条路的输出书上都有,不影响学习。

1.4 数据指纹:确认"我和书上是同一份数据" ​

check_env.py 会自动核对,但你也可以手动比对:

bash
shasum -a 256 data/battery/*.csv        # macOS
sha256sum data/battery/*.csv            # Linux

期望值(前 16 位就够用):

文件SHA-256(前 16 位)
cycle_raw.csv984897071bbb06ef
soh_features.csvf01210e620c61d35
soh_features_v2.csv552e2cdb7d0c866c
cells.csvb51e6a54ac967dde

完整值(想逐字符比对时用):

text
cycle_raw.csv      984897071bbb06ef35e36e4118f6a0311df42d0faef4afdcf4842b94e842caae
soh_features.csv   f01210e620c61d35b5645f5e0a2c84030748e0a84bb7fbca90e1dbce5d1fd9a5
soh_features_v2.csv 552e2cdb7d0c866c99f2ab2502bbff8db87b2a0642212f1622963f7d5276f75d
cells.csv          b51e6a54ac967dde2b1710eb1d59e673e093870f2e0fdce7a73fab637c63c5a0

★ 为什么这一步不能跳过

后面某一章,你敲的代码跑出来的数字与书上不一样
    ↓
你会怀疑:是我写错了?还是书上写错了?
    ↓
如果校验和一致 → 数据相同 → 那就是代码问题(去查口径)
如果校验和不一致 → 先去重新下载
    ↓
【这一步把一个可能的排查方向直接排除掉,花 10 秒值回票价】

★ 一个重要的提醒:如果你改了数据(比如自己清洗), 后面的数字就会和书上不一样 —— 那是正常的,不是 bug。

1.5 三层数据:先建立地图 ​

数据包给的是三层结构,不是一张表:

text
第 1 层  cycle_raw.csv        69,591 行 ×  9 列   一行 = 一圈
             ↓  按滑动窗口汇总
第 2 层  soh_features.csv     21,239 行 ×  9 列   一行 = 一段(窗口)
             ↓  补上"带历史看"的列(行数不变,列数 9 → 27)
第 3 层  soh_features_v2.csv  21,239 行 × 27 列   一行 = 一段 + 它的过去  ★ 主表

★ 为什么三层都给?

第 05~06 章要让【你】亲手做窗口
    ↓
只有给你逐循环表,你才能看到"69,591 行压成 21,239 行"这个过程
    ↓
如果只给第 3 层,那两章就只能"看"而不能"做"

★ 三层里的两个关键数字

数字值含义
压缩比3.2766逐循环 → 窗口,约等于步长 3 圈(每 3 圈产出一行)
行数不变21,239 → 21,239第 2 层到第 3 层不加样本,只加列

★ 一个必须提前搞清的口径(第 05 章会详讲)

text
窗长 = 100 圈      ← 一个窗口【覆盖】100 圈的数据
步长 = 3 圈        ← 相邻两个窗口只错开 3 圈
    ↓
窗口是【高度重叠】的:相邻窗口共享 97 圈数据
    ↓
"过去 20 个窗口" = 20 × 3 = 60 圈   ★ 不是 2000 圈

混淆"窗口长度"和"窗口步长"会让所有"过去 N 个窗口"的量级差 30 倍 —— 这是本书最先要避免的坑。

★ 完整的字段说明在 data/battery/README.md 里(建议第 03 章之前读一遍), 包括:每一列怎么算、属于哪一组、是否泄漏。

1.6 动手:把代码敲进 demos/ ​

现在建一个 demos/ 目录,写两个小脚本热身。

脚本一:demos/ch01-checklist.py

python
"""第 01 章 · 环境与数据自检(本书的第一个脚本)。

把数据的基本情况打印出来,确认你拿到的是对的东西。
"""

import os

import pandas as pd

HERE = os.path.dirname(os.path.abspath(__file__))
DATA = os.path.abspath(os.path.join(HERE, "..", "data", "battery"))

print("=" * 62)
print("第 01 章 · 环境与数据自检")
print("=" * 62)

# ① 三层数据的规模
for name in ("cycle_raw.csv", "soh_features.csv", "soh_features_v2.csv", "cells.csv"):
    path = os.path.join(DATA, name)
    df = pd.read_csv(path)
    print(f"{name:<24}{df.shape[0]:>7,} 行 × {df.shape[1]:>2} 列")

# ② 主表的几个关键事实
v2 = pd.read_csv(os.path.join(DATA, "soh_features_v2.csv"))
print(f"\n设备数                  {v2['cell_id'].nunique()}")
print(f"标签 soh 的范围          {v2['soh'].min():.4f} ~ {v2['soh'].max():.4f}")
print(f"标签 soh 的均值 / 标准差  {v2['soh'].mean():.4f} / {v2['soh'].std():.4f}")

期望输出:

text
==============================================================
第 01 章 · 环境与数据自检
==============================================================
cycle_raw.csv              69,591 行 ×  9 列
soh_features.csv           21,239 行 ×  9 列
soh_features_v2.csv        21,239 行 × 27 列
cells.csv                      60 行 ×  5 列

设备数                  60
标签 soh 的范围          0.8032 ~ 1.0017
标签 soh 的均值 / 标准差  0.9088 / 0.0548

★ 注意标签最大值 1.0017 > 1.00

SOH 是"实测容量 ÷ 额定容量",理论上不该超过 1
    ↓
1.0017 是【测量误差】的表现(容量测量本身有噪声)
    ↓
这不是数据错误 —— 真实数据里也会出现这种情况
    ↓
【发现异常值时,先问"这是错误还是真实特征",不要急着删】

脚本二:demos/ch01-env-report.py

python
"""第 01 章 · 环境报告(打印你机器上的实际版本)。"""

import sys
from importlib.metadata import version

print(f"Python      {sys.version.split()[0]}")
print(f"解释器      {sys.executable}")
for pkg in ("numpy", "pandas", "matplotlib", "scikit-learn"):
    try:
        print(f"{pkg:<12}{version(pkg)}")
    except Exception:
        print(f"{pkg:<12}(未安装)")

期望输出(版本号可能不同):

text
Python      3.13.13
解释器      /path/to/code/.venv/bin/python
numpy       2.5.3
pandas      3.0.6
matplotlib  3.11.2
scikit-learn 1.9.1

★ 这个脚本的用途:当你要提问或报 bug 时,把这段输出贴上 —— 它能省掉"你用的什么版本"这一轮来回。

1.7 两个必须提前知道的前提 ​

① 数据里有两列是"反面教材"(故意留的)

列怎么算为什么危险
soh_marginsoh − 0.80它就是标签的线性函数(相关系数恰好 1.000000)
cycle_fraccycle / 最终总寿命分母是该设备的最终总寿命,跑到退役才知道

它们留着,是为了让你在第 21 章亲手复现 R² = 1.000000。

那是本书最值钱的体验之一:
   你会亲眼看到"完美的分数"是怎么被一列特征制造出来的
    ↓
但它们是泄漏列,【任何正式建模都不要用】

② 时序特征有"冷启动 NaN"

每支设备最早的窗口没有历史
    ↓
14 列时序特征因此有 NaN:1,260 行有 NaN、19,979 行齐全
    ↓
这不是 bug —— 新设备刚上线时本来就没有历史
    ↓
处理方式见第 07 章(【别用 fillna(0)】)

1.8 常见坑 ​

text
① 用系统 Python 装依赖 → externally-managed-environment(别用 --break-system-packages)
② 跳过校验和 → 后面数字对不上时,多了一个排查方向
③ 把"窗口长度"和"窗口步长"混了 → 所有"过去 N 个窗口"的量级差 30 倍
④ 看到 soh > 1.00 就去删掉那些行 → 那是测量误差,不是错误
⑤ 以为包里有 demos/ → 没有,那是你要自己建的
⑥ 一次装完所有能想到的库 → 只装 requirements.txt 里的就够,多了会干扰版本排查
⑦ 不记录自己的版本 → 出问题时无法定位(用第 1.6 节第二个脚本)

★ 关于 ⑥:为什么不要多装

多装的库可能带来版本冲突
    ↓
比如你装了个老版 numpy,matplotlib 就可能在 import 时崩
    ↓
而本书只需要四个库 —— 【依赖越少,环境问题越少】

1.9 完整示例与输出 ​

bash
cd code
python check_env.py

真实输出(节选):

text
✅ Python 版本:3.13.13  (/path/to/code/.venv/bin/python)
✅ numpy         2.5.3
✅ pandas        3.0.6
✅ matplotlib    3.11.2
✅ scikit-learn  1.9.1
⚠️ xgboost        未安装(可选)
------------------------------------------------------------
✅ 数据文件:/path/to/code/data/battery
✅ cycle_raw.csv             3676 KB   69,591 行
✅ soh_features.csv          1221 KB   21,239 行
✅ soh_features_v2.csv       3617 KB   21,239 行
✅ cells.csv                    2 KB       60 行
------------------------------------------------------------
✅ 全部通过。下一步:从第 03 章开始,把代码敲进 demos/ 里跑起来。

本章练习 ​

练习 1:确认你的环境与数据都对
  1. 按第 1.2 节建好虚拟环境并装依赖;
  2. 跑 check_env.py,确认四个数据文件的指纹都通过;
  3. 跑第 1.6 节的 ch01-checklist.py,核对三层数据的规模。

要求:把 check_env.py 的输出完整贴出来(这是你后面提问时的"基线")。

参考答案

① 三步命令

bash
cd code
python -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python check_env.py

② 期望看到的输出

text
✅ Python 版本:3.13.x  (…/code/.venv/bin/python)
✅ numpy        2.5.x
✅ pandas       3.0.x
✅ matplotlib   3.11.x
✅ scikit-learn 1.9.x
⚠️ xgboost       未安装(可选)
------------------------------------------------------------
✅ 数据文件:…/code/data/battery
✅ cycle_raw.csv             3676 KB   69,591 行
✅ soh_features.csv          1221 KB   21,239 行
✅ soh_features_v2.csv       3617 KB   21,239 行
✅ cells.csv                    2 KB       60 行
------------------------------------------------------------
✅ 全部通过。下一步:从第 03 章开始,把代码敲进 demos/ 里跑起来。

③ 三层数据的规模

text
cycle_raw.csv              69,591 行 ×  9 列
soh_features.csv           21,239 行 ×  9 列
soh_features_v2.csv        21,239 行 × 27 列
cells.csv                      60 行 ×  5 列

★ 三个必须核对上的数字

① 69,591 → 21,239     (压缩 3.2766 倍)
② 21,239 → 21,239     (第 2 层到第 3 层【行数不变】)
③ 60                  (设备数,三张表一致)

如果你看到的数字不是这三个:

先去重新下载数据包
    ↓
还是不对 → 在提问时贴出 check_env.py 的完整输出
    ↓
【这就是第 1.6 节那个"环境报告"脚本的用途】

★ 一个容易忽略的点

check_env.py 的最后一行是"下一步:从第 03 章开始"
    ↓
为什么从 03 而不是 01?因为 01(本章)和 02(地图)不需要数据
    ↓
【本书从第 03 章开始才有可运行的代码】
练习 2:给数据做一次"体检"

写一个脚本,对 soh_features_v2.csv 做一次基本体检,至少报告:

  1. 每一列的缺失值数量;
  2. 每一列的数据类型;
  3. 标签 soh 的最大值、最小值,以及有几个超过 1.00 的值。

要求:解释"标签超过 1.00"意味着什么。

参考答案
python
import pandas as pd

v2 = pd.read_csv("data/battery/soh_features_v2.csv")

print("=" * 72)
print("数据体检:soh_features_v2.csv")
print("=" * 72)
print(f"形状:{v2.shape[0]:,d} 行 × {v2.shape[1]} 列\n")

# ① 缺失值 + ② 数据类型
report = pd.DataFrame({
    "dtype": v2.dtypes.astype(str),
    "缺失数": v2.isna().sum(),
    "缺失比例": (v2.isna().mean() * 100).round(2),
})
print(report.to_string())

# ③ 标签的异常值
n_over = int((v2["soh"] > 1.0).sum())
print(f"\n标签 soh:{v2['soh'].min():.4f} ~ {v2['soh'].max():.4f}")
print(f"超过 1.00 的行数:{n_over}")
if n_over:
    print(v2.loc[v2["soh"] > 1.0, ["cell_id", "cycle", "soh"]].head(5).to_string(index=False))

期望输出(节选):

text
形状:21,239 行 × 27 列

                          dtype   缺失数   缺失比例
cell_id                  object       0     0.00
cycle                     int64       0     0.00
mean_charge_voltage_v   float64       0     0.00
...
volt_win_mean           float64    1200     5.65
volt_win_std            float64    1200     5.65
res_win_mean            float64    1200     5.65
res_win_std             float64    1200     5.65
temp_win_mean           float64    1200     5.65
volt_slope20            float64    1260     5.93
res_slope20             float64    1260     5.93
temp_slope20            float64    1260     5.93
res_diff1               float64    1260     5.93
temp_win_range20        float64    1200     5.65
cycle_frac              float64       0     0.00
soh_margin              float64       0     0.00
soh_lag1                float64      60     0.28
soh_lag5                float64      60     0.28
soh_win_mean5           float64      60     0.28
soh_win_std5            float64      60     0.28
batch                   object       0     0.00
test_condition          object       0     0.00
soh                     float64       0     0.00

标签 soh:0.8032 ~ 1.0017
超过 1.00 的行数:11

★ 缺失值的分布说明了一件事

窗口统计列    1,200 个缺失(= 60 支 × 20 行)
差分斜率列    1,260 个缺失(= 60 支 × 21 行)← 多一行,因为算式里有 shift(21)
轨迹列           60 个缺失(= 60 支 ×  1 行)
    ↓
【这不是随机缺失,是结构性的"冷启动"】
    ↓
每支设备最早的窗口没有历史 → 必然缺失

★ ③ "标签超过 1.00" 意味着什么

SOH 定义 = 实测容量 ÷ 额定容量
    ↓
理论上不该超过 1.00
    ↓
但实测有 11 行超过(最大 1.0017)
    ↓
这是【测量误差】:容量测量的噪声让比值偶尔略微超过 1
    ↓
【不是数据错误 —— 真实数据里也会出现】

★★ 这一题最重要的认知

发现异常值时,先分清三种可能: ① 数据错误(要修)、② 真实特征(要留)、③ 定义之外但无害(要记)。

"soh > 1" 属于第 ③ 种:它不违反定义到需要修复的程度, 但你必须【知道它存在】,否则后面某个平均值算出来略大于 1 时你会困惑。

★ 一个实用的做法:把体检结果存下来

python
report.to_csv("outputs/health-check.csv", encoding="utf-8-sig")
print("已保存体检报告")
半年后数据更新了 → 重跑一次 → 对比两份报告
    ↓
【能立刻看出"哪里变了",比人眼扫一遍可靠得多】
练习 3:验证"窗口是重叠的"

第 1.5 节说"窗长 100 圈、步长 3 圈,所以窗口高度重叠"。

请用 soh_features_v2.csv 验证这个说法:

  1. 算出相邻窗口的 cycle 差(应该接近 3);
  2. 算出"过去 20 个窗口"对应多少圈;
  3. 用一个具体的例子说明"窗口是重叠的"。
参考答案
python
import pandas as pd

v2 = pd.read_csv("data/battery/soh_features_v2.csv").sort_values(["cell_id", "cycle"])

# ① 相邻窗口的 cycle 差
d = v2.groupby("cell_id")["cycle"].diff().dropna()
print("相邻窗口 cycle 差的分布:")
print(d.value_counts().head().to_string())
print(f"\n均值 = {d.mean():.2f} 圈")

# ② "过去 20 个窗口" = 多少圈
print(f"\n过去 20 个窗口 ≈ {d.mean() * 20:.0f} 圈")

# ③ 具体例子:C001 的前 21 个窗口
c1 = v2[v2["cell_id"] == "C001"].head(21)
print(f"\nC001 的窗口 1 与窗口 21:")
print(f"  窗口 1 的 cycle  = {c1['cycle'].iloc[0]}")
print(f"  窗口 21 的 cycle = {c1['cycle'].iloc[20]}")
print(f"  跨度 = {c1['cycle'].iloc[20] - c1['cycle'].iloc[0]} 圈")
print(f"  但窗长是 100 圈 → 这两个窗口【共享了 {100 - (c1['cycle'].iloc[20] - c1['cycle'].iloc[0])} 圈】的数据")

期望输出:

text
相邻窗口 cycle 差的分布:
cycle
4.0    10607
2.0    10572

均值 = 3.00 圈

过去 20 个窗口 ≈ 60 圈

C001 的窗口 1 与窗口 21:
  窗口 1 的 cycle  = 50
  窗口 21 的 cycle = 110
  跨度 = 60 圈
  但窗长是 100 圈 → 这两个窗口【共享了 40 圈】的数据

★ ① 的分布很有意思:只有 2 和 4,没有 3

步长参数是 3,但实测 cycle 差是 2 或 4 交替
    ↓
为什么?因为窗口的 cycle 范围是【取整】后的结果:
   起点按 3 圈递增,但每支设备的 cycle 序列本身是 2/4 交替的
    ↓
【参数写 3,实际落点是 2 和 4 —— 这类"名义值 vs 实测值"的差别,
   在特征工程里很常见】

★ ③ 说明了"重叠"到底有多严重

窗长 100 圈,而两个相隔 20 步的窗口,起点只差 60 圈
    ↓
所以它们【共享 40 圈】数据(100 - 60 = 40)
    ↓
再近一点:相邻两个窗口共享 97 圈(只错开 3 圈)

★★ 这个"重叠"是本书后面一个核心结论的根源

窗口高度重叠 → 同一支设备的相邻行【几乎一样】
    ↓
如果按【行】随机划分 → 训练集和测试集里有大量"近亲样本"
    ↓
模型只要"背住"这些近亲就能拿高分
    ↓
【这就是第 18 章讲的"划分泄漏",它的根源就在这里】

所以这一题不只是"验证一个数字",而是理解后面为什么必须按设备划分。

面向工程实践:环境可复现是"可交付"的前提 ​

① 一个真实的场景

你把模型和报告交给同事
    ↓
同事跑不出你的数字
    ↓
排查两天:是他的 numpy 版本不同?还是数据不同?还是随机种子不同?
    ↓
【两天里有一半的时间花在"确认我们面对的是同一个东西"上】

★ 这一章的两个动作就是为这件事设计的:

check_env.py      →  一行命令确认"环境 + 数据"都对
校验和             →  确认"数据"这一项,10 秒

② 一个可以直接用的"项目基线"模板

markdown
# 项目基线(贴在 README 里)

## 环境

Python 3.13.13 numpy 2.5.3 pandas 3.0.6 matplotlib 3.11.2 scikit-learn 1.9.1


## 数据
| 文件 | 行 × 列 | SHA-256(前 16 位) |
|---|---|---|
| soh_features_v2.csv | 21,239 × 27 | 552e2cdb7d0c866c |

## 复现方式
```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python check_env.py          # 应该全部 ✅
python demos/ch30-stage-target.py   # 应该产出与报告一致的数字

**★ 这个模板的价值**

任何"数字对不上"的讨论,都从"先跑 check_env 和复现命令"开始 ↓ 把排查成本从"两天"降到"两分钟" ↓ 【这是团队协作里最便宜的一项基础设施】


**③ 一个纪律**

> **在报告任何数字之前,先记录产生它的环境与数据版本。
> 没有版本信息的数字,半年后就是无法验证的传说。**

**这也是第 30 章那份报告第七节(适用范围与局限)的第一条要做的事。**

## 本章检查清单

**读完并动手做完后,逐条确认**:

<ul class="task-list">
<li><label><input type="checkbox" class="tc" data-task="ch01-01"> 已建虚拟环境并激活(命令行前面有 <code>(.venv)</code>)</label></li>
<li><label><input type="checkbox" class="tc" data-task="ch01-02"> <code>python -c "import sys; print(sys.executable)"</code> 的路径里有 <code>.venv</code></label></li>
<li><label><input type="checkbox" class="tc" data-task="ch01-03"> <code>pip install -r requirements.txt</code> 成功(注意是 <code>scikit-learn</code> 不是 <code>sklearn</code>)</label></li>
<li><label><input type="checkbox" class="tc" data-task="ch01-04"> <code>check_env.py</code> 四个数据文件的指纹全部 ✅</label></li>
<li><label><input type="checkbox" class="tc" data-task="ch01-05"> 我知道 <code>xgboost</code> 那个 ⚠️ 不影响学习</label></li>
<li><label><input type="checkbox" class="tc" data-task="ch01-06"> 我自己建了 <code>demos/</code>,并敲通了 <code>ch01-checklist.py</code></label></li>
<li><label><input type="checkbox" class="tc" data-task="ch01-07"> 我记住了三层数据的规模:69,591 / 21,239 / 21,239 × 27</label></li>
<li><label><input type="checkbox" class="tc" data-task="ch01-08"> 我分得清「窗长 100 圈」和「步长 3 圈」,知道"过去 20 个窗口 ≈ 60 圈"</label></li>
<li><label><input type="checkbox" class="tc" data-task="ch01-09"> 我知道 <code>soh_margin</code> 和 <code>cycle_frac</code> 是反面教材,正式建模不能用</label></li>
<li><label><input type="checkbox" class="tc" data-task="ch01-10"> 我读过了 <code>data/battery/README.md</code> 的字段说明</label></li>
</ul>

<p class="task-progress">已勾选 <span class="task-done">0</span> / <span class="task-total">0</span> 项</p>

**全部打勾了?** 你已经有了一台可复现的"工作台"。

---

**下一章**:[第 02 章 · 课程地图](/books/feature-engineering-pipeline/02-roadmap.html)