한동안 캐글과 데이콘에 집중한다는 핑계로 티스토리에 거의 들어오지도 않았다. 반성해야지. 사실 그렇게 죽을만큼 바쁘지도 않았던 것 같은데.
일단 당장 하다 끊긴 데이콘 5장부터 좀 마무리를 지어야겠다는 생각에 거의 2주만에 다시 글을 쓰기 시작한다.
직전까지는 우선 외국인 투수 관련된 자료들의 형태와, 대략적인 경향성을 EDA를 통해 확인해 보았고 이제는 본격적으로 데이터 전처리를 시작하자.
한 2주동안 전처리만 계속 하다가, 관련된 우스갯소리를 들었다. 데이터 분석가는 전체 시간의 80%를 데이터 전처리하는데에 쓰고, 나머지 20%는 전처리가 제대로 되었기를 바라는데 쓴다나... 정말 내가 마주한 현실을 이보다 잘 표현한 것은 없을 것 같다.
서두가 길었는데, 바로 코드로 들어가자.
import pandas as pd
import os
os.chdir('/Users/heechankang/projects/pythonworkspace/dacon_data/baseball_pitcher')
이건 뭐 굳이 써야할까 싶지만, 이래저래 귀찮을 때 바로 경로 바꿔버리면 되니 내가 아는 방법중에는 제일 간단한 것 같다. 왜 매일 어딘가 이상한곳으로 바뀌어 있는걸까.. 공부가 부족한 건가? 여하튼, 디렉토리를 원하는 곳으로 바꿔준다.
# 데이터셋 불러오기
atKbo_11_18_KboRegSsn = pd.read_csv('kbo_yearly_foreigners_2011_2018.csv')
atKbo_11_18_MlbTot = pd.read_csv('fangraphs_foreigners_2011_2018.csv')
atKbo_11_18_StatCast = pd.read_csv('baseball_savant_foreigners_2011_2018.csv')
atKbo_19_MlbTot = pd.read_csv('fangraphs_foreigners_2019.csv')
atKbo_19_StatCast = pd.read_csv('baseball_savant_foreigners_2019.csv')
데이터셋 불러오기.
atKbo_11_18_KboRegSsn.head(10)
atKbo_11_18_MlbTot.head(10)
atKbo_11_18_StatCast.head(10)
이제 파일 불러오면 습관적으로 head부터 찍어본다.
뭐 지난번에 다 봤던 자료들이지만, 자료마다 컬럼이 다 다르다. 다루기 불편할것같다. 다만 다행히도 세 자료 모두 투수이름이 표기되어있다. (스탯캐스트는 컬럼이 많아 잘렸다. 마지막 열에 투수이름이 있다)
투수이름을 보기 위해 unique()함수를 이용하면
print('KBO: ', len(atKbo_11_18_KboRegSsn['pitcher_name'].unique()))
print('MLB: ', len(atKbo_11_18_MlbTot['pitcher_name'].unique()))
print('StatCast: ', len(atKbo_11_18_StatCast['pitcher_name'].unique()))
KBO: 62
MLB: 60
StatCast: 60
이런 결과를 볼 수 있다. 다시 말해 해당 기간동안 KB에서는 62명의 선수가, MLB에서는 60명의 선수가 출전했다는 걸 알 수 있다. 다만 주의해야 할 점은, statcast에 60명이 있고, MLB에도 60명이 있다고 무의식적으로 같겠거니 해서는 안된다는 것이다.
이런 점은 이번에 캐글과 다른 주제의 데이콘을 진행하면서 더더욱 뼈저리게 느꼈다. 모든 것을 의심해야겠다...
여하튼, 결론적으로 각각의 자료에 중복되는 인원도 있지만, 다른 인원들이 일부 포함되어있다. 다시말해, 우리는 세 개의 자료 모두에서 등장하는 투수의 명단만을 뽑아내야 한다. 그래야만 비교를 할 수 있을테니 말이다.
target = (set(atKbo_11_18_KboRegSsn['pitcher_name']) &
set(atKbo_11_18_MlbTot['pitcher_name']) &
set(atKbo_11_18_StatCast['pitcher_name']))
# set를 통해 중복을 제거하고 전체 인원을 하나의 세트로 만들기.
print(type(target)) # set
target = sorted(list(target)) # 편의를 위해 오름차순 정의 후 리스트로 변환
print(type(target)) # list
print(len(target)) # 중복제거 후 총 명단 - 57명
당장 빠르게 중복을 제거해야한다면, set만큼 좋은게 없는 것 같다. 뭐 크게 계산을 하거나 코드를 써야하는 것도 아니니 번거롭지도 않고, 속도도 빠르다. 다만, 종종 같은 대상을 다르게 표기하는 경우에는 어쩔 수 없으므로 최대한 꼼꼼히 하나씩 찍어보는 노력도 필요하다. 다행히 여기서는 그런 경우는 없었다.
_idx = atKbo_11_18_KboRegSsn.query('pitcher_name in @target').\
groupby('pitcher_name')['year'].idxmin().values
firstYearInKBO_11_18 = atKbo_11_18_KboRegSsn.loc[_idx, :]
firstYearInKBO_11_18.head()
이 코드에서 나는 @의 사용을 처음 봤다.
이런식으로 쿼리문 내에서 명단을 불러내고, 동시에 선수 이름별로 묶어서 등장한 기록들의 year행의 가장 작은값을 뽑아내는 과정까지 만약 당장 나에게 시킨다면 여러 과정을 거쳐서 했을텐데, 이런걸 한번에 쓰는 걸 보면 난 아직 멀었구나 싶다..
아무튼, 여기까지 해서 이제 분석대상을 분리 해 낼 수 있다. 즉, MLB와 KBO에서 모두 활약했던 선수들의 데이터를 뽑아 낼 수 있으니 이를 바탕으로 두 리그에서의 성적을 비교하고, 어떠한 요인이 어떻게, 얼마나 영향을 끼치는지를 자세히 살펴 볼 차례이다.
다만, 그렇다고 이 모든 선수들의 자료를 그대로 가져다 쓸 수는 없다.
TBF_median = firstYearInKBO_11_18['TBF'].median() # TBF: 상대한 타자 수
ERA_median = firstYearInKBO_11_18['ERA'].median() # ERA: 평균자책점
Elite_11_18 = firstYearInKBO_11_18.query('TBF >= @TBF_median & ERA <= @ERA_median')
Elite_11_18
그래서 우승자분은 상대한 타자 수는 중앙값 이상인 경우에만, 그리고 평균자책점은 중앙값 이하인 경우만 반영해서 우수 타자를 선별하기로 했다. 그래서 각 중앙값을 변수로 설정한 후, 쿼리함수를 통해 Elite_11_18이라는 변수에 분리된 값을 저장했다. 이렇게 걸러진 엘리트들은 총 18명으로, 명단은 아래와 같다.
이제는 이 대상자들과 그들의 데이터를 통해서 본격적으로 모델을 구축하고 결과를 검증 해 보도록 할 예정이다.