This project is a browser-based MLB analytics application that projects 2021 on-base percentage (OBP_21) in plain JavaScript using a custom two-stage ensemble model.
The core narrative is intentionally unusual:
Can the "human element" of a player profile, such as biometrics and demographics, stabilize or slightly improve projections when traditional performance inputs are deliberately throttled?
Instead of training one monolithic regression, the application trains two separate linear models:
Model Auses only traditional baseball performance inputs.Model Buses only human-element inputs.- The final prediction is blended deterministically with a fixed weight split:
Final_Pred = (0.40 * Model_A_Pred) + (0.60 * Model_B_Pred)
That fixed 60/40 human-over-stats structure is a feature of the experiment, not an accident. It forces the project to test whether an intentionally overweighted human-element block can contribute signal without relying entirely on historical stats.
The app now reads from player_data.csv.
The expected columns are:
OBP_16,OBP_17,OBP_18,OBP_19,OBP_20PA_16,PA_17,PA_18,PA_19,PA_20
height_inchesweight_lbsage_2021hometown_region
OBP_21
birth_date
If age_2021 is missing but birth_date is present, the script can derive the player’s 2021 age. If the required columns are missing, the page fails fast with a schema error instead of silently training the wrong model.
The new CSV stores hometown buckets as strings rather than numeric codes:
region_eastregion_centralregion_westregion_foreign
The script converts those string labels into one-hot style indicator columns before training the human-element model:
region_eastregion_centralregion_west
region_foreign is used as the reference bucket and is therefore left out of the regression feature matrix to avoid perfect multicollinearity.
This is more defensible than treating region as a single number because it avoids implying that one region is inherently "greater than" another.
All modeling logic lives in script.js and is implemented in pure JavaScript without external machine learning libraries.
- PapaParse loads
player_data.csvdirectly in the browser. - The script validates that the required schema exists before training.
- Missing numeric inputs are parsed safely.
- Missing feature values are mean-imputed within each model pipeline.
- Standard Z-score scaling is applied separately to each feature group:
z = (x - mean) / std
This is important because the models combine variables on very different scales, such as OBP, weight, height, age, and plate appearances.
Model A is trained strictly on:
OBP_16throughOBP_20PA_16throughPA_20
Model B is trained strictly on:
height_inchesweight_lbsage_2021- One-hot regional indicators derived from
hometown_region
Each model uses multiple linear regression fit via the normal equation:
Beta = (X^T * X)^-1 * X^T * Y
To support this, script.js includes manual implementations of:
- Matrix transpose
- Matrix multiplication
- Determinant calculation
- Matrix inversion
If the system is near-singular, the script adds a tiny diagonal jitter before inversion to keep the math stable in the browser.
After both models generate a prediction:
Final_Pred = (0.40 * Model_A_Pred) + (0.60 * Model_B_Pred)
The final result is then clamped to a realistic OBP range.
The app calculates mean absolute error (MAE) for two benchmarks:
-
Model A MAEThis is the traditional-stat baseline. -
Blended Ensemble MAEThis tests whether the fixed40/60stats-to-human blend improves on the traditional-only baseline.
The interface also shows:
- Player-by-player predictions
- Actual 2021 OBP
- Absolute error for Model A
- Absolute error for the blended ensemble
- Coefficient summary for the human-element model
You mentioned you will describe industry context in the README instead of comparing against a custom ESPN subset. That is a cleaner fit for the current project.
A reasonable interview framing is:
- OBP projection errors around
0.025to0.035per season are often in the right rough range for simple public-facing models, depending on player pool, training design, and whether the evaluation is truly out of sample. - This project should be presented as a lightweight explanatory prototype rather than a production forecasting benchmark.
These coefficients are standardized-feature coefficients, not raw-unit slopes.
Typical interpretation:
- Positive
height_inchessuggests taller players may receive a slight bump after scaling, though likely a small one. - Negative
age_2021suggests mild age-related decline pressure after scaling. - Regional indicators measure difference relative to the omitted
region_foreignbucket.
- The current browser app trains and evaluates on the same dataset unless you add a formal train/test split.
- Mean imputation is simple and readable, but not always optimal.
- The
60/40human-over-stats blend is a fixed business or storytelling constraint, not a learned optimum. - A linear model may miss non-linear relationships between age, body type, and offensive outcomes.
- The hometown buckets are still coarse abstractions rather than a rich developmental-history feature set.
Serve the project from a local web server because the page loads player_data.csv in the browser.
Example:
python -m http.server 8000Then open:
http://localhost:8000/index.htm
.
|-- index.htm
|-- script.js
|-- player_data.csv
|-- obp.csv
`-- README.md
- Add a true train/validation split or rolling time-based validation
- Compare the fixed blend with a learned blend
- Add residual analysis by player archetype
- Test whether the human-element model helps more for low-sample or injury-shortened histories
- Compare MAE with RMSE and median absolute error