Head-to-Head: Wine Quality
from sklearn.datasets import load_wine
from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import (
RandomForestClassifier,
GradientBoostingClassifier,
HistGradientBoostingClassifier
)
X, y = load_wine(return_X_y=True)
models = {
"Decision Tree": DecisionTreeClassifier(
max_depth=5, random_state=42),
"Random Forest": RandomForestClassifier(
n_estimators=100, random_state=42),
"Gradient Boost": GradientBoostingClassifier(
n_estimators=100, learning_rate=0.1,
max_depth=3, random_state=42),
"HistGradBoost": HistGradientBoostingClassifier(
max_iter=100, random_state=42),
}
for name, model in models.items():
scores = cross_val_score(
model, X, y, cv=5, scoring="accuracy"
)
print(f"{name:18s} {scores.mean():.3f} "
f"± {scores.std():.3f}")
Expected Results
Wine dataset (178 samples, 13 features, 3 classes):
Decision Tree 0.899 ± 0.050
Random Forest 0.978 ± 0.022
Gradient Boost 0.966 ± 0.033
HistGradBoost 0.972 ± 0.028
Key takeaways:
• Single tree: 90% — decent but unstable (±5%)
• Random Forest: 98% — best here, low variance
• Boosting: 97% — close, would win on larger data
• All ensembles crush the single tree
When to use which:
Small data (<1K): Random Forest (robust)
Medium data (1K-1M): HistGradientBoosting
Large data (>1M): HistGradientBoosting/XGBoost
Need interpretability: Single tree + pruning
Need feature importance: Random Forest + perm
Key insight: Tree-based ensembles are the Swiss Army knife of ML. They handle mixed feature types, missing values, nonlinear relationships, and feature interactions without preprocessing. Random Forests are the safe default; gradient boosting squeezes out the last few percent of accuracy. For tabular data, they remain the state of the art — even in the age of deep learning.