관리 메뉴

커리까지

NBA 선수 은퇴 나이 예측해보기_01 본문

프로젝트/NBA 선수 은퇴나이 추청

NBA 선수 은퇴 나이 예측해보기_01

목표는 커리 2021. 1. 1. 21:56
728x90
SMALL

데이터 준비

  1. kaggle
    1. 2020년까지 NBA Players 정보
    2. 2010-2020년까지 NBA 선수들 부상 정보
  2. 위키백과
    1. 크롤링으로 2010-2020년까지 은퇴 선수 목록

위키백과 NBA 선수 은퇴 정보 크롤링

필요한 패키지 불러오기

1
2
3
4
5
6
from urllib.request import urlopen
from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.error   import HTTPError
from urllib.error   import URLError
import pandas as pd
cs

셀리엄 불러오기

1
2
3
4
from selenium import webdriver
import time
path = './driver/chromedriver.exe'
driver = webdriver.Chrome(path)
cs

크롤링 함수 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def craw():
    time_list = ['2010-11','2011-12','2012-13','2013-14','2014-15','2015-16','2016-17','2017-18','2018-19','2019-20','2020-21']
    day_list = ['2010','2011','2012','2013','2014','2015','2016','2017','2018','2019','2020']
    name_list = []
    age_list = []
    year_list = []
    cnt = 0
    for day in time_list:
        driver.get('https://en.wikipedia.org/wiki/List_of_'+str(day)+'_NBA_season_transactions')
        page = driver.find_elements_by_css_selector('.wikitable')
        page = page[0]
        for i in page.find_elements_by_tag_name('tbody'):
            k = i.find_elements_by_tag_name('tr')
            for idx,j in enumerate(k):
                if idx == 0:
                    continue
                else:
                    td_list = j.find_elements_by_tag_name('td')
                    if len(td_list) == 6
                        name_list.append(j.find_elements_by_tag_name('td')[1].text)
                        age_list.append(j.find_elements_by_tag_name('td')[3].text)
                        year_list.append(day_list[cnt])
                    elif len(td_list) == 5:
                        name_list.append(j.find_elements_by_tag_name('td')[0].text)
                        age_list.append(j.find_elements_by_tag_name('td')[2].text)
                        year_list.append(day_list[cnt])
            cnt +=1
            print(cnt)
    return name_list, age_list, year_list
cs
  1. 01
    • 가져와야 할 데이터가 wikitable class로 되어있고 그 안에 tbody와 td, tr로 이루어져 있었다.
    • 그거에 맞게 크롤링 구조를 구성하였다.

크롤링하기

1
name_list, age_list, year_list = craw()
cs

df로 만들기

1
2
3
4
5
nba_df = pd.DataFrame({
    'name' : name_list,
    'age' : age_list,
    'season' : year_list
})
cs

csv로 저장

1
nba_df.to_csv('./data/nba_df.csv',mode='w',index=False)
cs

분석해보기

필요한 패키지 import

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.pylab as plt
import datetime 
%matplotlib inline
import matplotlib
matplotlib.rcParams['axes.unicode_minus'= False
 
import matplotlib.pyplot as plt
%matplotlib inline
 
import platform
import seaborn as sns
 
import warnings
warnings.filterwarnings('ignore')
 
from matplotlib import font_manager, rc
from matplotlib import style
 
if platform.system() == 'Darwin':
    rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
    path = "c:/Windows/Fonts/malgun.ttf"
    font_name = font_manager.FontProperties(fname=path).get_name()
    rc('font', family=font_name)
else:
    print('Unknown system... sorry~~~~'
cs

파일 불러오기

1
2
3
nba_retire = pd.read_csv('nba_df.csv')
nba_all = pd.read_csv('all_seasons.csv')
nba_injury = pd.read_csv('injuries_2010-2020.csv')
cs

정보 확인 함수

1
2
3
4
def info(df):
    display(df.describe())
    display(df.info())
    display(df.isna().sum())
cs
  • 비어있는 값이 있는지 어떤 행의 값이 어떤 type인지 확인해본다.

EDA

02
1
nba_all.drop('Unnamed: 0',axis=1,inplace=True)
cs
  • 필요없는 행이 있어서 제거
1
2
nba_injury_sum = nba_injury.groupby('Relinquished'
as_index=False).agg({'Notes':'count'}).sort_values('Notes',ascending=False).reset_index(drop=True)
cs
  • 선수들의 부상 횟수를 묶어서 저장한다.
1
2
nba_player = nba_all.groupby('player_name',
as_index=False).agg({'season':'count'}).sort_values('season',ascending=False).reset_index(drop=True)
cs
  • 선수들의 선수생활 기간을 저장한다.
1
2
nba_1020_injury = pd.merge(nba_player,nba_injury_sum,left_on='player_name',right_on='Relinquished')
nba_1020_injury.drop('Relinquished',axis=1,inplace=True)
cs
  • 부상횟수와 선수생활 파일을 합친다.
1
2
3
nba_retire_merge = pd.merge(nba_1020_injury, nba_retire,left_on='player_name',right_on='name')
nba_retire_merge.drop('name',axis=1,inplace=True)
nba_retire_merge.columns=['name','year','count','retire_age','retire_season']
cs
  • 위에서 만든 파일과 은퇴 파일을 합친다.
1
nba_retire_merge.head()
cs
03
  • 최종적으로 만들어진 파일은 다음과 같다.
1
nba_retire_merge.describe()
cs
  • 요약해보았다
    07

상관관계 구해보기

1
2
3
4
5
6
def corr(data,text):
    corr = data.corr(method='pearson')
    display(corr)
    style.use('ggplot')
    plt.title(text)
    sns.heatmap(data = corr, annot=True, fmt = '.2f', linewidths=.5, cmap='Blues')
cs
1
corr(nba_retire_merge,'상관관계')
cs
0504
  • 생각처럼 부상횟수가 은퇴에 큰 영향을 미친다고 보기 어려웠다. 오히려 year(리그에서 생활한 기간)이 은퇴나이와 연관이 더 높았다.

부상 횟수 상위 10명 알아보기

1
nba_retire_merge.sort_values('count',ascending=False).head(10).reset_index(drop=True)
cs
06
  • 드웨인 웨이드는 아직 은퇴하기 아쉬울 정도로 일찍 은퇴한 감이 있었다. 많은 부상에도 불구하고 평균보다 더 많이 뛰었다. 1위와 2위는 챔피언을 경험한 선수들이다. 많은 경기를 뛴 만큼 데미지가 많았을 텐데 현대 의학의 발전덕분일까?

부상 정보 다시 크롤링해서 가져오기

  • 2010~2020년 사이에 은퇴한 선수들의 데뷔초부터 부상정보를 가져오기 위해 다시 크롤링 하였다.
    • 기준은 빈스카터의 데뷔 연도 1998년도를 기준으로 가져왔다.

필요한 패키지 import

1
2
3
4
5
6
7
8
9
10
11
12
from urllib.request import urlopen
from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.error   import HTTPError
from urllib.error   import URLError
 
import pandas as pd
 
from selenium import webdriver
import time
path = '../driver/chromedriver.exe'
driver = webdriver.Chrome(path)
cs

크롤링 함수 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def craw(start,end):
    page_list = [ i for i in range(start,end,25)]
    Date = []
    Team = []
    Acquired = []
    Relinquished = []
    Notes = []
    for page in page_list:
        driver.get('http://www.prosportstransactions.com/basketball/Search/SearchResults.php?Player=&Team=&BeginDate=1998-01-01&EndDate=2020-12-31&ILChkBx=yes&Submit=Search&start='+str(page))
        page = driver.find_elements_by_css_selector('.datatable')
        if len(page) != 0:
            for i in page[0].find_elements_by_tag_name('tbody'):
                k = i.find_elements_by_tag_name('tr')
                for data in k:
                    ll = data.find_elements_by_tag_name('td')
                    Date.append(ll[0].text)
                    Team.append(ll[1].text)
                    Acquired.append(ll[2].text)
                    Relinquished.append(ll[3].text)
                    Notes.append(ll[4].text)
    return   Date,Team, Acquired,Relinquished, Notes
cs

![08](https://user-images.githubusercontent.com/52574837/103480098-979f2080-4e15-11eb-8755-1ccacf32e9e2.png)

  • 테이블이 datatable class로 되어있어서 이걸로 tbody와 tr, td로 찾아서 저장하였다.

함수 실행하기

1
2
3
4
5
6
Date,Team, Acquired,Relinquished, Notes = craw(0,5001)
Date1,Team1, Acquired1,Relinquished1, Notes1 = craw(5001,10001)
Date2,Team2, Acquired2,Relinquished2, Notes2 = craw(10001,15001)
Date3,Team3, Acquired3,Relinquished3, Notes3 = craw(15000,20001)
Date4,Team4, Acquired4,Relinquished4, Notes4 = craw(20001,25001)
Date5,Team5, Acquired5,Relinquished5, Notes5 = craw(25001,28526)
cs
  • 25개씩 이루어져있어 start, end로 url을 맞춰주었다. 28525를 해버리니 중간에 렉이 걸려서 따로 따로 실행하였다.

df로 만들기

1
2
3
4
5
6
7
df1 = pd.DataFrame({
    'Date':Date,
    'Team' : Team,
    'Acquired' : Acquired,
    'Relinquished' : Relinquished,
    'Notes' : Notes    
                   })
cs
  • 이런 식으로 5개의 df를 만들었다.
1
df1.to_csv('df1.csv',mode='w',index=False)
cs
  • 혹시 몰라서 csv로 우선 저장하였다.

중복 컬럼 제거하기

1
2
3
4
5
6
7
nba_injury_1998 = pd.concat([df1,df2,df3,df4,df5,df6])
drop_index = list(nba_injury_1998[nba_injury_1998['Date']==' Date'].index)
nba_injury_1998 = nba_injury_1998.drop(drop_index).reset_index(drop=True)
none_Relinquished = list(nba_injury_1998[nba_injury_1998['Relinquished'==''].index)
nba_injury_1998 = nba_injury_1998.drop(none_Relinquished).reset_index(drop=True)
nba_injury_1998 = nba_injury_1998.drop(['Acquired'],axis=1)  
nba_injury_1998.to_csv('nba_injury_1998.csv',mode='w',index=False)
cs
  • 데이터들을 행으로 합치고 중간에 컬럼이 계속 중복으로 들어가서 그것의 인덱스를 찾아서 제거해준다.
  • 또한 Relinquished가 비어있는 행을 지우고 Acquired 열도 지운다.

정리

1
2
3
4
5
6
7
8
9
10
11
for i in range(nba_injury_1998.shape[0]):
    if nba_injury_1998.loc[i,'Relinquished'!= '':
        nba_injury_1998.loc[i,'Relinquished'= nba_injury_1998.loc[i,'Relinquished'].split('•')[1].strip()
        nba_injury_1998.loc[i,'Date'= nba_injury_1998.loc[i,'Date'].strip()
        nba_injury_1998.loc[i,'Team'= nba_injury_1998.loc[i,'Team'].strip()
        nba_injury_1998.loc[i,'Notes'= nba_injury_1998.loc[i,'Notes'].strip()
    if nba_injury_1998.loc[i,'Relinquished'=='':
        nba_injury_1998.loc[i,'Relinquished'= nba_injury_1998.loc[i,'Relinquished']
        nba_injury_1998.loc[i,'Date'= nba_injury_1998.loc[i,'Date'].strip()
        nba_injury_1998.loc[i,'Team'= nba_injury_1998.loc[i,'Team'].strip()
        nba_injury_1998.loc[i,'Notes'= nba_injury_1998.loc[i,'Notes'].strip()
cs
  • • Elliot Williams 데이터 앞에 기호와 띄어쓰기가 있어서 정리해주었다. 다른 행도 띄어쓰기를 정리하였다.
1
2
3
4
5
6
7
for i in range(nba_injury_1998.shape[0]):
    data = nba_injury_1998.loc[i,'Notes'].split('with')
    print(data)
    if data[0in  ['placed on IL ','placed on IR ']:
        nba_injury_1998.loc[i,'Notes2'= data[1].strip()
    else:
        nba_injury_1998.loc[i,'Notes2'= nba_injury_1998.loc[i,'Notes']
cs
  • 부상 앞에 placed on IR with 혹은 placed on IL with 가 있어서 뒤에것만 저장하였다.

EDA2

1
2
3
4
5
6
7
8
9
10
11
12
name_list = nba_retire.groupby(['name']).count().sort_values('age',ascending=False)
name_list = list(name_list[name_list['age'== 2].index)
name_lis
 
>
['Keyon Dooling',
 'Rasheed Wallace',
 'Elton Brand',
 'Nazr Mohammed',
 'Brandon Roy',
 'Nick Collison',
 'Boštjan Nachbar']
cs
  • 다음의 선수들이 2개씩 있어서 이른 시즌에 있는 것들을 지우기로 하였다.
1
2
3
4
5
6
7
8
index_list = []
while len(name_list) > 0:
    cnt = len(name_list)
    for idx, value in nba_retire.iterrows():
        if value[0in name_list:
            index_list.append(idx)
            name_list.remove(value[0]) 
nba_retire = nba_retire.drop(index_list).reset_index(drop=True)
cs
  • 정상적으로 지워졌다.
1
2
nba_player = nba_all.groupby('player_name',
as_index=False).agg({'season':'count'}).sort_values('season',ascending=False).reset_index(drop=True)
cs

이름 바꿔주기

1
2
3
4
5
6
7
8
9
nba_01 = pd.merge(nba_retire, nba_player, left_on='name', right_on='player_name',how='left').sort_values('season_y').reset_index(drop=True)
nba_01 = nba_01.drop(['season_x','player_name'],axis=1).rename({'season_y':'season'},axis=1)
name_list = ['Rasho Nesterovic','Zydrunas Ilgauskas','Peja Stojakovic','T.J. Ford','Eduardo Najera','Vladimir Stepania','Darko Milicic',
             'Hedo Turkoglu','Kosta Perovic','Raul Lopez','Andres Nocioni','Primoz Brezec','Bostjan Nachbar','Jiri Welsch',
            'PJ Hairston','Manu Ginobili','Mike Dunleavy','Mirza Teletovic','Gerald Henderson','Jose Calderon','Kevin Seraphin']
cnt = 0
for i in range(155,176):
    nba_01.loc[i,'name'= name_list[cnt]
    cnt += 1
cs
  • 은퇴 정보와 player정보를 합쳐서 어떤 선수의 정보가 합쳐지지 않았는지 확인하고 nba_all 원래 파일에 이름을 대조하여 리스트를 만들었다.
    • 그 다음 해당 리스트의 있는 정보들을 바꾸어주었다.
1
2
nba_02 = pd.merge(nba_01, nba_player, left_on='name'
         right_on='player_name').drop(['season_x','player_name'],axis=1).rename({'season_y':'season'},axis=1)
cs
  • 이름을 바꿔준 파일을 다시 merge하였다.
1
2
nba_injury_sum = nba_injury.groupby('name'
                as_index=False).agg({'Notes':'count'}).sort_values('Notes',ascending=False).reset_index(drop=True)
cs
  • 부상 횟수를 합쳤을 때 이름에 전처리 해야할 것들이 많았다.

injury에 있는 이름 바꿔주기

</br<
1
2
import re
re.split('[/)]',nba_injury_sum.loc[0,'name'])
cs
  • 이러한 식으로 바꿔주려고 한다.

이름 바꿔주기 적용

1
2
3
4
5
6
import re
for i in range(nba_injury_sum.shape[0]):
    for namedata in re.split('[/()]',nba_injury_sum.loc[i,'name']):
        if namedata.strip() in list(nba_02['name']):
            print(namedata.strip())
            nba_injury_sum.loc[i,'name'= namedata.strip()
cs
  • 다음과 같이 특수 부호 있는 부분을 나누고 다시 for를 돌려 나눠진 이름중에 nba_02['name']에 있으면 그 이름을 행의 값으로 저장하였다.
1
2
           name    Notes
0    Tony Parker    50
cs
  • 토니 파거나 여타 다른 것들은 적용이 되었다.
1
2
3
                                 name              Notes
17    Nene / Nene Hilario / Maybyner Hilario            29
28    James Michael McAdoo / James McAdoo (Michael)    26
cs
  • 그러니 이렇게 적용 안 된 부분이 있어서 다시 확인하려고 한다.
  • 그냥 이대로 진행해도 될 것 같아서 그대로 진행하기로 하였다.

NaN값 제거

1
2
3
nba_injury_merge = pd.merge(nba_02,nba_injury_sum,on='name',how='left').sort_values('Notes',ascending=False).reset_index(drop=True)
nba_injury_merge.dropna(inplace=True)
nba_injury_merge.sort_values('season',ascending=False).reset_index(drop=True)
cs

다시 상관관계

1
corr(nba_injury_merge,'상관관계')
cs
09
  • 전보다 0.14가 올랐다. 데이터 수를 더 찾은게 도움이 된 듯하다. 그러나 아직도 높은 수치는 아니다.

부상 횟수 상위 10명 상관관계

1
2
top_10 = nba_injury_merge.sort_values('Notes',ascending=False).head(10).reset_index(drop=True)
corr(top_10,'상관관계')
cs
10
  • 상관관계가 많이 높아졌다. 상위로 뽑아서 그런지 어느정도 영향을 주나보다.

부상 횟수 상위 15명 상관관계

1
2
top_15 = nba_injury_merge.sort_values('Notes',ascending=False).head(15).reset_index(drop=True)
corr(top_15,'상관관계')
cs
11
  • 상위 15명부터 확 떨어진다.
12
  • 아마 5,6위 때문에 상위 10명은 높게 나온것 같다.
  • 그럼 이제 예측을 연구해보자.
728x90
LIST
Comments