[{"data":1,"prerenderedAt":501},["ShallowReactive",2],{"project-sofascore-rating-prediction-pt":3,"titles-pt":497},{"project":4,"notes":90},{"id":5,"title":6,"body":7,"cover":69,"date":70,"demo":69,"description":71,"draft":72,"extension":73,"featured":74,"meta":75,"navigation":74,"order":76,"path":77,"repo":78,"seo":79,"status":81,"stem":82,"summary":83,"tags":84,"__hash__":89,"translated":74},"projects\u002Fprojects\u002Fsofascore-rating-prediction.md","Previsão de notas do Sofascore",{"type":8,"value":9,"toc":62},"minimark",[10,14,19,22,45,49,52,56,59],[11,12,13],"p",{},"Meu TCC na FATEC Sorocaba, orientado pela Profa. Dra. Maria das Graças J. M. Tomazela. Uma pergunta: modelos Random Forest especializados por posição (goleiro, defensor, meio-campista, atacante) conseguem prever a nota de um jogador no Sofascore antes da partida com erro menor que um único modelo genérico?",[15,16,18],"h2",{"id":17},"pipeline","Pipeline",[11,20,21],{},"Três etapas, cada uma lendo do disco só a saída da etapa anterior, então qualquer uma pode ser rodada de novo sozinha.",[23,24,25,33,39],"ul",{},[26,27,28,32],"li",{},[29,30,31],"strong",{},"Coleta."," Um crawler educado pra API interna do Sofascore: limite de requisições, novas tentativas com backoff, checkpoint\u002Fretomada e validação com Pydantic em cada linha. 11.977 registros de jogador por partida, de todos os 380 jogos do Brasileirão Série A 2025.",[26,34,35,38],{},[29,36,37],{},"Limpeza."," Uma função por etapa. A parte interessante é que uma célula vazia pode significar três coisas diferentes, cada uma com sua própria regra.",[26,40,41,44],{},[29,42,43],{},"Modelagem."," Só features pré-jogo: mando de campo, força do adversário e o histórico do próprio jogador até a partida sendo prevista. Validação walk-forward nas últimas 80 partidas, intervalos de confiança por bootstrap sobre partidas inteiras, importância por permutação.",[15,46,48],{"id":47},"resultado","Resultado",[11,50,51],{},"A especialização não reduziu o erro. O modelo genérico superou em 6% uma baseline de média por posição (MAE 0,374 contra 0,398), e prever goleiros antes do jogo se mostrou um caso perdido. A maior parte da nota de uma partida se decide durante a partida.",[15,53,55],{"id":54},"notas","Notas",[11,57,58],{},"A história em ordem: as armadilhas dos dados, o primeiro modelo, bom demais pra ser verdade, e o que saiu quando o modelo só podia ver o passado.",[11,60,61],{},"Stack: Python, httpx, tenacity, Pydantic v2, pandas, scikit-learn, pytest.",{"title":63,"searchDepth":64,"depth":64,"links":65},"",2,[66,67,68],{"id":17,"depth":64,"text":18},{"id":47,"depth":64,"text":48},{"id":54,"depth":64,"text":55},null,"2026-08-27","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?",false,"md",true,{},3,"\u002Fprojects\u002Fsofascore-rating-prediction","https:\u002F\u002Fgithub.com\u002Falmeidaota\u002Fsofascore-rating-prediction",{"title":80,"description":71},"Sofascore rating prediction","active","projects\u002Fsofascore-rating-prediction","Meu TCC. Modelos Random Forest especializados por posição preveem a nota de um jogador no Sofascore antes do jogo melhor do que um modelo genérico? A resposta foi não.",[85,86,87,88],"python","scraping","ml","random-forest","IcTcWXpYn5Kpmy9fqNl7cIFEbUs7Jrm8SMXO46pBHt8",[91,258,386],{"id":92,"title":93,"body":94,"date":246,"description":247,"draft":72,"extension":73,"kind":248,"meta":249,"navigation":74,"path":250,"pinned":72,"project":237,"seo":251,"stem":253,"summary":254,"tags":255,"__hash__":257,"translated":74},"notes\u002Fnotes\u002Fpre-match-ratings.md","A maior parte da nota de uma partida não dá pra prever",{"type":8,"value":95,"toc":239},[96,107,190,194,202,206,209,213,216,220,223,227,230],[11,97,98,99,106],{},"O cenário de ",[100,101,105],"a",{"href":102,"className":103},"\u002Fnotes\u002Fr2-too-good",[104],"wikilink","Quando o R² é bom demais",": prever a nota de cada jogador no Sofascore antes do apito inicial, usando só mando de campo, força do adversário e o histórico do próprio jogador. Validação walk-forward nas últimas 80 partidas do Brasileirão 2025, 2.494 atuações.",[108,109,110,126],"table",{},[111,112,113],"thead",{},[114,115,116,120,123],"tr",{},[117,118,119],"th",{},"Modelo",[117,121,122],{},"MAE",[117,124,125],{},"R²",[127,128,129,141,152,163,174],"tbody",{},[114,130,131,135,138],{},[132,133,134],"td",{},"Média dos últimos 5 jogos do jogador",[132,136,137],{},"0,412",[132,139,140],{},"-0,06",[114,142,143,146,149],{},[132,144,145],{},"Média da posição",[132,147,148],{},"0,398",[132,150,151],{},"0,01",[114,153,154,157,160],{},[132,155,156],{},"Média da temporada do jogador",[132,158,159],{},"0,389",[132,161,162],{},"0,05",[114,164,165,168,171],{},[132,166,167],{},"Especialistas por posição",[132,169,170],{},"0,375",[132,172,173],{},"0,10",[114,175,176,181,186],{},[132,177,178],{},[29,179,180],{},"Random Forest genérico",[132,182,183],{},[29,184,185],{},"0,374",[132,187,188],{},[29,189,173],{},[15,191,193],{"id":192},"os-especialistas-não-ajudaram","Os especialistas não ajudaram",[11,195,196,197,201],{},"O MAE do modelo genérico menos o MAE dos especialistas deu -0,0014, com intervalo de confiança de 95% de ",[198,199,200],"span",{},"-0,0036; 0,0008",". O intervalo vem de um bootstrap com 2.000 reamostragens, sorteando partidas inteiras em vez de linhas soltas, já que jogadores do mesmo jogo não são independentes. Ele inclui o zero, então é empate. A regra de decisão foi definida antes de ver qualquer resultado: em caso de empate, fica o modelo genérico, porque um modelo é mais simples que quatro. Os especialistas não venceram em nenhuma posição.",[15,203,205],{"id":204},"fase-recente-é-ruído","Fase recente é ruído",[11,207,208],{},"A média do jogador nos últimos 5 jogos se saiu pior do que ignorar o jogador por completo e chutar a média da posição. A média da temporada inteira se saiu melhor que as duas. Em cinco jogos, uma fase boa ou ruim é quase só ruído. A importância por permutação concorda: o modelo genérico se apoiou em duas features, o mando de campo e a nota média do jogador na temporada. O histórico das outras 61 estatísticas contribuiu com quase nada.",[15,210,212],{"id":211},"goleiros","Goleiros",[11,214,215],{},"Pros goleiros, nenhum modelo superou a baseline de forma relevante (MAE 0,559 tanto pro modelo genérico quanto pra média da posição, R² um pouco abaixo de zero). A nota de um goleiro é decidida pelas defesas e pelos gols sofridos naquela partida, e antes do jogo não tem como saber se ele vai enfrentar 2 finalizações ou 12.",[15,217,219],{"id":218},"as-previsões-grudam-na-média","As previsões grudam na média",[11,221,222],{},"As notas reais no conjunto de teste foram de 3,0 a 9,7, com desvio padrão de 0,54. As previsões do modelo genérico foram de 6,63 a 7,41, com desvio padrão de 0,16. No conjunto de teste, 108 atuações tiveram nota 8,0 ou mais. O modelo nunca chegou perto de nenhuma delas. Ele empurra as previsões pra cima em jogos em casa e pra jogadores com boa temporada até ali, mas não consegue ver chegando um jogo fora do comum, pra melhor ou pra pior.",[15,224,226],{"id":225},"a-resposta","A resposta",[11,228,229],{},"A especialização por posição não reduziu o erro. O modelo genérico superou a média da posição em 6,0% e a média da temporada do próprio jogador em 3,9%, e essa segunda diferença é real (o intervalo de confiança dela não inclui o zero). Números pequenos, mas esse é o tamanho honesto do sinal: a maior parte da nota de uma partida se decide durante a partida.",[11,231,232,233,238],{},"Código e método completo: ",[100,234,237],{"href":78,"rel":235},[236],"nofollow","sofascore-rating-prediction",".",{"title":63,"searchDepth":64,"depth":64,"links":240},[241,242,243,244,245],{"id":192,"depth":64,"text":193},{"id":204,"depth":64,"text":205},{"id":211,"depth":64,"text":212},{"id":218,"depth":64,"text":219},{"id":225,"depth":64,"text":226},"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.","article",{},"\u002Fnotes\u002Fpre-match-ratings",{"title":252,"description":247},"Most of a match rating can't be predicted","notes\u002Fpre-match-ratings","Os especialistas por posição não superaram um único modelo genérico, a fase recente foi pior do que nenhuma informação, e ninguém consegue prever um goleiro. A resposta do TCC foi não.",[87,88,256],"football","lRXhmendunPnntPdV0Kyh7MYFU2L_Af49JqWx0-YMFc",{"id":259,"title":105,"body":260,"date":246,"description":378,"draft":72,"extension":73,"kind":248,"meta":379,"navigation":74,"path":102,"pinned":72,"project":237,"seo":380,"stem":382,"summary":383,"tags":384,"__hash__":385,"translated":74},"notes\u002Fnotes\u002Fr2-too-good.md",{"type":8,"value":261,"toc":374},[262,265,273,317,320,324,327,330,333,337,340,351,364,367],[11,263,264],{},"A pergunta do TCC: modelos Random Forest especializados por posição (goleiro, defensor, meio-campista, atacante) preveem a nota de um jogador no Sofascore melhor do que um único modelo genérico?",[11,266,267,268,272],{},"A primeira versão alimentava cada modelo com as estatísticas do jogador na própria partida: finalizações, passes, duelos, desarmes, toques, 63 colunas no total. A validação era limpa. Treino nas rodadas 1 a 30, teste nas 31 a 38, hiperparâmetros ajustados com ",[269,270,271],"code",{},"GroupKFold"," agrupado por partida, pra que jogadores do mesmo jogo nunca caíssem dos dois lados de um fold. Uma baseline que só prevê a nota média da posição definia o piso.",[108,274,275,285],{},[111,276,277],{},[114,278,279,281,283],{},[117,280,119],{},[117,282,122],{},[117,284,125],{},[127,286,287,296,307],{},[114,288,289,291,294],{},[132,290,145],{},[132,292,293],{},"0,391",[132,295],{},[114,297,298,301,304],{},[132,299,300],{},"Genérico",[132,302,303],{},"0,203",[132,305,306],{},"0,72",[114,308,309,312,315],{},[132,310,311],{},"Especialistas",[132,313,314],{},"0,207",[132,316,306],{},[11,318,319],{},"Os atacantes chegaram a R² 0,80. Cortar pela metade o erro da baseline fica ótimo num TCC. Só que a conta não fechava: as estatísticas de uma única partida não deveriam explicar tanto assim de nada no futebol.",[15,321,323],{"id":322},"não-era-vazamento-era-circularidade","Não era vazamento. Era circularidade.",[11,325,326],{},"O primeiro suspeito foi vazamento entre treino e teste. Não havia nenhum. A divisão e os folds mantinham cada partida inteira, e nenhuma linha de teste encostou no treino.",[11,328,329],{},"O problema era o alvo. A nota do Sofascore sai de uma fórmula proprietária alimentada por estatísticas muito parecidas com aquelas 63 features: gols, passes, duelos, desarmes. O Random Forest não estava aprendendo o que é uma boa atuação. Estava aproximando a fórmula que gerou o número. O R² alto media o quanto o modelo imita o algoritmo do Sofascore, e nada mais.",[11,331,332],{},"Esse experimento ainda respondeu uma pergunta mais estreita (com as estatísticas da partida em mãos, os especialistas só ganharam nos goleiros, a posição estatisticamente mais distinta). Mas não respondia a que importa: dá pra saber a nota antes do jogo?",[15,334,336],{"id":335},"o-redesenho","O redesenho",[11,338,339],{},"O segundo experimento proíbe qualquer estatística da partida que está sendo prevista. Cada feature tem que existir antes do apito inicial:",[23,341,342,345,348],{},[26,343,344],{},"mandante ou visitante;",[26,346,347],{},"força do adversário: gols marcados e sofridos pelo adversário em partidas anteriores;",[26,349,350],{},"o histórico do próprio jogador: médias da temporada de 61 estatísticas, mais médias da temporada e dos últimos 5 jogos de nota e minutos, todas calculadas só com partidas estritamente anteriores a esta.",[11,352,353,354,359,360,363],{},"O \"estritamente anteriores\" é o motivo de a ordem das rodadas em ",[100,355,358],{"className":356,"href":357},[104],"\u002Fnotes\u002Fsofascore-data-traps","Uma temporada de dados do Sofascore, e onde eles mentem"," ter deixado de ser detalhe. Jogadores ainda sem histórico (14,9% deles jogam 3 partidas ou menos na temporada inteira) recebem ",[269,361,362],{},"NaN",", que o Random Forest do scikit-learn trata nativamente desde a versão 1.4. Junto entra uma contagem de partidas anteriores, pro modelo aprender a confiar mais em 20 jogos de histórico do que em 1.",[11,365,366],{},"A validação passou a ser walk-forward: treinar com tudo antes de um lote de 10 partidas, prever esse lote, somá-lo ao treino e seguir em frente, ao longo das últimas 80 partidas da temporada. A busca de hiperparâmetros usa folds em ordem temporal dentro da janela de treino, sempre validando em partidas posteriores às de treino.",[11,368,369,370,373],{},"O R² caiu pra 0,10. Esse é o resultado esperado, não uma piora. Explicar uma nota que já aconteceu, a partir de estatísticas próximas da própria fórmula, é uma tarefa. Prever a nota antes do jogo é outra, bem mais difícil. ",[100,371,93],{"className":372,"href":250},[104]," conta o que saiu.",{"title":63,"searchDepth":64,"depth":64,"links":375},[376,377],{"id":322,"depth":64,"text":323},{"id":335,"depth":64,"text":336},"The thesis question: can Random Forest models specialized by position (goalkeeper, defender, midfielder, forward) predict a player's Sofascore rating better than one generic model?",{},{"title":381,"description":378},"When R² is too good","notes\u002Fr2-too-good","O primeiro modelo explicava 80% da variância das notas dos atacantes. Esse era o problema: ele estava reconstruindo a fórmula do Sofascore, não prevendo futebol.",[87,88,85],"CP8qnPZk8uwlUZW8_GuAtolxx3ul1AWgwUEW6IlLme8",{"id":387,"title":358,"body":388,"date":246,"description":488,"draft":72,"extension":73,"kind":248,"meta":489,"navigation":74,"path":357,"pinned":72,"project":237,"seo":490,"stem":492,"summary":493,"tags":494,"__hash__":496,"translated":74},"notes\u002Fnotes\u002Fsofascore-data-traps.md",{"type":8,"value":389,"toc":483},[390,393,396,400,407,411,414,446,449,459,463,466,476],[11,391,392],{},"Meu TCC precisava de todas as notas de jogadores do Brasileirão Série A 2025. O Sofascore tem, atrás da mesma API interna que o próprio site usa. Como não é uma API pública oficial, o crawler foi feito pra ser entediante: uma requisição a cada 1,2 a 2,4 segundos, novas tentativas com backoff, um checkpoint ao fim de cada rodada pra que uma queda retome em vez de recomeçar, e validação com Pydantic em cada linha antes de ela chegar no CSV.",[11,394,395],{},"Isso rendeu 11.977 registros de jogador por partida, de todos os 380 jogos, disputados entre 29 de março e 7 de dezembro de 2025. Coletar foi a parte fácil. Os dados tinham três armadilhas, e nenhuma delas gerou erro.",[15,397,399],{"id":398},"datas-no-fuso-errado","Datas no fuso errado",[11,401,402,403,406],{},"O crawler guardava as datas das partidas em UTC. Os jogos noturnos no Brasil começam às 21h ou mais tarde, o que já é o dia seguinte em UTC, então 67 das 380 partidas estavam com a data errada. A correção foi recalcular a data a partir do timestamp do início do jogo em ",[269,404,405],{},"America\u002FSao_Paulo",", tanto na limpeza quanto no próprio crawler.",[15,408,410],{"id":409},"três-tipos-de-ausência","Três tipos de ausência",[11,412,413],{},"Uma célula vazia significava três coisas diferentes, e cada uma ganhou sua própria regra:",[23,415,416,426,432],{},[26,417,418,421,422,425],{},[29,419,420],{},"Não se aplica."," Defesas de um centroavante. Essas chaves nunca aparecem pra jogadores de linha, então recebem ",[269,423,424],{},"-1",", um valor que nenhuma contagem real pode ter.",[26,427,428,431],{},[29,429,430],{},"Não medido."," Estatísticas físicas (distância, sprints, velocidade máxima) existem em só 349 das 380 partidas, conduções de bola em só 83. Essas continuam ausentes. Preencher com zero seria afirmar que o jogador não correu.",[26,433,434,437,438,441,442,445],{},[29,435,436],{},"Zero, omitido."," O Sofascore não envia estatísticas que valem 0. Nos dados brutos, ",[269,439,440],{},"goals"," estava vazio em 11.124 linhas e ",[269,443,444],{},"expected_goals"," em 6.314, exatamente as linhas em que o jogador não finalizou. Esses viram 0.",[11,447,448],{},"Dentro dos grupos de físicas e de conduções, a regra é por jogador: se ele tem qualquer métrica do grupo, as que faltam são zeros omitidos. Se não tem nenhuma, o grupo não foi medido pra ele.",[11,450,451,452,455,456,458],{},"Um caso de borda: ",[269,453,454],{},"goals_prevented"," pode ser negativo pra goleiros (de -3,79 a 3,12), então ali o sentinela ",[269,457,424],{}," colide com valores reais. A coluna de posição desfaz o empate.",[15,460,462],{"id":461},"rodadas-fora-de-ordem","Rodadas fora de ordem",[11,464,465],{},"O jeito óbvio de ordenar uma temporada é pelo número da rodada. Está errado. Rodadas são remarcadas por causa das competições continentais, e a maior parte da rodada 34 só foi jogada em novembro, depois de as rodadas 35 a 37 terem sido jogadas em outubro. Ao longo da temporada, 1.795 dos 72.010 pares possíveis de partidas (2,49%) têm números de rodada que contradizem o horário de início.",[11,467,468,469,472,473,238],{},"Isso deixa de ser curiosidade assim que você calcula o histórico de um jogador. Ordenar por rodada em vez de horário muda a ordem das partidas de 335 dos 686 jogadores (48,8%) com dois jogos ou mais. Pra um modelo que só pode olhar partidas passadas, isso é vazamento: um jogo futuro se infiltrando no histórico de um jogo passado. Tudo dali pra frente ordena por ",[269,470,471],{},"kickoff_timestamp",", nunca por ",[269,474,475],{},"round",[11,477,478,479,482],{},"Depois da limpeza, 163 atuações sem nota foram descartadas, todas de 7 minutos ou menos. Sobraram 11.814 linhas de 732 jogadores, que foi com o que os modelos de ",[100,480,105],{"className":481,"href":102},[104]," foram treinados.",{"title":63,"searchDepth":64,"depth":64,"links":484},[485,486,487],{"id":398,"depth":64,"text":399},{"id":409,"depth":64,"text":410},{"id":461,"depth":64,"text":462},"My thesis needed every player rating from the 2025 Brazilian Série A. Sofascore has them, behind the same internal API its website uses. It isn't an official public API, so the crawler was built to be boring: one request every 1.2 to 2.4 seconds, retries with backoff, a checkpoint after every round so a crash resumes instead of starting over, and Pydantic validation on every row before it reaches the CSV.",{},{"title":491,"description":488},"A season of Sofascore data, and where it lies","notes\u002Fsofascore-data-traps","Coletando todas as notas de jogadores do Brasileirão 2025, e as três armadilhas nos dados: datas no fuso errado, zeros que nunca são enviados e rodadas jogadas fora de ordem.",[85,86,495],"data","eXQcuImFjV98ThxZr0pIfb9o9ar0a4y7Y8iJLRS93iQ",{"\u002Fnotes\u002Fva-longe-mas-va-acompanhado":498,"\u002Fnotes\u002Fpre-match-ratings":93,"\u002Fnotes\u002Fr2-too-good":105,"\u002Fnotes\u002Fsofascore-data-traps":358,"\u002Fprojects\u002Fargus-maximus":499,"\u002Fprojects\u002Frobbie":500,"\u002Fprojects\u002Fsofascore-rating-prediction":6},"Vá longe, mas vá acompanhado","Argus Maximus","robbie",1793409192617]