2024 KBO MVP를 선수 스탯을 통해 머신러닝으로 예측해보기
- 목차
서론
올해로 4년째 해보는 토이 프로젝트이다. 먼저 결론부터 쓰자면 2024 KBO MVP는 김도영이 될 것으로 머신러닝 예측 결과가 나왔다.
2021년부터 계속해온 MVP 예측 결과를 정리하면 아래와 같다.
연도 | 예측 | 결과 |
2021 | 미란다 | 미란다 |
2022 | (잊음) | 이정후 |
2023 | 페디 | 페디 |
2024 | 김도영 | ??? |
2022년에는 잊고 있어서 빼먹었고, 2021, 2023년은 MVP 발표가 된 후 예측했던 거라 감흥이 적었다면 올해는 MVP 발표가 되기 전이라 결과가 기대된다.
아래부터는 어떻게 예측했는지를 설명하고자 한다.
데이터 수집 및 정제
당연한 소리지만 우선 데이터를 수집해야한다. 투, 타 모두 연도별로 선수 성적 데이터를 가져오는데 전체 데이터를 가지고 오면 MVP가 매우 적은 불균형한 데이터가 된다. 이를 막기 위해 연도별 WAR 기준으로 상위 10명의 데이터만 가져왔다. 사실 그렇게 하더라도 19:1 (투타 합 20명 중 1명)이기 때문에 여전히 불균형하긴 하지만, 너무 줄이면 데이터가 너무 적어진다는 걸 감안했다.
선수들의 상세 스탯은 스탯티즈에서 가져왔었는데, 스탯티즈가 스포키로 바뀌면서 수집 코드를 다시 작성해야했다. 또, WAR 계산 방식이 이전과 달라져서 82년부터 새로 싹 긁어왔다.
import requests
from bs4 import BeautifulSoup
import numpy as np
import pandas as pd
def batter(year):
url = f'https://statiz.sporki.com/stats/?m=main&m2=batting&m3=default&so=WAR&ob=DESC&year={year}&sy={year}&ey={year}&te=&po=<=10100®=R&pe=&ds=&de=&we=&hr=&ha=&ct=&st=&vp=&bo=&pt=&pp=&ii=&vc=&um=&oo=&rr=&sc=&bc=&ba=&li=&as=&ae=&pl=&gc=&lr=&pr=50&ph=&hs=&us=&na=&ls=&sf1=&sk1=&sv1=&sf2=&sk2=&sv2='
r = requests.get(url).text
soup = BeautifulSoup(r, 'lxml')
raw_data = soup.find_all('table')[0].find_all('tr')[2:12]
data = []
for row in raw_data:
data.append([td.text for td in row])
return data
def pitcher(year):
url = f'https://statiz.sporki.com/stats/?m=main&m2=pitching&m3=default&so=WAR&ob=DESC&year={year}&sy={year}&ey={year}&te=&po=<=10100®=R&pe=&ds=&de=&we=&hr=&ha=&ct=&st=&vp=&bo=&pt=&pp=&ii=&vc=&um=&oo=&rr=&sc=&bc=&ba=&li=&as=&ae=&pl=&gc=&lr=&pr=50&ph=&hs=&us=&na=&ls=&sf1=&sk1=&sv1=&sf2=&sk2=&sv2='
r = requests.get(url).text
soup = BeautifulSoup(r, 'lxml')
raw_data = soup.find_all('table')[0].find_all('tr')[2:12]
data = []
for row in raw_data:
data.append([td.text for td in row])
return data
추가적으로 데이터를 처음부터 새로 수집하긴 하지만 작년까지 썼던 컬럼 그대로 사용하고 싶어 한 번 더 손수 거르는 작업을 했다.
from tqdm.notebook import tqdm
stats_org = ['순위', '이름', '연도', 'WAR', 'oWAR', 'dWAR', 'G', '타석', 'ePA', '타수', '득점', '안타', '2타', '3타', '홈런', '루타', '타점', '도루', '도실', '볼넷', '사구', '고4', '삼진', '병살', '희타', '희비', '타율', '출루', '장타', 'OPS', 'R/ePA', 'wRC+', 'WAR2']
stats_use = ['이름', '연도', 'WAR', 'G', '타석', '타수', '득점', '안타', '2타', '3타', '홈런', '루타', '타점', '도루', '도실', '볼넷', '사구', '고4', '삼진', '병살', '희타', '희비',
'타율', '출루', '장타', 'OPS', 'wRC+']
bat = pd.DataFrame(columns=stats_use)
for year in tqdm(range(1982, 2024+1)):
data = pd.DataFrame(batter(year), columns=stats_org)[stats_use]
data['연도'] = year
bat = pd.concat([data, bat], axis=0, ignore_index=True)
stats_org = ['순위', '이름', '연도', 'WAR', '출장', '선발', '구원', 'GF', '완투', '완봉', '승', '패', '세', '홀', '이닝', '자책', '실점', 'rRA', '타자', '피안', '피2', '피3', '피홈', '피볼', '사구', '고4', '삼진', 'ROE', '보크', '폭투', 'ERA', 'RA9', 'rRA9', 'rRA9pf', 'FIP', 'WHIP', 'WAR2']
stats_use = ['이름', '연도', 'WAR', '출장', '완투', '완봉', '선발', '승', '패', '세', '홀', '이닝', '실점', '자책', '타자', '피안', '피2', '피3', '피홈', '피볼', '고4', '사구', '삼진', '보크', '폭투', 'ERA', 'FIP', 'WHIP']
pit = pd.DataFrame(columns=stats_use)
for year in tqdm(range(1982, 2024+1)):
data = pd.DataFrame(pitcher(year), columns=stats_org)[stats_use]
data['연도'] = year
pit = pd.concat([data, pit], axis=0, ignore_index=True)
타이틀홀더 및 MVP 데이터는 수기로 직접 작성해서 성적 데이터와 합쳐줬다. 작년까지 넣었던 우승팀 데이터는 1. 아직 한국시리즈가 안 끝나기도 했고 2. 우승팀 소속 여부가 MVP를 결정하는데 큰 관계가 없다는 것을 과거 예측 경험을 통해 확인했기 때문이다.
지금 생각해보니 오히려 외국인 여부를 넣는게 더 좋지 않을까 생각도 든다.
예측
가장 큰 문제점은 투, 타의 스탯이 완전히 다르기 때문에 어떻게 비교를 할 수가 없다는 것이다. 소가 뒷걸음질치다 쥐 잡듯이, 이를 회피하고자 시험해봤던 게 의외로 결과가 잘 나와서 이번에도 그 방법을 사용했다.
방법은 매우 간단하다.
1. 투수, 타자 각각 MVP를 예측하는 모델을 학습한다.
- 모델은 LightGBM를 사용했는데, RandomForest든 XGBoost든 다 비슷했다.
- MVP=1, 나머지=0으로 놓은 분류 모델
2. 1에서 학습한 모델에 올해의 투수, 타자 데이터를 넣고 확률을 예측한다.
3. 모델의 예측 확률이 더 높은 쪽이 MVP 확률이 더 높은 것으로 한다.
- MVP 예측 결과가 0이 나오더라도, 예측 확률이 조금이라도 더 높은 선수를 선택한다.
- ex) A 타자 확률 30%, B 투수 확률 25%일 때, 예측 결과 자체는 둘 다 MVP가 아니지만 확률상 더 높은 A를 선택한다.
이렇게 보면 참 엉터리같지만 이상하게 결과는 잘 나온다.
2023년 결과
먼저 2023년 결과를 돌이켜본다.
타자에서는 노시환이 30홈런-100타점을 넘으면서 MVP 후보로 올랐고, 투수에서는 페디가 20승-200삼진 평자책 1위로 MVP 후보로 올랐다.
모델이 예측한 노시환의 확률은 18%였다. 18%까지 오르는데까지 기여한 이유로는 타이틀홀더가 2개, 피삼진이 118개 등으로 shapley value 계산 결과로 나타났다.
모델이 예측한 페디의 확률은 56%였다. 타이틀홀더가 3개, 삼진이 209개, 승수가 20개 등이 주요 이유로 나타났다.
결과적으로 [페디의 확률(56%)이 노시환(18%)보다 높으므로 머신러닝 모델은 페디가 MVP라고 예측했다]고 말할 수 있겠다.
참고로 21년의 확률은 각각 미란다(14~35%), 최정(7~14%)였고 50%보다 확률이 낮았지만 2위인 최정보다 높았던 미란다가 MVP로 머신러닝 결과 예측됐고, 실제로도 MVP를 수상했다.
2024년 결과
2024년은 30-30을 달성한 김도영이 MVP가 될 것으로 거의 확실시 되는 가운데 확률을 계산해보았다.
모델은 단 4%라고 계산해주었다. 가장 큰 마이너스 이유로는 고의사구가 7개라는 것으로 나왔는데, [과거의 강력한 MVP 타자들에게 보이는 다수의 고의사구가 김도영에겐 적었기 때문에]라고 해석할 수 있을 것 같다.
한편 투수 쪽에서는 의외의 결과가 나온 것이, 투수 MVP라면 하트라고 생각했지만 모델의 생각은 달랐던 것 같다. 매우매우 작은 값이긴 하지만 하트의 확률은 0.00%가 나온데 비해 윌커슨은 0.03%가 나왔기 때문이다.
"하트는 홀더가 1개 있지만 승이 13개밖에(?) 안 되고 볼넷이 38개였던 반면 윌커슨은 홀더가 0개이지만 사구가 2개밖에 안 되고 210개의 피안타를 맞았기 때문(?)에 매우 근소하게 윌커슨이 0.03%p 높았다"라고 해석할 수 있을 것 같지만 너무 작은 차이라 큰 의미는 없는 것 같다.
아무튼, 김도영의 확률(4%)가 윌커슨의 확률(0.03%)보다 높기 때문에 2024 MVP는 김도영이 받을 것으로 모델은 예측했다고 볼 수 있다.
여기서 생각해볼 점은, 만약 2023 노시환의 성적이 올해 나왔다면 노시환이 MVP를 받을 수 있지 않았을까 하는 점이다. 2023 노시환은 18%고, 2024 김도영은 4%이니 말이다. 물론 예측한 데이터에는 클래식 및 세이버 매트릭스 데이터만 있을 뿐, 30-30 달성, 30-100 달성 등의 타이틀은 제외되었으니 이런 타이틀 기록까지 생각하면 확률은 달라질 수 있겠지만.
전체 코드
'데이터 분석 > 머신러닝' 카테고리의 다른 글
분류 모델의 shap value를 확률값으로 확인하기 (0) | 2024.05.27 |
---|---|
shap 명목형 변수 표기 방법 바꾸기 (0) | 2024.05.21 |
선형회귀로 시계열 예측 모델 만들 때 조심할 점 (0) | 2024.04.16 |
AutoML, Autogluon vs mljar-supervised (0) | 2024.02.29 |
Regression - IBUG 알고리즘 논문 읽기 (0) | 2024.02.27 |