Sunday, August 30, 2026

Random Forest vs Gradient Boosting in Python

Random Forest and Gradient Boosting are two of the strongest starting points for structured, tabular machine-learning problems. Both combine decision trees, but they learn in very different ways. That difference affects accuracy, training speed, resistance to overfitting, and how much tuning each model needs.

Short answer: start with Random Forest when you want a reliable baseline that is easy to train and parallelize. Try gradient boosting when predictive accuracy is the priority and you can spend more time tuning. For datasets with tens of thousands of rows, scikit-learn's histogram-based gradient boosting is usually the better boosting implementation to test.

Random Forest vs Gradient Boosting at a glance

QuestionRandom ForestGradient Boosting
How are trees trained?Mostly independently on bootstrapped samples, with random feature subsets.Sequentially; each new tree corrects errors made by the current ensemble.
Main effectReduces variance through averaging.Reduces bias by iteratively improving the model.
Tuning difficultyUsually forgiving.More sensitive to learning rate, tree size, and number of iterations.
Parallel trainingEasy because trees are independent.Limited because later trees depend on earlier trees.
Overfitting riskGenerally low, although very deep trees can still fit noise.Higher when trees are too complex or too many iterations are used.
Best first useFast, dependable baseline.High-quality tabular prediction after careful validation.

How Random Forest works

A Random Forest trains many decision trees on different bootstrap samples of the training data. At each split, a tree evaluates only a random subset of features. Classification predictions are combined by voting; regression predictions are averaged.

The randomness makes individual trees less correlated. Averaging those trees stabilizes the final prediction and usually generalizes better than one unconstrained decision tree. Important parameters include n_estimators, max_depth, min_samples_leaf, max_features, and max_samples.

How Gradient Boosting works

Gradient Boosting builds an additive model in stages. The first tree provides an initial prediction. Each later tree is trained to improve the ensemble according to the chosen loss function. A learning rate controls how strongly each new tree changes the prediction.

Small trees are often used as weak learners. The combination of learning_rate and the number of iterations is crucial: a smaller learning rate usually needs more trees, while an aggressive learning rate can overfit or produce unstable results.

Scikit-learn provides both classic GradientBoostingClassifier and histogram-based HistGradientBoostingClassifier. The official guide notes that the histogram version can be much faster on datasets larger than roughly 10,000 samples and supports missing values directly.

Complete Python comparison

The example below creates a repeatable binary-classification dataset, evaluates both models with the same stratified cross-validation folds, and reports ROC AUC, accuracy, and training time.

For a reusable preprocessing and validation workflow, see the scikit-learn Pipeline tutorial. After selecting a model, analyze its classification errors with the Confusion Matrix in Python guide.

from time import perf_counter

import pandas as pd
from sklearn.datasets import make_classification
from sklearn.ensemble import (
    HistGradientBoostingClassifier,
    RandomForestClassifier,
)
from sklearn.model_selection import StratifiedKFold, cross_validate

X, y = make_classification(
    n_samples=12_000,
    n_features=30,
    n_informative=12,
    n_redundant=6,
    weights=[0.65, 0.35],
    class_sep=1.0,
    random_state=42,
)

models = {
    "Random Forest": RandomForestClassifier(
        n_estimators=300,
        min_samples_leaf=2,
        n_jobs=-1,
        random_state=42,
    ),
    "Histogram Gradient Boosting": HistGradientBoostingClassifier(
        learning_rate=0.08,
        max_iter=250,
        max_leaf_nodes=31,
        l2_regularization=1.0,
        random_state=42,
    ),
}

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
rows = []

for name, model in models.items():
    start = perf_counter()
    scores = cross_validate(
        model,
        X,
        y,
        cv=cv,
        scoring={"auc": "roc_auc", "accuracy": "accuracy"},
        n_jobs=-1,
    )
    rows.append({
        "model": name,
        "mean_auc": scores["test_auc"].mean(),
        "mean_accuracy": scores["test_accuracy"].mean(),
        "fit_seconds": perf_counter() - start,
    })

results = pd.DataFrame(rows).sort_values("mean_auc", ascending=False)
print(results.round(4))

Your exact scores will depend on hardware, library version, and data. The important point is to compare models on identical folds and choose the metric that matches the business problem. Accuracy can be misleading for imbalanced targets, so ROC AUC, precision-recall AUC, recall, or a cost-based metric may be more useful.

How to tune both models fairly

Do not tune on the held-out test set. Use cross-validation on the training data, select the best configuration, and evaluate the chosen model once on untouched test data. RandomizedSearchCV is often more efficient than testing every possible combination.

from scipy.stats import loguniform, randint
from sklearn.model_selection import RandomizedSearchCV

rf_search = RandomizedSearchCV(
    estimator=RandomForestClassifier(
        n_jobs=-1,
        random_state=42,
    ),
    param_distributions={
        "n_estimators": randint(200, 800),
        "max_depth": [None, 8, 12, 18, 25],
        "min_samples_leaf": randint(1, 12),
        "max_features": ["sqrt", "log2", 0.5],
        "max_samples": [None, 0.7, 0.9],
    },
    n_iter=30,
    scoring="roc_auc",
    cv=cv,
    n_jobs=-1,
    random_state=42,
)

hgb_search = RandomizedSearchCV(
    estimator=HistGradientBoostingClassifier(random_state=42),
    param_distributions={
        "learning_rate": loguniform(0.02, 0.2),
        "max_iter": randint(100, 500),
        "max_leaf_nodes": randint(15, 64),
        "min_samples_leaf": randint(10, 60),
        "l2_regularization": loguniform(1e-3, 10),
    },
    n_iter=30,
    scoring="roc_auc",
    cv=cv,
    n_jobs=-1,
    random_state=42,
)

rf_search.fit(X, y)
hgb_search.fit(X, y)

print("RF:", rf_search.best_score_, rf_search.best_params_)
print("HGB:", hgb_search.best_score_, hgb_search.best_params_)

When Random Forest is the better choice

  • You need a strong baseline with little preprocessing.
  • You want stable performance without delicate learning-rate tuning.
  • You have many CPU cores and want tree training to run in parallel.
  • You need out-of-bag evaluation or straightforward permutation importance.
  • Your dataset is small or medium-sized and training simplicity matters.

When Gradient Boosting is the better choice

  • You are optimizing predictive performance on tabular data.
  • Subtle interactions and residual patterns remain after a Random Forest baseline.
  • You can use cross-validation and early stopping to control overfitting.
  • You have a medium or large dataset suited to histogram-based training.
  • You need direct support for missing values in HistGradientBoostingClassifier.

Common mistakes to avoid

1. Comparing models on different data splits

Use the same folds and the same scoring metric. Otherwise, the comparison mixes model quality with sampling luck.

2. Tuning before creating a test set

Split off the test set first. Repeatedly checking test performance leaks information and makes the final score optimistic.

3. Using accuracy for an imbalanced target

A model can look accurate simply by predicting the majority class. Select a metric that reflects the cost of false positives and false negatives.

4. Trusting impurity importance blindly

Tree-based impurity importance can favor high-cardinality features. Validate important features with permutation importance and domain knowledge.

5. Ignoring probability calibration

Good ranking performance does not guarantee reliable probabilities. If decisions depend on probability thresholds, inspect calibration curves and consider calibration on separate validation data.

A practical decision workflow

  1. Build a simple baseline and define the business metric.
  2. Train Random Forest with sensible defaults.
  3. Train histogram gradient boosting on the same folds.
  4. Tune only the model that shows meaningful potential.
  5. Check stability across folds, not just the mean score.
  6. Evaluate once on the untouched test set.
  7. Measure prediction latency and model size before deployment.
Next step: if a single tree is still your baseline, read our Decision Tree Hyperparameter Tuning with GridSearchCV guide. For linear regression regularization, see Ridge vs Lasso vs Elastic Net.

Frequently asked questions

Is Gradient Boosting always more accurate than Random Forest?

No. Boosting often performs extremely well on tabular data, but the winner depends on noise, sample size, feature quality, metric, and tuning. Cross-validation is the correct way to decide.

Do these models require feature scaling?

Tree splits are based on thresholds, so standardization is usually unnecessary. Scaling may still matter elsewhere in a pipeline, especially when combining tree models with distance-based or linear components.

Which model handles missing values?

Current scikit-learn documentation describes native missing-value support for Random Forest estimators and histogram gradient boosting. Confirm behavior for the exact estimator and version used in your project.

What should I try first?

Use Random Forest as a dependable baseline, then compare it with histogram gradient boosting using the same cross-validation folds. Choose the simplest model that meets accuracy, speed, and interpretability requirements.

References: scikit-learn ensemble user guide, RandomForestClassifier documentation, and HistGradientBoostingClassifier documentation.

No comments:

Post a Comment