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,否则结果与代码的对应关系被破坏。

项目目录结构与数据流向 data/ raw/ 原始数据 只读 · 不修改 interim/ 中间数据 清洗 · 拼接 processed/ 分析集 建模输入 data_dictionary.md code/ 01_clean.py 02_features.py 03_model.py utils/ 公共函数 README.md 运行入口 results/ figures/ 图 tables/ 表 models/ 模型 manifest.csv 元数据 docs/methods.md 读取 读取 读取 写出 写出 写出 数据单向流动:raw → interim → processed → 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),保证他人能取到同一份原始数据。

判断目录规范是否合格,可以用一个简单测试:把项目压缩包发给一位未参与的同学,只给 README,看他能否在不询问你的情况下找到原始数据、跑出主要结果图。若能,说明结构、命名与元数据已经形成闭环。

五、常见反模式

  • “最终版”命名:分析_final_改.csv 无法排序、无法判断新旧,应改为带版本号与日期的规范名。
  • 结果与代码混放:把图直接存到 code/ 下,导致代码目录被产物污染,清理时容易误删脚本。
  • 原始数据被就地修改:在 raw 上直接删列、改类型,后续任何复现都失去基准。
  • 元数据只存在于脑中:单位、缺失规则、参数只写在聊天记录里,换人即失传。

把上述规则落到项目里,通常只需半天:建骨架、写 README、在脚本末尾加几行 manifest 追加逻辑、统一一次历史文件命名。换来的是整个分析周期内“文件在哪、从哪来、能不能重跑”三个问题都能被快速回答,这正是可重复分析流程最基础也最容易被低估的一层。