Book Ratings Prediction
Predicts Goodreads book ratings from book metadata and sparse NY Times Best Sellers signals.
The primary dataset is a Kaggle Goodreads catalog (~1.85M books), filtered to ~342k books with
at least 100 total ratings and deduplicated by title/author. NYT Best Sellers weekly overview
snapshots are joined in via a deterministic ISBN-10↔13 conversion — only a small fraction of
the catalog matches (most books were never bestsellers), handled with an is_bestseller
flag rather than treated as a data-quality problem.
A Makefile-orchestrated pipeline (ingest → clean → enrich → features → train → evaluate → score) trains and MLflow-tracks six models — Ridge, Lasso, ElasticNet, Random Forest, XGBoost, and LightGBM — and compares them on RMSE/MAE/R², with SHAP explainability and error analysis surfacing which features (rating volume, author/publisher history, bestseller status) drive predictions.
Results are served through an R Shiny dashboard with four tabs: model performance, SHAP feature drivers, a data-quality/catalog explorer with a book-detail drill-through, and a "Predict a Book" what-if tool. Since the trained model only knows a book's rating from signals it accrues after publication, that last tab takes hypothetical engagement inputs and scores them with the actual trained XGBoost booster — exported to portable JSON and ported to R, not re-implemented, and verified to match the Python pipeline's predictions to the decimal.
pct_1_star…pct_5_star) are excluded from the model entirely, since
they mathematically reconstruct Rating (Goodreads' own weighted average of those
counts) and would leak the target if used as predictors. The R port also replicates a subtler
detail exactly: scikit-learn's ColumnTransformer one-hot output is sparse, and XGBoost
treats its implicit zero entries as missing rather than literal 0 — true during
training, so predict_helpers.R matches it or predictions silently drift.
Six-model MLflow comparison with SHAP explainability; the winning XGBoost booster is exported and ported to R so the live Shiny "Predict a Book" tab can score new inputs without a Python runtime at request time.