The choice depends on how much is missing, why it's missing, and what the downstream model needs.
Missing < ~5% of rows, and missingness looks random (MCAR). Or drop a whole column if >50–60% missing and it can't be reasonably imputed.
Numeric: mean (symmetric data), median (skewed / has outliers). Categorical: mode, or a new "Missing" category. Advanced: KNN or model-based imputation when missingness is meaningful and data volume allows it.
Add a binary is_missing indicator column alongside the (imputed) value — useful when the fact that it's missing is itself predictive (e.g. "income not disclosed").
Outlier if value < Q1 − 1.5×IQR or > Q3 + 1.5×IQR, where IQR = Q3 − Q1. Robust to skew and extreme values — the default for non-normal data.
Outlier if |z| = |(x − mean) / std| > ~3. Assumes roughly normal distribution — misleading on skewed data since mean/std are themselves pulled by outliers.
Boxplot view: whiskers extend to 1.5×IQR from the box; points beyond (pink dots) flag as outliers.
| Method | How it works | Use when |
|---|---|---|
| One-Hot | New binary column per category | Low cardinality (few unique values), no ordinal relationship, linear/distance-based models. |
| Label | Each category → an integer | Ordinal categories with real order (e.g. Low/Medium/High), or tree-based models that split on thresholds anyway. |
| Target | Category → mean of target for that category | High-cardinality categoricals (zip code, product ID) — but must use CV/smoothing to avoid leakage. |
import pandas as pd # One-hot encoding df_ohe = pd.get_dummies(df, columns=['city'], drop_first=True) # Label encoding from sklearn.preprocessing import LabelEncoder df['size_encoded'] = LabelEncoder().fit_transform(df['size']) # Target encoding (with smoothing via category_encoders) import category_encoders as ce enc = ce.TargetEncoder(cols=['zip_code'], smoothing=10) df['zip_encoded'] = enc.fit_transform(df['zip_code'], df['target'])
(x − mean) / std → mean 0, std 1. Doesn't bound values to a fixed range. Preferred default for most ML algorithms, especially when data is roughly Gaussian.
from sklearn.preprocessing import StandardScaler X_scaled = StandardScaler().fit_transform(X)
(x − min) / (max − min) → bounded to [0,1]. Sensitive to outliers (a single extreme value compresses everything else). Good when you need a fixed range, e.g. neural net inputs, image pixels.
from sklearn.preprocessing import MinMaxScaler X_scaled = MinMaxScaler().fit_transform(X)
Which models actually need scaling: anything based on distance or gradient step size — k-NN, k-means, SVM, PCA, linear/logistic regression, neural networks. Tree-based models (Decision Tree, Random Forest, XGBoost, LightGBM) split on thresholds per feature independently, so they're scale-invariant and don't need it.
Drop one of a pair of features highly correlated with each other (e.g. |r| > 0.9) — redundant info, hurts interpretability and can destabilize linear models.
Variance Inflation Factor quantifies multicollinearity across multiple features at once (not just pairs). VIF > 5–10 typically flags a feature worth dropping or combining.
From tree-based models (Gini/split importance) or permutation importance — ranks features by how much they actually help predictions, model-driven rather than purely statistical.
| Technique | What it does | Trade-off |
|---|---|---|
| SMOTE | Synthesizes new minority-class samples by interpolating between existing ones | Can create unrealistic points in noisy/high-dimensional data; apply only on training fold. |
| Class weights | Penalize misclassifying the minority class more in the loss function | No new data created — often the simplest, safest first try. |
| Undersampling | Randomly drop majority-class rows to balance classes | Throws away data — risky when the majority class is already small in absolute terms. |
from imblearn.over_sampling import SMOTE from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y) # fit SMOTE ONLY on the training data, never on test data sm = SMOTE(random_state=42) X_train_res, y_train_res = sm.fit_resample(X_train, y_train)
| Model | Encoding | Scaling |
|---|---|---|
| Linear / Logistic Regression | One-hot (low cardinality); target encoding (high cardinality) | Standardization — required |
| k-NN | One-hot | Standardization or Min-Max — required (distance-based) |
| SVM | One-hot | Standardization — required |
| k-Means / PCA | One-hot (rarely used directly on categoricals) | Standardization — required |
| Decision Tree / Random Forest | Label encoding is fine | Not needed — scale-invariant |
| XGBoost / LightGBM | Label or target encoding; native categorical support in LightGBM | Not needed |
| Neural Networks | One-hot or learned embeddings | Min-Max or Standardization — required (helps gradient descent converge) |
| Naive Bayes | Label / one-hot depending on variant | Not needed for categorical NB; standardization for Gaussian NB helps stability |