skip to content

What does class_weight='balanced' compute, and which scikit-learn estimators accept it?

level: seniorimportance: should knowfreq 50%

answer

  1. Inverse frequency, normalized to one
  2. A constructor argument, not universal
  3. Formula divides by class count
  4. Gradient boosting needs sample_weight instead
  5. Probabilities stop being calibrated

basics

~20 s

class_weight='balanced' sets each class's weight to n_samples / (n_classes * count of that class), so rare classes contribute proportionally more loss. LogisticRegression, SVC, LinearSVC, the tree and forest classifiers and HistGradientBoostingClassifier accept it; GradientBoostingClassifier does not.

solid answer

~40 s

`class_weight='balanced'` computes `n_samples / (n_classes * np.bincount(y))` per class — on a 95/5 split that is roughly 0.53 for the majority and 10 for the minority — and multiplies each sample's contribution to the loss by its class weight. You can also pass an explicit dict like `{0: 1, 1: 8}`. It is a constructor argument on `LogisticRegression`, `SGDClassifier`, `RidgeClassifier`, `SVC`, `LinearSVC`, `DecisionTreeClassifier`, `RandomForestClassifier`, `ExtraTreesClassifier` and `HistGradientBoostingClassifier`. `GradientBoostingClassifier` has no such parameter — reweight there by passing `sample_weight` to `fit`. `RandomForestClassifier` additionally offers `'balanced_subsample'`, which recomputes the weights on each bootstrap sample. Two caveats worth stating: reweighting distorts `predict_proba` away from calibrated probabilities, and it changes the loss, not the 0.5 decision threshold that `predict` applies.

code

python · 11 lines
python
import numpy as np
from sklearn.utils.class_weight import compute_class_weight, compute_sample_weight

y = np.array([0] * 950 + [1] * 50)

print(compute_class_weight("balanced", classes=np.unique(y), y=y))
# -> [0.52631579 10.0]

# for estimators without class_weight, build per-row weights and pass to fit
w = compute_sample_weight("balanced", y)
print(w[0], w[-1])

go deeper

for a junior

Know that class_weight='balanced' exists for imbalanced data and that it makes the rare class count more in the loss. Being able to say roughly why is enough here.

for a middle

State the formula — n_samples over n_classes times class count — and name which estimators take the parameter versus which need sample_weight in fit. Mention that accuracy is the wrong metric afterwards.

for a senior

Show the production consequences: distorted predict_proba, threshold tuning as the more controllable alternative, and per-fold weight computation inside cross-validation. Say when you would pass an explicit cost dict instead of 'balanced'.

for a principal

Own the cost model — whether the weight ratio should reflect observed frequency or the business cost of each error type, and who signs off on that ratio when it moves the operating point of a deployed decision system.

## The formula `class_weight='balanced'` is a request for automatically computed weights that are inversely proportional to class frequency. The concrete formula, which `sklearn.utils.class_weight.compute_class_weight` implements and every supporting estimator uses, is: `weight_for_class_k = n_samples / (n_classes * count_of_class_k)` On a binary problem with 950 negatives and 50 positives, that is `1000 / (2 * 950) ≈ 0.526` for the majority class and `1000 / (2 * 50) = 10.0` for the minority. The weights are normalized such that a perfectly balanced dataset produces all-ones — that is why the majority weight is slightly below 1 rather than exactly 1. Each sample's contribution to the training loss is then multiplied by its class weight, so getting a minority sample wrong costs roughly nineteen times what a majority mistake costs. You are not limited to `'balanced'`. Passing an explicit dict — `class_weight={0: 1.0, 1: 8.0}` — sets the ratio yourself, which is what you want when the real cost asymmetry is a business fact rather than a frequency artifact. A false negative on a fraud model might cost 300x a false positive regardless of what the class balance happens to be this quarter. ## Which estimators expose it The parameter is a constructor argument, not universal. Present on: - `LogisticRegression`, `LogisticRegressionCV`, `SGDClassifier`, `RidgeClassifier`, `Perceptron`, `PassiveAggressiveClassifier` - `SVC`, `NuSVC`, `LinearSVC` - `DecisionTreeClassifier`, `ExtraTreeClassifier`, `RandomForestClassifier`, `ExtraTreesClassifier` - `HistGradientBoostingClassifier` Absent on `GradientBoostingClassifier`, on `KNeighborsClassifier`, on `GaussianNB`, and on every regressor (there are no classes to weight). Where it is absent but `fit` accepts `sample_weight`, you build the per-row weights yourself — `compute_sample_weight('balanced', y)` does exactly that — and pass them to `fit`. That is the standard workaround for `GradientBoostingClassifier`. `RandomForestClassifier` and `ExtraTreesClassifier` carry an extra option, `'balanced_subsample'`. Plain `'balanced'` computes the weights once from the full `y`; `'balanced_subsample'` recomputes them on every bootstrap sample, so a tree whose bootstrap happened to draw few minority rows reweights accordingly. On heavily imbalanced data the difference is real but usually second-order compared to whether you weighted at all. ## What weighting does and does not do **Does:** change the loss surface, so the fitted decision boundary moves toward the minority class. In a tree, weights flow into the impurity computation, so splits that isolate minority samples look more attractive. In a linear model, the weighted log-loss simply penalizes minority errors harder. **Does not:** change the decision rule that `predict` applies. For a classifier with `predict_proba`, `predict` still takes the argmax — effectively a 0.5 threshold in the binary case. Weighting shifts the probabilities, which shifts which side of 0.5 samples land on, but it is an indirect and blunt way to move an operating point. If what you actually want is "catch more positives at a known precision cost," thresholding `predict_proba` output directly is more controllable and does not require refitting. **Does, unhelpfully:** break probability calibration. A model fitted with `class_weight='balanced'` outputs probabilities that systematically overstate the minority class, because you told it the minority was more common than it is. If downstream systems consume those numbers as probabilities — expected-value calculations, risk scores, thresholds tuned against a prior — that is a real defect, not a cosmetic one. The usual resolution is to fit unweighted, keep calibrated probabilities, and move the threshold; or to weight and then calibrate on a held-out set. ## The evaluation trap The most common mistake around this parameter is not the parameter. It is turning it on, watching accuracy fall, and concluding it did not help. On a 95/5 split, an always-majority classifier scores 95% accuracy; a usefully weighted model that catches half the minority class will score lower on accuracy while being far more useful. Reweighting must be judged on the metric you actually care about, and the accuracy number will usually get worse by design. ## Interaction with cross-validation When `'balanced'` is used inside cross-validation, the weights are computed per fold from that fold's training labels. That is correct behaviour — the weights are learned from training data only — but it means the effective weighting varies slightly across folds on small or unevenly stratified datasets. It is one more reason to be explicit with a dict when the cost ratio is a business constant rather than a data artifact. ## What a strong answer sounds like Name the formula, name the estimators that have the parameter and at least one that does not, and reach immediately for the two consequences: probabilities are no longer calibrated, and accuracy is now the wrong scoreboard. That combination distinguishes someone who has shipped an imbalanced-data model from someone who has read that `class_weight='balanced'` is a thing you can set.

  • GradientBoostingClassifier has no class_weight. How do you weight classes there?
    Build per-row weights and pass them to `fit`: `compute_sample_weight('balanced', y_train)` from `sklearn.utils.class_weight` produces exactly the array that `class_weight='balanced'` would apply internally, and `GradientBoostingClassifier.fit` accepts `sample_weight`. Inside a `Pipeline` you route it with the step-prefixed key, e.g. `fit(X, y, clf__sample_weight=w)`.
  • What is 'balanced_subsample' on RandomForestClassifier?
    Plain `'balanced'` computes the class weights once from the full training labels. `'balanced_subsample'` recomputes them on each tree's bootstrap sample, so a tree whose bootstrap drew unusually few minority rows reweights to match what it actually sees. It matters most on severe imbalance with small bootstraps; otherwise the two behave similarly.
  • Why might a colleague say weighting 'broke' the model's probabilities?
    Because it did, in a specific sense. Telling the loss that the minority class is ten times more important is equivalent to training under a distorted prior, so `predict_proba` systematically overstates the minority class. Rankings and AUC survive; absolute probabilities do not. Either fit unweighted and move the threshold, or weight and then calibrate on held-out data.
  • Weighting or resampling — how do you choose?
    Weighting is cheaper and leaves the data untouched, so it is the default for estimators that support it. Resampling changes what the model sees and can help methods without weight support, but oversampling duplicates rows into the training set, which risks leakage if done before the split, and undersampling discards real signal. Prefer weights, and resample only when the estimator gives you no other lever.

saying these in an interview costs you the question

  • Assumes every classifier has a class_weight parameter
  • Judges the reweighted model on accuracy
  • Treats weighted predict_proba as calibrated probabilities
  • Thinks class_weight moves the 0.5 decision threshold
  • Confuses class_weight with sample_weight passed to fit

context