ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 리그오브레전드 데이터 분석 - Match Data Analytics(3)
    ML, DL & Python/Riot API를 활용환 데이터 분석 2020. 3. 15. 17:20

    이번 포스팅은 리그오브레전드의 경기기록(오브젝트 위주)이 승패에 얼만큼 영향을 미치는지 확인해보겠습니다.

     

    저번 분석에서는 상관도를 파악하고, 승리와 패배에 따른 오브젝트 변수의 비율 차이를 EDA를 통해서 알아보았는데요.

    실제로 우리가 게임을 하면서 말하는 얘기들에 일맥상통하는 인사이트들이 비교적 많이 도출되었습니다.

     

    이처럼 게임 승패에 상관성이 있는 변수들은 "과연 승패에 얼만큼 영향을 미칠까?" 라는 고민까지 이어졌습니다.

    따라서 이번에는 Logistic Regression 을 활용하여 오브젝트 변수들이 승패에 미치는 영향을 분석해보려고 합니다. 

     

    1. Logistic Regression

    분석에 앞서 Logistic Regression에 대해서 간략하게 설명하고 넘어가도록 하겠습니다.

    위의 그림은 Sigmoid 곡선을 그린 것입니다. 시그모이드 곡선은 y값의 범위를 [0,1]을 가지며 미지의 변수에 따른 타겟값이 나타날 확률을 계산합니다. 그리고 Logistic Regression 은 odds라는 승산을 이용하여 일어나지 않을 확률에 대비한 일어날 확률 값을 계산하여 회귀계수(beta)를 도출하면 계수 값으로 0보다 1이 나타날 확률을 구할 수 있습니다.

     

    linear regression에서는 독립변수로 설명할 수 있는 결정변수의 데이터 타입이 연속형 변수여야 합니다. 그래서 각각의 독립변수와의 데이터 분포도가 선형성을 띄는 상태일 때 설명력도 높아지고 해당 변수를 통한 영향성을 확인할 수 있습니다.

    데이터 타입이 Sequence한 타입일 때는 위와 같은 분포를 그릴 수 있습니다.

     

    하지만, 결정변수의 데이터 타입이 만일 0과 1값을 갖는 binary한 데이터 타입일 때는 그래프가 어떻게 나타날까요?

    이처럼 데이터가 연속성을 가지지 않기 때문에 데이터가 분리된 형태를 가질 것입니다. 위의 그래프는 제가 설명을 위해 샘플로 뽑은 데이터라 원래 데이터를 그려보면 엄청 깨끗하지 않은 그래프이지만 y값이 0과 1로 똑같이 나뉘겠죠?

     

    그러면 이와 같이 이산형 데이터에 대한 회귀분석은 할 수 없는것인가? 아닙니다.

    이산형 데이터에 대한 회귀분석을 할 수 있게 한 것이 Logistic Regression입니다. 이는 1이 나올 확률 혹은 0이 나올 확률을 Logistic Function으로 계산하여 구할 수 있습니다. (로지스틱 회귀분석에 대한 이론적인 내용은 다음에 정리하도록 하겠습니다.)

     

    간단하게 수식만 알아보면

    Logistic Function은 위와 같은 exponential 즉, 지수함수로 표현할 수 있습니다. 그리고 다음은 odds 공식을 보겠습니다.

     

    odds(승산)를 활용한 Logistic Function을 도출하는 공식은 다음 포스팅에서 자세히 다루도록 하겠습니다.

     

    따라서 odds를 활용하여 저희는 이제부터 리그오브레전드의 오브젝트 경기기록이 승패에 얼마만큼 영향을 미치는지 알아보도록 하겠습니다.

     

    2. 경기 승패 인과분석

    회귀분석을 통해서 변수별 타겟변수에 얼만큼 영향을 주는지 분석하여 두 변수간의 인과관계를 분석할 수 있습니다.

     

    먼저 game 데이터셋을 구축하기 위한 코드를 다 실행시키겠습니다. EDA(1), EDA(2)를 순차적으로 거쳐오신 분들은 아마 game_df라는 데이터셋이 존재할 것이므로 따로 이 과정은 거치지 않으셔도 좋습니다.

     

    import pandas as pd
    import pickle
    import matplotlib.pyplot as plt
    import numpy as np
    import sys
    import os
    from pandas.io.json import json_normalize
    import seaborn as sns
    import statsmodels.api as sm
    
    #데이터 불러오기
    lol_df = pd.read_pickle('/Users/sinmin-yong/GitHub/모든저장소데이터셋/파일 저장용(my-study)/롤17500개.pickle')
    lol_df.head()
    
    #a팀 b팀 분리
    team_a_error = []
    team_b_error = []
    team_a = pd.DataFrame()
    team_b = pd.DataFrame()
    for i in range(len(lol_df)):
        if i % 1000 == 0:
            print(str(i)+'행 처리중')
        try:
            team_a = team_a.append(json_normalize(lol_df['teams'].iloc[i][0]))
            team_b = team_b.append(json_normalize(lol_df['teams'].iloc[i][1]))
        except:
            team_a_error.append(i)
            team_b_error.append(i)
            print(str(i)+'행에서 오류')
            pass
            
    # 팀a와 팀 b의 승패가 반대인지 데이터 정합성 검정 -모두 반대임을 확인
    for i in range(len(team_a)):
        wf_valid = team_a['win'].iloc[i]
        
        if team_b['win'].iloc[i] != wf_valid:
            pass
        else:
            print(str(i)+'행 데이터 정합성 문제')
            
    #각 경기별 게임 시간 병합
    lol_df = lol_df.drop(index = team_a_error)
    
    team_a['gameDuration'] = lol_df['gameDuration'].tolist()
    team_b['gameDuration'] = lol_df['gameDuration'].tolist()
    
    lol_df['gameDuration'].index = range(len(lol_df))
    team_a['gameDuration'].index = range(len(team_a))
    team_b['gameDuration'].index = range(len(team_b))
    
    game_df = pd.concat([team_a,team_b],axis=0)
    
    #분석의 용이성을 위해서 타겟 데이터를 제외한 범주형 데이터를 인코딩
    '''
    True : 1
    False : 0
    '''
    tf_mapping = {True:1,False:0}
    bool_column = game_df.select_dtypes('bool').columns.tolist()
    
    for i in bool_column:
        game_df[i] = game_df[i].map(tf_mapping)
        
    wl_mapping = {'Win':'Win','Fail':'Lose'}
    game_df['win'] = game_df['win'].map(wl_mapping)
    
    game_df['game_time'] = game_df['gameDuration']/60
    
    game_df['win_encoding'] = game_df['win'].map({'Win':1,'Lose':0})
    

     

    타겟 값은 0과 1로만 이루어진 정수형 변수로 만들어줘야 하기 때문에 꼭 승패 변수에 대한 라벨 인코딩 혹은 mapping을 해주셔야 합니다.

     

    자 이제 Target variance와의 상관관계를 확인하겠습니다.

    game_df.select_dtypes(['int64','float64']).corr()[['win_encoding']]

    위의 상관계수를 확인해보면 승리한 팀일수록 당연히 타워를 많이 밀었을 것이며, 억제기를 먼저 밀었을 것이며, 억제기를 더 많이 밀었을 것입니다.

    아마 리그오브레전드를 많이 해보신 분들이라면 위의 상관성에 대한 Domain Knowledge가 있기 때문에 편할텐데 리그오브레전드를 해보지 않으신분들은 왜 저 승패와의 상관계수가 저렇게 나오는지 의아하실텐데 리그오브레전드라는 게임은 포탑, 억제기 부시는 게임이라고 이해하시면 편할 듯 합니다.

     

    Logistic Regression을 돌리기 전에 꼭 알아두셔야 할 점이 있습니다.

    독립변수로 들어갈 데이터 타입은 정수형, 연속형, 실수형 데이터든지 상관없지만 상관분석을 했을 때 계수가 null값인 변수들은 제거하고 돌리셔야 Error가 없이 코드가 실행될 것입니다.

    만약 Null인 변수도 같이 넣고 회귀분석을 실시한다면 Singular Matrix라는 Error Code가 반환 될 것입니다. 꼭 유념해주세요!

    (계수가 null 값인 경우 추측해보건대 해당 변수의 표준편차가 0이 나와서 그러지 않을까 싶습니다. 상관계수는(p) = cov(x,y) / sigma(x)*sigma(y) 이기 때문에)

     

    아무튼! 이제 해당 변수들을 제거하고 회귀분석을 시행해보도록 하겠습니다.

    reg_df = game_df.drop(columns=['teamId','vilemawKills','dominionVictoryScore','win','bans','game_time'])
    reg_df = reg_df.dropna()
    reg_df['win_encoding'] = reg_df['win_encoding'].astype('int64')
    

    먼저 모델에 넣지 않을 변수들을 제거합니다.

    logit = sm.Logit(reg_df[['win_encoding']],reg_df[reg_df.columns.tolist()[:-2]]) #로지스틱 회귀분석 시행
    result = logit.fit()
    

    그 다음 모델에 적합시킨 뒤  

    result.summary2()

    summary값을 뽑아보겠습니다. 이 때 summary와 summary2 값이 있는데 그 차이는 일단 회귀 계수값을 봤을 때 차이가 전혀 없지만 r-squared 값이나 AIC 값 등이 부재하여 summary2 값으로 계수를 해석했습니다.

     

     

    한눈에 관련된 통계값들이 반환되었는데요. 분석에 필요한 값들을 해석해보겠습니다.

     

    일단 전체 변수에 관한 F p-value는 0.0000으로 통계적으로 유의미한 변수들이 적합되었습니다.

    그리고 오브젝트 변수들만을 가지고 확인했는데도 불구하고 R-Squared가 0.266이 도출되었습니다.

     

    그리고 각 독립변수들은 전령킬, 첫 전령킬 을 제외하고는 모두 p-value가 유의수준 0.05하에서(95% 신뢰구간) 통계적으로 유의미하다는 결론이 도출되었습니다.

     

    회귀식을 보시겠습니다. 

    

    각 변수별 회귀계수(Coefficient)를 회귀식으로 나타낸 것인데요.

    해당 회귀식이 나타내는 것은 각 변수가 Y(Target variance)에 얼만큼 영향을 미치는지를 나타낸 것입니다.

     

    이제 각 변수별 해석을 할텐데요. 선형회귀식이라면 그대로 회귀계수값을 해석하면 됩니다.

    예를 들어서 다른 변수들이 고정되어 있는 상태에서 FirstTower변수가 한 단위 증가하면 Y변수가 0.5160 증가한다.

     

    하지만 저희는 로지스틱 회귀를 한 것이므로 다른 해석이 필요합니다.

    위에서 언급했던 것처럼 로지스틱 회귀식의 오즈를 해석할 때는 일어나지 않을 확률 대비 일어날 확률을 나타내는데요. 저희는 그래서 회귀계수를 exponential^x에서 x에 대입한 값으로 해석을 진행하면 됩니다.

     

    해석해보면 다른 변수들이 고정되어 있는 상태에서 FirstTower변수가 한 단위 증가하면 승리할 확률이 1.67배(exponential ^ 0.5160) 증가한다. 혹은 67% 증가한다.

     

    이처럼 모든 변수에 대해서 해석해보면 다음과 같습니다.

    for i in range(len(result.params)):
        print('다른 변수가 고정되어 있으며, {} 이 한단위 상승할 때 승리할 확률이 {} 배 증가한다.\n'.format(result.params.keys()[i],np.exp(result.params.values[i])))

    전체 변수를 넣고 모델링

    승리 확률에 가장 크게 기여하고 있는 것은 주로 First에 관련한 변수들 인 것을 확인할 수 있는데요.

    특히 첫 억제기, 첫 바론, 첫 타워가 승리에 큰 영향을 끼치는 것을 확인할 수 있습니다. 그리고 특히 승리 변수와 상관성이 높았던 억제기, 타워 부신 개수도 승리에 영향을 미치는 것을 알 수 있습니다.

     

    여기서 한가지 문제점을 집어보자면 저는 현재 모든 변수들을 하나의 모델에 넣어서 진행했고, 그에 대한 해석을 하여 보시다시피 바론 처치 횟수, 드래곤 처치 횟수, First Blood 등의 변수는 오히려 승리 확률에 영향을 미치지 않는 결과가 도출되었는데요. 이는 변수간의 상관성도 고려되어야 해서 첫 억제기와 억제기를 부신 개수는 상관성이 높기 때문에 모델에서 인식하는바로 더 영향성이 높은 변수를 승산비에 더 포함시키고, 영향성이 낮은 변수는 그와 반대로 승산비에 안좋게 반영되기 때문에 이러한 결과가 도출되는 것으로 생각됩니다.(혹시 다른 의견이 있으신 분들은 댓글 달아주세요!!)

    reg_df2 = reg_df[reg_df.columns.tolist()[:-2] + ['win_encoding']]
    explain_var = reg_df2.columns.tolist()[:-1]
    
    coef_ls = []
    pvalue_ls = []
    exp_ls = []
    var_ls = []
    simple_model = pd.DataFrame()
    
    for i in explain_var:
        logit = sm.Logit(reg_df2[['win_encoding']],reg_df[[i]]) #로지스틱 회귀분석 시행
        result = logit.fit()
        
        coef_ls.append(result.params.values[0])
        pvalue_ls.append(result.pvalues.values[0])
        exp_ls.append(str(round(np.exp(result.params.values[0]),3)) + '배 증가')
        var_ls.append(i)
    
    simple_model['variance'] = var_ls
    simple_model['coefficient'] = coef_ls
    simple_model['solution'] = exp_ls
    simple_model['p_value'] = pvalue_ls
    
    

     

    단일변수로 모델링 했을 때의 해석

    위와 같이 단일모델로 해석을 했을 때는 위에서 모든 변수들을 같이 넣고 모델링을 했을 때와 다른 결과값을 볼 수 있습니다. 아까 위에서 언급한 것처럼 회귀 모델은 변수간 상관성이 크게되면 공선성 때문에 모델에서 인식할 때 영향성이 높은 변수를 우선적으로 인식하기 때문에 이러한 결과가 나오지 않나 생각이 됩니다.

     

     

    3. Logistic Regression 한계점

    로지스틱 회귀의 가장 큰 한계점은 정확히 승산이 아닌 안좋게 될 (0이 될) 확률을 모른다는 점입니다. 왜냐하면 아무리 변수가 한 단위 증가하여 승리(1일 확률)할 확률이 1.x 배 높아진다 하더라도 그 변수로 인한 패배할 확률 자체가 낮다면 1.x배, 2.x배의 자체가 무의미할 수도 있는 것입니다.

     

    단순히 회귀식을 작성하고 회귀계수를 해석하는 것에서 끝나는 것이 아니라 해당 회귀 모델의 정확한 한계점과 꼭 유념해야할 점들을 숙지하고 분석에 들어가는 것이 중요한데요.

    저도 아직 공부를 하고 있는 단계라 아는 부분이 극히 적은 것일 수 있겠지만 추후에 이부분에 대한 공식 유도 및 명확하게 모델을 뜯어보면서 좀 더 첨언할 부분이 있으면 추가 해놓도록 하겠습니다.


    댓글

Designed by Tistory.