Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
🤖 Week 7 – Machine Learning Core  ·  Day 43

Day 43: Random Forest & Gradient Boosting

⏱ 2.5 hours📶 Intermediate → Advanced🎯 100% self-study
Day 43 / 60

✅ Today you will learn

  • Wisdom of the crowd: why ensembles win
  • Random forest = bagging + random features
  • Gradient boosting = trees that fix mistakes
  • Key hyper-parameters and early stopping
  • Permutation importance and model choice

🏢 In the real world

On real business tables – loans, churn, prices, demand – the winning models in industry and on Kaggle are almost always tree ensembles: random forests, XGBoost, LightGBM. Today you learn why, and how to use scikit-learn’s versions that need no extra installation.

Why many models beat one

Python
import numpy as np
rng = np.random.default_rng(0)
true_price = 60.0
one_expert = rng.normal(true_price, 12, 10000)                      # one noisy guess
crowd = rng.normal(true_price, 12, (10000, 50)).mean(axis=1)        # average of 50 independent guesses
print(f"one model  : typical error ±{np.std(one_expert):.1f} lakh")
print(f"50 averaged: typical error ±{np.std(crowd):.1f} lakh")
one model : typical error ±12.0 lakh 50 averaged: typical error ±1.7 lakh

Bagging (Random Forest)

  • Trees built independently, in parallel
  • Each on a bootstrap sample + random features
  • Average the votes → lower variance
  • Hard to overfit, little tuning

Boosting (Gradient Boosting)

  • Trees built one after another
  • Each new tree fits the previous errors
  • Small steps (learning rate) → lower bias
  • Usually most accurate, needs tuning

Random forest

Python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error
h = pd.read_csv("house_prices.csv")
X = pd.get_dummies(h.drop(columns="price_lakh"), drop_first=True, dtype=int); y = h["price_lakh"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=42)
tree = DecisionTreeRegressor(random_state=0).fit(X_tr, y_tr)
rf = RandomForestRegressor(n_estimators=300, oob_score=True, random_state=0, n_jobs=-1).fit(X_tr, y_tr)
print(f"single tree   MAE ₹{mean_absolute_error(y_te, tree.predict(X_te)):.2f} lakh")
print(f"random forest MAE ₹{mean_absolute_error(y_te, rf.predict(X_te)):.2f} lakh | OOB R² {rf.oob_score_:.3f}")
single tree MAE ₹7.13 lakh random forest MAE ₹5.13 lakh | OOB R² 0.900
🧠 Out-of-bag (OOB) score

Each tree sees only ~63% of rows. The rows it did not see act as a free validation set – the OOB score – so you can estimate performance without a separate split.

Python
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error
h = pd.read_csv("house_prices.csv")
X = pd.get_dummies(h.drop(columns="price_lakh"), drop_first=True, dtype=int); y = h["price_lakh"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=42)
ns = [1, 3, 10, 30, 100, 300]
maes = [mean_absolute_error(y_te, RandomForestRegressor(n_estimators=n, random_state=0, n_jobs=-1).fit(X_tr, y_tr).predict(X_te)) for n in ns]
plt.figure(figsize=(8, 3.6)); plt.plot(ns, maes, marker="o", color="#16a34a"); plt.xscale("log")
plt.xlabel("number of trees"); plt.ylabel("test MAE (₹ lakh)"); plt.title("More trees → lower error, then it levels off")
plt.show()
Test MAE falling as number of trees grows
Adding trees never hurts accuracy – it only costs time. 200–500 is usually enough.

Gradient boosting

Python
import numpy as np
import pandas as pd
from sklearn.tree import DecisionTreeRegressor
h = pd.read_csv("house_prices.csv"); p = h[h["city"] == "Pune"]
X, y = p[["area_sqft", "metro_km", "age_years"]], p["price_lakh"].to_numpy()
pred = np.full(len(y), y.mean())                       # start with the average
lr = 0.3
for step in range(1, 6):
    resid = y - pred                                   # what we still get wrong
    t = DecisionTreeRegressor(max_depth=2, random_state=0).fit(X, resid)
    pred = pred + lr * t.predict(X)                    # take a small step
    print(f"step {step}: mean absolute error = {np.abs(y - pred).mean():.2f} lakh")
step 1: mean absolute error = 15.22 lakh step 2: mean absolute error = 12.16 lakh step 3: mean absolute error = 10.24 lakh step 4: mean absolute error = 8.75 lakh step 5: mean absolute error = 7.99 lakh
Python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor, HistGradientBoostingRegressor
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error
h = pd.read_csv("house_prices.csv")
X = pd.get_dummies(h.drop(columns="price_lakh"), drop_first=True, dtype=int); y = h["price_lakh"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=42)
models = {"Linear regression": LinearRegression(),
          "Random forest": RandomForestRegressor(n_estimators=300, random_state=0, n_jobs=-1),
          "Hist gradient boosting": HistGradientBoostingRegressor(learning_rate=0.05, max_iter=600, early_stopping=True, random_state=0)}
for name, m in models.items():
    m.fit(X_tr, y_tr)
    print(f"{name:24s} MAE ₹{mean_absolute_error(y_te, m.predict(X_te)):.2f} lakh")
print("boosting stopped after", models["Hist gradient boosting"].n_iter_, "trees (early stopping)")
Linear regression MAE ₹4.63 lakh Random forest MAE ₹5.13 lakh Hist gradient boosting MAE ₹4.96 lakh boosting stopped after 132 trees (early stopping)
💡 Linear won here – why?

House prices in our data follow a mostly multiplicative formula (area × city rate × small % effects). A linear model on good features captures that well. Ensembles shine when relationships are messy and interacting – like churn and loans below. Always compare!

Classification: the loan model

Python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score, accuracy_score
l = pd.read_csv("loan_applications.csv")
l["lti"] = l["loan_amount"] / (l["monthly_income"] * 12); l["salaried"] = (l["employment"] == "Salaried").astype(int)
X = l[["monthly_income", "credit_score", "loan_amount", "years_employed", "salaried"]]   # no lti – let models find it
y = l["approved"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)
models = {"Logistic": LogisticRegression(max_iter=5000), "Tree": DecisionTreeClassifier(max_depth=5, random_state=0),
          "Random forest": RandomForestClassifier(n_estimators=400, min_samples_leaf=3, random_state=0, n_jobs=-1),
          "Gradient boosting": HistGradientBoostingClassifier(learning_rate=0.05, max_iter=300, random_state=0)}
for name, m in models.items():
    p = m.fit(X_tr, y_tr).predict_proba(X_te)[:, 1]
    print(f"{name:18s} accuracy={accuracy_score(y_te, p > 0.5):.3f}  AUC={roc_auc_score(y_te, p):.3f}")
Logistic accuracy=0.603 AUC=0.654 Tree accuracy=0.803 AUC=0.860 Random forest accuracy=0.837 AUC=0.904 Gradient boosting accuracy=0.810 AUC=0.884
🧠 Ensembles find interactions

Without the hand-made loan_to_income feature, the linear model struggles, while forests and boosting discover the income × loan interaction on their own. With good features, simple models catch up – feature engineering still matters.

Hyper-parameters that matter

ModelParameterTypical valuesEffect
Random forestn_estimators200–1000More = stabler, slower
Random forestmax_features"sqrt", 0.3–0.6Randomness between trees
Random forestmin_samples_leaf1–20Bigger = smoother
Boostinglearning_rate0.01–0.2Smaller = needs more trees, often better
Boostingmax_iter + early_stopping200–2000Stops when validation stops improving
Boostingmax_depth / max_leaf_nodes3–8 / 15–63Tree complexity

Which features drive the model?

Python
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.inspection import permutation_importance
l = pd.read_csv("loan_applications.csv"); l["salaried"] = (l["employment"] == "Salaried").astype(int)
X = l[["monthly_income", "credit_score", "loan_amount", "years_employed", "salaried"]]; y = l["approved"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)
rf = RandomForestClassifier(n_estimators=400, min_samples_leaf=3, random_state=0, n_jobs=-1).fit(X_tr, y_tr)
pi = permutation_importance(rf, X_te, y_te, n_repeats=20, random_state=0, scoring="roc_auc")
imp = pd.Series(pi.importances_mean, index=X.columns).sort_values()
imp.plot(kind="barh", figsize=(7.5, 3.4), color="#4f46e5")
plt.title("Permutation importance: AUC drop when a feature is shuffled"); plt.xlabel("AUC drop"); plt.show()
print(imp.round(3).sort_values(ascending=False).to_string())
credit_score 0.357 loan_amount 0.052 monthly_income 0.016 years_employed 0.001 salaried 0.000
Permutation importance bar chart for the loan model
Shuffle a column and see how much the score falls – an honest, model-agnostic importance.
⚠️ Built-in importances can mislead

feature_importances_ of trees favours features with many unique values (like income). Permutation importance on the test set is more reliable.

Which model should I use?

📏
Linear / logisticNeed explanations, small data, roughly linear effects, a strong baseline.
🌲
Random forestRobust default with little tuning; good OOB estimate; handles interactions.
🚀
Gradient boostingBest accuracy on tabular data when tuned; XGBoost / LightGBM / CatBoost in industry.
Start simple, then move right

🏅 Job skills unlocked today

✔ Explain bagging vs boosting✔ Train random forests and gradient boosting✔ Tune key hyper-parameters with early stopping✔ Measure permutation importance

Practice tasks

🏋️ Practice – do it yourself

Task 1: Predict student average with RandomForestRegressor vs LinearRegression (all features one-hot). Which wins?
Show solution
Python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.linear_model import LinearRegression
st = pd.read_csv("students_performance.csv")
X = pd.get_dummies(st.drop(columns=["student_id", "maths", "science", "english", "average", "result"]), drop_first=True, dtype=int)
a, b, c, d = train_test_split(X, st["average"], test_size=0.25, random_state=0)
for m in [LinearRegression(), RandomForestRegressor(n_estimators=300, random_state=0)]:
    print(type(m).__name__, round(m.fit(a, c).score(b, d), 3))
LinearRegression 0.814 RandomForestRegressor 0.739
Task 2: Try learning_rate 0.3, 0.1 and 0.03 for HistGradientBoostingClassifier on loans. Compare AUC and n_iter_.
Show solution
Python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score
l = pd.read_csv("loan_applications.csv")
X, y = l[["monthly_income", "credit_score", "loan_amount", "years_employed"]], l["approved"]
a, b, c, d = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)
for lr in [0.3, 0.1, 0.03]:
    m = HistGradientBoostingClassifier(learning_rate=lr, max_iter=1000, early_stopping=True, random_state=0).fit(a, c)
    print(lr, round(roc_auc_score(d, m.predict_proba(b)[:, 1]), 3), m.n_iter_)
0.3 0.885 16 0.1 0.895 23 0.03 0.893 68
Task 3: Add the lti feature to the loan comparison. How much does logistic regression improve?

Quick quiz

🧠 Quick Quiz – test yourself

1. Random forest reduces mainly…
Averaging many noisy trees.
2. In boosting each new tree is trained on…
It corrects residuals step by step.
3. A smaller learning rate usually needs…
Smaller steps, more of them.
4. OOB score is computed on…
A built-in validation.
5. Permutation importance measures…
Model-agnostic and honest.

Interview corner

💼 Interview Corner

Explain the difference between bagging and boosting.

Bagging trains many models independently on bootstrap samples and averages them, reducing variance (random forest). Boosting trains models sequentially, each correcting the previous errors, reducing bias (gradient boosting, XGBoost).

Why might a random forest overfit less than a single tree?

Each tree sees a different bootstrap sample and random subset of features, so their errors are less correlated; averaging cancels much of the noise each tree memorised.

Summary

  • Ensembles combine many trees: bagging lowers variance, boosting lowers bias.
  • Random forest: robust default, OOB score, little tuning.
  • Gradient boosting: learning rate + early stopping, top accuracy.
  • Compare with linear baselines and use permutation importance.
➡️ Next

Day 44: feature engineering and scaling – encoding, scaling, binning, interactions and avoiding leakage.

Frequently Asked Questions

Should I install XGBoost or LightGBM?
For learning, scikit-learn’s HistGradientBoosting is very close in speed and accuracy. In jobs you will meet XGBoost/LightGBM – the ideas (learning rate, trees, early stopping) are identical.
Do ensembles need scaling or one-hot encoding?
No scaling needed. Categories still need encoding (one-hot or ordinal); HistGradientBoosting can also handle categorical features natively.
← Data Science (60 Days)
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicDay 44: Feature Engineering & Scaling Your progress: 43 / 60 (72%) · Lesson 44 / 60 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free