ML Lab

I trained two scikit-learn models on the Gen 1–3 Pokédex — 386 Pokémon, six base stats each. One model predicts Base Stat Total from physical and categorical traits. The other classifies legendary status from the six stats alone. Neither is impressive by production ML standards, but both do something non-trivial with a small, weird dataset, and building them taught me more about feature engineering than most tutorials do.

BST Regressor

R² 0.59 ± 0.06

Predicts a Pokémon's Base Stat Total from height, weight, generation, and type — no stats used. R² ≈ 0.59 on cross-validation.

Feature importances

height dm
53.8%
weight hg
19.4%
is legendary
12.3%
generation
1.8%
primary: bug
1.7%
primary: normal
1.0%
primary: fighting
1.0%
secondary: flying
0.9%

Predicted vs actual BST

Each dot is one Pokémon. Points above the dashed line are over-predicted; below are under-predicted.

Learning curve

Legendary Classifier

F1 0.83 ± 0.12

Classifies legendary status from the six base stats alone, with no categorical features. F1 ≈ 0.83 on cross-validation — good, given only 15 legendaries in the training set.

Feature importances

bst
43.9%
hp
12.2%
sp defense
11.5%
sp attack
9.6%
defense
7.0%
height dm
6.8%
speed
3.9%
attack
3.0%

Confusion matrix

Predicted: Not Legendary
Predicted: Legendary
Actual: Not Legendary
365TN · 94.6%
0FP · 0.0%
Actual: Legendary
0FN · 0.0%
21TP · 5.4%
Precision 100.0%Recall 100.0%F1 100.0%

Only 15 of 386 Pokémon are legendary — a 4% class rate. With that imbalance, recall matters more than accuracy: a model that predicted "never legendary" would score 96% accuracy but 0% recall.

Learning curve

→ Try the predictor

Training code is in scripts/train-models.py in the repo. The models are pickled and served via a Vercel Python runtime function at api/predict.py. Cold start is slow (~20s on first request); warm requests are under 100ms. github.com/jleo28/pokedex-plus