3. 项目结构与目录规范
科研项目在分析阶段最容易失控的,往往不是算法本身,而是文件散落。同一份原始数据被复制出三个版本,脚本里写死了本机绝对路径,结果图不知道由哪次运行产生,半年后连作者自己都无法复现。目录规范与命名规则的价值,就是把这些隐性成本提前消除:让每个文件的位置可预测、名字可解读、来源可追溯。本节围绕“数据、代码、结果、文档”四类核心对象,给出可直接落地的目录骨架、命名约定与元数据描述规则。
一、四类对象与目录骨架
一个可重复分析项目的目录,应当按“对象角色”而非“时间先后”划分。推荐的最小骨架如下(示例项目名为 proj-2024-airquality):
proj-2024-airquality/
├── README.md # 项目说明、环境、运行入口
├── data/
│ ├── raw/ # 原始数据,只读,永不修改
│ ├── interim/ # 清洗、拼接后的中间数据
│ └── processed/ # 可直接用于建模的分析数据集
├── code/
│ ├── 01_clean.py
│ ├── 02_features.py
│ ├── 03_model.py
│ └── utils/ # 公共函数
├── results/
│ ├── figures/
│ ├── tables/
│ └── models/
├── docs/
│ ├── data_dictionary.md # 字段含义、单位、缺失规则
│ └── methods.md # 方法说明与参数记录
└── metadata/
└── manifest.csv # 文件级元数据清单
关键约束有三条。第一,data/raw 只读:任何清洗都输出到 interim 或 processed,原始数据一旦被覆盖,追溯链就断了。第二,代码按执行顺序编号(01_、02_),使流水线顺序在文件名层面即可读出。第三,结果目录只存放“由代码生成”的产物,手工修改过的图不应放回 results,否则结果与代码的对应关系被破坏。
二、文件命名规范
命名规范的目标是“不打开文件也能判断内容”。建议采用“主题_处理阶段_版本_日期”的分段式,段间用下划线,段内用短横线,避免空格与中文标点(跨平台与命令行更安全)。例如:
pm25_hourly_raw_v01_20240315.csv
pm25_hourly_clean_v02_20240318.csv
model_rf_baseline_v01_20240320.pkl
fig_pm25_timeseries_v03_20240321.png
几条硬规则值得写进项目约定:日期统一用 YYYYMMDD,保证字典序即时间序;版本号用两位数字 v01、v02,避免 final、final2、最终版 这类无法排序的命名;同一逻辑对象的不同版本共享前缀,只改版本段,便于用通配符批量定位。文件名中不要出现 / \ : * ? " < > | 等字符,也不要依赖大小写区分(Windows 与 macOS 默认不区分)。
三、元数据描述规则
命名只能承载有限信息,字段含义、单位、缺失值处理、生成脚本等必须由元数据承担。推荐在 metadata/manifest.csv 中为每个数据与结果文件登记一行,字段示例如下:
| 字段 | 含义 | 示例 |
|---|---|---|
| file_id | 文件相对路径 | data/processed/pm25_hourly_clean_v02_20240318.csv |
| stage | 所处阶段 | processed |
| source | 上游文件 | data/interim/pm25_hourly_merge_v01.csv |
| script | 生成脚本 | code/02_features.py |
| rows / cols | 规模 | 8760 / 14 |
| unit_note | 关键单位 | PM2.5: μg/m³;温度: ℃ |
| missing_rule | 缺失处理 | 连续缺失≤2h 线性插值,其余置 NaN |
| created | 生成时间 | 2024-03-18 10:42 |
这张表不必手工维护,可在脚本末尾追加写入:读取当前输出文件的路径、行数、列数、上游输入与脚本名,并附上运行时间。这样每次运行都会留下“谁由谁生成”的边,形成可追溯的数据血缘。字段含义与单位则集中写在 docs/data_dictionary.md,manifest 只引用不重复,避免两处定义不一致。
四、路径与可移植性
目录规范要真正生效,代码中不能出现本机绝对路径。推荐在项目根目录放一个配置入口,所有脚本从根目录解析相对路径:
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
RAW = ROOT / "data" / "raw"
PROCESSED = ROOT / "data" / "processed"
RESULTS = ROOT / "results"
这样项目整体拷贝到另一台机器或另一名合作者手中,只要目录结构不变即可直接运行。若数据量较大不便入库,可在 data/raw 下放置 README.md 说明数据获取方式与校验值(如 SHA-256),保证他人能取到同一份原始数据。
五、常见反模式
- “最终版”命名:
分析_final_改.csv无法排序、无法判断新旧,应改为带版本号与日期的规范名。 - 结果与代码混放:把图直接存到
code/下,导致代码目录被产物污染,清理时容易误删脚本。 - 原始数据被就地修改:在
raw上直接删列、改类型,后续任何复现都失去基准。 - 元数据只存在于脑中:单位、缺失规则、参数只写在聊天记录里,换人即失传。
把上述规则落到项目里,通常只需半天:建骨架、写 README、在脚本末尾加几行 manifest 追加逻辑、统一一次历史文件命名。换来的是整个分析周期内“文件在哪、从哪来、能不能重跑”三个问题都能被快速回答,这正是可重复分析流程最基础也最容易被低估的一层。