공부 삼아 리그 오브 레전드 api를 활용해 데이터를 받아 이리저리 만져보았습니다.
여러 날에 걸쳐서 조금씩 진행한 터라 중간중간 정리가 안 된 코드도 있으니 혹시 안 되시는 부분이 있으면 레퍼런스 페이지를 참고해주세요.
이 페이지에서는 밀리고 있던 게임에서 승리한 팀은 어떤 전략을 사용했는지 확인해보았습니다.
전체적인 진행 순서는 다음과 같습니다.
- API key 발급
- 데이터 추출
- JSON 내 필요한 데이터 데이터프레임화
- timeline 로그 데이터 동일하게 작업
- 3, 4번 데이터 통합해서 PCA 및 분류분석
- 레퍼런스
1. API key 발급
api를 통해 데이터를 받아보기 위해서는 먼저 아래 페이지에서 로그인한 뒤 api key를 발급받아야 합니다.
링크를 통해 Riot Developer Portal에 접속한 뒤 우측 상단 로그인 버튼을 눌러서 로그인 후 다시 우측 상단의 이름을 눌러 드롭다운 메뉴를 엽니다.
나타난 메뉴 중 첫 번째 DASHBOARD를 누르면 나타나는 페이지에서 API key를 발급받을 수 있습니다.
API KEY는 24시간마다 만료되며 REGENERATE API KEY 버튼을 누르면 새로운 키를 발급받을 수 있습니다.
또한 RATE LIMITS에 기재된 것처럼 요청을 보내는 횟수가 제한되어 있으니 꼭 참고하세요!
2. 데이터 추출
2-1. 패키지 불러오기
import requests
import json
import time
import pandas as pd
from tqdm import tqdm
api_key = '발급받은 api key 붙여넣기'
2-2. 유저 데이터 불러오기(summoner id, puuid)
# 마스터 등급 솔로 랭크 게임을 플레이한 플레이어의 summoner id 추출
url = 'https://kr.api.riotgames.com/lol/league/v4/masterleagues/by-queue/RANKED_SOLO_5x5?api_key=' + api_key
summonerId = {}
r = requests.get(url)
r = r.json()['entries']
num = 0
for i in r:
summonerId[i['summonerName']] = i['summonerId']
num += 1
print(num)
# summonerId를 통해 puuid 추출
puuid = {}
for i,j in zip(tqdm(summonerId.values()),summonerId.keys()):
url2 = 'https://kr.api.riotgames.com/lol/summoner/v4/summoners/' + i + '?api_key=' + api_key
r = requests.get(url2)
if r.status_code == 200:
pass
elif r.status_code == 429:
print('api cost full : infinite loop start')
print('loop location : ',i)
start_time = time.time()
while True:
if r.status_code == 429:
print('try 120 second wait time')
time.sleep(120)
r = requests.get(url2)
print(r.status_code)
elif r.status_code == 200:
print('total wait time : ', time.time() - start_time)
print('recovery api cost')
break
df_puuid = pd.DataFrame(puuid, index = [0])
df_puuid = df_puuid.T
df_puuid = df_puuid.reset_index()
df_puuid.columns = ['id','puuid']
2-3. 최근 플레이한 5개 게임의 match id 추출하기
matchId = []
for i in tqdm(df_puuid['puuid']):
#count=5의 숫자를 바꾸면 경기 수가 달라집니다
url_match = 'https://asia.api.riotgames.com/lol/match/v5/matches/by-puuid/'+ i +'/ids?start=1&count=5&api_key='+api_key
r_match = requests.get(url_match)
if r_match.status_code == 200:
pass
elif r_match.status_code == 429:
print('api cost full : infinite loop start')
print('loop location : ',i)
start_time = time.time()
matchId.extend(requests.get(url_match).json())
len(matchId)
# 중복 제거
match_set = set(matchId)
match_list = list(match_set)
len(match_list)
2-4. 게임별 정보 추출하여 JSON으로 저장하기
matchdata = {}
num = 0
for i in tqdm(match_list):
if num%7 == 0:
time.sleep(4)
elif num%100 == 0:
print('Wait 121s')
time.sleep(121)
num += 1
url4 = 'https://asia.api.riotgames.com/lol/match/v5/matches/' + str(i) +'?api_key=' + api_key
r = requests.get(url4)
if r.status_code == 200:
pass
elif r.status_code == 429:
print('api cost full : infinite loop start')
print('loop location : ',i)
start_time = time.time()
while True:
if r.status_code == 429:
print('try 120 second wait time')
time.sleep(120)
r = requests.get(url2)
print(r.status_code)
elif r.status_code == 200:
print('total wait time : ', time.time() - start_time)
print('recovery api cost')
break
try:
r = r.json()['info']['participants']
matchdata[i] = r
except:
print("기타 에러")
with open('./matchdata.json','w') as f:
json.dump(matchdata, f, ensure_ascii=False, indent=4)
3. JSON 내 필요한 데이터 데이터프레임화
# 빈 데이터프레임 만들기
temp_df = pd.DataFrame(columns=['no','gameNo','playerNo','assists',
'baronKills',
'bountyLevel',
'champExperience',
'champLevel',
'championId',
'championName',
'championTransform',
'consumablesPurchased',
'damageDealtToBuildings',
'damageDealtToObjectives',
'damageDealtToTurrets',
'damageSelfMitigated',
'deaths',
'detectorWardsPlaced',
'doubleKills',
'dragonKills',
'eligibleForProgression',
'firstBloodAssist',
'firstBloodKill',
'firstTowerAssist',
'firstTowerKill',
'gameEndedInEarlySurrender',
'gameEndedInSurrender',
'goldEarned',
'goldSpent',
'individualPosition',
'inhibitorKills',
'inhibitorTakedowns',
'inhibitorsLost',
'item0',
'item1',
'item2',
'item3',
'item4',
'item5',
'item6',
'itemsPurchased',
'killingSprees',
'kills',
'lane',
'largestCriticalStrike',
'largestKillingSpree',
'largestMultiKill',
'longestTimeSpentLiving',
'magicDamageDealt',
'magicDamageDealtToChampions',
'magicDamageTaken',
'neutralMinionsKilled',
'nexusKills',
'nexusLost',
'nexusTakedowns',
'objectivesStolen',
'objectivesStolenAssists',
'participantId',
'pentaKills',
'physicalDamageDealt',
'physicalDamageDealtToChampions',
'physicalDamageTaken',
'profileIcon',
'puuid',
'quadraKills',
'riotIdName',
'riotIdTagline',
'role',
'sightWardsBoughtInGame',
'spell1Casts',
'spell2Casts',
'spell3Casts',
'spell4Casts',
'summoner1Casts',
'summoner1Id',
'summoner2Casts',
'summoner2Id',
'summonerId',
'summonerLevel',
'summonerName',
'teamEarlySurrendered',
'teamId',
'teamPosition',
'timeCCingOthers',
'timePlayed',
'totalDamageDealt',
'totalDamageDealtToChampions',
'totalDamageShieldedOnTeammates',
'totalDamageTaken',
'totalHeal',
'totalHealsOnTeammates',
'totalMinionsKilled',
'totalTimeCCDealt',
'totalTimeSpentDead',
'totalUnitsHealed',
'tripleKills',
'trueDamageDealt',
'trueDamageDealtToChampions',
'trueDamageTaken',
'turretKills',
'turretTakedowns',
'turretsLost',
'unrealKills',
'visionScore',
'visionWardsBoughtInGame',
'wardsKilled',
'wardsPlaced',
'win'])
# for 문으로 데이터 추출
i=0
for j in tqdm(list(matchdata.keys())):
for k in range(0,10):
temp_df.loc[i*10+k] = [i, j, k, matchdata[j][k]["assists"],
matchdata[j][k]["baronKills"],
matchdata[j][k]["bountyLevel"],
matchdata[j][k]["champExperience"],
matchdata[j][k]["champLevel"],
matchdata[j][k]["championId"],
matchdata[j][k]["championName"],
matchdata[j][k]["championTransform"],
matchdata[j][k]["consumablesPurchased"],
matchdata[j][k]["damageDealtToBuildings"],
matchdata[j][k]["damageDealtToObjectives"],
matchdata[j][k]["damageDealtToTurrets"],
matchdata[j][k]["damageSelfMitigated"],
matchdata[j][k]["deaths"],
matchdata[j][k]["detectorWardsPlaced"],
matchdata[j][k]["doubleKills"],
matchdata[j][k]["dragonKills"],
matchdata[j][k]["eligibleForProgression"],
matchdata[j][k]["firstBloodAssist"],
matchdata[j][k]["firstBloodKill"],
matchdata[j][k]["firstTowerAssist"],
matchdata[j][k]["firstTowerKill"],
matchdata[j][k]["gameEndedInEarlySurrender"],
matchdata[j][k]["gameEndedInSurrender"],
matchdata[j][k]["goldEarned"],
matchdata[j][k]["goldSpent"],
matchdata[j][k]["individualPosition"],
matchdata[j][k]["inhibitorKills"],
matchdata[j][k]["inhibitorTakedowns"],
matchdata[j][k]["inhibitorsLost"],
matchdata[j][k]["item0"],
matchdata[j][k]["item1"],
matchdata[j][k]["item2"],
matchdata[j][k]["item3"],
matchdata[j][k]["item4"],
matchdata[j][k]["item5"],
matchdata[j][k]["item6"],
matchdata[j][k]["itemsPurchased"],
matchdata[j][k]["killingSprees"],
matchdata[j][k]["kills"],
matchdata[j][k]["lane"],
matchdata[j][k]["largestCriticalStrike"],
matchdata[j][k]["largestKillingSpree"],
matchdata[j][k]["largestMultiKill"],
matchdata[j][k]["longestTimeSpentLiving"],
matchdata[j][k]["magicDamageDealt"],
matchdata[j][k]["magicDamageDealtToChampions"],
matchdata[j][k]["magicDamageTaken"],
matchdata[j][k]["neutralMinionsKilled"],
matchdata[j][k]["nexusKills"],
matchdata[j][k]["nexusLost"],
matchdata[j][k]["nexusTakedowns"],
matchdata[j][k]["objectivesStolen"],
matchdata[j][k]["objectivesStolenAssists"],
matchdata[j][k]["participantId"],
matchdata[j][k]["pentaKills"],
matchdata[j][k]["physicalDamageDealt"],
matchdata[j][k]["physicalDamageDealtToChampions"],
matchdata[j][k]["physicalDamageTaken"],
matchdata[j][k]["profileIcon"],
matchdata[j][k]["puuid"],
matchdata[j][k]["quadraKills"],
matchdata[j][k]["riotIdName"],
matchdata[j][k]["riotIdTagline"],
matchdata[j][k]["role"],
matchdata[j][k]["sightWardsBoughtInGame"],
matchdata[j][k]["spell1Casts"],
matchdata[j][k]["spell2Casts"],
matchdata[j][k]["spell3Casts"],
matchdata[j][k]["spell4Casts"],
matchdata[j][k]["summoner1Casts"],
matchdata[j][k]["summoner1Id"],
matchdata[j][k]["summoner2Casts"],
matchdata[j][k]["summoner2Id"],
matchdata[j][k]["summonerId"],
matchdata[j][k]["summonerLevel"],
matchdata[j][k]["summonerName"],
matchdata[j][k]["teamEarlySurrendered"],
matchdata[j][k]["teamId"],
matchdata[j][k]["teamPosition"],
matchdata[j][k]["timeCCingOthers"],
matchdata[j][k]["timePlayed"],
matchdata[j][k]["totalDamageDealt"],
matchdata[j][k]["totalDamageDealtToChampions"],
matchdata[j][k]["totalDamageShieldedOnTeammates"],
matchdata[j][k]["totalDamageTaken"],
matchdata[j][k]["totalHeal"],
matchdata[j][k]["totalHealsOnTeammates"],
matchdata[j][k]["totalMinionsKilled"],
matchdata[j][k]["totalTimeCCDealt"],
matchdata[j][k]["totalTimeSpentDead"],
matchdata[j][k]["totalUnitsHealed"],
matchdata[j][k]["tripleKills"],
matchdata[j][k]["trueDamageDealt"],
matchdata[j][k]["trueDamageDealtToChampions"],
matchdata[j][k]["trueDamageTaken"],
matchdata[j][k]["turretKills"],
matchdata[j][k]["turretTakedowns"],
matchdata[j][k]["turretsLost"],
matchdata[j][k]["unrealKills"],
matchdata[j][k]["visionScore"],
matchdata[j][k]["visionWardsBoughtInGame"],
matchdata[j][k]["wardsKilled"],
matchdata[j][k]["wardsPlaced"],
matchdata[j][k]["win"]]
i += 1
4. timeline 로그 데이터 동일하게 작업
이제 만들어진 데이터프레임을 가지고 자유롭게 활용하시면 됩니다.
검색해보니 승리를 예측하는 모델을 만드는 등등 다양한 활용 예시가 있었는데요.
이번에는 MATCH-V5의 timeline에서 받을 수 있는 로그 데이터를 추가로 활용해보기 위해서 먼저 사용한 것과 같은 방식으로 JSON 파일을 거쳐 데이터프레임을 만들었습니다.
4.1 timeline 데이터 JSON으로 추출하기
matchdata = {}
num = 0
for i in tqdm(matchlist):
if num%100 != 0:
time.sleep(1)
elif num%100 == 0:
print('Wait 121s')
time.sleep(121)
num += 1
url = 'https://asia.api.riotgames.com/lol/match/v5/matches/' + str(i) +'/timeline/?api_key=' + api_key
r = requests.get(url)
if r.status_code == 200:
pass
elif r.status_code == 429:
print('api cost full : infinite loop start')
print('loop location : ',i)
start_time = time.time()
while True:
if r.status_code == 429:
print('try 120 second wait time')
time.sleep(121)
r = requests.get(url)
print(r.status_code)
elif r.status_code == 200:
print('total wait time : ', time.time() - start_time)
print('recovery api cost')
break
try:
r = r.json()['info']['frames']
matchdata[i] = r
except:
print("기타 에러")
with open('./timedata_all.json','w') as f:
json.dump(matchdata, f, ensure_ascii=False, indent=4)
4-2. JSON 내 필요한 데이터 데이터프레임화
df_gap = pd.DataFrame(columns = ['gameId', 'timestamp', 'blue_kill', 'red_kill'])
for i in tqdm(json_data.keys()):
blue_kill = 0
red_kill = 0
for j in range(0, len(json_data[i])):
if len(json_data[i][j]['events']) > 1:
for k in range(0, len(json_data[i][j]['events'])):
if json_data[i][j]['events'][k]['type'] == 'CHAMPION_KILL':
if json_data[i][j]['events'][k]['killerId'] <= 5:
blue_kill += 1
elif json_data[i][j]['events'][k]['killerId'] > 5:
red_kill += 1
if abs(blue_kill-red_kill) > 5:
df_gap.loc[len(df_gap)] = [i, json_data[i][j]['events'][k]['timestamp'], blue_kill, red_kill]
elif len(json_data[i][j]['events']) == 1:
k = 0
if json_data[i][j]['events'][k]['type'] == 'CHAMPION_KILL':
if json_data[i][j]['events'][k]['killerId'] <= 5:
blue_kill += 1
elif json_data[i][j]['events'][k]['killerId'] > 5:
red_kill += 1
if abs(blue_kill-red_kill) > 5:
df_gap.loc[len(df_gap)] = [i, json_data[i][j]['events'][k]['timestamp'], blue_kill, red_kill]
4-3. 킬 차이 5 이상을 임의의 조건으로 두어 특정 게임 추려내기
df_gap['red_lead'] = df_gap['blue_kill'] - df_gap['red_kill'] < 0
df_gap['blue_lead'] = df_gap['blue_kill'] - df_gap['red_kill'] > 0
# 플레이어별 데이터를 게임별로 합산
df_t = df_gap.groupby('gameId')[['timestamp']].count()
df_b = df_gap.groupby('gameId')[['blue_kill']].sum()
df_r = df_gap.groupby('gameId')[['red_kill']].sum()
df_rl = df_gap.groupby('gameId')[['red_lead']].sum()
df_bl = df_gap.groupby('gameId')[['blue_lead']].sum()
df_pivot = pd.concat([df_t,df_b,df_r, df_rl, df_bl], axis = 1)
# 형식 변경
df_pivot['turnovers'] = (df_pivot['red_lead'] > 0) & (df_pivot['blue_lead'] > 0)
df = df_pivot[['red_lead', 'blue_lead', 'turnovers']]
df.reset_index(drop = False, inplace = True)
# 팀 별로 데이터 형식 다시 변경
df_tov = pd.DataFrame(columns = ['gamdId','teamID', 'leads', 'turnovers'])
k = 0
for i in df['gameId']:
df_tov.loc[k] = [i, 100, df.iloc[df.index[(df['gameId'] == i)][0]]['blue_lead'], df.iloc[df.index[(df['gameId'] == i)][0]]['turnovers']]
df_tov.loc[k+1] = [i, 200, df.iloc[df.index[(df['gameId'] == i)][0]]['red_lead'], df.iloc[df.index[(df['gameId'] == i)][0]]['turnovers']]
k += 2
5. PCA 및 분류분석
분류분석 코드는 사라져 PCA까지만 남겨둡니다.
분석 결과 킬 수가 밀리던 팀이 역전한 경기에서는 바론 위주의 전략이 가장 유효한 것으로 나타났습니다. 이외에는 잘 죽지 않는 것, 경기 시간이 길어지는 것이 중요하게 나타나 지고 있는 경기에서는 경기를 길게 보고 덜 죽으면서 버텨서 바론을 먹는 것이 중요하다고 할 수 있겠습니다.
import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
# 불필요한 열 삭제 상황별 데이터프레임 분리
COL_DEL = ['Unnamed: 0','gamdId','teamID','gameNo','turnovers']
df_all = df.drop(COL_DEL, axis = 1)
df_win = df[df['win']==True].drop(COL_DEL, axis = 1)
df_lose = df[df['win']==False].drop(COL_DEL, axis = 1)
df_tov = df[df['turnovers']==True].drop(COL_DEL, axis = 1)
df_tov_win = df[df['turnovers']==True][df['win']==True].drop(COL_DEL, axis = 1)
df_tov_lose = df[df['turnovers']==True][df['win']==False].drop(COL_DEL, axis = 1)
# X, y 분리
df_X = df_all.drop('win', axis = 1).values
df_y = df_all['win'].values
df_win_X = df_win.drop('win', axis = 1).values
df_win_y = df_win['win'].values
df_lose_X = df_lose.drop('win', axis = 1).values
df_lose_y = df_lose['win'].values
df_tov_X = df_tov.drop('win', axis = 1).values
df_tov_y = df_tov['win'].values
df_tov_win_X = df_tov_win.drop('win', axis = 1).values
df_tov_win_y = df_tov_win['win'].values
df_tov_lose_X = df_tov_lose.drop('win', axis = 1).values
df_tov_lose_y = df_tov_lose['win'].values
# 테스트, 트레인 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(df_X, df_y, test_size = 0.3, random_state = 0)
win_X_train, win_X_test, win_y_train, win_y_test = train_test_split(df_win_X, df_win_y, test_size = 0.3, random_state = 0)
lose_X_train, lose_X_test, lose_y_train, lose_y_test = train_test_split(df_lose_X, df_lose_y, test_size = 0.3,random_state = 0)
tov_X_train, tov_X_test, tov_y_train, tov_y_test = train_test_split(df_tov_X, df_tov_y, test_size = 0.3, random_state = 0)
tov_win_X_train, tov_win_X_test, tov_win_y_train, tov_win_y_test = train_test_split(df_tov_win_X, df_tov_win_y, test_size = 0.3, random_state = 0)
tov_lose_X_train, tov_lose_X_test, tov_lose_y_train, tov_lose_y_test = train_test_split(df_tov_lose_X, df_tov_lose_y, test_size = 0.3, random_state = 0)
# 표준화
scaler = StandardScaler()
X_train_std = scaler.fit_transform(X_train)
X_test_std = scaler.transform(X_test)
win_X_train_std = scaler.fit_transform(win_X_train)
win_X_test_std = scaler.transform(win_X_test)
lose_X_train_std = scaler.fit_transform(lose_X_train)
lose_X_test_std = scaler.transform(lose_X_test)
tov_X_train_std = scaler.fit_transform(tov_X_train)
tov_X_test_std = scaler.transform(tov_X_test)
tov_win_X_train_std = scaler.fit_transform(tov_win_X_train)
tov_win_X_test_std = scaler.transform(tov_win_X_test)
tov_lose_X_train_std = scaler.fit_transform(tov_lose_X_train)
tov_lose_X_test_std = scaler.transform(tov_lose_X_test)
# 나머지는 동일하여 생략하고 역전 경기 PCA만 기재합니다
# 주성분 개수 확인을 위한 표와 그래프
pca = PCA(n_components = 10)
X_pca = pca.fit_transform(tov_X_train_std)
pca_result = pd.DataFrame({'고유값':pca.explained_variance_, '기여율':pca.explained_variance_ratio_})
pca_result['누적기여율'] = pca_result['기여율'].cumsum()
pca_result
plt.bar(range(1,11), pca.explained_variance_ratio_, alpha = 0.5, align = 'center')
plt.step(range(1,11), np.cumsum(pca.explained_variance_ratio_), where = 'mid')
plt.xlabel('PCA')
plt.ylabel('Variance explained')
plt.show()
# 3개로 주성분 분석 진행하여 확인
pca = PCA(n_components = 3)
X_train_pca = pca.fit_transform(tov_X_train_std)
X_test_pca = pca.transform(tov_X_test_std)
plt.matshow(pca.components_, cmap="viridis")
plt.yticks([0, 1, 2], [1, 2, 3])
plt.colorbar()
plt.xticks(range(len(df_all.drop('win', axis = 1).columns)), df_all.drop('win', axis = 1).columns, rotation=60, ha='left')
plt.xlabel("attr")
plt.ylabel("principle comp")
plt.show()
tov_pca = pd.DataFrame(pca.components_, columns = df_all.drop('win', axis = 1).columns, index = ['PCA 1', 'PCA 2', 'PCA 3'])
tov_pca
6. 레퍼런스
'데이터 분석 > 파이썬' 카테고리의 다른 글
파이썬으로 영단어 공부하기 (0) | 2022.05.04 |
---|---|
파이썬으로 간단한 자판기 만들기 (0) | 2022.04.28 |