1. 别急着调参:先把手头的数据变成算法能“吃”的样子
走到第八讲,NumPy和Pandas的底子应该已经打得差不多了。前面几讲我们折腾过数组、DataFrame、缺失值清洗、分组聚合,说实话,那些才是整个数据分析流程里最磨人的部分。到了机器学习这一步,反而轻松不少——因为算法是现成的,你真正要操心的核心问题只有一个:怎么把手头这份“不听话”的业务数据,变成模型能直接消费的矩阵。
这一讲的定位很明确:用NumPy和Pandas做数据预处理,然后用scikit-learn跑通线性回归和决策树两个经典模型。不需要推导复杂的数学公式,也不需要手写梯度下降,你只要搞清楚每个环节在干什么、为什么这么干,后面调参、换模型都是顺水推舟的事。
先说一个很多人绕进去的误区:以为机器学习就是从from sklearn.linear_model import LinearRegression开始的。实际项目里,算法调用只占整个流程的五分之一不到。你把Excel表导进来,发现日期列是字符串、收入列有缺失值、地区列是中文文本——这些才是真正决定模型上限的地方。所以这一讲我会把数据准备的比重拉高,模型部分反而讲得“糙”一点,重点放在“为什么这么做”而不是“API怎么调”。
学完这一讲,你应该能独立完成这样一条链路:读入数据 → 清洗与类型转换 → 特征编码 → 切分训练测试集 → 训练模型 → 评估指标 → 简单调参。整个过程用的就是你手头这辆“老马车”——NumPy负责底层数组运算,Pandas负责表结构和清洗,sklearn只在你最后需要时出来收个尾。这种组合的好处是:即使脱离机器学习框架,你依然能理解每个数据变换背后的含义,不至于变成只会调包的“API调用师”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境与数据准备:少走弯路,从装对包开始
2.1 版本兼容问题:numpy、pandas和sklearn的“三角关系”
很多人一上来就pip install numpy pandas sklearn,结果跑代码时被一堆报错劝退。这里先解决几个高频坑。首先是numpy和Python版本的匹配。由于numpy的预编译wheel要求特定CPU指令集,如果你的机器本身不支持某些优化指令,会出现类似RuntimeError: numpy was built with baseline optimizations: (x86_v2) but your这样的报错。这不是你代码的问题,是numpy版本与CPU不匹配。解决办法很简单:强制安装旧版或纯源码版本,比如在Windows上直接pip install numpy==1.26.4,或者去官网下载对应Python版本的wheel文件手动安装。
其次是pandas和numpy的兼容。因为pandas底层大量依赖numpy的C扩展,numpy版本太新或太旧都会导致pandas某些方法异常。我个人的测试经验是:Python 3.9配numpy 1.23.5 + pandas 2.0.3 + scikit-learn 1.3.2这一套组合非常稳,在Windows和Linux上都没出过大问题。Python 3.11以上建议直接装最新版numpy和pandas,但要注意,如果遇到Importerror: numba needs numpy 2.4 or less. Got numpy 2.5.这种报错,说明你的numpy版本超过了某些依赖库的限制,降级numpy即可。
最后是scikit-learn和pandas之间的接口变化。老版本sklearn接受DataFrame输入时会自动识别列名,新版本也支持,但要求列名必须是字符串。如果你之前的DataFrame列名是数字或者含有特殊符号,训练时会报特征名不匹配的错误。保险做法是在训练前统一df.columns = [str(c) for c in df.columns]。
2.2 用Pandas造一份可复现的演示数据集
为了避免“巧妇难为无米之炊”,我建议你直接构造一份带点“脏乱差”特征的数据来练手。下面这份数据是我专门为这一讲设计的,模拟的是某公司员工收入预测场景——正好对应热词里那个“收入预测”的案例。生成数据的代码很简单,但它包含了真实数据分析中常见的三类问题:列类型不对、文本类别特征、缺失值。
python复制import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n = 1000
df = pd.DataFrame({
'age': rng.integers(22, 60, n),
'years_experience': rng.integers(0, 35, n),
'education': rng.choice(['高中', '本科', '硕士', '博士'], n, p=[0.2, 0.4, 0.3, 0.1]),
'city': rng.choice(['一线', '二线', '三线'], n, p=[0.4, 0.35, 0.25]),
'department': rng.choice(['技术', '产品', '运营', '市场'], n),
'monthly_income': np.nan
})
# 真实逻辑:收入由经验、教育、城市决定,再加点噪声
base = 8000
df['monthly_income'] = (
base
+ df['years_experience'] * 800
+ df['education'].map({'高中': 0, '本科': 1000, '硕士': 2500, '博士': 5000})
+ df['city'].map({'一线': 3000, '二线': 1000, '三线': 0})
+ rng.normal(0, 1500, n)
)
# 人为制造缺失值
missing_idx = rng.choice(n, 50, replace=False)
df.loc[missing_idx, 'monthly_income'] = np.nan
df.head()
这份数据把收入定成了“经验×800 + 学历补贴 + 城市补贴 + 噪声”,背后隐藏的线性关系非常清晰,方便你回头验证模型学到的系数是否接近真实值。
2.3 预处理四板斧:类型修正、缺失值处理、文本编码、特征选择
拿到这份数据,跟着下面四步走,基本能应付绝大多数表格类数据。
第一步,看类型。 用df.dtypes跑一遍,把明显不对的列修正过来。比如日期列如果是字符串,要用pd.to_datetime转换;收入列如果有千分位逗号,要先去掉逗号再转成float。这一步看似简单,但漏掉一个对象类型的数值列,模型会直接报错或者把它当作类别特征处理,结果完全跑偏。
第二步,处理缺失值。 数值列的缺失值我一般用中位数填充,因为中位数对异常值不敏感,比均值更稳。类别列的缺失值可以填众数,或者直接新增一个“缺失”类别,让模型自己学。千万别一上来就dropna,样本本来就少,删一行就少一行信息。填充之后记得验证一下:df.isnull().sum() 应该全部是0。
第三步,文本编码。 这也是热词里被翻了无数次牌子的“pandas 数据类型转换”的实用场景。分类变量分两种情况:
- 有序类别(比如学历高中<本科<硕士<博士),用`df['education'].map(
