[{"data":1,"prerenderedAt":255},["ShallowReactive",2],{"note-pre-match-ratings-en":3,"titles-en":254},{"note":4,"project":182},{"id":5,"title":6,"body":7,"date":166,"description":167,"draft":168,"extension":169,"kind":170,"meta":171,"navigation":172,"path":173,"pinned":168,"project":155,"seo":174,"stem":175,"summary":176,"tags":177,"__hash__":181,"translated":172},"notes\u002Fnotes\u002Fpre-match-ratings.md","Most of a match rating can't be predicted",{"type":8,"value":9,"toc":157},"minimark",[10,22,106,111,119,123,126,130,133,137,140,144,147],[11,12,13,14,21],"p",{},"The setup from ",[15,16,20],"a",{"href":17,"className":18},"\u002Fnotes\u002Fr2-too-good",[19],"wikilink","When R² is too good",": predict each player's Sofascore rating before kickoff, using only home\u002Faway, opponent strength and the player's own history. Walk-forward validation over the last 80 matches of the 2025 Brasileirão, 2,494 appearances.",[23,24,25,41],"table",{},[26,27,28],"thead",{},[29,30,31,35,38],"tr",{},[32,33,34],"th",{},"Model",[32,36,37],{},"MAE",[32,39,40],{},"R²",[42,43,44,56,67,78,89],"tbody",{},[29,45,46,50,53],{},[47,48,49],"td",{},"Player's last-5 average",[47,51,52],{},"0.412",[47,54,55],{},"-0.06",[29,57,58,61,64],{},[47,59,60],{},"Position average",[47,62,63],{},"0.398",[47,65,66],{},"0.01",[29,68,69,72,75],{},[47,70,71],{},"Player's season average",[47,73,74],{},"0.389",[47,76,77],{},"0.05",[29,79,80,83,86],{},[47,81,82],{},"Position specialists",[47,84,85],{},"0.375",[47,87,88],{},"0.10",[29,90,91,97,102],{},[47,92,93],{},[94,95,96],"strong",{},"Generic Random Forest",[47,98,99],{},[94,100,101],{},"0.374",[47,103,104],{},[94,105,88],{},[107,108,110],"h2",{"id":109},"specialists-didnt-help","Specialists didn't help",[11,112,113,114,118],{},"The generic model's MAE minus the specialists' MAE was -0.0014, with a 95% confidence interval of ",[115,116,117],"span",{},"-0.0036, 0.0008",". The interval comes from a bootstrap with 2,000 resamples, drawing whole matches instead of single rows, since players in the same game aren't independent. It includes zero, so it's a tie. The decision rule was set before seeing any result: on a tie, keep the generic model, because one model is simpler than four. Specialists didn't win any position.",[107,120,122],{"id":121},"recent-form-is-noise","Recent form is noise",[11,124,125],{},"The player's average over the last 5 games did worse than ignoring the player entirely and guessing the position average. The full season average did better than both. Over five games, a hot or cold streak is mostly noise. Permutation importance agrees: the generic model leaned on two features, home\u002Faway and the player's season-average rating. The histories of the other 61 stats contributed close to nothing.",[107,127,129],{"id":128},"goalkeepers","Goalkeepers",[11,131,132],{},"For goalkeepers, no model meaningfully beat the baseline (MAE 0.559 for the generic model and for the position average alike, R² slightly below zero). A keeper's rating is decided by saves and goals conceded in that match, and before kickoff there's no way to know whether the keeper will face 2 shots or 12.",[107,134,136],{"id":135},"predictions-hug-the-mean","Predictions hug the mean",[11,138,139],{},"Real ratings in the test set ran from 3.0 to 9.7, with a standard deviation of 0.54. The generic model's predictions ran from 6.63 to 7.41, with a standard deviation of 0.16. 108 appearances in the test set were rated 8.0 or higher. The model never got close to any of them. It nudges predictions up for home games and for players with a good season so far, but it can't see an unusually good or bad game coming.",[107,141,143],{"id":142},"the-answer","The answer",[11,145,146],{},"Specialization by position did not reduce the error. The generic model beat the position average by 6.0% and the player's own season average by 3.9%, and that second gap is real (its confidence interval doesn't include zero). Small numbers, but that's the honest size of the signal: most of a match rating is decided during the match.",[11,148,149,150,156],{},"Code and full method: ",[15,151,155],{"href":152,"rel":153},"https:\u002F\u002Fgithub.com\u002Falmeidaota\u002Fsofascore-rating-prediction",[154],"nofollow","sofascore-rating-prediction",".",{"title":158,"searchDepth":159,"depth":159,"links":160},"",2,[161,162,163,164,165],{"id":109,"depth":159,"text":110},{"id":121,"depth":159,"text":122},{"id":128,"depth":159,"text":129},{"id":135,"depth":159,"text":136},{"id":142,"depth":159,"text":143},"2026-10-22","The setup from When R² is too good: predict each player's Sofascore rating before kickoff, using only home\u002Faway, opponent strength and the player's own history. Walk-forward validation over the last 80 matches of the 2025 Brasileirão, 2,494 appearances.",false,"md","article",{},true,"\u002Fnotes\u002Fpre-match-ratings",{"title":6,"description":167},"notes\u002Fpre-match-ratings","Position specialists didn't beat a single generic model, recent form was worse than no information at all, and nobody can predict a goalkeeper. The thesis answer was no.",[178,179,180],"ml","random-forest","football","lRXhmendunPnntPdV0Kyh7MYFU2L_Af49JqWx0-YMFc",{"id":183,"title":184,"body":185,"cover":241,"date":242,"demo":241,"description":189,"draft":168,"extension":169,"featured":172,"meta":243,"navigation":172,"order":244,"path":245,"repo":152,"seo":246,"status":247,"stem":248,"summary":249,"tags":250,"__hash__":253,"translated":172},"projects\u002Fprojects\u002Fsofascore-rating-prediction.md","Sofascore rating prediction",{"type":8,"value":186,"toc":236},[187,190,194,197,219,223,226,230,233],[11,188,189],{},"My undergraduate thesis at FATEC Sorocaba, supervised by Prof. Dr. Maria das Graças J. M. Tomazela. One question: can Random Forest models specialized by position (goalkeeper, defender, midfielder, forward) predict a player's Sofascore rating before the match with lower error than a single generic model?",[107,191,193],{"id":192},"pipeline","Pipeline",[11,195,196],{},"Three stages, each one reading only the previous stage's output from disk, so any of them can be rerun alone.",[198,199,200,207,213],"ul",{},[201,202,203,206],"li",{},[94,204,205],{},"Collection."," A polite crawler for Sofascore's internal API: rate limiting, retries with backoff, checkpoint\u002Fresume and Pydantic validation on every row. 11,977 player-match records from all 380 matches of the 2025 Brazilian Série A.",[201,208,209,212],{},[94,210,211],{},"Cleaning."," One function per step. The interesting part is that an empty cell can mean three different things, each with its own rule.",[201,214,215,218],{},[94,216,217],{},"Modelling."," Pre-match features only: home\u002Faway, opponent strength and the player's own history up to the match being predicted. Walk-forward validation over the last 80 matches, bootstrap confidence intervals over whole matches, permutation importance.",[107,220,222],{"id":221},"result","Result",[11,224,225],{},"Specialization didn't reduce the error. The generic model beat a position-average baseline by 6% (MAE 0.374 vs 0.398), and predicting goalkeepers turned out to be hopeless before kickoff. Most of a match rating is decided during the match.",[107,227,229],{"id":228},"notes","Notes",[11,231,232],{},"The story in order: the data traps, the first model that was too good to be true, and what came out once the model could only see the past.",[11,234,235],{},"Stack: Python, httpx, tenacity, Pydantic v2, pandas, scikit-learn, pytest.",{"title":158,"searchDepth":159,"depth":159,"links":237},[238,239,240],{"id":192,"depth":159,"text":193},{"id":221,"depth":159,"text":222},{"id":228,"depth":159,"text":229},null,"2026-08-27",{},3,"\u002Fprojects\u002Fsofascore-rating-prediction",{"title":184,"description":189},"active","projects\u002Fsofascore-rating-prediction","My undergraduate thesis. Can Random Forest models specialized by position predict a player's Sofascore rating before kickoff better than one generic model? The answer was no.",[251,252,178,179],"python","scraping","IcTcWXpYn5Kpmy9fqNl7cIFEbUs7Jrm8SMXO46pBHt8",{},1793409193528]