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=&lt=10100&reg=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=&lt=10100&reg=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 계산 결과로 나타났다.

2023 노시환

 

모델이 예측한 페디의 확률은 56%였다. 타이틀홀더가 3개, 삼진이 209개, 승수가 20개 등이 주요 이유로 나타났다.

2023 페디

결과적으로 [페디의 확률(56%)이 노시환(18%)보다 높으므로 머신러닝 모델은 페디가 MVP라고 예측했다]고 말할 수 있겠다. 

 

참고로 21년의 확률은 각각 미란다(14~35%), 최정(7~14%)였고 50%보다 확률이 낮았지만 2위인 최정보다 높았던 미란다가 MVP로 머신러닝 결과 예측됐고, 실제로도 MVP를 수상했다. 

 

 

2024년 결과

2024년은 30-30을 달성한 김도영이 MVP가 될 것으로 거의 확실시 되는 가운데 확률을 계산해보았다.

 

모델은 단 4%라고 계산해주었다. 가장 큰 마이너스 이유로는 고의사구가 7개라는 것으로 나왔는데, [과거의 강력한 MVP 타자들에게 보이는 다수의 고의사구가 김도영에겐 적었기 때문에]라고 해석할 수 있을 것 같다.

2024 김도영

 

한편 투수 쪽에서는 의외의 결과가 나온 것이, 투수 MVP라면 하트라고 생각했지만 모델의 생각은 달랐던 것 같다. 매우매우 작은 값이긴 하지만 하트의 확률은 0.00%가 나온데 비해 윌커슨은 0.03%가 나왔기 때문이다.

 

2024 하트
2024 윌커슨

"하트는 홀더가 1개 있지만 승이 13개밖에(?) 안 되고 볼넷이 38개였던 반면 윌커슨은 홀더가 0개이지만 사구가 2개밖에 안 되고 210개의 피안타를 맞았기 때문(?)에 매우 근소하게 윌커슨이 0.03%p 높았다"라고 해석할 수 있을 것 같지만 너무 작은 차이라 큰 의미는 없는 것 같다.

 

아무튼, 김도영의 확률(4%)가 윌커슨의 확률(0.03%)보다 높기 때문에 2024 MVP는 김도영이 받을 것으로 모델은 예측했다고 볼 수 있다.

 

 

여기서 생각해볼 점은, 만약 2023 노시환의 성적이 올해 나왔다면 노시환이 MVP를 받을 수 있지 않았을까 하는 점이다. 2023 노시환은 18%고, 2024 김도영은 4%이니 말이다. 물론 예측한 데이터에는 클래식 및 세이버 매트릭스 데이터만 있을 뿐, 30-30 달성, 30-100 달성 등의 타이틀은 제외되었으니 이런 타이틀 기록까지 생각하면 확률은 달라질 수 있겠지만.

 

 

전체 코드

github

728x90
반응형