Project_Log

[ Python Project ] EPL 데이터 분석을 통한 토트넘의 현실 - 2

Jerry_JH 2021. 4. 16. 15:53
728x90

이전 포스팅에서 이어지는 내용이므로 꼭 보고 오는 것을 추천한다.

 

2021.04.15 - [University] - [ Python Project ] EPL 데이터 분석을 통한 토트넘의 현실 - 1

 

[ Python Project ] EPL 데이터 분석을 통한 토트넘의 현실 - 1

참고로 나는 이제 막 4년 차가 된 토트넘 팬이다. 2018년 DESK의 경기력과 16-17 시즌의 준우승을 보고 그때부터 토트넘 팬이 되었다. (특히 2018-19 챔스는 정말 잊을 수가 없다.) 2019년 여름에는 토트

jerrys-ai-lab.tistory.com

 

저번 포스팅에서 토트넘의 현재 순위까지 알아보았었다.

4월 5일 기준(30R)으로 7등이었고, 골득실로 보았을 때는 5등에 위치하였다. 

여기서 궁금증이 생겼는데, 

우승을 하기 위해서는 어느 정도의 승점이 필요하고, 어느정도의 골과 슈팅이 필요할까? 

이번 포스팅은 이 궁금증을 해결하고 결론으로 마무리 될 예정이다. 


[ 우승팀 / 강등팀의 승점 ] 

 

[ 우승팀의 승점 / 분석 ] 

 

각 시즌마다 우승팀을 뽑아보기 위해 시즌마다 순위를 정리해주었다.

# 시즌 list
season=list(EPL_data['Season'].unique())

한번에 처리하기 위해 season 리스트를 만들어주었다.

 

for count,i in enumerate(season,1994):
    rank = {}  
    s=EPL_data.loc[EPL_data['Season']==i,:] # 시즌별로 추출
    
    for j in s.index:
        if s['FTR'][j]=='H': # 홈팀 승리
            if s['HomeTeam'][j] in rank: # 딕셔너리 안에 있으면 추가
                rank[s['HomeTeam'][j]] = rank[s['HomeTeam'][j]]+ 3
            else:
                rank[s['HomeTeam'][j]] = 3
        elif s['FTR'][j]=='D': # 동점 
            if s['HomeTeam'][j] in rank: # 딕셔너리 안에 있으면 추가
                rank[s['HomeTeam'][j]] = rank[s['HomeTeam'][j]]+ 1
            else:
                rank[s['HomeTeam'][j]] = 1
            if s['AwayTeam'][j] in rank: # 딕셔너리 안에 있으면 추가
                rank[s['AwayTeam'][j]] = rank[s['AwayTeam'][j]]+ 1
            else:
                rank[s['AwayTeam'][j]] = 1
        else: # 어웨이팀 승리 
            if s['AwayTeam'][j] in rank: # 딕셔너리 안에 있으면 추가
                rank[s['AwayTeam'][j]] = rank[s['AwayTeam'][j]]+ 3
            else:
                rank[s['AwayTeam'][j]] = 3
    
    df = pd.DataFrame(list(rank.items()), columns=['TEAM', 'Point'])   
    df=df.sort_values(by='Point',ascending=False)
    df= df.reset_index(drop=True)
    df.index = df.index + 1
    df["season"] = count
    
    globals()["s_"+str(count)] = df

처음으로 enumerate 라는 함수를 사용해보았다. 

zip과 비슷한 역할을 하고 1개 반복문의 다수의 변수를 넣을 수 있다는 장점이 있다.

 

season 별로 loc를 뽑아서 한 행씩 처리를 한다.

홈팀이 승리 했을 경우 / 어웨이 팀이 승리 했을 경우 / 동점인 경우  - 3가지로 나눠서 처리했다.

EPL 시스템에서는 이기면 승점 3점이고 지면 0점 동점이면 1점이다. 

 

그리고 계속 누적을 해야했기에 딕셔너리 안에 있으면 초기화가 안되고 누적되는 방법을 이용했다.

그 딕셔너리를 DataFrame으로 만들어 승점별로 내림차순을 하여 보기 쉽게 인덱스를 초기화 해주었고,

globals를 이용해 동적으로 변수가 생성되게 만들었다.

 

concat을 통해 각 DataFrame을 통합하면 이러하다. 

1993-94 ~ 1997-98 시즌 순위이다. (1995-96시즌 부터 20개의 팀으로 줄여서 NaN으로 나온다)

 

전체를 concat을 한 후 index가 1인 즉, 그 시즌에서 우승팀들만 뽑아보았다.

total=pd.concat([s_1994,s_1995,s_1996,s_1997,s_1998,s_1999,s_2000,s_2001,s_2002,s_2003,s_2004,s_2005,s_2006,s_2007,s_2008,s_2009,s_2010,s_2011,s_2012,s_2013,s_2014,s_2015,s_2016,s_2017,s_2018,s_2019,s_2020,], axis=0)
best_season=total[total.index==1]

각 시즌의 우승팀들과 우승 당시 승점이다.

 

우승팀의 count를 뽑아보면 이러하다. 

93-94 시즌부터 지금까지 우승횟수이다.

위에 DataFrame에서도 볼 수 있듯이 맨체스터 유나이티드의 압도적인 우승 횟수를 볼 수있다.

 

각 우승팀의 승점을 알아보고 분석을 하기 위해 시각화와 여러 통계자료를 만들어보았다. 

 

시즌마다 우승팀들의 승점에 대한 시각화자료 
각 시즌마다 우승팀승점에 대한 통계자료 분석 

이 시각화와 통계자료를 보고 생각을 해보았다. 

 

통계자료에서 중요하게 본 것은 최솟값과 중앙값, 평균이다.

min은 최소 승점이 75점은 되어야 우승을 할 가능성이 있다는 것을 보여준다.

( 현재 토트넘은 30경기 49점이며 8경기 남은 상태 )

( 전승을 한다고 하여도 49+(8x3) = 73점... 우승은 가능성없다.)

 

mean과 50%(중앙값)에서 보여주는 것은 승점이 87점~89점 정도 되면 우승이라는 수치를 보여준다.

 


[ 강등팀의 승점 / 분석 ] 

 

강등팀도 우승팀과 마찬가지로 한 DataFrame으로 모아보았다.

강등을 할 수 있는 조건을 보는 것이기 때문에 

강등팀중에서 높은 승점으로 떨어지는 팀들만 모았다. (아슬아슬하게 커트라인으로 강등되는 팀들)

lo_1=pd.concat([s_1996,s_1997,s_1998,s_1999,s_2000,s_2001,s_2002,s_2003,s_2004,s_2005,s_2006,s_2007,s_2008,s_2009,s_2010,s_2011,s_2012,s_2013,s_2014,s_2015,s_2016,s_2017,s_2018,s_2019,s_2020,], axis=0)
lo_2=s_1994[s_1994.index==20]
lo_3=s_1995[s_1995.index==19]
lo_1=lo_1[lo_1.index==18]
lo_total=pd.concat([lo_1,lo_2,lo_3],axis=0)
lo_total=lo_total.sort_values(by='season',ascending=False)

우승팀에서 모았던 것과 다른점이 있을텐데, 

일단 93-94시즌에는 20, 21, 22 등이 강등을 했고, 

94-95시즌에는 19,20,21,22 등이 강등을 했다. 

그 후로는 18,19,20 등만 강등을 해서 저렇게 분할하여 concat를 하였다.

 

93-94시즌부터 강등팀(제일 높은 승점) 목록 

이 DataFrame을 토대로하여 우승팀처럼 count와 시각화, 통계분석을 해보았다.

count에서 놀라운 점은 맨시티가 강등된 적이 있다는 점!
강등팀 승점들을 시각화
강등팀중에서 상위권만 뽑아서 살펴본 통계자료

여기서도 아까 우승팀처럼 나름의 분석을 해보자면

mean와 50%를 보았을 때, 승점이 36점정도 되면 강등할 가능성이 크다는 것을 알 수있다.

 

 

이렇게 우승팀과 강등팀의 승점과 나름의 분석을 해보았다. 

다음은 우승팀들의 공통점이 무엇인지.. 공격력과 수비력 측면에서 살펴보겠다. 

 


[ 우승하는 팀들의 공통점 (공격력, 수비력 측면) ] 

[ 공격력 ]

공격력은 득점과 슈팅 수 , 유효 슈팅 수로 살펴보았다.

 

득점부터 코딩을 해보았다.

 

best_team=list(best_season['TEAM'])
count =1994

for s,t in zip(season,best_team):
    b_goal = {}  
    s_data=EPL_data.loc[(EPL_data['Season']==s),:] 
    s_data=s_data.loc[(s_data['HomeTeam']==t)|(s_data['AwayTeam']==t),:] 

    for j in s_data.index:
        #득점
        if s_data['HomeTeam'][j] == t: # 홈팀일 때
            if s_data['HomeTeam'][j] in b_goal: # 홈팀에서 골 
                b_goal[s_data['HomeTeam'][j]] = b_goal[s_data['HomeTeam'][j]]+ s_data['FTHG'][j]
            else:
                b_goal[s_data['HomeTeam'][j]] = s_data['FTHG'][j]

        elif s_data['AwayTeam'][j]==t: # 어웨이 팀일때    
            if s_data['AwayTeam'][j] in b_goal: # 어웨이팀에서 골 
                b_goal[s_data['AwayTeam'][j]] = b_goal[s_data['AwayTeam'][j]]+ s_data['FTAG'][j]
            else:
                b_goal[s_data['AwayTeam'][j]] = s_data['FTAG'][j]

                
    
    df = pd.DataFrame(list(b_goal.items()), columns=['TEAM', 'Goal'])     
    df["season"] = count
    globals()["b_"+str(count)] = df
    
    count += 1
    

슈팅과 유효슈팅은 데이터 유실로 인해 2000-01 시즌부터 있기 때문에 따로 뽑았다. 

# 슈팅
# 유효슈팅
best_team=list(best_season['TEAM'])
count =2001

for s,t in zip(season,best_team):
    b_shot = {}
    b_shot_target = {}
    
    s_data=EPL_data.loc[(EPL_data['Season']==s),:] 
    s_data=s_data.loc[(s_data['HomeTeam']==t)|(s_data['AwayTeam']==t),:] 

    for j in s_data.index:
        #슈팅
        if s_data['HomeTeam'][j] == t: # 홈팀일 때
            if s_data['HomeTeam'][j] in b_shot: # 홈팀에서 슈팅 
                b_shot[s_data['HomeTeam'][j]] = b_shot[s_data['HomeTeam'][j]]+ s_data['HS'][j]
            else:
                b_shot[s_data['HomeTeam'][j]] = s_data['HS'][j]

        elif s_data['AwayTeam'][j] == t: # 어웨팀일 때
            if s_data['AwayTeam'][j] in b_shot: # 홈팀에서 슈팅 
                b_shot[s_data['AwayTeam'][j]] = b_shot[s_data['AwayTeam'][j]]+ s_data['AS'][j]
            else:
                b_shot[s_data['AwayTeam'][j]] = s_data['AS'][j]        
       
        # 유효슈팅
        if s_data['HomeTeam'][j] == t: # 홈팀일 때
            if s_data['HomeTeam'][j] in b_shot_target: # 홈팀에서 슈팅 
                b_shot_target[s_data['HomeTeam'][j]] = b_shot_target[s_data['HomeTeam'][j]]+ s_data['HST'][j]
            else:
                b_shot_target[s_data['HomeTeam'][j]] = s_data['HST'][j]

        elif s_data['AwayTeam'][j] == t: # 어웨팀일 때
            if s_data['AwayTeam'][j] in b_shot_target: # 홈팀에서 슈팅 
                b_shot_target[s_data['AwayTeam'][j]] = b_shot_target[s_data['AwayTeam'][j]]+ s_data['AST'][j]
            else:
                b_shot_target[s_data['AwayTeam'][j]] = s_data['AST'][j]   
        
    df = pd.DataFrame(list(b_shot.items()), columns=['TEAM', 'Shot'])   
    df_f = pd.DataFrame(list(b_shot_target.items()), columns=['TEAM', 'Shot_target'])  
    t_df = pd.merge(df,df_f,on='TEAM')
                
    t_df["season"] = count
    globals()["b_s_"+str(count)] = t_df
    
    count += 1

기본적인 매커니즘은 승점뽑아내는 것과 비슷하다.

 

 

그래서 모아보면 이러한 DataFrame 값이 나온다.

 

데이터 유실로 인해 NaN값이 나왔다.
우승팀들의 [득점, 슈팅, 유효슈팅 수] 시각화 
우승팀들의 [득점, 슈팅, 유효슈팅 수] 통계자료 // 왼쪽이 한 시즌의 총합이고 오른쪽은 경기당으로 나눈 값이다.

주석에도 나와있듯이 mean 값들을 보아  

우승팀의 공격력은 경기당 2골정도 넣고, 14번의 슈팅으로 7번의 유효슈팅 하는 것을 알 수 있다.

 

역시 우승팀이라서 그런지 굉장히 높은 수치이다. 

 

 


[ 수비력 ]

수비력은 실점과 파울 수, 경고, 퇴장을 뽑아보았다.

경고와 퇴장이 수비력과 큰 상관이 있는지는 모르겠다. 

수비력에 대한 마땅한 데이터가 없어서 일단 넣긴 했다.

 

count =1994

for s,t in zip(season,best_team):
    b_aga = {}  
    s_data=EPL_data.loc[(EPL_data['Season']==s),:] 
    s_data=s_data.loc[(s_data['HomeTeam']==t)|(s_data['AwayTeam']==t),:] 

    for j in s_data.index:
        #실점
        if s_data['HomeTeam'][j] == t: # 홈팀일 때
            if s_data['HomeTeam'][j] in b_aga: # 홈에서 실점
                b_aga[s_data['HomeTeam'][j]] = b_aga[s_data['HomeTeam'][j]]+ s_data['FTAG'][j]
            else:
                b_aga[s_data['HomeTeam'][j]] = s_data['FTAG'][j]

        elif s_data['AwayTeam'][j]==t: # 어웨이 팀일때    
            if s_data['AwayTeam'][j] in b_aga: # 어웨이에서 실점
                b_aga[s_data['AwayTeam'][j]] = b_aga[s_data['AwayTeam'][j]]+ s_data['FTHG'][j]
            else:
                b_aga[s_data['AwayTeam'][j]] = s_data['FTHG'][j]

                
    
    df = pd.DataFrame(list(b_aga.items()), columns=['TEAM', 'Goal_Against'])     
    df["season"] = count
    globals()["b_a_"+str(count)] = df
    
    count += 1

실점에 대한 코딩이다. 득점과 큰 차이점은 없다. (홈팀일 때 어웨이 골이랑 어웨이팀일 때 홈 골 - 이 차이일 뿐)

# 파울
# 카드 수 (옐로, 레드)

best_team=list(best_season['TEAM'])
best_team = best_team[7:]
season = season[7:]
count =2001

for s,t in zip(season,best_team):
    b_foul = {}
    b_y_card = {}
    b_r_card = {}
    
    s_data=EPL_data.loc[(EPL_data['Season']==s),:] 
    s_data=s_data.loc[(s_data['HomeTeam']==t)|(s_data['AwayTeam']==t),:] 

    for j in s_data.index:
        #파울 
        if s_data['HomeTeam'][j] == t: # 홈팀일 때
            if s_data['HomeTeam'][j] in b_foul: # 홈에서 파울
                b_foul[s_data['HomeTeam'][j]] = b_foul[s_data['HomeTeam'][j]]+ s_data['HF'][j]
            else:
                b_foul[s_data['HomeTeam'][j]] = s_data['HF'][j]

        elif s_data['AwayTeam'][j] == t: # 어웨팀일 때
            if s_data['AwayTeam'][j] in b_foul: # 어웨이일 때 파울
                b_foul[s_data['AwayTeam'][j]] = b_foul[s_data['AwayTeam'][j]]+ s_data['AF'][j]
            else:
                b_foul[s_data['AwayTeam'][j]] = s_data['AF'][j]        
       
        # 옐로 카드
        if s_data['HomeTeam'][j] == t: # 홈팀일 때
            if s_data['HomeTeam'][j] in b_y_card: # 홈에서 옐로 카드 
                b_y_card[s_data['HomeTeam'][j]] = b_y_card[s_data['HomeTeam'][j]]+ s_data['HY'][j]
            else:
                b_y_card[s_data['HomeTeam'][j]] = s_data['HY'][j]

        elif s_data['AwayTeam'][j] == t: # 어웨팀일 때
            if s_data['AwayTeam'][j] in b_y_card: # 어웨이일 때 옐로 카드
                b_y_card[s_data['AwayTeam'][j]] = b_y_card[s_data['AwayTeam'][j]]+ s_data['AY'][j]
            else:
                b_y_card[s_data['AwayTeam'][j]] = s_data['AY'][j]   
        
        # 레드 카드
        if s_data['HomeTeam'][j] == t: # 홈팀일 때
            if s_data['HomeTeam'][j] in b_r_card: # 홈에서 레드 카드
                b_r_card[s_data['HomeTeam'][j]] = b_r_card[s_data['HomeTeam'][j]]+ s_data['HR'][j]
            else:
                b_r_card[s_data['HomeTeam'][j]] = s_data['HR'][j]

        elif s_data['AwayTeam'][j] == t: # 어웨팀일 때
            if s_data['AwayTeam'][j] in b_r_card: # 어웨이일 때 레드 카드
                b_r_card[s_data['AwayTeam'][j]] = b_r_card[s_data['AwayTeam'][j]]+ s_data['AR'][j]
            else:
                b_r_card[s_data['AwayTeam'][j]] = s_data['AR'][j]   
    
        
    df_1 = pd.DataFrame(list(b_foul.items()), columns=['TEAM', 'foul'])   
    df_2 = pd.DataFrame(list(b_y_card.items()), columns=['TEAM', 'yellow_card'])     
    df_3 = pd.DataFrame(list(b_r_card.items()), columns=['TEAM', 'red_card'])     

    
    df_1 = pd.merge(df_1,df_2,on='TEAM')
    df_de = pd.merge(df_1,df_3,on='TEAM')
    
                
    df_de["season"] = count
    globals()["b_f_"+str(count)] = df_de
    
    count += 1

파울, 경고, 퇴장에 대한 코딩으로 이것도 슈팅과 마찬가지로 2000-01 시즌부터의 데이터만 있고, 그 전 데이터는 유실되어 따로 코딩을 진행하였다.

 

우승팀의 수비력 수치들 (데이터 유실로 인해 NaN)

이 수치를 가지고 시각화랑 통계자료를 만들어 보았다.

우승팀 수비력에 대한 시각화자료 
우승팀 수비력에 대한 통계자료 

이것도 공격력과 마찬가지로 통계자료를 38로 나누어 경기당의 수치로 보았다.

Mean 값을 보았을 때, 

경기당 실점은 0.8골, 파울은 10.7번, 경고 1.5번, 퇴장 0.06번을 한다는 것을 알 수있다.

 

득점에 비하여 실점이 상당히 적은 것을 알 수있고, 경기당 10번의 파울로 생각보다 적은 값이 나온 것 같다.

( 그만큼 수비를 잘한다고 생각한다. )

 


[ 결론 ] 

마지막 결론으로 현재 토트넘과 비교를 하기위해 위에서 했던 우승팀의 공격력과 수비력의 똑같은 컬럼으로 뽑아보았다.

우승팀의 공격력과 수비력이다. 

 

2020-21시즌 토트넘의 공격력과 수비력이다.

토트넘은 30경기만 한 데이터를 가지고 통합하여 /30을 해주어 경기당으로 비교할 수 있게 하였다. 

(파란색은 안좋은 것이고 빨간색은 좋은 것이라고 생각하면 된다.)

 

공격력은 예상한 대로 많이 부족하다는 것을 보여주고 있다. 

수비력은 수비축구답게 큰 차이를 안보이지만, 우승팀보다는 부족한 수치이다. 

경고만 더 좋은 수치를 가진 것을 보아, 역시 경고와 퇴장은 수비력과 큰 차이가 없는 것같다.

 

토트넘만 보기에는 저 데이터가 믿을 수 있는 수치인지 알 수 없기 때문에

현재 1등인 맨시티의 공격력과 수비력도 비교를 해보았다.

2020-21 시즌 맨시티의 공격력과 수비력이다. 

확실히 토트넘과 많이 비교된다는 것을 보여준다.

(파란색은 안좋은 것 , 빨간색은 좋은 것)

 

공격력은 약간 부족하지만, 아직 7경기나 남아있어서 충분히 따라잡을 수 있는 수치이다.

수비력은 우승하기 충분한 수비력이라는 것을 보여준다.

 


이번 데이터 분석을 통해 토트넘이 현재 얼마나 부족한 점이 많은지 알아보았다.

Kane 과 Son 의 선수 기량이 세계최고급이지만, 공격력에서도 부족한 점이 많다.

수비는 부족한 점이 한 두가지가 아니라서 말을 아끼도록 하겠다. 

 

일단 이번 2020-21시즌은 챔스권만 진출해도 정말로 잘한 것이라고 판단된다.

(아마 못할 것 같다.)

 


(수정)

생각보다 보시는 분들도 많고 댓글로 코드 질문이 많아서 EPL 프로젝트 했던 코드 올렸습니다.

공개하기 부끄러울 정도로 미흡한 코드입니다...  감사합니다.

 

https://github.com/LEEJEHEON/2021_04_EPL_Analysis

 

GitHub - LEEJEHEON/2021_04_EPL_Analysis: I did a data analysis on EPL Tottenham in April 2019.

I did a data analysis on EPL Tottenham in April 2019. - GitHub - LEEJEHEON/2021_04_EPL_Analysis: I did a data analysis on EPL Tottenham in April 2019.

github.com

 

728x90