Robust Scaling
Robust scaling centers a feature using its median and scales it using a quantile-based spread. The usual spread is the interquartile range (IQR), the gap between the 75th and 25th percentiles.
The aim is to keep a few extreme observations from setting the coordinate system for everyone else. The observations themselves are retained. Robust scaling is not clipping, outlier removal, or a robust prediction model.
Locate the middle and the middle-half spread
Use the nine sorted training values 0, 1, 2, 3, 4, 5, 6, 7, and 100. The median is 4. Under the linear percentile convention used here, the 25th percentile is 2 and the 75th percentile is 6. Their difference is 4.
Let $m$ be the saved training median and let $r=Q_3-Q_1$ be the saved positive IQR. The default transform is
The value 6 becomes (6 − 4) / 4 = 0.5: half an IQR above the fitted median. The value 100 becomes (100 − 4) / 4 = 24. It remains extreme.
Move the largest value and compare the fitted statistics
In this example, increasing the largest value from 8 to 100 does not move the median or either quartile. Their positions in the sorted sample are unchanged. The mean and standard deviation, however, use the magnitudes of all observations.
Scale: 4.0006 → 0.500
Scale: 30.4046 → -0.270
With a large extreme value, standardization can compress the ordinary values into a small part of the transformed scale. Robust scaling can preserve more separation among those ordinary values, while making the extreme observation large relative to the IQR. Which representation helps the downstream estimator still needs validation.
Quantile definitions are part of the fitted transform
Percentiles in a finite sample sometimes lie between observed values. This note uses linear interpolation at position $p(n-1)$ in a sorted sample of $n$ values, with positions starting at 0 and $p$ between 0 and 1.
For [1, 2, 3, 4], the 25th-percentile position is 0.75, giving 1.75. The 75th percentile is 3.25, so the IQR is 1.5. A textbook rule that takes the median of each half can give different numbers. Match the percentile convention when reproducing a stored scaler.
Other quantile ranges and unit-variance calibration
quantile_range can use endpoints other than 25 and 75. A wider interval can involve more of the tails. In the nine-value example, the 90th percentile interpolates between 7 and the largest value, so it does change when that extreme value moves.
Default robust scaling gives a nonzero training IQR of 1 under the same percentile convention. It does not generally give variance 1. Scikit-learn’s unit_variance=True adjusts the divisor using a normal-distribution reference for the chosen quantiles; it does not force an arbitrary observed distribution to have unit variance.
A zero IQR does not always mean a constant feature
Consider eight zeros and one value of 10. The median, Q1, and Q3 are all zero, even though the feature varies. There is no positive IQR to divide by.
Scikit-learn’s default configuration uses a scale factor of 1 for a zero IQR. In this example, 10 stays 10 after transformation. Inspect zero and very small fitted spreads, especially for discrete or zero-heavy features. A small IQR can amplify tail values substantially.
Python: inspect the saved median and IQR
The reference pool below is the original sample with largest value 100. Future inputs reuse its center and spread. The inverse multiplies by the saved scale and adds the saved median.
Run the transform and zero-IQR example
import numpy as np
from sklearn.preprocessing import RobustScaler
train = np.array([0., 1., 2., 3., 4., 5., 6., 7., 100.]).reshape(-1, 1)
scaler = RobustScaler().fit(train)
new = np.array([[-4.], [6.], [100.]])
print(scaler.center_.tolist())
print(scaler.scale_.tolist())
print(scaler.transform(new).ravel().tolist())
assert np.allclose(scaler.inverse_transform(scaler.transform(new)), new)
# Nonconstant data can still have a zero IQR.
mostly_zero = np.array([0.] * 8 + [10.]).reshape(-1, 1)
zero_iqr = RobustScaler().fit(mostly_zero)
assert zero_iqr.scale_.tolist() == [1.]
assert zero_iqr.transform([[10.]]).tolist() == [[10.]]
[4.0]
[4.0]
[-2.0, 0.5, 24.0]
A robust coordinate system is only one part of the model
Robust scaling does not limit a transformed value to a fixed interval. It does not remove skewness or make least squares robust to unusual observations. A model may still be strongly influenced by the retained extreme inputs or targets.
The fitted median and quantiles can also move when enough observations change, when the sample is small, or when a different population is used. “Robust” describes reduced sensitivity of these statistics to some contamination patterns; it is not immunity to data problems.
Choose the scaler and model together. If clipping, a nonlinear transform, or a robust loss is justified by the task, treat it as a separate operation with a separate effect. The clipping example shows the information that saturation loses.
Fit only the permitted reference data
Learn medians and quantiles inside each cross-validation training fold. Do not fit them on validation data or update them independently of the model at serving time. Preserve the feature order, quantile settings, and fitted statistics.
Centering a sparse matrix can destroy sparsity. Consider with_centering=False or another appropriate transform when sparse storage matters; that configuration does not produce median-zero columns. See scaling sparse data. Missing-data treatment also needs its own explicit policy.
C++: the same interpolation convention
This implementation sorts a finite reference feature, interpolates quantiles, and saves the median and IQR. It implements the default 25–75% range and unit-variance option off. Sorting costs O(n log n); each later lookup uses the two saved numbers.
Compile the median/IQR scaler
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <vector>
// Input is already sorted; p is a fraction from 0 to 1.
double quantile(const std::vector<double>& sorted, double p) {
if (sorted.empty() || !std::isfinite(p) || p < 0 || p > 1)
throw std::invalid_argument("invalid quantile input");
const double position = (sorted.size() - 1) * p;
const auto lo = static_cast<std::size_t>(std::floor(position));
const auto hi = static_cast<std::size_t>(std::ceil(position));
return sorted[lo] + (position - lo) * (sorted[hi] - sorted[lo]);
}
class RobustScaler {
double center = 0, scale = 1;
public:
explicit RobustScaler(std::vector<double> reference) {
if (reference.empty()) throw std::invalid_argument("empty reference");
for (double x : reference)
if (!std::isfinite(x)) throw std::invalid_argument("finite values required");
std::sort(reference.begin(), reference.end());
center = quantile(reference, .5);
const double iqr = quantile(reference, .75) - quantile(reference, .25);
if (iqr > 0) scale = iqr;
}
double transform(double x) const { return (x - center) / scale; }
double inverse(double z) const { return center + z * scale; }
};
int main() {
const RobustScaler encoder({0,1,2,3,4,5,6,7,100});
std::cout << std::fixed << std::setprecision(2);
for (double x : {-4,6,100})
std::cout << x << " -> " << encoder.transform(x) << "\n";
}
-4.00 -> -2.00
6.00 -> 0.50
100.00 -> 24.00References and further reading
- Scikit-learn: RobustScaler — median centering, quantile-range scaling, and configuration options.
- Scikit-learn: compare scalers on outliers — the distinction between robust reference statistics and retaining outlying observations.
- NumPy: percentile — finite-sample interpolation methods used when calculating quantiles.