Data Preparation / Data Preprocessing 📂 Data Collection · 9 of 13 150 min read

Scaling in Machine Learning

A story-driven, hands-on guide to feature scaling techniques — Min-Max Normalisation, Z-Score Standardisation, Robust Scaling, and Max-Abs Scaling — explaining when each method is essential, when to skip it, and how to implement them correctly in sklearn pipelines without data leakage.

🌐
SectionSection第 01 节

Why Feature Scaling Matters 📏Feature Scaling क्यों ज़रूरी है 📏为什么特征缩放很重要 📏

Real data mixes units: age in years (18–90), blood pressure in mmHg (80–209), creatinine in mg/dL (0.2–7) and income in rupees (₹80,000 to ₹44 lakh). Many models compare rows by distance or learn by gradient steps. Both treat a change of 1 the same in every column — so the column with the biggest numbers takes over, whatever it means. Feature scaling puts the columns on comparable scales so each one can count.

असली data में इकाइयाँ मिली होती हैं: उम्र सालों में (18–90), blood pressure mmHg में (80–209), creatinine mg/dL में (0.2–7) और income रुपयों में (₹80,000 से ₹44 लाख)। कई models rows की तुलना दूरी से करते हैं या gradient क़दमों से सीखते हैं। दोनों हर column में 1 के बदलाव को बराबर मानते हैं — इसलिए सबसे बड़ी संख्याओं वाला column हावी हो जाता है, चाहे उसका मतलब कुछ भी हो। Feature scaling columns को तुलनीय scales पर लाता है ताकि हर column गिना जाए।

真实数据混合了各种单位:年龄以岁计(18–90),血压以 mmHg 计(80–209),肌酐以 mg/dL 计(0.2–7),收入以卢比计(₹8 万到 ₹440 万)。很多模型用距离比较样本,或用梯度步来学习。两者都把每一列中“变化 1”视为相同 —— 于是数值最大的那一列就会占据主导,不管它代表什么。特征缩放把各列放到可比的尺度上,让每一列都能发挥作用。

The Triage Model That Only Saw Incomeवह Triage Model जिसे सिर्फ़ Income दिखी只看得见收入的分诊模型
We built a 3,000-patient triage dataset (Section 11). Whether a patient is critical depends on creatinine, age and blood pressure — income has no effect at all. A KNN classifier on the raw columns finds critical patients with F1 0.202. Income's variance is so large that it makes up 100.00% of the squared distance between patients, so "nearest neighbour" just means "similar salary". Add one line — StandardScaler() — and F1 jumps to 0.584. Same data, same model.
हमने 3,000 मरीज़ों का triage dataset बनाया (Section 11)। मरीज़ critical है या नहीं, यह creatinine, उम्र और blood pressure पर निर्भर है — income का कोई असर नहीं। Raw columns पर KNN classifier critical मरीज़ों को F1 0.202 से पकड़ता है। Income का variance इतना बड़ा है कि मरीज़ों के बीच squared distance का 100.00% उसी से आता है, इसलिए "नज़दीकी पड़ोसी" का मतलब बस "मिलती salary" है। एक line जोड़ें — StandardScaler() — और F1 0.584 हो जाता है। वही data, वही model।
我们构建了一个 3,000 名患者的分诊数据集(第 11 节)。患者是否危重取决于肌酐、年龄和血压 —— 收入完全没有影响。在原始列上训练的 KNN 分类器识别危重患者的 F1 只有 0.202。收入的方差太大,占了患者之间平方距离的 100.00%,所以“最近邻”只意味着“工资相近”。加一行 StandardScaler(),F1 就升到 0.584。数据相同,模型相同。
LIVELIVE互动Who Are the Nearest Patients?सबसे नज़दीकी मरीज़ कौन हैं?谁是最近的患者?

A new patient and their 5 nearest training patients, found using all five features. Each bar shows how much each feature adds to that distance. Switch the scaler and watch who counts as "near".

एक नया मरीज़ और उसके 5 सबसे नज़दीकी training मरीज़, सभी पाँच features से ढूँढे गए। हर bar दिखाता है कि उस दूरी में हर feature कितना जोड़ता है। Scaler बदलें और देखें कि "नज़दीक" कौन गिना जाता है।

一位新患者及其 5 个最近的训练患者(用全部五个特征计算)。每根条显示各特征在该距离中所占的份额。切换缩放器,看看谁算“近”。

Scaler

📊 PICTURE 1 — THE SAME FIVE COLUMNS, RAW AND STANDARDISEDतस्वीर 1 — वही पाँच COLUMNS, RAW और STANDARDISED图 1 — 同样五列:原始与标准化后
Feature ranges raw and standardised raw values (one shared axis)raw मान (एक ही axis)原始值(同一坐标轴) age systolic_bp creatinine heart_rate annual_income 0 ₹10L ₹20L ₹30L ₹40L after StandardScalerStandardScaler के बादStandardScaler 之后 age systolic_bp creatinine heart_rate annual_income -2 0 2 4 6 bar = middle 50% · line = 1st to 99th percentilebar = बीच के 50% · line = 1वें से 99वें percentile तक柱 = 中间 50% · 线 = 第 1 到第 99 百分位

On one shared raw axis, only income is visible — the four medical columns are squashed against zero. After StandardScaler they all sit on the same scale (creatinine keeps its long right tail).

एक साझा raw axis पर सिर्फ़ income दिखती है — चारों medical columns शून्य से चिपके हैं। StandardScaler के बाद सब एक scale पर हैं (creatinine की लंबी दाईं tail बनी रहती है)।

在同一原始坐标轴上只看得见收入 —— 四个医学列都被压在零附近。StandardScaler 之后它们处在同一尺度上(肌酐仍保留长长的右尾)。

# 1) The scale problem in numbers
import numpy as np, pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.metrics import f1_score
df = pd.read_csv("triage.csv")
feats = ["age", "systolic_bp", "creatinine", "heart_rate", "annual_income"]
X_train, X_test, y_train, y_test = train_test_split(df[feats], df["critical"], test_size=0.25, random_state=0, stratify=df["critical"])
print(X_train.agg(["min", "max", "std"]).T.round(2))
var = X_train.var()
print("\nshare of squared distance (raw):", (var / var.sum() * 100).round(4).to_dict())
for name, model in [("raw", KNeighborsClassifier(15)), ("StandardScaler", make_pipeline(StandardScaler(), KNeighborsClassifier(15)))]:
    model.fit(X_train, y_train)
    print(f"KNN F1 on critical patients, {name:<14}: {f1_score(y_test, model.predict(X_test)):.3f}")
OUTPUT
min max std age 18.0 90.00 21.03 systolic_bp 80.0 209.00 19.53 creatinine 0.2 7.12 0.60 heart_rate 45.0 114.00 12.07 annual_income 80000.0 4429000.00 420504.99 share of squared distance (raw): {'age': 0.0, 'systolic_bp': 0.0, 'creatinine': 0.0, 'heart_rate': 0.0, 'annual_income': 100.0} KNN F1 on critical patients, raw : 0.202 KNN F1 on critical patients, StandardScaler: 0.584

SectionSection第 02 节

The Four Core Scalers 🧰चार मुख्य Scalers 🧰四种核心缩放器 🧰

All four are linear: they shift the column and divide by a number. They differ only in which centre and which spread they use — and that decides how they react to extreme values and to sparse data. The worked examples use the real training split.

चारों linear हैं: column को खिसकाते हैं और किसी संख्या से भाग देते हैं। फ़र्क़ बस इसमें है कि कौन सा केंद्र और कौन सा फैलाव लेते हैं — और यही तय करता है कि वे अति मानों और sparse data पर कैसा बर्ताव करते हैं। उदाहरण असली training split से हैं।

四者都是线性的:先平移,再除以一个数。区别只在于用哪个中心、哪个尺度 —— 这决定了它们如何应对极端值和稀疏数据。下面的例子使用真实的训练集。

Min-Max 📏Min-Max 📏Min-Max 📏
x' = (x − min) / (max − min)
Age 35, train min 18, max 90 → (35 − 18) / 72 = 0.236. Output [0, 1] on training data; new data can fall outside.
उम्र 35, train min 18, max 90 → (35 − 18) / 72 = 0.236। Training data पर output [0, 1]; नया data बाहर जा सकता है।
年龄 35,训练集最小 18、最大 90 → (35 − 18) / 72 = 0.236。训练数据输出在 [0, 1];新数据可能超出。
Standard 📐Standard 📐Standard 📐
x' = (x − mean) / std
Age 70, mean 53.65, std 21.02 → 0.78. Mean 0, std 1; no fixed range and no need for normal data.
उम्र 70, mean 53.65, std 21.02 → 0.78। Mean 0, std 1; कोई तय range नहीं और normal data की ज़रूरत नहीं।
年龄 70,均值 53.65,标准差 21.02 → 0.78。均值 0、标准差 1;没有固定范围,也不要求正态。
Robust 🛡️Robust 🛡️Robust 🛡️
x' = (x − median) / IQR
Creatinine 3.0, median 1.07, IQR 0.68 → 2.84. Resistant to extremes — they still stay large.
Creatinine 3.0, median 1.07, IQR 0.68 → 2.84। अति मानों से कम प्रभावित — वे फिर भी बड़े रहते हैं।
肌酐 3.0,中位数 1.07,IQR 0.68 → 2.84。对极端值稳健 —— 极端值本身仍然很大。
Max-Abs 🔢Max-Abs 🔢Max-Abs 🔢
x' = x / max|x|
Creatinine 3.0, max 7.12 → 0.421. No shift, so zeros stay zero — made for sparse data.
Creatinine 3.0, max 7.12 → 0.421। कोई खिसकाव नहीं, इसलिए शून्य शून्य रहते हैं — sparse data के लिए।
肌酐 3.0,最大值 7.12 → 0.421。不平移,零仍是零 —— 专为稀疏数据设计。
💡
Scaling Does Not Change the ShapeScaling आकार नहीं बदलता缩放不改变分布形状

These scalers move and stretch a column; they do not make a skewed column symmetric or remove outliers. Creatinine is just as right-skewed after StandardScaler (max 9.95 standard deviations). To change the shape, use a transform such as np.log or PowerTransformer — see the Data Transformation tutorial.

ये scalers column को खिसकाते और खींचते हैं; ये टेढ़े column को सममित नहीं बनाते न outliers हटाते हैं। StandardScaler के बाद भी creatinine उतना ही दाईं ओर टेढ़ा है (max 9.95 standard deviations)। आकार बदलने के लिए np.log या PowerTransformer जैसा transform इस्तेमाल करें — Data Transformation tutorial देखें।

这些缩放器只平移、拉伸一列,不会让偏斜的列变对称,也不会去掉离群值。StandardScaler 之后肌酐依然右偏(最大值为 9.95 个标准差)。要改变形状,请用 np.log 或 PowerTransformer 这类变换 —— 见“数据变换”教程。

# 2) The four scalers, fitted on TRAIN, checked with describe()
from sklearn.preprocessing import MinMaxScaler, RobustScaler, MaxAbsScaler
cols = ["age", "creatinine", "annual_income"]
for scaler in [MinMaxScaler(), StandardScaler(), RobustScaler(), MaxAbsScaler()]:
    Z = pd.DataFrame(scaler.fit_transform(X_train[cols]), columns=cols)
    print(f"{type(scaler).__name__:<14}", "  ".join(f"{c}: [{Z[c].min():6.2f}, {Z[c].max():6.2f}] mean {Z[c].mean():5.2f}" for c in ["age", "creatinine"]))
mm = MinMaxScaler().fit(X_train[["age"]])
print(f"\nage 35 → MinMax {(35 - mm.data_min_[0]) / mm.data_range_[0]:.3f}  (min {mm.data_min_[0]:.0f}, max {mm.data_max_[0]:.0f})")
rs = RobustScaler().fit(X_train[["creatinine"]])
print(f"creatinine 3.0 → Robust {(3.0 - rs.center_[0]) / rs.scale_[0]:.2f}  (median {rs.center_[0]:.2f}, IQR {rs.scale_[0]:.2f})")
mm2 = MinMaxScaler(feature_range=(-1, 1)).fit(X_train[["age"]])
new = pd.DataFrame({"age": [18, 54, 90, 100]})
print("\nfeature_range=(-1, 1):", mm2.transform(new).ravel().round(3), "← age 100 was never seen, so it lands above 1")
print("clip=True           :", MinMaxScaler(feature_range=(-1, 1), clip=True).fit(X_train[["age"]]).transform(new).ravel().round(3))
OUTPUT
MinMaxScaler age: [ 0.00, 1.00] mean 0.50 creatinine: [ 0.00, 1.00] mean 0.14 StandardScaler age: [ -1.70, 1.73] mean 0.00 creatinine: [ -1.67, 9.95] mean 0.00 RobustScaler age: [ -1.00, 1.06] mean 0.02 creatinine: [ -1.28, 8.90] mean 0.18 MaxAbsScaler age: [ 0.20, 1.00] mean 0.60 creatinine: [ 0.03, 1.00] mean 0.17 age 35 → MinMax 0.236 (min 18, max 90) creatinine 3.0 → Robust 2.84 (median 1.07, IQR 0.68) feature_range=(-1, 1): [-1. 0. 1. 1.278] ← age 100 was never seen, so it lands above 1 clip=True : [-1. 0. 1. 1.]

SectionSection第 03 节

Min-Max Scaling — A Fixed Range 📏Min-Max Scaling — तय Range 📏Min-Max 缩放 —— 固定范围 📏

MinMaxScaler maps the training minimum to 0 and the training maximum to 1 (or any feature_range). It is useful when an algorithm or a format expects bounded inputs — image pixels (divide by 255), some neural-network inputs, or features fed into distance measures that assume [0, 1].

MinMaxScaler training minimum को 0 और training maximum को 1 पर ले जाता है (या किसी भी feature_range पर)। यह तब काम का है जब algorithm या format सीमित inputs चाहता है — image pixels (255 से भाग), कुछ neural-network inputs, या [0, 1] मानने वाले distance measures।

MinMaxScaler 把训练集最小值映射为 0、最大值映射为 1(或任意 feature_range)。当算法或格式要求有界输入时很有用 —— 图像像素(除以 255)、某些神经网络输入,或假定取值在 [0, 1] 的距离度量。

❌ Two Weak Pointsदो कमज़ोरियाँ两个弱点
Problemसमस्या问题What happensक्या होता है会发生什么
One extreme valueएक अति मान一个极端值Creatinine max 7.12 squeezes everyone else into a narrow band (mean 0.14)Creatinine max 7.12 बाक़ी सबको पतली पट्टी में दबा देता है (mean 0.14)肌酐最大值 7.12 把其他人挤进窄带(均值 0.14)
New data outside the rangeRange के बाहर नया data超出范围的新数据Age 100 → 1.278 with range (−1, 1)उम्र 100 → range (−1, 1) में 1.278年龄 100 → 在 (−1, 1) 范围下为 1.278
✅ What To Doक्या करें怎么办
Fixउपाय对策Whenकब何时
Log first, or RobustScalerपहले log, या RobustScaler先取对数,或用 RobustScalerLong tails and real extremesलंबी tails और असली अति मान长尾和真实极端值
clip=TrueA model that truly needs the boundsजब model को सच में सीमाएँ चाहिए模型确实需要边界时
🐛
Bug in the Original Exampleमूल उदाहरण में Bug原示例中的错误

The earlier code fitted a second scaler on data that was already scaled, and its comment said "min should be 0" after asking for range (−1, 1) — where the minimum is −1. Fit each scaler once, on the raw training data. The output above shows the correct result: −1, 0, 1, and 1.278 for an unseen age of 100 (1.0 with clip=True).

पहले के code ने दूसरा scaler पहले से scaled data पर fit किया, और range (−1, 1) माँगने के बाद comment में लिखा था "min should be 0" — जबकि minimum −1 है। हर scaler एक बार, raw training data पर fit करें। ऊपर का output सही नतीजा दिखाता है: −1, 0, 1, और अनदेखी उम्र 100 के लिए 1.278 (clip=True के साथ 1.0)।

原来的代码在已经缩放过的数据上又拟合了一个缩放器,并且在设定范围 (−1, 1) 之后注释写着“最小值应为 0” —— 实际最小值是 −1。每个缩放器只在原始训练数据上拟合一次。上面的输出显示了正确结果:−1、0、1,以及未见过的年龄 100 对应 1.278(clip=True 时为 1.0)。


SectionSection第 04 节

Standardisation (Z-score) — The Default 📐Standardisation (Z-score) — Default 📐标准化(Z 分数)—— 默认选择 📐

StandardScaler subtracts the training mean and divides by the training standard deviation, so each column has mean 0 and standard deviation 1. A value of +2 means "two standard deviations above the average patient". It is the usual first choice for KNN, SVM, logistic and linear models with regularisation, PCA and neural networks.

StandardScaler training mean घटाता है और training standard deviation से भाग देता है, ताकि हर column का mean 0 और standard deviation 1 हो। +2 का मतलब है "औसत मरीज़ से दो standard deviations ऊपर"। KNN, SVM, regularisation वाले logistic और linear models, PCA और neural networks के लिए यह आम पहली पसंद है।

StandardScaler 减去训练集均值、除以训练集标准差,使每列均值为 0、标准差为 1。+2 表示“比平均患者高两个标准差”。它是 KNN、SVM、带正则化的逻辑/线性模型、PCA 和神经网络的常用首选。

📌
It Does NOT Need Normal Dataइसे Normal Data की ज़रूरत नहीं它并不要求正态分布

A common myth says z-scores only work for "approximately normal" data. The scaler is just a shift and a stretch — it works on any column. What normality changes is how you read the numbers: only for a bell curve does "±2 covers about 95%" hold. Two details: sklearn uses the population standard deviation (ddof=0), while pandas .std() uses ddof=1; and the outputs have no fixed range.

एक आम भ्रम है कि z-scores सिर्फ़ "लगभग normal" data पर चलते हैं। Scaler बस खिसकाव और खिंचाव है — किसी भी column पर काम करता है। Normality सिर्फ़ यह बदलती है कि आप संख्याओं को कैसे पढ़ते हैं: "±2 में लगभग 95%" सिर्फ़ bell curve पर सही है। दो बातें: sklearn population standard deviation (ddof=0) इस्तेमाल करता है, जबकि pandas .std() ddof=1; और outputs की कोई तय range नहीं होती।

一个常见误解是 Z 分数只适用于“近似正态”的数据。缩放器只是平移加拉伸 —— 对任何列都有效。正态性只影响你如何解读这些数字:只有钟形曲线才满足“±2 覆盖约 95%”。两个细节:sklearn 用总体标准差(ddof=0),而 pandas 的 .std() 用 ddof=1;输出没有固定范围。

🏦
Comparing CoefficientsCoefficients की तुलना比较系数

In a linear or logistic model, a raw coefficient means "change per 1 rupee" or "per 1 year", so their sizes cannot be compared. After standardising, each coefficient means "change per 1 standard deviation", which makes a rough side-by-side ranking possible (correlated features still blur it).

Linear या logistic model में raw coefficient का मतलब है "हर 1 रुपये पर बदलाव" या "हर 1 साल पर", इसलिए उनके आकार की तुलना नहीं हो सकती। Standardise करने के बाद हर coefficient का मतलब "हर 1 standard deviation पर बदलाव" है, जिससे मोटी तुलना संभव होती है (correlated features फिर भी इसे धुँधला करते हैं)।

在线性或逻辑模型中,原始系数表示“每 1 卢比的变化”或“每 1 岁的变化”,大小无法相互比较。标准化之后,每个系数表示“每 1 个标准差的变化”,可以粗略并排比较(相关特征仍会使其模糊)。


SectionSection第 05 节

Robust Scaling — Resistant to Extremes 🛡️Robust Scaling — अति मानों से कम प्रभावित 🛡️稳健缩放 —— 抗极端值 🛡️

RobustScaler subtracts the median and divides by the IQR (the 75th minus the 25th percentile). A few huge values hardly move the median or the IQR, so the bulk of the data keeps a sensible spread. Use it when a column has real extreme values you want to keep — incomes, claim amounts, transaction sizes.

RobustScaler median घटाता है और IQR (75वाँ minus 25वाँ percentile) से भाग देता है। कुछ बहुत बड़े मान median या IQR को मुश्किल से हिलाते हैं, इसलिए ज़्यादातर data का फैलाव समझदार रहता है। इसे तब इस्तेमाल करें जब column में असली अति मान हों जिन्हें आप रखना चाहते हैं — income, claim राशि, transaction आकार।

RobustScaler 减去中位数,再除以 IQR(第 75 百分位减第 25 百分位)。少数巨大值几乎不影响中位数和 IQR,所以大部分数据保持合理的分布。当某列含有你想保留的真实极端值时使用 —— 收入、理赔金额、交易额。

⚠️
Resistant, Not Outlier-Proofकम प्रभावित, Outlier-Proof नहीं稳健,但不是对离群值免疫

RobustScaler protects the scaling of the other rows; it does not shrink the extremes themselves. Creatinine 7.12 still becomes 8.90 after RobustScaler (and 9.95 after StandardScaler). A KNN or SVM will still feel that point strongly. If extremes are errors, fix them; if they are real but too influential, log-transform or clip first.

RobustScaler बाक़ी rows की scaling बचाता है; ख़ुद अति मानों को छोटा नहीं करता। Creatinine 7.12, RobustScaler के बाद भी 8.90 बनता है (और StandardScaler के बाद 9.95)। KNN या SVM उस बिंदु को फिर भी ज़ोर से महसूस करेगा। अगर अति मान ग़लती हैं तो ठीक करें; अगर असली पर बहुत प्रभावशाली हैं तो पहले log-transform या clip करें।

RobustScaler 保护的是其他行的缩放,并不缩小极端值本身。肌酐 7.12 经 RobustScaler 后仍是 8.90(经 StandardScaler 后为 9.95)。KNN 或 SVM 仍会强烈感受到这个点。如果极端值是错误,就修正;如果是真实但影响过大,先取对数或截断。

🏢
Illustrative Exampleउदाहरण (काल्पनिक)示意性例子

An illustrative case, not a real company: an insurer clusters claims where most are ₹20,000–₹2 lakh but a few are ₹2 crore. With MinMaxScaler, ordinary claims are squeezed near 0 and the clusters split only on "huge claim or not". RobustScaler keeps the ordinary claims spread out, so clusters can follow the other features too.

एक काल्पनिक उदाहरण, असली कंपनी नहीं: एक insurer claims को cluster करता है जिनमें ज़्यादातर ₹20,000–₹2 लाख हैं पर कुछ ₹2 करोड़। MinMaxScaler के साथ सामान्य claims 0 के पास दब जाते हैं और clusters सिर्फ़ "बड़ा claim या नहीं" पर बँटते हैं। RobustScaler सामान्य claims को फैला रखता है, ताकि clusters दूसरे features भी देख सकें।

示意性例子,并非真实公司:某保险公司对理赔做聚类,大多数金额在 ₹2 万–₹20 万,但少数达 ₹2000 万。用 MinMaxScaler 时,普通理赔被挤在 0 附近,聚类只按“是否巨额”划分。RobustScaler 让普通理赔保持分散,聚类也能考虑其他特征。


SectionSection第 06 节

Max-Abs Scaling — For Sparse Data 🔢Max-Abs Scaling — Sparse Data के लिए 🔢Max-Abs 缩放 —— 用于稀疏数据 🔢

Text features (TF-IDF, word counts) and one-hot columns are sparse: mostly zeros, stored by keeping only the non-zero values. Subtracting a mean would turn every zero into a non-zero number and blow up memory. MaxAbsScaler only divides by the largest absolute value, so zeros stay zero and the matrix stays sparse. StandardScaler(with_mean=False) does the same with the standard deviation.

Text features (TF-IDF, word counts) और one-hot columns sparse होते हैं: ज़्यादातर शून्य, जिन्हें सिर्फ़ non-zero मान रखकर store किया जाता है। Mean घटाने से हर शून्य non-zero बन जाएगा और memory फट जाएगी। MaxAbsScaler सिर्फ़ सबसे बड़े absolute मान से भाग देता है, इसलिए शून्य शून्य रहते हैं और matrix sparse रहती है। StandardScaler(with_mean=False) यही standard deviation से करता है।

文本特征(TF-IDF、词频)和独热列都是稀疏的:大部分是零,只存储非零值。减去均值会把每个零变成非零数,内存随之爆炸。MaxAbsScaler 只除以最大绝对值,零仍是零,矩阵保持稀疏。StandardScaler(with_mean=False) 则用标准差做同样的事。

# 3) Sparse text features: MaxAbsScaler keeps the zeros, StandardScaler cannot centre sparse data
from sklearn.feature_extraction.text import TfidfVectorizer
docs = ["fast delivery good packing", "late delivery poor packing", "good product fast refund",
        "poor product late refund", "good delivery", "late late late"] * 50
X_sparse = TfidfVectorizer().fit_transform(docs)
X_mas = MaxAbsScaler().fit_transform(X_sparse)
print(f"TF-IDF: {X_sparse.shape}, stored values {X_sparse.nnz} → after MaxAbsScaler {X_mas.nnz} (still sparse)")
try:
    StandardScaler().fit(X_sparse)
except ValueError as e:
    print("StandardScaler():", str(e).split(".")[0])
print("StandardScaler(with_mean=False) works:", StandardScaler(with_mean=False).fit_transform(X_sparse).nnz, "stored values")
OUTPUT
TF-IDF: (300, 8), stored values 950 → after MaxAbsScaler 950 (still sparse) StandardScaler(): Cannot center sparse matrices: pass `with_mean=False` instead StandardScaler(with_mean=False) works: 950 stored values

SectionSection第 07 节

Which Algorithms Need Scaling? 🤖किन Algorithms को Scaling चाहिए? 🤖哪些算法需要缩放?🤖

We trained eight models on the same split, once on raw columns and once after StandardScaler. The rule behind the results is simple: models that use distances, gradient descent or a penalty on coefficient size care about scale. Tree-based models split one column at a time on a threshold, so they do not.

हमने एक ही split पर आठ models train किए, एक बार raw columns पर और एक बार StandardScaler के बाद। नतीजों के पीछे नियम सरल है: जो models दूरियाँ, gradient descent या coefficient आकार पर penalty इस्तेमाल करते हैं, वे scale की परवाह करते हैं। Tree-based models एक बार में एक column को threshold पर बाँटते हैं, इसलिए नहीं करते।

我们在同一划分上训练了八个模型,一次用原始列,一次在 StandardScaler 之后。背后的规律很简单:使用距离、梯度下降或系数大小惩罚的模型在乎尺度。树模型每次只按阈值切分一列,所以不在乎。

LIVELIVE互动Raw vs Scaled, Model by Modelहर Model: Raw बनाम Scaled逐个模型:原始 vs 缩放

Grey: F1 on raw columns. Green: F1 after StandardScaler. Click a model to see why it reacts the way it does.

धूसर: raw columns पर F1। हरा: StandardScaler के बाद F1। किसी model पर click करें और देखें कि वह ऐसा क्यों करता है।

灰色:原始列上的 F1。绿色:StandardScaler 之后的 F1。点击模型查看原因。

AlgorithmAlgorithm算法Scale?Scale?缩放?Whyक्यों原因Our F1 raw → scaledहमारा F1 raw → scaled我们的 F1 原始 → 缩放
KNN, K-Means, DBSCANYesहाँ需要Euclidean distanceEuclidean दूरी欧氏距离0.202 → 0.584
SVM (RBF / linear)Yesहाँ需要Distances in the kernel, and the margin penaltyKernel में दूरियाँ, और margin penalty核函数中的距离,以及间隔惩罚0.000 → 0.598
Neural networksYesहाँ需要Gradient descent; very slow or failed training without itGradient descent; इसके बिना training बहुत धीमी या असफल梯度下降;不缩放训练极慢或失败0.000 → 0.621
PCA, LDAYesहाँ需要Variance-based: biggest units winVariance-आधारित: सबसे बड़ी इकाई जीतती है基于方差:单位最大者胜出see Section 8Section 8 देखें见第 8 节
Logistic / Ridge / LassoUsuallyआमतौर पर通常需要The penalty treats all coefficients alike; solvers converge fasterPenalty सब coefficients को बराबर मानती है; solvers जल्दी converge होते हैं惩罚对所有系数一视同仁;求解器收敛更快0.598 → 0.598 (217 → 6 iterations)(217 → 6 iterations)(217 → 6 次迭代)
Plain LinearRegression (OLS)Noनहीं不需要Predictions identical (max difference 2.6e-13); scale only for reading coefficientsPredictions बिल्कुल वही (max अंतर 2.6e-13); सिर्फ़ coefficients पढ़ने के लिए scale करें预测完全相同(最大差 2.6e-13);只为解读系数才缩放—
Gaussian Naive BayesNot in theoryसिद्धांत में नहीं理论上不需要Each feature has its own variance, but var_smoothing borrows from the largest oneहर feature का अपना variance, पर var_smoothing सबसे बड़े से उधार लेता है每个特征有自己的方差,但 var_smoothing 借用了最大的方差0.431 → 0.600
Decision tree, Random forest, Gradient boostingNoनहीं不需要Threshold splits do not depend on unitsThreshold splits इकाइयों पर निर्भर नहीं阈值切分与单位无关0.619 → 0.619 · 0.620 → 0.615 · 0.616 → 0.616
📊 PICTURE 2 — F1 BEFORE AND AFTER SCALINGतस्वीर 2 — SCALING से पहले और बाद F1图 2 — 缩放前后的 F1
F1 before and after scaling, by algorithm 0.0 0.2 0.4 0.6 0.20 0.58 KNN 0.00 0.60 SVM 0.60 0.60 Logistic 0.00 0.62 Neural net 0.43 0.60 Naive Bayes 0.62 0.62 Decision tree 0.62 0.62 Random forest 0.62 0.62 HistGradBoosting rawStandardScalerF1 on critical patients (test set)

Real results from the code below. The random forest moved by 0.005 — just the randomness of which rows land near a threshold.

नीचे के code से असली नतीजे। Random forest 0.005 हिला — बस इस randomness से कि कौन सी rows threshold के पास आती हैं।

来自下方代码的真实结果。随机森林变化了 0.005 —— 只是阈值附近样本落点的随机性。

# 4) Which algorithms care? Same split, raw vs StandardScaler
import warnings
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression, LinearRegression
from sklearn.neural_network import MLPClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, HistGradientBoostingClassifier
models = {"KNN": KNeighborsClassifier(15), "SVM (RBF)": SVC(), "Logistic (L2)": LogisticRegression(max_iter=5000),
          "Neural net (MLP)": MLPClassifier(hidden_layer_sizes=(32,), max_iter=500, random_state=0),
          "Naive Bayes": GaussianNB(), "Decision tree": DecisionTreeClassifier(max_depth=5, random_state=0),
          "Random forest": RandomForestClassifier(300, random_state=0), "HistGradBoosting": HistGradientBoostingClassifier(random_state=0)}
rows = []
with warnings.catch_warnings():
    warnings.simplefilter("ignore")                            # the raw MLP / logistic emit ConvergenceWarnings
    for name, m in models.items():
        raw = f1_score(y_test, m.fit(X_train, y_train).predict(X_test))
        sc = f1_score(y_test, make_pipeline(StandardScaler(), m).fit(X_train, y_train).predict(X_test))
        rows.append((name, raw, sc))
bench = pd.DataFrame(rows, columns=["model", "F1 raw", "F1 scaled"]).set_index("model")
bench["change"] = bench["F1 scaled"] - bench["F1 raw"]
print(bench.round(3))
lin_raw = LinearRegression().fit(X_train, y_train).predict(X_test)
lin_sc = make_pipeline(StandardScaler(), LinearRegression()).fit(X_train, y_train).predict(X_test)
print("\nplain LinearRegression: max difference in predictions raw vs scaled =", f"{abs(lin_raw - lin_sc).max():.1e}")
nb = GaussianNB().fit(X_train, y_train)
print(f"GaussianNB var_smoothing adds {nb.epsilon_:,.0f} to EVERY variance (1e-9 × the largest one, from income);"
      f" creatinine's own variance is {X_train['creatinine'].var():.2f}")
OUTPUT
F1 raw F1 scaled change model KNN 0.202 0.584 0.382 SVM (RBF) 0.000 0.598 0.598 Logistic (L2) 0.598 0.598 0.000 Neural net (MLP) 0.000 0.621 0.621 Naive Bayes 0.431 0.600 0.168 Decision tree 0.619 0.619 0.000 Random forest 0.620 0.615 -0.005 HistGradBoosting 0.616 0.616 0.000 plain LinearRegression: max difference in predictions raw vs scaled = 2.6e-13 GaussianNB var_smoothing adds 177 to EVERY variance (1e-9 × the largest one, from income); creatinine's own variance is 0.36
🧐
Two Corrections to the Usual Tableआम Table में दो सुधार对常见表格的两处更正

1. Linear regression is not "must scale". Ordinary least squares gives exactly the same predictions either way (we measured a difference of 2.6e-13). Scale it when you add a penalty (Ridge, Lasso), use a gradient solver, or want to compare coefficients. 2. Naive Bayes is not fully scale-free in sklearn. GaussianNB adds 1e-9 × the largest variance to every feature for stability. Here that is 177 — far bigger than creatinine's own variance of 0.36 — so the medical signal was blurred until we scaled.

1. Linear regression "ज़रूर scale करें" नहीं है। Ordinary least squares दोनों तरह बिल्कुल वही predictions देता है (हमने 2.6e-13 का अंतर मापा)। तब scale करें जब penalty जोड़ें (Ridge, Lasso), gradient solver इस्तेमाल करें, या coefficients की तुलना करनी हो। 2. sklearn में Naive Bayes पूरी तरह scale-मुक्त नहीं। GaussianNB स्थिरता के लिए हर feature में सबसे बड़े variance का 1e-9 गुना जोड़ता है। यहाँ यह 177 है — creatinine के अपने variance 0.36 से कहीं बड़ा — इसलिए scaling तक medical संकेत धुँधला रहा।

1. 线性回归并非“必须缩放”。普通最小二乘无论是否缩放,预测完全相同(我们测得差值为 2.6e-13)。只有在加入惩罚(Ridge、Lasso)、使用梯度求解器或需要比较系数时才缩放。2. sklearn 中的朴素贝叶斯并不完全与尺度无关。GaussianNB 为了数值稳定,会给每个特征加上最大方差的 1e-9 倍。这里是 177 —— 远大于肌酐自身的方差 0.36 —— 所以在缩放之前医学信号被抹平了。


SectionSection第 08 节

Gradient Descent and PCA ⛰️Gradient Descent और PCA ⛰️梯度下降与 PCA ⛰️

Gradient descent: a stretched bowlGradient descent: खिंचा हुआ कटोरा梯度下降:被拉长的碗

For a model trained by gradient descent, the loss surface is a bowl. If one feature is far wider than another, the bowl becomes a long, thin valley: steep in one direction, almost flat in the other. A learning rate small enough not to blow up in the steep direction crawls along the flat one. The real run below shows this — and it does not mean convergence is impossible, only slow or fragile.

Gradient descent से train होने वाले model के लिए loss surface एक कटोरा है। अगर एक feature दूसरे से बहुत चौड़ा है, तो कटोरा लंबी, पतली घाटी बन जाता है: एक दिशा में खड़ा, दूसरी में लगभग सपाट। खड़ी दिशा में न फटने लायक छोटी learning rate सपाट दिशा में रेंगती है। नीचे का असली run यही दिखाता है — और इसका मतलब यह नहीं कि convergence असंभव है, बस धीमा या नाज़ुक है।

对于用梯度下降训练的模型,损失曲面是一个碗。如果某个特征比另一个宽得多,碗就变成又长又窄的山谷:一个方向陡峭,另一个方向几乎平坦。小到不会在陡峭方向发散的学习率,在平坦方向上就会爬得很慢。下面的真实运行展示了这一点 —— 这并不意味着无法收敛,只是慢或脆弱。

LIVELIVE互动Gradient Descent on Two Bowlsदो कटोरों पर Gradient Descent两个碗上的梯度下降

Contours of the loss for two features. Pick raw or standardised features, set the learning rate, and run. Try the largest rate on both.

दो features के loss की contours। Raw या standardised features चुनें, learning rate तय करें, और चलाएँ। दोनों पर सबसे बड़ी rate आज़माएँ।

两个特征的损失等高线。选择原始或标准化特征,设置学习率并运行。在两者上都试试最大的学习率。

Features

# 5) Gradient descent: same model, same learning rate — raw vs scaled
from sklearn.linear_model import SGDClassifier
for name, X in [("raw", X_train), ("scaled", StandardScaler().fit_transform(X_train))]:
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        lr = LogisticRegression(max_iter=10000).fit(X, y_train)
    print(f"LogisticRegression (lbfgs) {name:<6}: {lr.n_iter_[0]:>5} iterations")
for name, (Xa, Xb) in [("raw", (X_train, X_test)), ("scaled", tuple(make_pipeline(StandardScaler()).fit(X_train).transform(v) for v in (X_train, X_test)))]:
    sgd = SGDClassifier(loss="log_loss", random_state=0, max_iter=50, tol=None)
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        sgd.fit(Xa, y_train)
    print(f"SGDClassifier 50 epochs {name:<6}: F1 {f1_score(y_test, sgd.predict(Xb)):.3f}")
OUTPUT
LogisticRegression (lbfgs) raw : 217 iterations LogisticRegression (lbfgs) scaled: 6 iterations SGDClassifier 50 epochs raw : F1 0.000 SGDClassifier 50 epochs scaled: F1 0.580

LogisticRegression still reached the same answer on raw data — its lbfgs solver just needed 217 iterations instead of 6. Plain SGDClassifier, with a fixed budget of 50 epochs, never got anywhere on raw data (F1 0.000) and reached 0.580 when scaled. The MLP in Section 7 failed the same way.

LogisticRegression raw data पर भी उसी जवाब तक पहुँचा — उसके lbfgs solver को बस 6 की जगह 217 iterations लगे। सीधा SGDClassifier, 50 epochs के तय budget के साथ, raw data पर कहीं नहीं पहुँचा (F1 0.000) और scaled पर 0.580 पहुँचा। Section 7 का MLP भी इसी तरह असफल हुआ।

LogisticRegression 在原始数据上也得到了同样的答案 —— 只是 lbfgs 求解器需要 217 次迭代而不是 6 次。普通的 SGDClassifier 在固定 50 轮的预算下,原始数据上毫无进展(F1 0.000),缩放后达到 0.580。第 7 节中的 MLP 也是这样失败的。

PCA: finding the units instead of the patternPCA: pattern की जगह इकाइयाँ ढूँढनाPCA:找到的是单位而不是规律

PCA looks for the direction of largest variance. On raw columns, variance is measured in each column's own units — and rupees squared are enormous. So PC1 is simply "income" and explains 100.0% of the variance. After scaling, PC1 explains 29.4% and is driven by age and blood pressure, which really do move together in this data.

PCA सबसे बड़े variance की दिशा ढूँढता है। Raw columns पर variance हर column की अपनी इकाई में मापा जाता है — और रुपये का square बहुत बड़ा है। इसलिए PC1 बस "income" है और variance का 100.0% समझाता है। Scaling के बाद PC1 29.4% समझाता है और उम्र व blood pressure से चलता है, जो इस data में सच में साथ बदलते हैं।

PCA 寻找方差最大的方向。在原始列上,方差以各列自身单位计算 —— 卢比的平方极其巨大。所以 PC1 就是“收入”,解释了 100.0% 的方差。缩放之后,PC1 解释 29.4%,由年龄和血压驱动,它们在这份数据中确实一同变化。

LIVELIVE互动What Does PC1 Point At?PC1 किस ओर इशारा करता है?PC1 指向哪里?

Bars show how much each feature contributes to the first principal component (absolute loading).

Bars दिखाते हैं कि पहले principal component में हर feature कितना योगदान देता है (absolute loading)।

柱子表示每个特征对第一主成分的贡献(载荷绝对值)。

Features

# 6) PCA: without scaling, the first component is just "income"
from sklearn.decomposition import PCA
for name, X in [("raw", X_train), ("scaled", StandardScaler().fit_transform(X_train))]:
    p = PCA().fit(X)
    load = pd.Series(np.abs(p.components_[0]), index=feats).round(3)
    print(f"{name:<6} PC1 explains {p.explained_variance_ratio_[0]:.1%} | biggest loadings:", load.nlargest(2).to_dict())
OUTPUT
raw PC1 explains 100.0% | biggest loadings: {'annual_income': 1.0, 'age': 0.0} scaled PC1 explains 29.4% | biggest loadings: {'age': 0.666, 'systolic_bp': 0.609}

SectionSection第 09 节

Leakage: Fit the Scaler on Training Data Only 🔒Leakage: Scaler सिर्फ़ Training Data पर Fit करें 🔒泄漏:只在训练数据上拟合缩放器 🔒

A scaler learns numbers — a mean, a standard deviation, a min and max. If it learns them from all rows before the split, the test rows have helped shape the training features. The rule is always the same: split first, fit on the training rows, transform the test rows.

Scaler संख्याएँ सीखता है — mean, standard deviation, min और max। अगर वह split से पहले सभी rows से सीखे, तो test rows ने training features को आकार देने में मदद की। नियम हमेशा एक है: पहले split, training rows पर fit, test rows को transform।

缩放器会学习一些数字 —— 均值、标准差、最小值和最大值。如果在划分之前从全部行学习,测试行就参与塑造了训练特征。规则始终如一:先划分,在训练行上 fit,再对测试行 transform。

🔒 PICTURE 3 — THE WRONG ORDER AND THE RIGHT ORDERतस्वीर 3 — ग़लत क्रम और सही क्रम图 3 — 错误顺序与正确顺序
Wrong and right way to fit a scaler WRONG — leaksग़लत — leak होता है错误 —— 会泄漏 scaler.fit(ALL rows)scaler.fit(सभी rows)scaler.fit(全部行) then split train / testफिर train / test split然后划分训练 / 测试 test rows already shaped the mean and stdtest rows पहले ही mean और std बना चुकी हैं测试行已经影响了均值和标准差 RIGHT — honestसही — ईमानदार正确 —— 诚实 split train / test FIRSTपहले train / test split先划分训练 / 测试 scaler.fit(X_train) onlyसिर्फ़ scaler.fit(X_train)只做 scaler.fit(X_train) scaler.transform(X_test) — no fitscaler.transform(X_test) — कोई fit नहींscaler.transform(X_test) —— 不拟合

How much does this particular mistake cost? We measured it: 200 random splits at each dataset size, KNN accuracy with a leaky scaler minus accuracy with an honest one.

यह ख़ास ग़लती कितनी महँगी है? हमने मापा: हर dataset आकार पर 200 random splits, leaky scaler वाली KNN accuracy minus ईमानदार scaler वाली।

这个具体错误代价多大?我们做了测量:每种数据规模下 200 次随机划分,用泄漏缩放器的 KNN 准确率减去诚实缩放器的准确率。

LIVELIVE互动How Big Is Scaler Leakage?Scaler Leakage कितना बड़ा है?缩放器泄漏有多大?

Histogram of the 200 gaps. Slide the dataset size.

200 अंतरों का histogram। Dataset का आकार बदलें।

200 个差值的直方图。拖动数据规模。

# 7) Leakage: fit the scaler on ALL rows vs on TRAIN rows only — how big is the effect?
def gap(n_rows, reps=200):
    out = []
    for r in range(reps):
        d = df.sample(n_rows, random_state=r)
        Xa, Xb, ya, yb = train_test_split(d[feats], d["critical"], test_size=0.3, random_state=r)
        leaky = StandardScaler().fit(pd.concat([Xa, Xb]))
        honest = StandardScaler().fit(Xa)
        knn = KNeighborsClassifier(5)
        a1 = knn.fit(leaky.transform(Xa), ya).score(leaky.transform(Xb), yb)
        a2 = knn.fit(honest.transform(Xa), ya).score(honest.transform(Xb), yb)
        out.append(a1 - a2)
    return np.array(out)
for n_rows in [60, 300, 3000]:
    g = gap(n_rows)
    print(f"{n_rows:>5} rows: leaky − honest accuracy, mean {g.mean():+.4f}, middle 90% [{np.percentile(g, 5):+.3f}, {np.percentile(g, 95):+.3f}]")
OUTPUT
60 rows: leaky − honest accuracy, mean +0.0019, middle 90% [-0.056, +0.111] 300 rows: leaky − honest accuracy, mean -0.0002, middle 90% [-0.022, +0.022] 3000 rows: leaky − honest accuracy, mean +0.0001, middle 90% [-0.003, +0.004]
⚖️
An Honest Resultएक ईमानदार नतीजा一个诚实的结果

For a plain scaler the leak is small: the average gap is about zero, and at 3,000 rows the scores almost never differ by more than half a point. With 60 rows the scores swing widely in both directions, so the leaky number is simply less trustworthy. Keep the habit anyway — the same "fit on everything" mistake with target encoding, feature selection or SMOTE can inflate scores a lot, and a Pipeline makes it impossible.

सामान्य scaler के लिए leak छोटा है: औसत अंतर लगभग शून्य है, और 3,000 rows पर scores शायद ही कभी आधे point से ज़्यादा अलग होते हैं। 60 rows पर scores दोनों दिशाओं में बहुत झूलते हैं, इसलिए leaky संख्या बस कम भरोसेमंद है। फिर भी आदत रखें — target encoding, feature selection या SMOTE में यही "सब पर fit" ग़लती scores को बहुत फुला सकती है, और Pipeline इसे असंभव बना देती है।

对普通缩放器来说泄漏很小:平均差值约为零,3,000 行时得分几乎不会相差超过半个百分点。只有 60 行时得分向两个方向大幅波动,因此泄漏得到的数字只是更不可信。但仍要保持这个习惯 —— 同样“在全部数据上拟合”的错误放在目标编码、特征选择或 SMOTE 上,可能大幅虚高得分,而 Pipeline 能彻底杜绝它。


SectionSection第 10 节

Choosing a Scaler and Building the Pipeline 🧭Scaler चुनना और Pipeline बनाना 🧭选择缩放器并搭建管道 🧭

🧭 PICTURE 4 — WHICH SCALER?तस्वीर 4 — कौन सा SCALER?图 4 — 选哪个缩放器?
Choosing a scaler Does the model need scaling at all?क्या model को scaling की ज़रूरत है?模型到底需不需要缩放? noनहीं否 Trees, forests, boosting: skip itTrees, forests, boosting: छोड़ दें树、随机森林、提升树:跳过 Is the data sparse (TF-IDF, counts)?क्या data sparse है (TF-IDF, counts)?数据是稀疏的吗(TF-IDF、计数)? yesहाँ是 MaxAbsScaler · StandardScaler(with_mean=False) Real extreme values you keep?असली अति मान जो आप रखते हैं?要保留的真实极端值? yesहाँ是 RobustScaler (or log first, then scale)RobustScaler (या पहले log, फिर scale)RobustScaler(或先取对数再缩放) Need a fixed range (pixels, bounded inputs)?तय range चाहिए (pixels, सीमित inputs)?需要固定范围(像素、有界输入)? yesहाँ是 MinMaxScaler StandardScaler — the defaultStandardScaler — defaultStandardScaler —— 默认选择

A starting point, not a law. When two choices seem fine, compare them with cross-validation.

शुरुआत की जगह, क़ानून नहीं। जब दो विकल्प ठीक लगें, cross-validation से तुलना करें।

这是起点而非定律。两种选择都可行时,用交叉验证比较。

🧠
Neural Networks: MinMax or Standard?Neural Networks: MinMax या Standard?神经网络:MinMax 还是 Standard?

Both are common. Standardised inputs (mean 0, std 1) suit the usual weight initialisations well and are the default in most tabular work; [0, 1] is natural for pixels. What matters most is that every input is on a similar small scale.

दोनों आम हैं। Standardised inputs (mean 0, std 1) आम weight initialisations के साथ अच्छे बैठते हैं और ज़्यादातर tabular काम में default हैं; pixels के लिए [0, 1] स्वाभाविक है। सबसे ज़रूरी यह है कि हर input मिलते-जुलते छोटे scale पर हो।

两者都很常见。标准化输入(均值 0、标准差 1)与常见的权重初始化配合良好,是大多数表格数据任务的默认;像素用 [0, 1] 很自然。最重要的是每个输入都处在相近的小尺度上。

In a real project, different columns need different treatment. ColumnTransformer sends each group of columns to its own scaler, Pipeline joins it to the model, and cross_val_score refits the scalers inside every fold. Here creatinine is log-transformed first (its skew), income gets RobustScaler, and the model is an SVM — a model that actually needs scaling.

असली project में अलग columns को अलग इलाज चाहिए। ColumnTransformer हर column समूह को उसके अपने scaler पर भेजता है, Pipeline उसे model से जोड़ती है, और cross_val_score हर fold के अंदर scalers को फिर से fit करता है। यहाँ creatinine पहले log-transform होता है (उसका skew), income को RobustScaler मिलता है, और model SVM है — ऐसा model जिसे सच में scaling चाहिए।

在真实项目中,不同列需要不同处理。ColumnTransformer 把每组列交给各自的缩放器,Pipeline 把它与模型串联,cross_val_score 在每一折内重新拟合缩放器。这里肌酐先取对数(因为偏斜),收入用 RobustScaler,模型是 SVM —— 一个真正需要缩放的模型。

# 8) A scaling pipeline: different scalers for different columns
import joblib
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.model_selection import cross_val_score
X_train2 = X_train.assign(log_creatinine=np.log(X_train["creatinine"]))
X_test2 = X_test.assign(log_creatinine=np.log(X_test["creatinine"]))
pre = ColumnTransformer([
    ("standard", Pipeline([("imp", SimpleImputer(strategy="median")), ("sc", StandardScaler())]), ["age", "systolic_bp", "heart_rate", "log_creatinine"]),
    ("robust", Pipeline([("imp", SimpleImputer(strategy="median")), ("sc", RobustScaler())]), ["annual_income"]),
])
pipe = Pipeline([("prep", pre), ("model", SVC())])
cv = cross_val_score(pipe, X_train2, y_train, cv=5, scoring="f1")
pipe.fit(X_train2, y_train)
print(f"SVM pipeline: 5-fold CV F1 {cv.mean():.3f} ± {cv.std():.3f} | test F1 {f1_score(y_test, pipe.predict(X_test2)):.3f}")
joblib.dump(pipe, "triage_pipeline.joblib")
sc = pipe["prep"].named_transformers_["standard"]["sc"]
print("learned on train only → means:", dict(zip(["age", "systolic_bp", "heart_rate", "log_creatinine"], sc.mean_.round(2).tolist())))
OUTPUT
SVM pipeline: 5-fold CV F1 0.544 ± 0.044 | test F1 0.602 learned on train only → means: {'age': 53.65, 'systolic_bp': 126.07, 'heart_rate': 78.08, 'log_creatinine': 0.07}
🌲
Why Not Gradient Boosting Here?यहाँ Gradient Boosting क्यों नहीं?这里为什么不用梯度提升?

The original pipeline scaled features and then fed them to gradient boosting — which ignores scale (Section 7: 0.616 → 0.616). It does no harm, but it teaches the wrong lesson. Put scalers in front of models that need them.

मूल pipeline features scale करके gradient boosting को देती थी — जो scale की परवाह नहीं करता (Section 7: 0.616 → 0.616)। नुक़सान नहीं, पर ग़लत सबक़ सिखाता है। Scalers उन models के आगे लगाएँ जिन्हें उनकी ज़रूरत है।

原来的管道先缩放特征再交给梯度提升 —— 而它不在乎尺度(第 7 节:0.616 → 0.616)。这样做无害,但传达了错误的经验。请把缩放器放在真正需要它的模型前面。


SectionSection第 11 节

Make the Practice Data 🐍Practice Data बनाएँ 🐍生成练习数据 🐍

Run this first. Creatinine (right-skewed) carries most of the signal, with age and blood pressure helping; heart rate and income have no effect. Income is in rupees, so its numbers are about 20,000 times larger than any medical column.

इसे पहले चलाएँ। ज़्यादातर संकेत creatinine (दाईं ओर टेढ़ा) में है, उम्र और blood pressure मदद करते हैं; heart rate और income का कोई असर नहीं। Income रुपयों में है, इसलिए उसकी संख्याएँ किसी भी medical column से लगभग 20,000 गुना बड़ी हैं।

请先运行它。大部分信号来自肌酐(右偏),年龄和血压起辅助作用;心率和收入没有影响。收入以卢比计,数值比任何医学列大约大 20,000 倍。

# 0) Practice data: kidney-risk triage. The signal is in creatinine; income is huge in scale but irrelevant.
import numpy as np, pandas as pd
rng = np.random.default_rng(3)
n = 3000
age = rng.integers(18, 91, n)
systolic_bp = np.clip(np.round(rng.normal(125 + 0.35 * (age - 50), 18, n)), 80, 220)
creatinine = np.round(np.exp(rng.normal(0.05 + 0.006 * (age - 50), 0.45, n)), 2)        # mg/dL, long right tail
heart_rate = np.clip(np.round(rng.normal(78, 12, n)), 45, 150)
income = np.round(np.exp(rng.normal(13.2, 0.6, n)), -3)                                  # ₹ per year — no effect
z = 2.4 * np.log(creatinine) + 0.025 * (age - 50) + 0.02 * (systolic_bp - 125) - 1.2 + rng.normal(0, 0.4, n)
critical = (rng.random(n) < 1 / (1 + np.exp(-z))).astype(int)
pd.DataFrame({"age": age, "systolic_bp": systolic_bp, "creatinine": creatinine, "heart_rate": heart_rate,
              "annual_income": income, "critical": critical}).to_csv("triage.csv", index=False)
print(f"triage.csv: {n:,} rows, critical {critical.mean():.1%}")
OUTPUT
triage.csv: 3,000 rows, critical 33.6%

SectionSection第 12 节

Golden Rules of Feature Scaling 🏆Feature Scaling के सुनहरे नियम 🏆特征缩放黄金法则 🏆

🎯 9 Rules Every Data Scientist Should Follow9 नियम जो हर Data Scientist को मानने चाहिए每位数据科学家都应遵守的 9 条法则
1
Scale for models that use distances, gradients or coefficient penalties: KNN, K-Means, SVM, neural nets, PCA, Ridge/Lasso/logistic.
दूरी, gradients या coefficient penalties वाले models के लिए scale करें: KNN, K-Means, SVM, neural nets, PCA, Ridge/Lasso/logistic।
对使用距离、梯度或系数惩罚的模型做缩放:KNN、K-Means、SVM、神经网络、PCA、Ridge/Lasso/逻辑回归。
2
Skip it for trees, random forests and gradient boosting — it changes nothing.
Trees, random forests और gradient boosting के लिए छोड़ दें — कुछ नहीं बदलता।
树、随机森林和梯度提升可以跳过 —— 没有任何变化。
3
Start with StandardScaler. It does not need normal data.
StandardScaler से शुरू करें। इसे normal data की ज़रूरत नहीं।
从 StandardScaler 开始。它不要求正态分布。
4
Use RobustScaler (or a log first) when real extreme values would squash everyone else.
जब असली अति मान बाक़ी सबको दबा दें तब RobustScaler (या पहले log) इस्तेमाल करें।
当真实极端值会挤压其他样本时,用 RobustScaler(或先取对数)。
5
Use MinMaxScaler when you truly need a bounded range; add clip=True if unseen values must stay inside it.
जब सच में सीमित range चाहिए तब MinMaxScaler; अगर अनदेखे मान अंदर ही रहने चाहिए तो clip=True जोड़ें।
确实需要有界范围时用 MinMaxScaler;若未见过的值也必须在范围内,加上 clip=True。
6
Keep sparse data sparse: MaxAbsScaler or StandardScaler(with_mean=False).
Sparse data को sparse रखें: MaxAbsScaler या StandardScaler(with_mean=False)।
保持稀疏数据稀疏:MaxAbsScaler 或 StandardScaler(with_mean=False)。
7
Split first, fit on training rows only, and keep scalers inside a Pipeline so cross-validation stays honest.
पहले split, सिर्फ़ training rows पर fit, और scalers को Pipeline के अंदर रखें ताकि cross-validation ईमानदार रहे।
先划分,只在训练行上拟合,并把缩放器放进 Pipeline,让交叉验证保持诚实。
8
Scaling does not fix skew or errors — clean and transform first, then scale.
Scaling skew या ग़लतियाँ ठीक नहीं करता — पहले साफ़ और transform करें, फिर scale।
缩放不能修正偏斜或错误 —— 先清洗和变换,再缩放。
9
Save the fitted pipeline and use the same one in production; never refit a scaler on live data.
Fit की हुई pipeline save करें और production में वही इस्तेमाल करें; live data पर scaler कभी दोबारा fit न करें।
保存拟合好的管道并在生产中使用同一个;绝不在线上数据上重新拟合缩放器。
🧮
Key Takeawayमुख्य सीख核心要点

Scaling decides which features a distance or a gradient can "see". On our triage data it lifted KNN from 0.202 to 0.584, rescued SVM and the neural net from 0, cut logistic regression from 217 to 6 iterations, and turned PCA from "income" into a real pattern — while trees did not change at all. Pick the scaler from the data (StandardScaler by default, Robust for extremes, MaxAbs for sparse), and keep it inside a pipeline.

Scaling तय करता है कि दूरी या gradient कौन से features "देख" सकते हैं। हमारे triage data पर इसने KNN को 0.202 से 0.584 किया, SVM और neural net को 0 से बचाया, logistic regression को 217 से 6 iterations पर लाया, और PCA को "income" से असली pattern बनाया — जबकि trees बिल्कुल नहीं बदले। Scaler data से चुनें (default StandardScaler, अति मानों के लिए Robust, sparse के लिए MaxAbs), और उसे pipeline के अंदर रखें।

缩放决定了距离或梯度能“看见”哪些特征。在我们的分诊数据上,它把 KNN 从 0.202 提升到 0.584,把 SVM 和神经网络从 0 救了回来,让逻辑回归从 217 次迭代降到 6 次,并让 PCA 从“收入”变成真实规律 —— 而树模型完全不变。根据数据选择缩放器(默认 StandardScaler,极端值用 Robust,稀疏数据用 MaxAbs),并把它放进管道。