数据科学全流程实战:从pandas到SQL、机器学习与Spark

数据科学全流程实战:从pandas到SQL、机器学习与Spark 先理清一个前提数据科学项目的学习成本不在记住某个库的接口而在理解一条完整链路。从拿到原始数据开始数据要经过读取、清洗、探索、建模、评估和交付每一环都会改变数据形态而上一环的输出就是下一环的输入。UC Berkeley 的 Data 100 数据科学全流程课程正是按这条链路组织教学内容核心工具覆盖 pandas、SQL、机器学习与 Spark。它也常被作为本科高年级核心课来上适合正在系统学习数据科学的人反复对照实践。这篇文章会按照类似的教学主线做一次技术整理先建立全局认知再准备本机环境然后跑通一个最小数据科学闭环接着单独拆解 SQL 和 Spark 里最容易卡住人的部分最后补充常见报错排查和工程化建议。读完这套内容后你可以直接拿它指导课程笔记、课程设计或者作为毕业设计里的数据科学项目主线。1. 为什么数据科学课程要把“全流程”当作主线1.1 全流程不是指工具多而是指问题闭环一个数据科学项目的起点通常是业务问题而不是某一个函数。以常见的房价预测为例先要想清楚要预测什么、有什么数据、数据存放在哪里。得到数据后第一步是确认表格里有多少行、多少列、每个字段是什么类型、是否含有空值。接下来是清洗再往后是做探索性分析观察目标变量分布、特征之间是否相关、有没有明显异常。这个过程中每一步之间都有明确的依赖关系。如果原始数据里混入非数值字符串后续所有统计计算都可能报错如果训练集和测试集拆分前就把整个数据集统一做了标准化预测结果会被数据泄漏污染。Data 100 这类课程的真正价值就是把这些问题放在同一个项目语境里反复训练而不是只教 pandas 有哪些 API、Spark 有哪些算子。课程主线通常可以压缩成下面五段数据获取与读写从 CSV、Excel、数据库或 API 进入 DataFrame。数据清洗与类型转换处理空值、重复值、错误类型、异常值。探索性分析与可视化查看分布、相关性、分组差异。机器学习建模与评估拆训练集和测试集训练基线模型计算指标。规模化与交付数据量变大后切换到 SQL 或 Spark最后把结果写成可复现的报告。这五段不是相互独立的模块而是一条闭环。每次从一个阶段进入下一个阶段都要验证一下“数据形态是否满足下一阶段的输入要求”。1.2 局部语法只解决局部问题流程设计决定项目成败新手容易陷入一种错觉把所有 pandas 函数学完就等于会做数据科学了。实际项目中更常遇到的困难是数据可能有几十个字段有的字段是时间戳字符串有的是带单位的价格文本有的是稀疏的分类值。仅仅调用dropna()或fillna()并不能解决所有问题你必须先弄清楚每个字段缺失的含义才能决定填充中位数、填充默认值还是单独标记为缺失。同样SQL 查询很快不等于数据质量好。一个 join 键在两张表里都出现重复时查询结果行数会被放大直接进入模型就会产生重复样本。机器学习模型跑出很高的分数不等于业务可用还要看是否发生过数据泄漏、测试集是否真实反映未来数据。这些问题的共同点是它们发生在流程设计层面而不是单纯的工具使用层面。所以学习时要以“完成一条完整流程”为目标而不是以“用某个库写了多少行代码”为目标。1.3 这套主线适合哪些读者这套内容适合四类读者准备系统学习数据科学的高年级本科生和研究生尤其是想把零散知识点串成项目实战的人。数据分析师已经在用 Excel 或 BI 工具做报表想补上 pandas、SQL 和建模能力。后端开发或大数据开发想理解数据科学项目如何组织特征、训练模型、评估效果。正在做课程设计或毕业论文的人需要一条可以直接映射到项目章节的技术主线。学完这条主线后至少可以独立完成一个从文件或数据库中取数、清洗、建模、评估的预测项目并且知道数据量增长到单机无法处理时应该在哪里切换到 Spark 或更完整的计算平台。2. 四个核心工具在数据科学里的分工2.1 pandas单机表格世界的日常主力pandas 是 Python 生态中最常用的表格计算库核心对象是 DataFrame 和 Series。它解决的问题是让你在代码里像操作 Excel 一样操作表格数据但保留编程语言的自动化、批处理和模块化能力。在个人分析、Notebook 实验和几百万行以内的数据场景里pandas 通常是第一选择。它适合做数据探索、清洗、特征工程和模型前处理。下面是最基础的读取与概览代码import pandas as pd df pd.read_csv(housing.csv) print(df.shape) # 查看行数和列数 print(df.dtypes) # 查看每列数据类型 print(df.isnull().sum()) # 统计每列空值数量这段代码解决的是“进入任何数据集后第一步要做什么”的问题。先看形状知道体量再看类型确认数值列有没有被读成字符串最后看空值决定清洗策略。pandas 的特点是接口丰富、灵活但灵活性也带来坑。索引对齐就是一个典型问题两个 DataFrame 合并时如果索引或 key 的取值不一致结果里会出现大量 NaN。读入 CSV 时如果数值列混入了千位分隔符或货币符号整列 dtype 会变成 object后续做mean()或sum()就会报错或直接跳过。这些细节在流程中很容易被忽略。2.2 SQL离数据越近分析越高效业务数据通常不是以 CSV 形式存在的而是存放在数据库、数据仓库或数据湖中。SQL 的作用是用声明式语法从关系数据中取出所需子集。所谓声明式是指只描述“我要什么”不描述“怎么遍历计算”。数据库引擎会自己决定 join 顺序、索引扫描方式和聚合执行计划。SQL 在数据科学流程中的位置通常位于清洗和建模之间。特征提取、报表统计、AB 实验取数几乎都离不开 SQL。很多情况下与其把几十 GB 的表导出到本地用 pandas 处理不如直接在数据库里完成过滤、分组和聚合把结果集压缩到可分析的大小。下面是一个典型分组统计 SQLSELECT region, COUNT(*) AS cnt, AVG(sales_amount) AS avg_sales FROM orders WHERE order_date 2024-01-01 GROUP BY region ORDER BY avg_sales DESC;这段 SQL 的核心逻辑是先过滤时间范围再按区域分组最后计算订单数和平均销售额。这里的关键顺序是 WHERE 在 GROUP BY 之前执行所以先缩小数据范围再分组聚合在大表里性能会更好。2.3 机器学习从历史数据中学习规律机器学习在数据科学流程里的作用是把清洗后的表格数据变成预测模型。它不是魔法而是从历史样本中寻找特征与目标之间的关系。课程里更重视的是训练流程、评估方式和误差来源而不是复杂的调参技巧。一个最小建模流程如下from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_squared_error X df.drop(price, axis1) y df[price] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model RandomForestRegressor(n_estimators100) model.fit(X_train, y_train) y_pred model.predict(X_test) rmse mean_squared_error(y_test, y_pred) ** 0.5 print(RMSE:, rmse)关键是train_test_split必须在任何使用全量数据的统计操作之前执行。无论是标准化、缺失值填充还是异常值截断都应该用训练集计算参数再用同一组参数转换测试集否则测试集信息会提前进入训练过程导致评估结果虚高。2.4 Spark数据超过单机内存时的分布式方案Spark 是一个分布式计算框架。在数据科学语境下最常用的是 Spark DataFrame它暴露的 API 与 pandas 相似但底层由集群并行执行并且引入惰性求值机制定义转换操作时不会立刻计算只有调用 show、write、count 等行动操作时才会真正执行。pandas 与 Spark 的差异可以从几个维度对比维度pandasSpark DataFrame数据规模单机内存适合 GB 级以下分布式存储与计算适合 TB 级执行方式即时计算单进程为主惰性计算集群并行学习成本低接口直观中等需要理解分区和行动操作核心场景EDA、清洗、建模前处理大规模 ETL、全量特征计算、分布式训练前处理很多人在学完 pandas 后再学 Spark会发现接口类似却总报错。原因常常不是 API 记错了而是没有理解“惰性执行”和“分区并行”这两个概念。Spark 的学习重点不是背算子而是理解一个 DataFrame 从读取到写出之间系统什么时候真正开始计算、数据如何被切分到不同节点。3. 环境准备把 pandas、SQL 和 Spark 都跑起来3.1 本地 Python 环境推荐用虚拟环境不要在系统 Python 里直接安装一堆数据科学库时间一长版本冲突很难处理。推荐先建虚拟环境python -m venv .venv # Linux / macOS source .venv/bin/activate # Windows .venv\Scripts\activate pip install --upgrade pip pip install pandas numpy scikit-learn matplotlib jupyter pyspark在 PyCharm 中新建项目时可以把 Project Interpreter 指向这个虚拟环境。之后在项目终端里执行pip install pandas右下角解释器会同步使用它。初学者经常遇到“在 PyCharm 里 import pandas 报 ModuleNotFoundError”的问题最常见原因不是 pandas 没安装而是终端安装包时用的是全局 PythonPyCharm 项目解释器却指向另一个虚拟环境。检查方式是打开 PyCharm 的 Settings 里的 Python Interpreter看当前解释器路径再在项目终端执行python -c import pandas; print(pandas.__version__)确认命令行解释器与项目解释器一致。3.2 SQL 练习环境怎么选如果只想练 SQL 语法推荐 SQLite 或 DuckDB。它们都是零配置嵌入式数据库不需要启动服务一个文件就是一个数据库适合在 Jupyter 里边跑边看结果。如果需要练习与生产接近的数据库语法可以使用 Docker 启动 PostgreSQL 或 MySQLdocker run --name pg-test \ -e POSTGRES_PASSWORDpostgres \ -p 5432:5432 \ -d postgres:16这样数据库运行在容器里不会污染宿主环境。用完可以docker stop pg-test需要重新使用时再启动。生产环境通常不会直接让你接触真实数据权限本地练熟增删改查、窗口函数和 join才是最好的准备。3.3 Spark 本地环境两种搭法第一种方式是直接安装 PySparkpip install pyspark安装完成后可以用下面代码验证from pyspark.sql import SparkSession spark SparkSession.builder.appName(test).getOrCreate() print(spark.version) spark.stop()PySpark 3.x 自带本地运行环境学习小数据集时不需要单独下载 Hadoop。如果课程要求与某个发布版的 Spark 集群完全一致再去下载对应的二进制发行版。第二种方式是利用 Docker 启动独立 Spark 镜像适合模拟集群环境但只有一台机器的同学。这种方式更接近真实部署但需要额外理解容器网络和端口映射本地学习不必一上来就搞。需要注意一个常见坑Spark 本地模式也对临时文件、路径中的空格敏感。如果在 Windows 上使用包含中文或空格的目录可能在提交任务时出现解析异常。建议把课程项目放在不含空格的路径中。3.4 环境版本检查清单初始化环境后按下面表格逐项确认检查项命令或方法期望结果Python 版本python --version3.9 或更高pandas 可用python -c import pandas; print(pandas.__version__)输出版本号无报错scikit-learn 可用python -c import sklearn; print(sklearn.__version__)输出版本号无报错Spark 可用运行 SparkSession 创建代码能输出 Spark 版本SQLite 可用python -c import sqlite3; print(sqlite3.sqlite_version)输出版本号Notebook 可用执行jupyter notebook浏览器打开并能新建 ipynb这个清单应该是开始任何数据科学项目前的固定动作。环境不一致导致的报错往往比代码逻辑错误更消耗时间。4. 手写一个最小数据科学闭环下面用一个房价数据集作为示例。不要关注数据本身是否真实存在重点看清洗、建模、评估之间的衔接逻辑。4.1 读入数据并做概览import pandas as pd df pd.read_csv(housing.csv) df.head() df.info()执行info()后要重点观察每列非空数量是否等于总行数哪些列是数值类型哪些列是 object。如果“price”“area”这类本应是数值的列显示为 object说明原始数据里混入了非数值内容必须进入清洗阶段。4.2 数据清洗空值、重复值、类型转换数据清洗的完整顺序建议是先看空值再看重复行最后做类型转换和异常值过滤。# 数值列统一转成 float无法转换的变成 NaN df[price] pd.to_numeric(df[price], errorscoerce) df[area] pd.to_numeric(df[area], errorscoerce) # 删除完全重复的行 df df.drop_duplicates() # 删除缺失目标值的行缺失特征值可以用中位数填充 df df.dropna(subset[price]) for col in [area, room_count]: df[col] df[col].fillna(df[col].median()) # 过滤不合理业务值 df df[df[price] 0] df df[df[area] 0]这里解释两个选择。第一特征列缺失值用中位数而不是均值填充是因为均值对异常值敏感。如果某个特征分布严重右偏少数极大值会把均值拉高中位数更能代表典型水平。实际项目中还要记录填充策略方便后续评审。第二在使用全量数据填中位数之前其实已经先完成了训练集和测试集的划分才能避免数据泄漏。更严谨的做法是只使用训练集的中位数填充测试集。示例为了讲解方便变得简化但你要记住这个区别。4.3 探索性分析与可视化清洗后不要急着建模。先用图表确认目标变量分布和特征相关性import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize(8, 6)) numeric_cols df.select_dtypes(includenumber).columns sns.heatmap(df[numeric_cols].corr(), annotTrue, cmapRdBu_r) plt.title(Correlation Matrix) plt.tight_layout() plt.show()相关矩阵可以帮助发现两点是否有特征与目标变量强相关这类特征通常是建模主力。是否有多列特征之间强相关这类特征会带来多重共线性在部分线性模型中影响稳定性。如果目标变量分布严重偏斜比如房价少数极高值拉长右尾可以先对目标做np.log1p变换让模型更关注相对误差。4.4 特征工程与模型训练分类变量需要编码。一种简单方式是独热编码df_model pd.get_dummies(df, columns[neighborhood], drop_firstTrue)然后拆分训练集和测试集from sklearn.model_selection import train_test_split X df_model.drop(price, axis1) y df_model[price] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 )这里random_state42的作用是固定随机拆分结果让实验可复现。实际项目中如果数据存在时间属性应该按时间切分而不是完全随机切分因为模型要预测的是未来数据。再训练一个随机森林回归模型from sklearn.ensemble import RandomForestRegressor model RandomForestRegressor(n_estimators200, max_depth10, random_state42) model.fit(X_train, y_train)随机森林的优势在于它可以处理数值和编码后的分类特征也能输出特征重要性适合作为第一个基线模型。真正调参要基于验证集或交叉验证不能直接拿测试集反复试。4.5 模型评估与结果交付from sklearn.metrics import mean_squared_error y_pred model.predict(X_test) rmse mean_squared_error(y_test, y_pred) ** 0.5 print(Test RMSE:, rmse) # 特征重要性 importance pd.Series(model.feature_importances_, indexX.columns) print(importance.sort_values(ascendingFalse).head(10))评估时不要只看 RMSE 一个数字。建议同时看测试集 RMSE 与训练集 RMSE 的差距差距过大说明过拟合。残差是否随预测值变化有规律说明模型漏掉重要特征。与一个简单基线模型对比比如直接用目标均值预测看看复杂模型是否真的有提升。交付时建议在 notebook 或报告里固定记录以下信息数据集行数、清洗后剩余行数、特征数量、模型类型、超参数、测试集指标、特征重要性排序、已知问题和下一步改进方向。这些记录在课程答辩和毕业设计中非常加分。5. SQL 部分要理解的三件事5.1 DataFrame 和 SQL 思维是一体两面pandas 和 SQL 虽然语法不同但表达的语义高度对应。语义pandasSQL选择列df[[a, b]]SELECT a, b过滤行df[df[a] 1]WHERE a 1分组聚合df.groupby(g)[v].mean()SELECT g, AVG(v) FROM t GROUP BY g排序df.sort_values(a)ORDER BY a连接两张表df1.merge(df2, onkey)JOIN理解这种对应关系能帮你快速把一段 pandas 代码改写成直接的 SQL 取数。实际项目中优秀的数据科学工程师不会把所有数据搬到本地而是尽量把过滤和聚合下推到数据库。有一个容易混淆的地方pandas 的groupby默认会把分组键变成索引需要reset_index()才能恢复为普通列SQL 的GROUP BY则直接把分组键作为输出列。两者执行结果相似但后续处理习惯不同。5.2 窗口函数的使用场景窗口函数解决的是“分组后还要保留明细行”的问题。普通 group by 会把多行压成一行窗口函数则在不改变明细行数的前提下计算分组聚合。典型场景是“每个部门薪资排名 Top 3”SELECT department, employee_name, salary FROM ( SELECT department, employee_name, salary, ROW_NUMBER() OVER ( PARTITION BY department ORDER BY salary DESC ) AS rn FROM employees ) t WHERE rn 3;这里PARTITION BY department表示按部门切分窗口ORDER BY salary DESC表示在窗口内排序ROW_NUMBER()生成组内序号。外层再过滤rn 3就能得到每个部门薪资最高的三个人。窗口函数还有RANK()、DENSE_RANK()、SUM() OVER()、LAG()、LEAD()等用法分别用于并列排名、累计求和、取上一行、取下一行。在时间序列特征提取和业务报表中这些能力非常常用。5.3 慢 SQL 的基本排查路径数据科学流程中SQL 取数经常是瓶颈。遇到慢查询按下面顺序排查用EXPLAIN查看执行计划确认是否全表扫描。检查 WHERE 和 JOIN 字段是否有索引。确认没有写成两表大结果集先做笛卡尔积再过滤的写法。不要把SELECT *带回所有列只选择需要的字段。把过滤条件放在 GROUP BY 之前尽早减少参与计算的数据量。如果是数据仓库场景以上路径仍然适用但要继续看分区裁剪是否生效。按分区字段过滤可以极大减少扫描量。5.4 写 SQL 时的几个容易忽略的坑第一个坑是 NULL 参与比较。SQL 中WHERE column ! x会排除掉column IS NULL的行因为NULL与任何值比较都返回未知。如果要保留缺失值要显式写WHERE column ! x OR column IS NULL。第二个坑是 join 键重复。两张表 join 之前要先确认关联键的基数。如果主键重复结果行数会成倍放大进入统计或建模后会造成双重计数。第三个坑是拼接 SQL 字符串。在业务系统或脚本中千万不要用字符串拼接用户输入来构造 SQL应该使用参数化查询或占位符方式否则会产生注入安全风险。这是工程底线任何场景都不能妥协。6. Spark 分布式计算的实战要点6.1 从 pandas 到 Spark 的思维迁移很多人在开始学 Spark 时自然希望 API 和 pandas 完全一样但这是误解。Spark DataFrame 的 API 虽然看起来像底层执行模型不同。先看最小示例from pyspark.sql import SparkSession spark SparkSession.builder.appName(example).getOrCreate() sdf spark.read.csv(large_data.csv, headerTrue, inferSchemaTrue) result sdf.groupBy(region).agg({sales: sum}) result.show()这里有一个关键点groupBy和agg只是构造了执行计划真正计算发生在show()被调用时。这种惰性求值设计让 Spark 可以把一系列转换操作组合成完整执行计划再自动做优化比如调整 join 顺序、下推过滤条件。pandas 中所有操作都立即执行Spark 中则要区分转换操作和行动操作。只定义转换而不触发行动程序不会报错也不会真正跑数据。很多初学者的疑惑是“代码没报错但等待时间很长”最后发现是在反复触发多个 action 导致重复计算。6.2 快速跑通本地 Spark 任务本地开发时可以使用 local 模式spark-submit --master local[2] your_job.pylocal[2]表示使用两个 CPU 核心并行执行本地任务。local[*]表示使用本机全部可用核数。如果是 YARN 集群环境提交命令会更复杂spark-submit \ --master yarn \ --deploy-mode cluster \ --driver-memory 2g \ --executor-memory 4g \ --executor-cores 2 \ --num-executors 10 \ your_job.py这几个参数直接决定作业能申请多少资源。executor-memory是每个 Executor 的 JVM 堆内存executor-cores是每个 Executor 可用的 CPU 核数num-executors是 Executor 数量。配置过高超过 YARN 队列上限任务不会启动配置过低则并行度不够作业会很慢。6.3 为什么 Executor 在 YARN 上每个容器只分配一个 vCore这是 Spark 学习群里出现频率很高的问题现象是明明机器有几十个核心作业里 Executor 的 Core 数却只有 1任务跑得很慢。可能原因按优先级排列提交时没有显式指定--executor-cores而默认值是 1。在 YARN 模式下不同发布版的默认配置不完全一致但很多场景下确实只有一个 Core。YARN 调度器限制了单个容器最大资源导致申请超过限制后被降级或失败。集群节点空闲资源不足即使申请了 2 个 Core资源管理器也只能按最小可用资源分配。没有合理设置spark.dynamicAllocation.enabled动态调整 Executor 数量的机制没有生效。排查方式很直接打开 Spark Web UI 的 Executors 标签页查看每个 Executor 的 Cores 属性同时打开 YARN ResourceManager UI看容器实际分配的资源。不要把 Spark UI 里的任务并发数当成 CPU 核数两者不同。如果是本地伪分布式环境只有一个节点几个核心不必过度纠结 vCore 数如果确实需要调高 executor 并行度要按集群节点规格估算可分配资源。假设一个节点有 16 vCore32GB 内存系统和其他服务要留一部分资源剩余资源才能分配给 Spark。7. 常见问题排查清单7.1 pandas 类问题问题现象可能原因检查方式处理建议数值列变成 object 类型数据中有“,”或其他非数字字符df.dtypespd.to_numeric(df[col], errorscoerce)合并表后行数异常增长join 键在两张表中有重复df[key].duplicated().sum()先查重复键再决定去重或确认是否需要多对多连接对某列赋值后出现大量 NaNDataFrame 索引不对齐检查df.index是否连续使用reset_index(dropTrue)后再赋值调用了dropna()但行数没变空值是以空字符串形式存在df[col].value_counts(dropnaFalse)把空字符串替换成None或直接过滤7.2 SQL 类问题问题现象可能原因检查方式处理建议查询结果出现重复行join 键非唯一分别查两张表的键基数确认关联是否多对多必要时先聚合再去重查询结果为空NULL 参与了比较检查 WHERE 条件和数据样例用IS NULL显式处理缺失值查询很慢无索引或全表扫描执行EXPLAIN查看执行计划建立索引、裁剪列、尽早过滤窗口函数报语法错误数据库版本过旧查看数据库文档中的窗口函数支持更换引擎或在 group by 后用自 join 替代7.3 Spark 类问题问题现象可能原因检查方式处理建议作业长时间卡住或 OOMshuffle 数据量过大Spark UI 的 Stages 页查看 shuffle 读写量增加分区数、调大执行内存、减少不必要的 action 次数每个 Executor 只有 1 个 vCore提交参数未配置或资源受限查看 Spark UI Executors显式指定--executor-cores确认 YARN 队列上限日志出现大量 WARN 但看不出错误配置或依赖版本不匹配找到第一条 ERROR 或 Exception按日志堆栈里提到的类名搜解决方法输出写入的文件数过多分区太碎查看输出目录 part 文件数量用coalesce()或调整分区策略控制文件数7.4 排错顺序参考遇到问题时不要一开始就怀疑框架有 Bug。多数情况下问题出在输入、路径、版本、配置这四个层面。建议按下面顺序排查输入数据格式是否符合预期。文件路径、列名、表名是否写对。Python 或 Spark 依赖版本是否与代码兼容。配置是否真的生效比如环境变量和 spark-defaults.conf。权限、端口、网络、资源限制是否正确。日志里是否有关键异常堆栈。框架本身是否存在已知限制。其中第 4 步最容易忽略。在本地改配置不一定同步到集群在 PyCharm 里改代码不一定同步到正在运行的环境。先确认所有配置文件和运行环境一致再开始查代码逻辑。8. 从课程实战走向工程化8.1 数据科学代码不能只是脚本课程练习里写一个从上到下的 notebook 脚本是可以接受的。但进入项目后代码应该更像工程而不是流水账。推荐做法把数据读取、清洗、特征工程、建模拆成函数或模块让每一步可以单独测试。用配置文件统一管理路径、参数和依赖版本而不是散落在代码里。在关键阶段打印日志比如“原始数据 10000 行清洗后 9600 行”方便定位数据问题。保留中间产物比如清洗后的 CSV 或 Parquet避免每次调试都重跑全部步骤。对异常分支做处理比如某个 CSV 文件缺失时不至于整个程序崩溃。下面是一个简单结构示例def load_data(path: str) - pd.DataFrame: df pd.read_csv(path) print(floaded rows: {len(df)}) return df def clean_data(df: pd.DataFrame) - pd.DataFrame: before len(df) df df.drop_duplicates() df df.dropna(subset[price]) print(fcleaned: {before} - {len(df)}) return df def train_model(df: pd.DataFrame): # 特征工程、拆分、建模 pass df load_data(housing.csv) df clean_data(df) train_model(df)日志的价值在数据科学项目中格外明显因为数据变化很快。今天能跑通的数据明天字段可能变了、空值比例可能变了、分布可能漂移了。没有日志很难定位差异点。8.2 学习环境与生产环境的差异维度学习环境生产环境数据量小样本单机内存可处理大样本可能超出单机内存数据质量相对干净重点在学方法字段漂移、缺失、来源不稳定代码要求能跑通、能解释可测试、可监控、可回滚依赖管理虚拟环境即可需要固定版本、容器化或锁文件模型上线Notebook 实验即可需要服务化、日志、监控、告警权限与安全本机文件为主数据权限、敏感信息脱敏、审计学习时不需要一步到位但要意识到课程里跑通的模型只是原型离线上服务还有一段距离。8.3 交付或发布前的可复用检查清单不管是课程设计、毕业设计还是真实项目交付下面这份清单都适用[ ] 是否清理了硬编码的数据库密码、API Key 和敏感路径[ ] 是否固定了 Python 包版本而不是依赖“刚装的最新版”[ ] 训练集和测试集是否严格分开所有统计参数是否只从训练集学习[ ] 是否设置随机种子实验是否可以复现[ ] 是否记录了数据清洗前和清洗后的行数和字段变化[ ] 是否检查过模型在测试集上的表现并与基线模型对比[ ] 是否输出了特征重要性或其它可解释性信息[ ] 是否把关键日志和中间结果保留下来[ ] 是否考虑了未来数据漂移后什么情况下需要重新训练[ ] 是否准备了回滚方案模型效果变差时如何回切旧版本这些条目看起来简单但实际项目里最容易出问题的反而是前几条代码里残留密码、数据泄漏、实验结果不可复现。8.4 下一步扩展方向走完 pandas、SQL、机器学习、Spark 这条主线后还可以继续扩展学习更系统的 ETL 工程理解数据管道是如何从源端流向数仓和特征表。学习数据质量测试用测试来保证字段分布、空值率、主键唯一性符合预期。学习模型服务化把训练好的模型变成 API 接口并加上监控和告警。学习特征存储和模型注册让特征和模型版本可追踪。在更大规模的场景中深入学习分布式计算比如分区策略、shuffle 调优和资源调度。如果你正在准备课程设计或毕业论文可以把“完整数据科学闭环”直接作为项目主线先说明问题背景再介绍数据来源和环境然后依次描述数据清洗、EDA、建模、评估和总结。每一章都要有可运行代码和验证结果这样评审老师每看一步都能复现通过率会高很多。学习这门课程时也不要只追求把 27 讲全部快速过一遍而是每学一个阶段就停下来用一份自己的数据重做一遍直到可以脱离课件独立完成整个闭环。