Data Preparation / Data Preprocessing 📂 Data Collection · 7 of 13 163 min read

Data Transformation — Normalisation & Standardisation in Python

A story-driven, hands-on guide to transforming raw features into model-ready form using normalisation, standardisation, encoding, and scaling techniques in Python — with live diagrams, before/after comparisons, and complete reusable code.

🌐
SectionSection第 01 节

Why Data Transformation Matters 🔧Data Transformation क्यों ज़रूरी है 🔧为什么需要数据变换 🔧

Raw data comes in units that suit people: age in years, salary in rupees, credit score in points. Many algorithms only see numbers. If salary runs into lakhs and dependents run from 0 to 8, a distance-based model like KNN measures almost everything in rupees and ignores the rest. Data transformation puts features on a fair footing — by scaling them, reshaping skewed ones, and turning categories into numbers.

Raw data लोगों के हिसाब की इकाइयों में आता है: उम्र सालों में, salary रुपयों में, credit score points में। कई algorithms सिर्फ़ संख्याएँ देखते हैं। अगर salary लाखों में है और dependents 0 से 8 तक, तो KNN जैसा दूरी-आधारित model लगभग सब कुछ रुपयों में मापता है और बाक़ी को नज़रअंदाज़ कर देता है। Data transformation features को बराबरी पर लाता है — उन्हें scale करके, skewed features का आकार बदलकर, और categories को संख्याओं में बदलकर।

原始数据使用适合人类的单位:年龄用年、薪资用卢比、信用分用分数。许多算法只看数字。如果薪资是几十万、家属人数是 0 到 8,KNN 这类基于距离的模型几乎完全用卢比来衡量,其他特征被忽略。数据变换让各特征处于公平的起点 —— 通过缩放、重塑偏态特征,以及把类别变成数字。

The KNN That Was Worse Than Guessingवह KNN जो अंदाज़े से भी ख़राब था比瞎猜还差的 KNN
On our 4,000-row loan file, KNN (k = 15) on the six raw numeric features scores 73.1% on the test set. Always answering "no default" scores 73.6% — so the model is worse than not having a model. Salary and loan amount supply more than 99.99% of every distance. After StandardScaler, the same KNN scores 76.8%. Nothing else changed.
हमारी 4,000-row loan file पर, छह raw numeric features पर KNN (k = 15) test set पर 73.1% स्कोर करता है। हमेशा "no default" कहना 73.6% स्कोर करता है — यानी model, model न होने से भी बुरा है। हर दूरी का 99.99% से ज़्यादा salary और loan amount से आता है। StandardScaler के बाद वही KNN 76.8% स्कोर करता है। और कुछ नहीं बदला।
在我们 4,000 行的贷款数据上,用六个原始数值特征训练的 KNN(k = 15)测试准确率为 73.1%。而永远回答“不违约”就能得到 73.6% —— 模型还不如没有模型。每个距离中超过 99.99% 来自薪资和贷款金额。使用 StandardScaler 后,同一个 KNN 的准确率为 76.8%。其他什么都没变。
# 1) Why scale? KNN on raw features vs scaled features
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

df = pd.read_csv("loans.csv", parse_dates=["registration_date", "application_date"])
num = ["age", "years_employed", "dependents", "salary", "credit_score", "loan_amount"]
X_train, X_test, y_train, y_test = train_test_split(df[num], df["default"], test_size=0.25,
                                                    random_state=0, stratify=df["default"])
print(df[num].agg(["min", "max"]).T.astype(int))

sq = (X_train - X_train.mean()) ** 2                     # typical squared gap per column
print("\nshare of squared distance from each column (raw):")
print((sq.mean() / sq.mean().sum() * 100).round(3).to_string())

raw = KNeighborsClassifier(n_neighbors=15).fit(X_train, y_train)
scaled = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=15)).fit(X_train, y_train)
print(f"\nKNN accuracy  raw: {raw.score(X_test, y_test):.3f}   scaled: {scaled.score(X_test, y_test):.3f}"
      f"   (always guessing 'no default': {1 - y_test.mean():.3f})")
OUTPUT
min max age 21 65 years_employed 0 39 dependents 0 8 salary 74000 4692000 credit_score 460 900 loan_amount 30000 9800000 share of squared distance from each column (raw): age 0.000 years_employed 0.000 dependents 0.000 salary 22.525 credit_score 0.000 loan_amount 77.475 KNN accuracy raw: 0.731 scaled: 0.768 (always guessing 'no default': 0.736)
📏 PICTURE 1 — REAL DATA: FEATURE RANGES BEFORE AND AFTER STANDARDISINGतस्वीर 1 — असली DATA: STANDARDISE करने से पहले और बाद FEATURE RANGES图 1 — 真实数据:标准化前后的特征范围
Feature ranges before and after StandardScaler raw values (one shared axis)raw मान (एक ही axis)原始值(同一坐标轴) age years_employed dependents salary credit_score loan_amount 0 ₹25L ₹50L ₹75L after StandardScalerStandardScaler के बादStandardScaler 之后 age years_employed dependents salary credit_score loan_amount -2 0 2 4 bar = middle 50% · line = 1st to 99th percentilebar = बीच के 50% · line = 1वें से 99वें percentile तक柱 = 中间 50% · 线 = 第 1 到第 99 百分位

On the raw axis, four of the six features are too small to see. After standardising, each has mean 0 and std 1, and all are visible.

Raw axis पर छह में से चार features दिखने के लिए बहुत छोटे हैं। Standardise करने के बाद हर एक का mean 0 और std 1 है, और सब दिखते हैं।

在原始坐标轴上,六个特征中有四个小到看不见。标准化后,每个特征均值为 0、标准差为 1,全部清晰可见。

LIVELIVE互动KNN Neighbours — With and Without ScalingKNN पड़ोसी — Scaling के साथ और बिनाKNN 邻居 —— 缩放与不缩放

300 real applicants. The purple diamond is a new applicant; the 7 nearest neighbours are ringed. Switch the scaling and watch who counts as "near".

300 असली आवेदक। बैंगनी हीरा नया आवेदक है; 7 सबसे नज़दीकी पड़ोसी घेरे में हैं। Scaling बदलें और देखें कि कौन "नज़दीक" गिना जाता है।

300 位真实申请人。紫色菱形是新申请人,7 个最近邻已圈出。切换缩放方式,看看谁算“近”。

scale

🎯 PICTURE 2 — REAL OUTPUT: TEST ACCURACY BY SCALERतस्वीर 2 — असली OUTPUT: SCALER के हिसाब से TEST ACCURACY图 2 — 真实输出:不同缩放器下的测试准确率
KNN and random forest test accuracy with different scalers 70% 72% 74% 76% 78% 80% 73.1% 78.1% raw 77.7% 78.4% MinMax 76.8% 78.0% Standard 77.1% 78.1% Robust KNN (k = 15)Random forest (200 trees) always "no default": 73.6%हमेशा "no default": 73.6%总是猜“不违约”:73.6%

Any scaler rescues KNN. The random forest barely moves (78.0–78.4%): trees split on thresholds, so rescaling a column does not change which splits are possible.

कोई भी scaler KNN को बचा लेता है। Random forest मुश्किल से हिलता है (78.0–78.4%): trees thresholds पर बँटते हैं, इसलिए column को rescale करने से संभव splits नहीं बदलते।

任何缩放器都能挽救 KNN。随机森林几乎不变(78.0–78.4%):树按阈值分裂,缩放某一列不会改变可选的分裂点。

⚖️
Which Models Need Scaling?किन Models को Scaling चाहिए?哪些模型需要缩放?

Need it: distance-based models (KNN, K-Means, SVM with RBF kernel), PCA, models trained by gradient descent (neural networks, SGDClassifier), and regularised linear models (Ridge, Lasso, logistic regression with a penalty — the penalty treats all coefficients alike, so their units must be comparable). Do not need it: decision trees, random forests and gradient-boosted trees (XGBoost, LightGBM). Scaling them does no harm, but it does not help either.

ज़रूरत है: दूरी-आधारित models (KNN, K-Means, RBF kernel वाला SVM), PCA, gradient descent से train होने वाले models (neural networks, SGDClassifier), और regularised linear models (Ridge, Lasso, penalty वाला logistic regression — penalty सभी coefficients को एक जैसा मानती है, इसलिए उनकी इकाइयाँ तुलनीय होनी चाहिए)। ज़रूरत नहीं: decision trees, random forests और gradient-boosted trees (XGBoost, LightGBM)। इन्हें scale करने से नुक़सान नहीं, पर फ़ायदा भी नहीं।

需要:基于距离的模型(KNN、K-Means、RBF 核 SVM)、PCA、用梯度下降训练的模型(神经网络、SGDClassifier),以及正则化线性模型(Ridge、Lasso、带惩罚项的逻辑回归 —— 惩罚项对所有系数一视同仁,所以它们的单位必须可比)。不需要:决策树、随机森林和梯度提升树(XGBoost、LightGBM)。对它们缩放没有坏处,但也没有好处。

01
Clean firstपहले साफ़ करें先清洗
Missing values, duplicates, errors — transformation comes after cleaning.
Missing values, duplicates, ग़लतियाँ — transformation सफ़ाई के बाद आता है।
缺失值、重复、错误 —— 变换在清洗之后。
02
SplitSplit करें划分
Split train / test before fitting any transformer.
कोई भी transformer fit करने से पहले train / test split करें।
在拟合任何变换器之前先划分训练集 / 测试集。
03
Reshape skewed columnsSkewed columns का आकार बदलें重塑偏态列
log, Box-Cox, Yeo-Johnson.
log, Box-Cox, Yeo-Johnson।
log、Box-Cox、Yeo-Johnson。
04
Scale numbersसंख्याएँ scale करें缩放数值
Standard, MinMax, Robust or MaxAbs.
Standard, MinMax, Robust या MaxAbs।
Standard、MinMax、Robust 或 MaxAbs。
05
Encode categoriesCategories encode करें编码类别
One-hot, ordinal, target or frequency.
One-hot, ordinal, target या frequency।
独热、有序、目标或频率编码。
06
Wrap in a PipelinePipeline में लपेटें封装进 Pipeline
fit on train only; save one object.
सिर्फ़ train पर fit; एक object save करें।
只在训练集上拟合;保存一个对象。

SectionSection第 02 节

The Main Tools at a Glance 🧰मुख्य Tools एक नज़र में 🧰主要工具一览 🧰

Min-Max 📏Min-Max 📏Min-Max 📏
x' = (x − min) / (max − min)
Maps the training range to [0, 1]. Keeps the shape. One extreme value squeezes everything else.
Training range को [0, 1] पर लाता है। आकार बना रहता है। एक अति मान बाक़ी सबको दबा देता है।
把训练集范围映射到 [0, 1]。保持形状。一个极端值就会把其他值全部压缩。
Standard (z-score) 📐Standard (z-score) 📐Standard(z 分数)📐
x' = (x − mean) / std
Mean 0, std 1. Does not need normal data. Extremes still pull the mean and std.
Mean 0, std 1। Normal data की ज़रूरत नहीं। अति मान फिर भी mean और std को खींचते हैं।
均值 0,标准差 1。不要求数据服从正态分布。极端值仍会拉动均值和标准差。
Robust 🛡️Robust 🛡️Robust 🛡️
x' = (x − median) / IQR
Uses the median and IQR, so a few extremes barely change it. Good when extremes are real.
Median और IQR इस्तेमाल करता है, इसलिए कुछ अति मान इसे मुश्किल से बदलते हैं। जब अति मान असली हों तब अच्छा।
使用中位数和 IQR,少量极端值几乎影响不了它。极端值真实存在时很合适。
Max-Abs 🔢Max-Abs 🔢Max-Abs 🔢
x' = x / max|x|
Range [−1, 1], no shift, so zeros stay zero — made for sparse data such as text features.
Range [−1, 1], कोई खिसकाव नहीं, इसलिए शून्य शून्य रहते हैं — sparse data जैसे text features के लिए।
范围 [−1, 1],不平移,零值保持为零 —— 专为稀疏数据(如文本特征)设计。
Log 📉Log 📉对数 📉
x' = log(1 + x)
Pulls in a long right tail (money, counts). log1p works for x > −1; it still fails on values ≤ −1.
लंबी right tail अंदर खींचता है (पैसा, गिनती)। log1p, x > −1 पर चलता है; ≤ −1 पर फिर भी fail होता है।
收回右侧长尾(金额、计数)。log1p 适用于 x > −1;≤ −1 的值仍会出错。
Power transform 🔲Power transform 🔲幂变换 🔲
Box-Cox (x > 0) · Yeo-Johnson (any x)
Searches for the power λ that makes the data most normal-looking, then (by default) standardises.
वह power λ ढूँढता है जो data को सबसे normal जैसा बनाए, फिर (default रूप से) standardise करता है।
寻找让数据最接近正态的幂 λ,然后(默认)再做标准化。

SectionSection第 03 节

Min-Max Normalisation 📏Min-Max Normalisation 📏Min-Max 归一化 📏

Min-Max maps the smallest training value to 0 and the largest to 1. The shape of the distribution does not change — a skewed column stays exactly as skewed. It is popular for neural-network inputs and image pixels (divide by 255).

Min-Max सबसे छोटे training मान को 0 और सबसे बड़े को 1 पर ले जाता है। Distribution का आकार नहीं बदलता — skewed column ठीक उतना ही skewed रहता है। Neural-network inputs और image pixels (255 से भाग) के लिए यह लोकप्रिय है।

Min-Max 把训练集最小值映射为 0,最大值映射为 1。分布形状不变 —— 偏态列的偏度完全保持不变。它常用于神经网络输入和图像像素(除以 255)。

# 2) Min-Max: fit on TRAIN, then transform both
from sklearn.preprocessing import MinMaxScaler
mm = MinMaxScaler()                                      # feature_range=(0, 1) by default
Xtr_mm = pd.DataFrame(mm.fit_transform(X_train), columns=num, index=X_train.index)
Xte_mm = pd.DataFrame(mm.transform(X_test), columns=num, index=X_test.index)
print("train range:", Xtr_mm.min().min().round(3), "to", Xtr_mm.max().max().round(3))
print("test  range:", Xte_mm.min().min().round(3), "to", Xte_mm.max().max().round(3), "← new data CAN fall outside [0, 1]; that is expected")
print("salary skew before", X_train["salary"].skew().round(2), "after", Xtr_mm["salary"].skew().round(2), "(shape unchanged)")
back = mm.inverse_transform(Xtr_mm)[:2, 3]
print("inverse_transform gives back salaries:", back.round(0), "| original:", X_train["salary"].iloc[:2].to_numpy())
OUTPUT
train range: 0.0 to 1.0 test range: 0.0 to 1.0 ← new data CAN fall outside [0, 1]; that is expected salary skew before 1.94 after 1.94 (shape unchanged) inverse_transform gives back salaries: [ 666000. 1302000.] | original: [ 666000. 1302000.]
🧠
Neural Networks and ScaleNeural Networks और Scale神经网络与尺度

Inputs on very different scales make gradient descent slow and unstable: one weight needs tiny steps while another needs big ones. Scaling inputs to a similar range (Min-Max, or standardising) usually makes training faster and smoother. It is strongly recommended, though modern layers such as batch normalisation reduce the problem.

बहुत अलग scales वाले inputs gradient descent को धीमा और अस्थिर बनाते हैं: एक weight को छोटे क़दम चाहिए, दूसरे को बड़े। Inputs को मिलती-जुलती range में scale करना (Min-Max, या standardise) आम तौर पर training को तेज़ और सहज बनाता है। यह ज़ोरदार सलाह है, हालाँकि batch normalisation जैसी आधुनिक layers इस समस्या को घटाती हैं।

尺度差异很大的输入会让梯度下降变慢、变得不稳定:一个权重需要很小的步长,另一个却需要很大的步长。把输入缩放到相近的范围(Min-Max 或标准化)通常能让训练更快、更平稳。强烈推荐这样做,不过批归一化等现代层能减轻这一问题。

🚰
Fit on Train, Transform BothTrain पर Fit करें, दोनों को Transform करें在训练集上拟合,对两者都做变换

Learn min/max (or mean/std) from the training rows only, then apply the same numbers to the test rows. Fitting on all the data lets test information into training. For a simple scaler this leak is usually small, but the same habit with target encoding (Section 7) can make useless features look brilliant. A Pipeline (Section 9) makes the right habit automatic.

Min/max (या mean/std) सिर्फ़ training rows से सीखें, फिर वही संख्याएँ test rows पर लगाएँ। पूरे data पर fit करने से test की जानकारी training में घुस जाती है। सादे scaler में यह leak आम तौर पर छोटा होता है, पर target encoding (Section 7) में यही आदत बेकार features को शानदार दिखा सकती है। Pipeline (Section 9) सही आदत को अपने-आप बना देती है।

只用训练行学习最小值/最大值(或均值/标准差),再把同样的数值用于测试行。在全部数据上拟合会让测试信息进入训练。对简单的缩放器,这种泄漏通常很小;但同样的习惯用在目标编码(第 7 节)上,会让无用的特征看起来非常出色。Pipeline(第 9 节)能自动保证正确的做法。


SectionSection第 04 节

Standardisation (Z-Score) 📐Standardisation (Z-Score) 📐标准化(z 分数)📐

Standardising subtracts the mean and divides by the standard deviation, so each feature has mean 0 and std 1. It does not cap values and does not require normal data. It is the usual default for linear and logistic regression, PCA and SVM.

Standardising, mean घटाता है और standard deviation से भाग देता है, ताकि हर feature का mean 0 और std 1 हो। यह मानों पर सीमा नहीं लगाता और normal data की ज़रूरत नहीं रखता। Linear और logistic regression, PCA और SVM के लिए यह आम default है।

标准化减去均值再除以标准差,使每个特征均值为 0、标准差为 1。它不限制取值范围,也不要求数据服从正态分布。它是线性回归、逻辑回归、PCA 和 SVM 的常用默认做法。

# 3) Standardisation: mean 0, std 1 — and comparable coefficients
from sklearn.linear_model import LogisticRegression
sc = StandardScaler().fit(X_train)
Z = pd.DataFrame(sc.transform(X_train), columns=num)
print(Z.agg(["mean", "std"]).round(3))
print("note: sklearn divides by the population std (ddof=0), pandas .std() uses ddof=1 → tiny difference")
lr_std = LogisticRegression(max_iter=1000).fit(Z, y_train)
coef = pd.Series(lr_std.coef_[0], index=num).sort_values(key=abs, ascending=False)
print("\nstandardised coefficients (effect of +1 std):"); print(coef.round(3).to_string())
OUTPUT
age years_employed dependents salary credit_score loan_amount mean 0.0 -0.0 -0.0 0.0 0.0 0.0 std 1.0 1.0 1.0 1.0 1.0 1.0 note: sklearn divides by the population std (ddof=0), pandas .std() uses ddof=1 → tiny difference standardised coefficients (effect of +1 std): loan_amount 1.035 credit_score -0.893 salary -0.844 years_employed -0.544 dependents 0.479 age 0.024
⚖️ PICTURE 3 — REAL OUTPUT: THE SAME MODEL, TWO WAYS OF READING ITS COEFFICIENTSतस्वीर 3 — असली OUTPUT: वही MODEL, COEFFICIENTS पढ़ने के दो तरीके图 3 — 真实输出:同一个模型,两种解读系数的方式
Logistic regression coefficients before and after standardising raw features: coefficient per 1 unitraw features: हर 1 unit पर coefficient原始特征:每 1 个单位的系数 loan_amount 9.53e-07 credit_score -0.003 salary -1.25e-06 years_employed -0.096 dependents 0.450 age 0.030 standardised: coefficient per 1 stdstandardised: हर 1 std पर coefficient标准化后:每 1 个标准差的系数 loan_amount 1.035 credit_score -0.893 salary -0.844 years_employed -0.544 dependents 0.479 age 0.024 red: raises default risk · blue: lowers itलाल: default जोखिम बढ़ाता है · नीला: घटाता है红:提高违约风险 · 蓝:降低风险

Raw: the salary coefficient is -1.3e-06 per rupee and looks like nothing next to dependents (0.45). Standardised: salary (-0.84 per std) matters more than dependents (0.48). The units, not the importance, made the raw numbers tiny.

Raw: salary का coefficient प्रति रुपया -1.3e-06 है और dependents (0.45) के आगे कुछ नहीं लगता। Standardised: salary (-0.84 प्रति std) dependents (0.48) से ज़्यादा मायने रखती है। Raw संख्याओं को इकाइयों ने छोटा बनाया, महत्व ने नहीं।

原始:薪资系数为每卢比 -1.3e-06,和家属人数(0.45)相比似乎微不足道。标准化后:薪资(每个标准差 -0.84)比家属人数(0.48)更重要。让原始数字变小的是单位,而不是重要性。

🔍
Comparable — With Careतुलनीय — सावधानी के साथ可比较 —— 但要谨慎

Standardised coefficients answer "what happens if this feature moves by one typical step?" That makes them comparable across features. They are still not a full measure of importance: correlated features share credit, and a coefficient shows association, not cause.

Standardised coefficients बताते हैं "अगर यह feature एक आम क़दम चले तो क्या होगा?" इससे वे features के बीच तुलनीय बनते हैं। फिर भी वे महत्व का पूरा माप नहीं हैं: correlated features श्रेय बाँटते हैं, और coefficient संबंध दिखाता है, कारण नहीं।

标准化系数回答的是:“这个特征变化一个典型步长会怎样?”这让它们在特征之间可以比较。但它们仍不是重要性的完整度量:相关的特征会分摊功劳,而且系数显示的是关联,不是因果。


SectionSection第 05 节

Robust Scaling — When Extremes Are Real 🛡️Robust Scaling — जब अति मान असली हों 🛡️稳健缩放 —— 当极端值真实存在时 🛡️

If a few very large values are genuine (a ₹6 crore salary does exist), you may want to keep them. RobustScaler centres on the median and divides by the IQR, so those few values cannot squash everyone else.

अगर कुछ बहुत बड़े मान असली हैं (₹6 करोड़ की salary होती है), तो आप उन्हें रखना चाह सकते हैं। RobustScaler median पर केंद्रित करता है और IQR से भाग देता है, इसलिए वे कुछ मान बाक़ी सबको दबा नहीं सकते।

如果少数极大值是真实的(确实存在 ₹6 千万的年薪),你可能想保留它们。RobustScaler 以中位数为中心、除以 IQR,所以这几个值无法把其他所有人挤压在一起。

# 4) One extreme value: MinMax vs Standard vs Robust vs MaxAbs
from sklearn.preprocessing import RobustScaler, MaxAbsScaler
s = X_train[["salary"]].copy()
s.iloc[0, 0] = 60_000_000                                # one real but extreme salary: ₹6 crore
mid = s["salary"].between(*s["salary"].quantile([0.25, 0.75]))
for name, scaler in [("MinMax", MinMaxScaler()), ("Standard", StandardScaler()), ("Robust", RobustScaler()), ("MaxAbs", MaxAbsScaler())]:
    v = scaler.fit_transform(s).ravel()
    print(f"{name:<8} middle 50% spans {v[mid].min():7.3f} to {v[mid].max():6.3f}  (width {v[mid].max() - v[mid].min():.3f})   extreme → {v.max():7.2f}")
OUTPUT
MinMax middle 50% spans 0.007 to 0.017 (width 0.010) extreme → 1.00 Standard middle 50% spans -0.320 to 0.168 (width 0.487) extreme → 49.16 Robust middle 50% spans -0.411 to 0.588 (width 1.000) extreme → 101.05 MaxAbs middle 50% spans 0.009 to 0.018 (width 0.010) extreme → 1.00
LIVELIVE互动Four Scalers, One Extreme Valueचार Scalers, एक अति मान四种缩放器,一个极端值

120 salaries. Drag the size of one extra salary and watch the shaded middle 50% in each scaler.

120 salaries। एक अतिरिक्त salary का आकार खींचें और हर scaler में छायांकित बीच के 50% को देखें।

120 个薪资。拖动一个额外薪资的大小,观察每种缩放器中阴影标出的中间 50%。

ScalerScaler缩放器Formulaसूत्र公式Output rangeOutput range输出范围Affected by extremes?अति मानों से प्रभावित?受极端值影响?Good forकिसके लिए अच्छा适用于
MinMaxScaler(x − min) / (max − min)[0, 1] on traintrain पर在训练集上Yes, stronglyहाँ, बहुत是,非常大neural networks, bounded inputsneural networks, सीमित inputs神经网络、有界输入
StandardScaler(x − mean) / stdunboundedअसीमित无界Yesहाँ是linear models, PCA, SVMlinear models, PCA, SVM线性模型、PCA、SVM
RobustScaler(x − median) / IQRunboundedअसीमित无界Hardlyमुश्किल से几乎不real extreme values you keepअसली अति मान जो आप रखते हैं你要保留的真实极端值
MaxAbsScalerx / max|x|[−1, 1]Yes, stronglyहाँ, बहुत是,非常大sparse data (keeps zeros)sparse data (शून्य बचाता है)稀疏数据(保留零值)

SectionSection第 06 节

Log and Power Transforms — Changing the Shape 📉Log और Power Transforms — आकार बदलना 📉对数与幂变换 —— 改变形状 📉

Scalers change the range; they keep the shape. A long right tail — common in loan amounts, incomes and page views — needs a shape-changing transform. These help linear models, which can be dominated by a few huge values, and they often make relationships more linear.

Scalers range बदलते हैं; आकार बना रहता है। लंबी right tail — जो loan amounts, incomes और page views में आम है — के लिए आकार बदलने वाला transform चाहिए। ये linear models की मदद करते हैं, जिन पर कुछ बहुत बड़े मान हावी हो सकते हैं, और अक्सर संबंधों को ज़्यादा linear बनाते हैं।

缩放器改变的是范围,保留的是形状。贷款金额、收入、页面浏览量中常见的右侧长尾,需要能改变形状的变换。这对可能被少数巨大值主导的线性模型很有帮助,也常能让关系更接近线性。

# 5) Changing the SHAPE: log, sqrt, Yeo-Johnson, Box-Cox, quantile
from sklearn.preprocessing import PowerTransformer, QuantileTransformer
x = X_train[["loan_amount"]]
out = {"original": x["loan_amount"],
       "sqrt": np.sqrt(x["loan_amount"]),
       "log1p": np.log1p(x["loan_amount"])}
yj = PowerTransformer(method="yeo-johnson").fit(x)
bc = PowerTransformer(method="box-cox").fit(x)                         # needs x > 0
qt = QuantileTransformer(output_distribution="normal", n_quantiles=1000, random_state=42).fit(x)
out["yeo-johnson"] = pd.Series(yj.transform(x).ravel())
out["box-cox"] = pd.Series(bc.transform(x).ravel())
out["quantile→normal"] = pd.Series(qt.transform(x).ravel())
for k, v in out.items():
    print(f"{k:<16} skew {v.skew():6.2f}")
print(f"lambda: Yeo-Johnson {yj.lambdas_[0]:.3f}, Box-Cox {bc.lambdas_[0]:.3f}  (0 would be a pure log)")
OUTPUT
original skew 2.36 sqrt skew 0.82 log1p skew -0.35 yeo-johnson skew -0.01 box-cox skew -0.01 quantile→normal skew 0.00 lambda: Yeo-Johnson 0.148, Box-Cox 0.148 (0 would be a pure log)
📉 PICTURE 4 — REAL OUTPUT: LOAN AMOUNT AFTER SIX TRANSFORMSतस्वीर 4 — असली OUTPUT: छह TRANSFORMS के बाद LOAN AMOUNT图 4 — 真实输出:贷款金额经过六种变换后
Loan amount after six transformations originalskew 2.36 MinMaxScalerskew 2.36 sqrtskew 0.82 log1pskew -0.35 Yeo-Johnsonskew -0.01 quantile → normalskew 0.00

MinMax gives the same shape as the original. sqrt helps a little. Here log1p over-corrects slightly (skew −0.35), because the best power is λ ≈ 0.15, not 0. Yeo-Johnson finds that λ automatically.

MinMax मूल जैसा ही आकार देता है। sqrt थोड़ी मदद करता है। यहाँ log1p थोड़ा ज़्यादा सुधार कर देता है (skew −0.35), क्योंकि सबसे अच्छी power λ ≈ 0.15 है, 0 नहीं। Yeo-Johnson वह λ अपने-आप ढूँढता है।

MinMax 的形状与原始数据完全相同。sqrt 略有帮助。这里 log1p 稍微矫枉过正(偏度 −0.35),因为最佳幂次是 λ ≈ 0.15 而不是 0。Yeo-Johnson 会自动找到这个 λ。

LIVELIVE互动Slide the Box-Cox λBox-Cox λ खिसकाएँ拖动 Box-Cox 的 λ

500 real loan amounts. λ = 1 leaves the shape alone, λ = 0 is a log. Find the λ that makes skewness 0.

500 असली loan amounts। λ = 1 आकार नहीं बदलता, λ = 0 log है। वह λ ढूँढें जो skewness को 0 बनाए।

500 个真实贷款金额。λ = 1 不改变形状,λ = 0 就是对数。找出让偏度为 0 的 λ。

📌
Skew 0 Is Not the Same as NormalSkew 0, Normal के बराबर नहीं偏度为 0 不等于正态

Skewness near 0 means the two sides balance; the data can still have heavy tails or two peaks — check a histogram or Q-Q plot. If you transform the target (for example predicting log(price)), remember to transform predictions back with np.expm1, and note that the back-transformed mean is biased low. Also, QuantileTransformer forces any shape into a normal curve, but it is non-linear and can distort distances between values.

0 के पास skewness का मतलब है दोनों तरफ़ संतुलन; data में फिर भी भारी tails या दो चोटियाँ हो सकती हैं — histogram या Q-Q plot से जाँचें। अगर आप target बदलते हैं (जैसे log(price) का अनुमान), तो predictions को np.expm1 से वापस बदलना याद रखें, और ध्यान दें कि वापस बदला mean नीचे की ओर biased होता है। साथ ही, QuantileTransformer किसी भी आकार को normal curve में ढाल देता है, पर यह non-linear है और मानों के बीच की दूरियाँ बिगाड़ सकता है।

偏度接近 0 只说明两侧平衡;数据仍可能有厚尾或双峰 —— 要用直方图或 Q-Q 图检查。如果你变换的是目标变量(例如预测 log(price)),记得用 np.expm1 把预测值转换回来,并注意转换回来的均值会偏低。另外,QuantileTransformer 能把任何形状强行变成正态曲线,但它是非线性的,可能扭曲数值之间的距离。


SectionSection第 07 节

Encoding Categories 🏷️Categories को Encode करना 🏷️类别编码 🏷️

Most models need numbers, so text columns must be encoded. The right method depends on whether the categories have a real order, how many there are, and whether you may use the target.

ज़्यादातर models को संख्याएँ चाहिए, इसलिए text columns को encode करना पड़ता है। सही तरीका इस पर निर्भर है कि categories में असली क्रम है या नहीं, कितनी हैं, और क्या आप target इस्तेमाल कर सकते हैं।

大多数模型需要数字,所以文本列必须编码。正确的方法取决于类别是否有真实顺序、类别有多少,以及能否使用目标变量。

# 6) Encoding categories
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder, TargetEncoder
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import KFold
tr, te = train_test_split(df, test_size=0.25, random_state=0, stratify=df["default"])

ohe = OneHotEncoder(handle_unknown="ignore", sparse_output=False).fit(tr[["city"]])
print("one-hot columns:", list(ohe.get_feature_names_out()))
oe = OrdinalEncoder(categories=[["Low", "Medium", "High", "Very High"]]).fit(tr[["income_bracket"]])
print("ordinal:", dict(zip(oe.categories_[0], range(4))))

# branch_id has 400 values and NO real link to default. Naive target encoding still "finds" one:
means = tr.groupby("branch_id")["default"].mean()
naive_tr, naive_te = tr["branch_id"].map(means), te["branch_id"].map(means).fillna(tr["default"].mean())
print(f"\nnaive target encoding  AUC train {roc_auc_score(tr['default'], naive_tr):.3f}   test {roc_auc_score(te['default'], naive_te):.3f}")
tenc = TargetEncoder(cv=KFold(5, shuffle=True, random_state=0))   # cross-fitting inside fit_transform
cf_tr = tenc.fit_transform(tr[["branch_id"]].astype(str), tr["default"]).ravel()
cf_te = tenc.transform(te[["branch_id"]].astype(str)).ravel()
print(f"TargetEncoder (cv)     AUC train {roc_auc_score(tr['default'], cf_tr):.3f}   test {roc_auc_score(te['default'], cf_te):.3f}")
freq = tr["branch_id"].map(tr["branch_id"].value_counts(normalize=True))
print("frequency encoding example:", freq.head(3).round(4).tolist())
OUTPUT
one-hot columns: ['city_Bengaluru', 'city_Chennai', 'city_Delhi', 'city_Hyderabad', 'city_Jaipur', 'city_Kolkata', 'city_Mumbai', 'city_Pune'] ordinal: {'Low': 0, 'Medium': 1, 'High': 2, 'Very High': 3} naive target encoding AUC train 0.738 test 0.512 TargetEncoder (cv) AUC train 0.521 test 0.512 frequency encoding example: [0.0033, 0.0023, 0.0017]
LIVELIVE互动Encoding BuilderEncoding Builder编码构建器

Six rows. Pick an encoding and watch the new columns appear.

छह rows। एक encoding चुनें और नए columns को आते देखें।

六行数据。选择一种编码,看新列逐一出现。

Methodतरीका方法Use forकिसके लिए适用于New columnsनए columns新列数Main riskमुख्य जोखिम主要风险
One-hotOne-hot独热unordered, few values (city, colour)बिना क्रम, कम मान (city, रंग)无序、取值少(城市、颜色)one per categoryहर category का एक每个类别一列Very wide with many categoriesकई categories में बहुत चौड़ा类别多时非常宽
OrdinalOrdinal有序real order (Low < Medium < High)असली क्रम (Low < Medium < High)真实顺序(Low < Medium < High)1Low — if you give the order yourselfकम — अगर क्रम आप ख़ुद दें低 —— 前提是由你指定顺序
Label (LabelEncoder)Label (LabelEncoder)标签(LabelEncoder)the target y onlyसिर्फ़ target y只用于目标 y1Alphabetical false order on featuresFeatures पर वर्णमाला का नक़ली क्रम在特征上产生按字母的虚假顺序
TargetTarget目标many values (branch, postcode)कई मान (branch, postcode)取值很多(分行、邮编)1Leakage unless cross-fittedCross-fit न करें तो leakage不交叉拟合就会泄漏
FrequencyFrequency频率many values, no target allowedकई मान, target की इजाज़त नहीं取值很多、不能用目标1Different categories can collideअलग categories टकरा सकती हैं不同类别可能得到相同编码
LIVELIVE互动Target-Encoding LeakageTarget-Encoding Leakage目标编码泄漏

2,000 rows with a purely random target. Increase the number of categories and compare naive and cross-fitted target encoding.

पूरी तरह random target वाली 2,000 rows। Categories की संख्या बढ़ाएँ और naive व cross-fitted target encoding की तुलना करें।

2,000 行数据,目标完全随机。增加类别数,比较朴素目标编码和交叉拟合目标编码。

🧩
Three More Encoding TrapsEncoding के तीन और जाल另外三个编码陷阱

(1) pd.get_dummies on train and test separately can give different columns (a city missing from test). OneHotEncoder(handle_unknown="ignore") fitted on train avoids this. (2) For plain linear regression without regularisation, all one-hot columns plus an intercept are perfectly collinear — use drop="first" there. Regularised models and trees do not need it. (3) ">15 categories → avoid one-hot" is only a rule of thumb; trees and linear models with regularisation handle a few dozen columns well.

(1) Train और test पर अलग-अलग pd.get_dummies अलग columns दे सकता है (test में कोई city ग़ायब)। Train पर fit किया OneHotEncoder(handle_unknown="ignore") इससे बचाता है। (2) बिना regularisation वाले सादे linear regression में सभी one-hot columns और intercept पूरी तरह collinear होते हैं — वहाँ drop="first" इस्तेमाल करें। Regularised models और trees को इसकी ज़रूरत नहीं। (3) ">15 categories → one-hot से बचें" बस एक rule of thumb है; trees और regularisation वाले linear models कुछ दर्जन columns अच्छे से संभालते हैं।

(1) 在训练集和测试集上分别使用 pd.get_dummies,可能得到不同的列(测试集缺少某个城市)。在训练集上拟合的 OneHotEncoder(handle_unknown="ignore") 能避免这个问题。(2) 对于没有正则化的普通线性回归,全部独热列加上截距项会完全共线 —— 这时用 drop="first"。正则化模型和树模型不需要。(3) “超过 15 个类别就别用独热”只是经验法则;树模型和带正则化的线性模型能很好地处理几十列。


SectionSection第 08 节

Feature Engineering — Creating New Signals 💡Feature Engineering — नए संकेत बनाना 💡特征工程 —— 创造新信号 💡

Sometimes the useful signal is a combination of columns. In lending, what matters is not the loan or the salary alone but the loan-to-salary ratio. A linear model cannot build that ratio by itself, so we give it one.

कभी-कभी काम का संकेत columns का मेल होता है। Lending में अकेला loan या अकेली salary नहीं, बल्कि loan-to-salary ratio मायने रखता है। Linear model यह ratio ख़ुद नहीं बना सकता, इसलिए हम उसे देते हैं।

有时有用的信号是多个列的组合。在放贷中,重要的不是贷款额或薪资本身,而是贷款与薪资之比。线性模型自己构造不出这个比值,所以我们替它构造。

# 7) Feature engineering: a ratio can matter more than its parts
from sklearn.model_selection import cross_val_score
d = df.copy()
d["loan_to_salary"] = d["loan_amount"] / d["salary"]
d["log_loan_to_salary"] = np.log(d["loan_to_salary"])
snapshot = pd.Timestamp("2024-12-31")                     # fixed date, NOT today(): reproducible
d["days_since_reg"] = (snapshot - d["registration_date"]).dt.days
d["days_reg_to_app"] = (d["application_date"] - d["registration_date"]).dt.days
d["app_month"] = d["application_date"].dt.month
d["age_group"] = pd.cut(d["age"], bins=[20, 30, 40, 50, 66], labels=["21-30", "31-40", "41-50", "51-65"])

model = make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000))
for cols in [num, num + ["log_loan_to_salary"]]:
    auc = cross_val_score(model, d[cols], d["default"], cv=5, scoring="roc_auc").mean()
    print(f"{len(cols)} features {'+ log(loan/salary)' if len(cols) > len(num) else '(raw only)'}".ljust(30) + f"CV AUC {auc:.3f}")
from sklearn.preprocessing import PolynomialFeatures
pf = PolynomialFeatures(degree=2, include_bias=False).fit(d[["age", "dependents"]])
print("polynomial names:", list(pf.get_feature_names_out()))
OUTPUT
6 features (raw only) CV AUC 0.800 7 features + log(loan/salary) CV AUC 0.820 polynomial names: ['age', 'dependents', 'age^2', 'age dependents', 'dependents^2']
📆
Do Not Use today() in FeaturesFeatures में today() इस्तेमाल न करें不要在特征中使用 today()

pd.Timestamp.today() - registration_date gives a different value every day you run it, so results cannot be reproduced, and the model is trained on "age as of today" but used on "age as of prediction day". Use a fixed snapshot date, or the event's own date (here: the application date). Also make sure a feature only uses information available at prediction time.

pd.Timestamp.today() - registration_date हर दिन चलाने पर अलग मान देता है, इसलिए नतीजे दोहराए नहीं जा सकते, और model "आज तक की उम्र" पर train होता है पर "prediction के दिन तक की उम्र" पर इस्तेमाल होता है। एक तय snapshot date, या घटना की अपनी date (यहाँ: application date) इस्तेमाल करें। यह भी पक्का करें कि feature सिर्फ़ वही जानकारी इस्तेमाल करे जो prediction के समय उपलब्ध हो।

pd.Timestamp.today() - registration_date 每天运行都会得到不同的值,结果无法复现;而且模型用“截至今天的时长”训练,却用“截至预测当天的时长”来预测。请使用固定的快照日期,或事件本身的日期(这里是申请日期)。还要确保特征只使用预测时刻能拿到的信息。


SectionSection第 09 节

One Pipeline for Everything 🔗हर चीज़ के लिए एक Pipeline 🔗一条管道搞定一切 🔗

ColumnTransformer sends each group of columns through its own steps; Pipeline chains those steps with the model. Calling fit learns every median, mean, std and category from the training rows only, and predict applies exactly the same steps to new data.

ColumnTransformer columns के हर समूह को उसके अपने क़दमों से गुज़ारता है; Pipeline उन क़दमों को model से जोड़ता है। fit बुलाने पर हर median, mean, std और category सिर्फ़ training rows से सीखी जाती है, और predict नए data पर ठीक वही क़दम लगाता है।

ColumnTransformer 让每组列各自经过对应的步骤;Pipeline 把这些步骤和模型串起来。调用 fit 时,所有中位数、均值、标准差和类别都只从训练行学习;predict 会对新数据执行完全相同的步骤。

# 8) Everything in one Pipeline: no leakage, one object to save
import joblib
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
d["log_loan_amount"], d["log_salary"] = np.log(d["loan_amount"]), np.log(d["salary"])
num_cols = ["age", "years_employed", "dependents", "credit_score", "log_salary", "log_loan_amount", "log_loan_to_salary"]
pre = ColumnTransformer([
    ("num", Pipeline([("impute", SimpleImputer(strategy="median")), ("scale", StandardScaler())]), num_cols),
    ("nom", Pipeline([("impute", SimpleImputer(strategy="most_frequent")),
                      ("ohe", OneHotEncoder(handle_unknown="ignore"))]), ["city"]),
    ("ord", OrdinalEncoder(categories=[["Low", "Medium", "High", "Very High"]]), ["income_bracket"]),
])
pipe = Pipeline([("prep", pre), ("model", LogisticRegression(max_iter=1000))])
cols = num_cols + ["city", "income_bracket"]
tr, te = train_test_split(d, test_size=0.25, random_state=0, stratify=d["default"])
pipe.fit(tr[cols], tr["default"])
print(f"test AUC: {roc_auc_score(te['default'], pipe.predict_proba(te[cols])[:, 1]):.3f}")
print("features after ColumnTransformer:", pipe["prep"].get_feature_names_out().size)
joblib.dump(pipe, "loan_pipeline.joblib")
same = (joblib.load("loan_pipeline.joblib").predict_proba(te[cols]) == pipe.predict_proba(te[cols])).all()
print("reloaded pipeline gives identical predictions:", bool(same))
OUTPUT
test AUC: 0.827 features after ColumnTransformer: 16 reloaded pipeline gives identical predictions: True
🔗 PICTURE 5 — THE PIPELINE ABOVE, DRAWN OUTतस्वीर 5 — ऊपर की PIPELINE, चित्र में图 5 — 上面那条管道的结构图
Pipeline and ColumnTransformer architecture X_trainraw columns ColumnTransformer 7 numeric SimpleImputer(median) StandardScaler city SimpleImputer(mode) OneHotEncoder income_bracket OrdinalEncoder 16columnsLogisticRegressionpredict_proba fit() learns medians, means, stds andfit() सिर्फ़ X_train से medians, means,fit() 只从 X_train 学习中位数、均值、 categories from X_train onlystds और categories सीखता है标准差和类别 save the WHOLE pipeline with joblibपूरी pipeline joblib से save करें用 joblib 保存整个管道

Cross-validation on the whole pipeline (cross_val_score(pipe, X, y)) refits the transformers inside every fold — the honest way to estimate performance.

पूरी pipeline पर cross-validation (cross_val_score(pipe, X, y)) हर fold के अंदर transformers को दोबारा fit करता है — performance आँकने का ईमानदार तरीका।

对整条管道做交叉验证(cross_val_score(pipe, X, y))会在每一折内部重新拟合变换器 —— 这才是诚实的性能评估方式。

💾
Saving and LoadingSave और Load करना保存与加载

joblib.dump(pipe, "loan_pipeline.joblib") stores the fitted scalers, encoders and model together. Load it with the same scikit-learn version; pickled models are not guaranteed to work across versions. Only load files you trust — loading a pickle can run code.

joblib.dump(pipe, "loan_pipeline.joblib") fit किए गए scalers, encoders और model को साथ में रखता है। इसे उसी scikit-learn version के साथ load करें; pickled models के versions के बीच चलने की गारंटी नहीं। सिर्फ़ भरोसेमंद files load करें — pickle load करने से code चल सकता है।

joblib.dump(pipe, "loan_pipeline.joblib") 会把拟合好的缩放器、编码器和模型一起保存。请用相同版本的 scikit-learn 加载;pickle 序列化的模型不保证跨版本可用。只加载你信任的文件 —— 加载 pickle 可能会执行代码。


SectionSection第 10 节

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

Run this first. Default risk is built from credit score, the loan-to-salary ratio, dependents and years employed (plus a little from city). branch_id has 400 values and no effect at all — ideal for showing leakage.

इसे पहले चलाएँ। Default का जोखिम credit score, loan-to-salary ratio, dependents और years employed (और city से थोड़ा) से बना है। branch_id के 400 मान हैं और कोई असर नहीं — leakage दिखाने के लिए आदर्श।

请先运行它。违约风险由信用分、贷款与薪资之比、家属人数和工作年限(外加少量城市因素)决定。branch_id 有 400 个取值,但完全没有影响 —— 非常适合演示泄漏。

# 0) A practice loan dataset — the default risk is planted, so we can check every step
import numpy as np, pandas as pd
rng = np.random.default_rng(5)
n = 4000
age = rng.integers(21, 66, n)
years_employed = np.clip(np.round((age - 21) * rng.uniform(0.2, 0.9, n)), 0, 40).astype(int)
dependents = rng.poisson(1.4, n).clip(0, 8)
salary = np.round(np.exp(rng.normal(13.4, 0.55, n) + 0.01 * years_employed), -3)          # ₹ per year
credit_score = np.clip(np.round(rng.normal(690, 70, n) + 1.2 * years_employed), 300, 900).astype(int)
loan_amount = np.round(salary * rng.uniform(0.2, 2.5, n), -3)
city = rng.choice(["Mumbai", "Delhi", "Bengaluru", "Chennai", "Hyderabad", "Pune", "Kolkata", "Jaipur"], n)
branch_id = rng.integers(1, 401, n)                                                       # 400 branches, no real effect
income_bracket = pd.cut(salary, [0, 4e5, 8e5, 15e5, np.inf], labels=["Low", "Medium", "High", "Very High"]).astype(str)
reg = pd.Timestamp("2018-01-01") + pd.to_timedelta(rng.integers(0, 2000, n), unit="D")
app = reg + pd.to_timedelta(rng.integers(30, 900, n), unit="D")
# risk: low credit score, high loan-to-salary ratio, many dependents, few years employed
z = (-0.012 * (credit_score - 690) + 1.6 * np.log(loan_amount / salary) + 0.35 * dependents
     - 0.06 * years_employed + 0.25 * (city == "Kolkata") - 1.4 + rng.normal(0, 0.5, n))
default = (rng.random(n) < 1 / (1 + np.exp(-z))).astype(int)
pd.DataFrame({"age": age, "years_employed": years_employed, "dependents": dependents, "salary": salary,
              "credit_score": credit_score, "loan_amount": loan_amount, "city": city, "branch_id": branch_id,
              "income_bracket": income_bracket, "registration_date": reg, "application_date": app,
              "default": default}).to_csv("loans.csv", index=False)
print(f"loans.csv: {n:,} rows, default rate {default.mean():.1%}")
OUTPUT
loans.csv: 4,000 rows, default rate 26.4%

SectionSection第 11 节

Golden Rules of Data Transformation 🏆Data Transformation के सुनहरे नियम 🏆数据变换黄金法则 🏆

🎯 9 Rules Every Data Scientist Should Follow9 नियम जो हर Data Scientist को मानने चाहिए每位数据科学家都应遵守的 9 条法则
1
Split first, then fit every scaler and encoder on the training data only — or let a Pipeline do it for you.
पहले split करें, फिर हर scaler और encoder सिर्फ़ training data पर fit करें — या यह काम Pipeline पर छोड़ दें।
先划分数据,再只在训练数据上拟合每个缩放器和编码器 —— 或者交给 Pipeline 来做。
2
Scale for distance-based, gradient-based and regularised models; skip it for tree models.
दूरी-आधारित, gradient-आधारित और regularised models के लिए scale करें; tree models के लिए छोड़ दें।
对基于距离、基于梯度和正则化的模型做缩放;树模型可以跳过。
3
Pick the scaler for your data: Standard as a default, MinMax for bounded inputs, Robust when real extremes stay, MaxAbs for sparse data.
अपने data के हिसाब से scaler चुनें: default के रूप में Standard, सीमित inputs के लिए MinMax, असली अति मान रहें तो Robust, sparse data के लिए MaxAbs।
按数据选择缩放器:默认用 Standard,有界输入用 MinMax,保留真实极端值用 Robust,稀疏数据用 MaxAbs。
4
Scaling never fixes skew. Check .skew() and a histogram; for a long tail try log or a power transform first.
Scaling कभी skew ठीक नहीं करता। .skew() और histogram जाँचें; लंबी tail हो तो पहले log या power transform आज़माएँ।
缩放永远修不好偏态。检查 .skew() 和直方图;有长尾时先试对数或幂变换。
5
Use OrdinalEncoder only with an order you write yourself; never LabelEncoder on features.
OrdinalEncoder सिर्फ़ उसी क्रम के साथ इस्तेमाल करें जो आप ख़ुद लिखें; features पर LabelEncoder कभी नहीं।
只在你亲自写明顺序时使用 OrdinalEncoder;绝不对特征使用 LabelEncoder。
6
Target-encode only with cross-fitting (TargetEncoder). A naive version can make pure noise look predictive.
Target-encode सिर्फ़ cross-fitting (TargetEncoder) के साथ करें। Naive version शुद्ध शोर को भी काम का दिखा सकता है।
目标编码一定要交叉拟合(TargetEncoder)。朴素做法能让纯噪声看起来很有预测力。
7
Engineer features from domain sense (ratios, differences, date parts), using only information available at prediction time — never today().
Domain समझ से features बनाएँ (ratios, अंतर, date के हिस्से), सिर्फ़ prediction के समय उपलब्ध जानकारी से — today() कभी नहीं।
依据领域常识构造特征(比值、差值、日期拆分),只用预测时能拿到的信息 —— 绝不用 today()。
8
Save the whole fitted pipeline, not just the model, and load it with the same library versions.
सिर्फ़ model नहीं, पूरी fit की गई pipeline save करें, और उसे उन्हीं library versions के साथ load करें।
保存整条拟合好的管道,而不仅是模型,并用相同版本的库来加载。
9
Clean before you transform, and check the result (describe(), histograms) after every step.
Transform से पहले साफ़ करें, और हर क़दम के बाद नतीजा जाँचें (describe(), histograms)।
先清洗再变换,并在每一步之后检查结果(describe()、直方图)。
🧮
Key Takeawayमुख्य सीख核心要点

Transformation decides what the model can see. On our loan data, the same KNN went from worse-than-guessing (73.1%) to 76.8% with one scaler; one ratio feature lifted a linear model's AUC from 0.800 to 0.820; and a careless target encoding made a useless column look predictive. Choose each transform for a reason, fit it on training data only, and keep it all in one pipeline.

Transformation तय करता है कि model क्या देख सकता है। हमारे loan data पर वही KNN एक scaler से अंदाज़े से भी ख़राब (73.1%) से 76.8% पर पहुँचा; एक ratio feature ने linear model का AUC 0.800 से 0.820 किया; और लापरवाह target encoding ने एक बेकार column को काम का दिखाया। हर transform किसी कारण से चुनें, सिर्फ़ training data पर fit करें, और सब एक pipeline में रखें।

变换决定了模型能看到什么。在我们的贷款数据上,同一个 KNN 只换了一个缩放器,就从不如瞎猜(73.1%)提升到 76.8%;一个比值特征让线性模型的 AUC 从 0.800 升到 0.820;而一次草率的目标编码让一个无用的列看起来很有预测力。每种变换都要有理由,只在训练数据上拟合,并全部放进一条管道。