EDA and Feature Engineeringpandas 3.0 · scikit-learn 1.9 · seaborn 0.13 · Python 3.12+
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
72 small wins to finish your pathNext lesson →

Robust scaling

RobustScaler subtracts the median and divides by the interquartile range, so the middle half of the data sets the scale and extreme values do not.

Last updated: 12 Oct, 2026 · pandas 3.0 · scikit-learn 1.9 · scipy 1.18

Min max scaling left half the Titanic fares inside the bottom three percent of the scaled range, because one ticket decided the maximum. The third scaler answers that by throwing away the two ends and measuring with the quartiles.

Scaling to the median and the quantiles · from the Live Feature Engineering sessions · 52:21 to 55:28
The denominator is the interquartile range, the 75th percentile minus the 25th

Percentiles and quartiles

A percentile is the value a given share of the data falls below. In the sorted list 0, 1, 2, 3, 4, 5, 6, 7, 9, 10 the value 9 is the 90th percentile, because 90 percent of the group is below it, and 1 is the 10th percentile. Quartiles are the three percentiles that cut the data into four equal parts: the 25th, the 50th, which is the median, and the 75th.

  • Q1 is the 25th percentile. A quarter of the rows sit below it.
  • Q2 is the median. Half sit below it.
  • Q3 is the 75th percentile. Three quarters sit below it.
  • IQR is Q3 minus Q1, the width of the middle half of the data. For Titanic Fare that is 31.0 minus 7.9104, which is 23.0896.

Syntax: the same three calls, with quantile_range if a different pair of percentiles suits the column better.

python
from sklearn.preprocessing import RobustScaler

scaler = RobustScaler()              # quantile_range=(25.0, 75.0) by default
out = scaler.fit_transform(df)

Scaling the Titanic columns by median and IQR

python
import pandas as pd

URL = "https://raw.githubusercontent.com/krishnaik06/Feature-Engineering-Live-sessions/master/titanic.csv"
df = pd.read_csv(URL, usecols=['Survived', 'Pclass', 'Age', 'Fare'])
df['Age'] = df['Age'].fillna(df['Age'].median())   # 177 missing ages, filled with 28.0
python
from sklearn.preprocessing import RobustScaler

scaler = RobustScaler().set_output(transform='pandas')
scaled = scaler.fit_transform(df)
ExampleThe four Titanic columns scaled by median and IQR
from sklearn.preprocessing import RobustScaler

scaler = RobustScaler().set_output(transform='pandas')
scaled = scaler.fit_transform(df)

print(scaled.head(3).round(6).to_string())
print("\nlearned center_ (medians):", scaler.center_.tolist())
print("learned scale_ (IQRs)    :", scaler.scale_.tolist())

q1, q3 = df["Fare"].quantile(0.25), df["Fare"].quantile(0.75)
print("\nFare Q1, Q3, IQR:", q1, q3, round(q3 - q1, 4))
print("first fare by hand:", round((df.loc[0, "Fare"] - df["Fare"].median()) / (q3 - q1), 6))

Reading the centred columns

  • center_ holds the medians and scale_ holds the interquartile ranges: 28.0 and 13.0 for Age, 14.4542 and 23.0896 for Fare.
  • A value of 0 means the median. The first passenger's Pclass is 0.0 because 3 is the median class, not because the value is zero.
  • One unit means one interquartile range. The second passenger's fare of 71.28 comes out at 2.46, which reads as two and a half middle halves above the typical fare.
  • The range is not bounded. The 512.33 fare still transforms to 21.56, so the extreme value is still in the data. It no longer decides what the other 890 values look like.

The same column through all three scalers

Fare is the column where the three disagree, so it is the one to plot. The first panel is the raw column and the other three are the same 891 fares after each transform. Every axis is windowed on the busy part of the range, because the 512.33 ticket would otherwise flatten all four charts.

ExampleTitanic Fare under each of the three scalers
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

# the window is the slice of each axis worth looking at, not a change to the data
panels = [(None, 'Fare, raw', (0, 120)),
          (StandardScaler(), 'StandardScaler', (-1, 2)),
          (MinMaxScaler(), 'MinMaxScaler', (0, 0.2)),
          (RobustScaler(), 'RobustScaler', (-1, 3))]
fig, ax = plt.subplots(1, 4, figsize=(13, 3.1))
for a, (scaler, name, window) in zip(ax, panels):
    fare = df['Fare'] if scaler is None else pd.Series(scaler.fit_transform(df[['Fare']])[:, 0])
    a.hist(fare, bins=40, range=window, color='#d8733a')
    a.set_title(f"{name}\nmedian at {fare.median():.3f}", fontsize=10)
    a.set_xlabel('Fare value')
    print(f"{name:15s} median {fare.median():8.4f}   max {fare.max():8.4f}")
fig.tight_layout()
Four histograms of the Titanic Fare column: the raw column with its median at 14.45, then the same values under StandardScaler with the median at minus 0.357, under MinMaxScaler crushed against the left edge at 0.028, and under RobustScaler at exactly zero with the middle of the data spread either side.
  • The raw column has its median at 14.4542 and a maximum of 512.3292. That is the shape all three scalers start from.
  • StandardScaler puts the median at −0.357, because the mean fare is pulled above the median by the expensive tickets.
  • MinMaxScaler puts it at 0.0282, pressed against the left edge.
  • RobustScaler puts it at exactly 0, by construction, with the middle half of the fares spread from about −0.28 to 0.72.

Choosing between the three scalers

StandardScalerMinMaxScalerRobustScaler
Centrethe meanthe minimumthe median
Spreadthe standard deviationthe full rangethe interquartile range
Moved by one extreme valuea littlecompletelynot at all
Output rangeopen0 to 1 on the fitted dataopen
Good whenthe column is roughly symmetrica bounded input is neededthe column is skewed or has extreme values

Where robust scaling is used

  • Money columns. Prices, fares, salaries and claim amounts nearly always have a long right tail that would otherwise set the scale.
  • Sensor and count data, where a single faulty reading can be orders of magnitude away from everything else.
  • Any column a box plot shows with points outside the whiskers, which The IQR fence covers in full.
Watch out. This scaler does not remove extreme values and does not make a column normal. The 512.33 fare is still there at 21.56 after the transform, and the distribution is still skewed. Changing the shape of a column is a different job, and Log, reciprocal and square root transformations is where it happens.
Try it yourself
  • Pass quantile_range=(10.0, 90.0). The denominator grows, so every scaled value shrinks towards zero.
  • Pass with_centering=False and look at where the median lands.
  • Scale Age instead of Fare with all three scalers. The three pictures are nearly the same, because Age has no extreme tail.

This is what real progress feels like.