用MinMaxScaler归一化更安全高效:自动处理常量列(分母为0时输出全0)、支持自定义范围、可复用且向量化快;手写apply易出ZeroDivisionError和整除截断,须封装safe_minmax函数并预处理缺失值。

用 apply 做归一化容易出错,别硬套公式
直接对 Series 或 DataFrame 列用 apply 手写 (x - x.min()) / (x.max() - x.min()) 看似简单,但实际会遇到 ZeroDivisionError: float division by zero——只要某列所有值相等,max() - min() 就是 0。更隐蔽的问题是:如果传入的是整数列,Python 2 风格除法或 pandas 默认 dtype 可能导致结果全为 0(整数除整数截断)。
- 先检查是否有常量列:
df.nunique() == 1,有就跳过或统一设为 0 - 强制转
float64再计算,避免整型除法陷阱:df[col].astype(float) -
apply是逐元素调用,性能差;列级向量化运算快得多,优先用df[col]直接算
推荐用 MinMaxScaler 而不是手写 apply
sklearn 的 MinMaxScaler 不仅自动处理分母为 0 的情况(默认缩放到 [0, 1],常量列输出全 0),还支持 feature_range 自定义范围、保留原始 dtype 意图、可复用(fit + transform 分离),而且底层是 NumPy 向量化实现,比 apply 快一个数量级。
- 只缩放数值列:
select_dtypes(include='number')提前过滤 - 注意
fit_transform只能在训练集上调用,新数据必须用transform,否则数据泄露 - 如果要保留列名和索引,别用
scaler.fit_transform(df)直接覆盖,而是:from sklearn.preprocessing import MinMaxScaler<br>scaler = MinMaxScaler()<br>df_scaled = pd.DataFrame(<br> scaler.fit_transform(df.select_dtypes(include='number')),<br> columns=df.select_dtypes(include='number').columns,<br> index=df.index<br>)
想用 apply 也行,但得封装成安全函数
真要上 apply(比如嵌入 pipeline、不引入 sklearn),必须把边界逻辑包进去,不能裸写公式。核心是:分母为 0 时返回全 0 或全 0.5,同时保持输入 dtype 和 shape 不变。
- 写成单函数,接受
Series,返回同长度Series:def safe_minmax(x):<br> x = x.astype(float)<br> x_min, x_max = x.min(), x.max()<br> if x_max == x_min:<br> return pd.Series(np.full(len(x), 0.5), index=x.index)<br> return (x - x_min) / (x_max - x_min)
- 应用时明确指定轴:
df.select_dtypes(include='number').apply(safe_minmax) - 别在
apply里调df.min().min()这种全局统计——它会把多列压成一个标量,破坏列独立性
归一化后记得检查 dtypes 和缺失值
归一化本身不处理 NaN,反而会让 NaN 在计算中传染(比如 min() 返回 NaN,整个列变 NaN)。而且 MinMaxScaler 默认抛异常,apply 则可能静默跳过。
- 归一化前用
df.isna().sum()看缺失分布,决定填还是删 - 若用
MinMaxScaler,先用SimpleImputer补缺,顺序不能反 - 归一化后检查
df_scaled.dtypes,确认没意外变成object(常见于混合类型列被apply搞乱)
归一化不是“套个公式就完事”,关键在分母是否为 0、缺失值怎么流、新数据怎么复用同一套参数——这些地方一松懈,模型效果就悄无声息地掉下去。

















