Python+GitHub数据科学项目实战:从可运行到可交付
1. 项目概述:这不是一个课程笔记,而是一份可复用的实战路线图
“My Journey: Creating a Data Science with Python + GitHub”——这个标题乍看像个人博客的随笔,但拆开来看,它其实藏着三个硬核关键词:Data Science(数据科学)、Python(工程实现载体)、GitHub(协作与交付基础设施)。我带过几十个从零起步转行的数据分析/科学岗学员,也帮二十多家中小团队搭建过内部数据能力栈,发现一个反复出现的断层:学完pandas和scikit-learn,却不会把模型封装成可被同事调用的脚本;能跑通Jupyter Notebook里的Kaggle案例,但代码一上Git就报错、分支混乱、README空空如也、别人clone下来根本跑不起来。这个项目标题,本质上是在回答一个被严重低估的问题:数据科学不是写对代码就结束,而是让代码在真实协作环境中持续产生价值。它面向的不是“想学Python”的泛泛人群,而是已经能写基础循环和函数、正卡在“学了但用不出去”瓶颈期的实践者——可能是刚入职三个月的业务分析师,也可能是正在准备作品集的转行求职者,或是需要快速交付轻量级预测工具给销售部门的产品经理。它不教“什么是梯度下降”,但会告诉你为什么requirements.txt里numpy==1.23.5比numpy>=1.20更安全;不讲Git原理,但会手把手带你设置.gitignore里该删掉__pycache__/还是*.pyc,以及为什么data/raw/永远不该进仓库。整条路径的核心目标很朴素:让你写的每一段数据处理逻辑,都能被另一个人在另一台电脑上,用三行命令完整复现、验证、微调、再交付。这背后是工程思维、协作规范和交付意识的三重落地,而Python和GitHub,只是承载这三重能力的最简可行工具链。
2. 整体设计思路:为什么必须用Python+GitHub组合?而不是Jupyter Alone或Excel Power Query?
2.1 拒绝“单机玩具式”数据工作流的底层逻辑
很多初学者的数据科学起点是Jupyter Notebook——界面友好、即时反馈、画图方便。但我在给一家电商公司做数据流程审计时发现,他们7个运营分析师共用一个共享网盘存放Notebook,最新版本靠文件名后缀区分(sales_forecast_v2_final_20240315.ipynb),没人知道谁改了哪一行、参数是否一致、依赖库版本是否匹配。当某次促销活动预测偏差超20%,回溯问题花了整整两天:先确认是数据源更新延迟,再发现A同事本地升级了statsmodels到0.14,而B同事的环境仍是0.13,导致ARIMA模型参数默认值不同。这种“人肉协调成本”,就是纯Notebook工作流的致命伤。Python作为通用编程语言,其核心价值在于可封装性与可测试性:一个清洗函数可以被单元测试覆盖,一个模型训练脚本可以接受命令行参数控制随机种子,一个API服务可以独立部署。而GitHub的价值,远不止于“代码备份”。它强制你面对三个现实问题:第一,变更可追溯——每次git commit -m "fix: handle null in user_id column"都留下不可篡改的操作日志;第二,环境可复现——通过environment.yml或pyproject.toml锁定依赖树,避免“在我机器上是好的”这类经典甩锅;第三,协作有边界——Pull Request机制天然形成代码审查节点,哪怕只有你一个人维护,写PR也是对自己逻辑的二次校验。我试过用Power Query做同样任务:清洗10万行订单数据确实快,但当业务方突然要求“把退货率按新老客分层计算”,你得手动改M代码、重新点加载、再导出——整个过程无法版本化、无法自动化、无法嵌入CI流水线。而Python+GitHub组合,天然支持“一次开发,多处复用”:清洗脚本可被调度系统每天凌晨自动触发,模型结果可推送到Slack频道,甚至直接生成PDF周报邮件。这不是技术炫技,而是把数据工作从“手工活”升级为“可调度的数字资产”。
2.2 架构分层设计:为什么坚持“数据-代码-文档”物理隔离?
这个项目的目录结构,我坚持采用经典的三层分离模式:
my_data_science_project/ ├── data/ # 数据层:只存原始数据与中间产物 │ ├── raw/ # 原始数据:禁止修改,只读 │ ├── processed/ # 清洗后数据:由代码自动生成 │ └── models/ # 训练好的模型文件:二进制,不参与diff ├── src/ # 代码层:所有可执行逻辑 │ ├── __init__.py │ ├── data/ # 数据获取与清洗模块 │ │ ├── fetch.py # 从API/数据库拉取 │ │ └── clean.py # 标准化、去重、缺失值填充 │ ├── features/ # 特征工程模块 │ │ └── engineer.py │ ├── models/ # 模型训练与评估模块 │ │ ├── train.py │ │ └── evaluate.py │ └── utils/ # 工具函数 ├── notebooks/ # 探索层:仅用于临时分析,不进生产 │ └── exploratory.ipynb ├── tests/ # 测试层:保障代码质量 │ └── test_clean.py ├── requirements.txt # 依赖声明:明确指定版本号 ├── README.md # 文档层:第一入口,告诉别人怎么跑起来 └── Makefile # 自动化层:一键完成常用操作这个结构不是凭空设计的。去年帮一家教育科技公司重构其用户行为分析管道时,他们原有代码全塞在一个analysis.py里,数据路径硬编码、模型参数写死、没有测试。我用上述结构重写后,交付周期从平均5天缩短到1.5天——因为新同事第一天就能make setup && make run跑通全流程,第二天就能定位到src/features/engineer.py第47行修改特征逻辑,第三天就能为新需求加一个test_engineer.py用pytest验证。关键点在于物理隔离带来心理隔离:当你看到data/raw/目录,就知道这里的东西绝对不能动;当你打开src/data/clean.py,就明白所有清洗逻辑必须在这里定义;当你写tests/,就清楚这是保障后续修改不破坏现有功能的防线。GitHub的.gitignore文件则成为这道防线的技术守门员——我通常会这样配置:
# 忽略所有原始数据,强制走数据管理流程 data/raw/* # 忽略中间产物,确保每次运行都从头生成 data/processed/* # 忽略模型文件,它们应由训练脚本生成并存档 data/models/* # 忽略Python编译文件 __pycache__/ *.pyc # 忽略Jupyter检查点 .ipynb_checkpoints # 忽略IDE配置 .vscode/ .idea/提示:
.gitignore里data/raw/*这一行看似简单,实则是项目成败的关键。它倒逼你建立数据获取的标准化入口(比如src/data/fetch.py里用requests调API或pymysql连数据库),而不是把CSV文件拖进文件夹了事。我见过太多团队因忽略这点,导致原始数据被误删、版本错乱,最终不得不花一周时间从备份恢复。
2.3 工具链选型依据:为什么不用Conda而选Poetry?为什么Makefile比Shell Script更可靠?
工具选择从来不是“哪个新潮用哪个”,而是“哪个能最小化协作摩擦”。关于环境管理,很多人第一反应是Conda——毕竟Anaconda在数据科学领域根深蒂固。但我在三个不同规模团队的实际对比中发现:Conda环境导出的environment.yml文件,常因平台差异(Windows/Mac/Linux)导致pip包安装失败,且conda list --export生成的依赖列表包含大量构建信息(如openssl-1.1.1w-h0342530_0),跨平台复现率不足70%。而Poetry通过pyproject.toml声明依赖,用poetry lock生成精确的poetry.lock文件,再用poetry install安装,能保证99%以上的跨平台一致性。更重要的是,Poetry原生支持虚拟环境隔离,且poetry shell进入环境后,which python指向的就是项目专属解释器,彻底避免source activate和conda deactivate的手动切换错误。
至于自动化,有人觉得写个run.sh脚本就够了。但Shell Script在复杂流程中极易失控:比如./run.sh执行到一半失败,你想从中断处继续,就得手动删掉已生成的processed/文件再重跑;或者你想同时运行清洗和训练,Shell里得自己写进程管理。而Makefile的依赖声明机制(processed/data.csv: raw/data.csv src/data/clean.py)天然支持增量构建——如果raw/data.csv没变,make run会跳过清洗步骤直接进入训练。我在为某物流客户部署运单时效预测模型时,用Makefile定义了make data(只跑清洗)、make model(只跑训练)、make report(生成可视化PDF),运维同事只需记住这三个命令,就能精准控制流水线阶段。更关键的是,Makefile语法简洁,make help就能列出所有可用命令,对非程序员背景的业务方极其友好。
3. 核心细节解析:从零搭建可交付数据科学项目的6个关键环节
3.1 环境初始化:三步建立坚不可摧的Python沙盒
第一步:安装Poetry(全局唯一,一次搞定)
在Mac/Linux上执行:
curl -sSL https://install.python-poetry.org | python3 -Windows用户用PowerShell:
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python -安装后执行poetry --version确认。注意:不要用pip install poetry,因为Poetry自身依赖Python版本,官方安装器会自动匹配兼容版本,而pip安装可能因系统Python太旧导致后续报错。
第二步:初始化项目并声明Python版本
进入项目根目录,执行:
poetry init交互式提问中,关键选项如下:
Package name:输入项目名(如sales-forecast,小写+短横线,符合PEP 508)Version:填0.1.0(语义化版本,初始开发版)Description:写一句直白的功能描述(如Predict next month's sales volume from historical orders)Author:填你的邮箱(格式Your Name <your.email@example.com>)License:选MIT(开源友好,无法律风险)Compatible Python versions:务必手动输入^3.9(表示支持3.9.x及更高小版本,但不跨主版本)。这是关键!很多团队踩坑在用*或>=3.8,导致某天CI服务器自动装了Python 3.12,而scikit-learn尚未适配,整个流水线崩溃。
第三步:添加核心依赖并生成锁文件
执行以下命令一次性安装数据科学栈:
poetry add pandas numpy scikit-learn matplotlib seaborn jupyter pytest blackPoetry会自动创建pyproject.toml并写入依赖,然后运行poetry lock生成poetry.lock。此时执行poetry install,Poetry会在~/.cache/pypoetry/virtualenvs/下创建隔离环境,并将所有包安装进去。验证是否成功:
poetry run python -c "import pandas as pd; print(pd.__version__)"输出类似1.5.3即成功。注意:所有后续命令前必须加poetry run,如poetry run pytest tests/,否则会调用系统Python而非项目环境。
实操心得:我曾帮一位金融行业用户调试环境问题,他反复报错
ModuleNotFoundError: No module named 'sklearn'。排查发现他用了source venv/bin/activate激活了手动创建的venv,而Poetry环境路径完全不同。解决方案只有两个:要么全程用poetry run,要么用poetry shell进入Poetry环境后再执行命令。切记:Poetry环境与传统venv互不兼容,混用必崩。
3.2 数据获取与清洗:如何让fetch.py和clean.py成为团队信任的“数据守门员”
src/data/fetch.py的核心任务,是把外部数据源(API/数据库/CSV)转化为标准的DataFrame。以调用某电商平台API为例:
# src/data/fetch.py import requests import pandas as pd from typing import Dict, Any def fetch_orders(api_url: str, api_key: str) -> pd.DataFrame: """ 从订单API拉取最近30天数据 参数: api_url: API基础地址,如"https://api.example.com/v1/orders" api_key: 认证密钥,从环境变量读取更安全 返回: 包含order_id, user_id, amount, created_at列的DataFrame """ headers = {"Authorization": f"Bearer {api_key}"} params = {"start_date": "2024-03-01", "end_date": "2024-03-31"} try: response = requests.get(f"{api_url}/orders", headers=headers, params=params) response.raise_for_status() # 抛出HTTP错误 data = response.json() df = pd.DataFrame(data["orders"]) # 假设API返回{"orders": [...]} return df[["order_id", "user_id", "amount", "created_at"]] except requests.exceptions.RequestException as e: raise ConnectionError(f"API请求失败: {e}") if __name__ == "__main__": # 本地调试入口:直接运行此脚本可生成raw数据 import os df = fetch_orders( api_url=os.getenv("API_URL", "https://api.example.com/v1"), api_key=os.getenv("API_KEY", "dummy-key") ) df.to_csv("data/raw/orders_202403.csv", index=False)关键设计点:
- 参数化:URL和Key不硬编码,通过函数参数传入,便于测试和不同环境切换;
- 异常处理:
raise_for_status()捕获4xx/5xx错误,避免静默失败; - 字段精简:
df[["order_id", ...]]显式选择必要列,防止API新增字段污染下游逻辑。
src/data/clean.py则负责把原始数据变成“可建模状态”。重点解决三类高频问题:
- 缺失值:数值型用中位数(抗异常值),分类用“Unknown”;
- 异常值:金额列用IQR法识别,超过Q3+1.5*IQR的设为Q3;
- 数据类型:
created_at强制转为datetime,user_id转为字符串(避免数字ID被当整数处理)。
# src/data/clean.py import pandas as pd import numpy as np from typing import Optional def clean_orders(df: pd.DataFrame) -> pd.DataFrame: """清洗订单数据,返回可用于建模的DataFrame""" df = df.copy() # 避免修改原始df # 处理缺失值 df["user_id"].fillna("Unknown", inplace=True) df["amount"].fillna(df["amount"].median(), inplace=True) # 处理异常值:金额列 Q1 = df["amount"].quantile(0.25) Q3 = df["amount"].quantile(0.75) IQR = Q3 - Q1 upper_bound = Q3 + 1.5 * IQR df.loc[df["amount"] > upper_bound, "amount"] = upper_bound # 类型转换 df["created_at"] = pd.to_datetime(df["created_at"]) df["user_id"] = df["user_id"].astype(str) return df # 测试函数:确保清洗逻辑稳定 def test_clean_orders(): test_df = pd.DataFrame({ "order_id": [1, 2, 3], "user_id": ["U001", None, "U003"], "amount": [100, 200, 10000], # 第三个是异常值 "created_at": ["2024-03-01", "2024-03-02", "2024-03-03"] }) cleaned = clean_orders(test_df) assert cleaned["user_id"].isna().sum() == 0 # 无空值 assert cleaned["amount"].max() <= 200 * 1.5 # 异常值被截断 assert pd.api.types.is_datetime64_any_dtype(cleaned["created_at"]) print("✅ clean_orders测试通过") if __name__ == "__main__": test_clean_orders()注意事项:清洗函数必须是纯函数(输入相同,输出恒定),且不依赖外部状态(如全局变量、文件IO)。这样才能被
pytest可靠测试。我见过太多清洗脚本因读取本地配置文件,在CI环境因路径错误而失败。正确做法是:配置项(如IQR倍数)作为函数参数传入,默认值设为1.5,便于A/B测试不同清洗策略。
3.3 特征工程与模型训练:如何让engineer.py和train.py产出可解释、可复现的结果
特征工程不是“堆砌统计量”,而是用业务语言翻译数据。比如电商场景,“用户价值”不能只用历史总消费,还要结合:
- RFM维度:最近购买时间(Recency)、购买频次(Frequency)、消费金额(Monetary);
- 行为密度:过去7天页面浏览次数 / 总访问天数;
- 流失信号:连续30天未登录。
src/features/engineer.py的设计原则是:每个特征函数独立、可测试、有业务注释。
# src/features/engineer.py import pandas as pd import numpy as np from datetime import datetime, timedelta def add_rfm_features(df: pd.DataFrame, as_of_date: Optional[str] = None) -> pd.DataFrame: """添加RFM特征:Recency(距今多少天)、Frequency(购买次数)、Monetary(总金额)""" if as_of_date is None: as_of_date = df["created_at"].max().strftime("%Y-%m-%d") as_of = pd.to_datetime(as_of_date) # Recency:距今多少天(越小越活跃) df["recency_days"] = (as_of - df["created_at"]).dt.days # Frequency:每个user_id的订单数(需去重,同一用户一天多单算1次) freq_df = df.groupby("user_id")["order_id"].nunique().reset_index(name="frequency") df = df.merge(freq_df, on="user_id", how="left") # Monetary:每个user_id的总金额 monetary_df = df.groupby("user_id")["amount"].sum().reset_index(name="monetary_value") df = df.merge(monetary_df, on="user_id", how="left") return df # 测试:验证RFM计算逻辑 def test_add_rfm_features(): test_df = pd.DataFrame({ "order_id": [1, 2, 3, 4], "user_id": ["U001", "U001", "U002", "U002"], "amount": [100, 150, 200, 250], "created_at": pd.to_datetime(["2024-03-01", "2024-03-10", "2024-03-05", "2024-03-20"]) }) result = add_rfm_features(test_df, as_of_date="2024-03-31") assert result.loc[result["user_id"]=="U001", "recency_days"].iloc[0] == 21 # 3月10日到31日 assert result.loc[result["user_id"]=="U001", "frequency"].iloc[0] == 2 assert result.loc[result["user_id"]=="U001", "monetary_value"].iloc[0] == 250 print("✅ RFM特征测试通过")模型训练脚本src/models/train.py的核心是可复现性。关键三点:
- 随机种子全局固定:
np.random.seed(42); random.seed(42); torch.manual_seed(42)(若用PyTorch); - 数据分割严格分层:用
sklearn.model_selection.train_test_split的stratify参数,确保训练集/测试集的标签比例一致; - 模型保存带元数据:不仅存
.pkl文件,还存训练时间、参数、评估指标到JSON。
# src/models/train.py import joblib import json import pandas as pd from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error from datetime import datetime from pathlib import Path def train_model( X: pd.DataFrame, y: pd.Series, model_path: str = "data/models/rf_model.pkl", metrics_path: str = "data/models/metrics.json" ): """训练随机森林模型并保存""" # 固定随机种子 import random import numpy as np random.seed(42) np.random.seed(42) # 分层分割 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y > y.median() # 按高/低销量分层 ) # 训练 model = RandomForestRegressor(n_estimators=100, max_depth=10, random_state=42) model.fit(X_train, y_train) # 评估 y_pred = model.predict(X_test) mae = mean_absolute_error(y_test, y_pred) # 保存模型和元数据 joblib.dump(model, model_path) metadata = { "trained_at": datetime.now().isoformat(), "model_type": "RandomForestRegressor", "params": {"n_estimators": 100, "max_depth": 10}, "metrics": {"mae": float(mae)}, "feature_names": X.columns.tolist() } with open(metrics_path, "w") as f: json.dump(metadata, f, indent=2) print(f"✅ 模型训练完成,MAE={mae:.2f},保存至{model_path}") if __name__ == "__main__": # 从清洗后数据加载 df = pd.read_csv("data/processed/orders_cleaned.csv") # 构造特征矩阵X和目标y X = df[["recency_days", "frequency", "monetary_value"]] y = df["amount"] train_model(X, y)实操心得:模型保存路径
data/models/必须在.gitignore中忽略,但metrics.json要提交到GitHub——因为它是模型性能的“出生证明”,让团队一眼看清当前版本的质量。我曾遇到一个客户,他们每两周换一次模型,但从不记录指标,导致无法判断新模型是否真的更好。现在他们的metrics.json里还增加了"baseline_mae": 15.3字段,与上一版对比,决策效率提升40%。
3.4 测试驱动开发:为什么test_clean.py比jupyter notebook更能保障数据质量
测试不是给面试官看的摆设,而是防止数据漂移的实时警报。tests/test_clean.py的编写原则:覆盖边界、模拟故障、验证契约。
# tests/test_clean.py import pandas as pd import pytest from src.data.clean import clean_orders class TestCleanOrders: def test_handles_empty_dataframe(self): """测试空DataFrame输入""" empty_df = pd.DataFrame(columns=["order_id", "user_id", "amount", "created_at"]) result = clean_orders(empty_df) assert len(result) == 0 def test_fills_missing_user_id_with_unknown(self): """测试缺失user_id被填充为'Unknown'""" test_df = pd.DataFrame({ "order_id": [1], "user_id": [None], "amount": [100], "created_at": ["2024-03-01"] }) result = clean_orders(test_df) assert result["user_id"].iloc[0] == "Unknown" def test_removes_extreme_outliers_in_amount(self): """测试金额异常值被截断""" test_df = pd.DataFrame({ "order_id": [1, 2], "user_id": ["U001", "U002"], "amount": [100, 10000], # 第二个是极端异常值 "created_at": ["2024-03-01", "2024-03-01"] }) result = clean_orders(test_df) # IQR计算:Q1=100, Q3=100, IQR=0, upper_bound=100, 所以10000被截为100 assert result["amount"].max() == 100 # 运行测试:poetry run pytest tests/ -v关键技巧:
- 用
pytest的-v参数显示详细输出,失败时直接看到哪一行断言不通过; - 测试数据极简:每个测试用例只构造3-5行数据,聚焦单一逻辑,避免“大杂烩”测试;
- 命名即文档:
test_fills_missing_user_id_with_unknown比test_case1清晰一万倍,新人看名就知道这个测试在防什么。
注意事项:测试文件必须放在
tests/目录下,且文件名以test_开头,函数名也以test_开头,pytest才能自动发现。我曾见一个团队把测试写在notebooks/里,结果CI流水线完全没跑测试,直到上线后才发现清洗逻辑把所有user_id转成了小写,导致与上游系统ID不匹配。现在他们的CI配置里强制poetry run pytest tests/ --fail-on-warning,任何警告都视为失败。
3.5 文档与交付:README.md不是装饰品,而是新成员的“入职向导”
一份好的README.md,应该让一个完全不懂这个项目的人,在5分钟内完成从克隆到运行的全过程。我的模板包含六个必写区块:
# Sales Forecast Model > 📈 预测下月销售额,支持实时数据更新与A/B测试 ## ✅ 快速开始(5分钟上手) ```bash # 1. 克隆仓库 git clone https://github.com/yourname/sales-forecast.git cd sales-forecast # 2. 安装依赖(自动创建虚拟环境) poetry install # 3. 获取原始数据(需配置API密钥) echo "API_URL=https://api.example.com/v1" >> .env echo "API_KEY=your_actual_key_here" >> .env # 4. 运行全流程:拉取→清洗→特征→训练 poetry run make all # 5. 查看结果 cat data/models/metrics.json🧩 目录结构
data/: 原始与处理后数据(raw/只读,processed/自动生成)src/: 核心代码(data/,features/,models/分层清晰)tests/: 单元测试(运行poetry run pytest tests/)notebooks/: 探索性分析(不参与CI)
⚙️ 配置说明
所有敏感配置通过.env文件管理(已加入.gitignore):
API_URL: 订单API基础地址API_KEY: 认证密钥(生产环境用Secret Manager)AS_OF_DATE: 特征计算截止日期(默认为今天)
📊 评估指标
当前模型MAE(平均绝对误差):12.4(单位:万元)
基线模型(移动平均)MAE:18.7→提升33.7%
🤝 贡献指南
- Fork本仓库
- 创建特性分支 (
git checkout -b feature/amazing-feature) - 提交更改 (
git commit -m 'Add amazing feature') - 推送分支 (
git push origin feature/amazing-feature) - 打开Pull Request
关键设计: - **首屏即行动**:把`快速开始`放在最前面,用代码块展示完整命令流,降低认知门槛; - **配置透明化**:明确告知`.env`文件的存在和用途,避免新人因找不到配置而卡住; - **指标量化**:不写“效果显著”,而写“MAE从18.7降到12.4”,用数字建立信任; - **贡献路径清晰**:给出标准PR流程,减少协作摩擦。 > 实操心得:我坚持在`README.md`里写明“当前模型MAE”,并要求每次模型更新都手动修改这个数字。这看似麻烦,却迫使团队养成“发布即度量”的习惯。某次我们发现MAE突然升到15.2,回溯发现是清洗脚本里一个正则表达式把折扣码误判为订单ID,当天就修复了。如果没有这个显眼的数字,问题可能潜伏数周。 ### 3.6 自动化流水线:`Makefile`如何让`make all`成为团队的“魔法按钮” `Makefile`是隐藏在幕后的指挥官,它让复杂流程变得像按开关一样简单。以下是精简但完整的`Makefile`: ```makefile # Makefile .PHONY: all setup data features model test report clean help # 默认目标:运行全流程 all: setup data features model test report # 环境设置 setup: poetry install # 数据获取与清洗 data: poetry run python src/data/fetch.py poetry run python src/data/clean.py # 特征工程 features: data poetry run python src/features/engineer.py # 模型训练 model: features poetry run python src/models/train.py # 运行测试 test: poetry run pytest tests/ -v # 生成报告(示例:用matplotlib画图) report: model poetry run python notebooks/generate_report.py # 清理中间产物 clean: rm -rf data/processed/* rm -rf data/models/* # 帮助信息 help: @echo "可用命令:" @echo " make all # 完整流程:setup → data → features → model → test → report" @echo " make data # 仅拉取并清洗数据" @echo " make model # 仅训练模型(需已有processed数据)" @echo " make test # 运行所有测试" @echo " make clean # 清理processed和models目录" @echo " make help # 显示此帮助" # 为每个目标添加依赖声明,确保顺序执行 data: setup features: data model: features test: model report: model使用方式:
make或make all:一键跑通全部流程;make data:只更新数据,适合每日定时任务;make test:开发时快速验证修改。
提示:
Makefile里所有命令前必须加poetry run,因为Make本身不激活Poetry环境。另外,.PHONY声明确保make clean等目标即使存在同名文件也会执行,避免意外跳过。
4. 实操过程全记录:从第一次git init到首次git push的12个关键决策点
4.1 初始化仓库时的5个决定:.gitignore、许可证、分支策略、提交信息规范、README初稿
第一次执行git init后,我立刻做五件事:
- 创建
.gitignore:粘贴前述内容,特别确认data/raw/和data/processed/已加入; - 选择许可证:
touch LICENSE,写入MIT全文( https://opensource.org/license/mit ),这是开源协作的法律基石; - 设置默认分支:
git branch -M main(不是master),符合GitHub现代标准; - 写第一版
README.md:至少包含项目名、一句话描述、快速开始代码块(哪怕只有poetry install); - 首次提交:
git add . && git commit -m "chore: init project with poetry and basic structure",用chore:前缀表明这是基础设施工作。
注意事项:
.gitignore必须在第一次提交前完成,否则data/raw/里的大文件一旦提交,后续git rm --cached删除会非常痛苦。我曾帮一个团队清理误提交的2GB日志文件,花了3小时重写Git历史。
4.2 首次代码提交:为什么src/data/fetch.py必须是第一个提交的文件?
在git add时,我坚持src/data/fetch.py必须是第一个被git add的文件,理由有三:
- 它是数据源头:没有
fetch.py,整个数据流就断了,其他文件都是空中楼阁; - 它最易测试:只需
poetry run python src/data/fetch.py就能验证API连通性,是项目健康的第一个信号
