-
Riot API(라이엇 api) Timeline API를 활용한 리그오브레전드 게임별 게임 시작 후 n분 까지의 데이터 수집ML, DL & Python/Riot API를 활용환 데이터 분석 2020. 5. 5. 17:43
안녕하세요.
일단 본론으로 들어가기 전에 구글에 라이엇 데이터 수집 관련해서 검색을 하게 되면 제 블로그가 최상단이더라구요!
일단 너무 신기하기도 했고, 더 열심히 해서 많은 사람들에게 좀 더 쉽게 정보를 전달할 수 있게끔 노력할 수 있는 원동력이 되더라구요 ㅎㅎ 정말 감사했습니다.
지금까지 Riot API를 통해서 수집했던 데이터는 경기가 완료되고 최종적인 통계 데이터를 수집했는데요. 이 부분만으로도 충분히 게임 결과를 예측하고, 여러 분석을 하신 분들도 많으실 것입니다.
여기까지의 데이터 셋을 확인하고 싶으신 분들은 아래의 링크를 참조해주세요.
1. 챌린저, 그마, 마스터 랭크게임(바로 분석할 수 있도록 정교하게 데이터 셋 구축한 버전)
2. 챌린저, 그마, 마스터 랭크게임(모든 경기 데이터가 다 담겨있지만 전처리가 필요한 데이터 셋)
3. 챔피언, 아이템에 대한 정보
본론으로 돌아와서 데이터 셋을 점점 정교하게 구축하다보니 한가지 의문점이 생기게 되었습니다. 바로 승패에 대한 예측 모델을 작성하기 위해서는 경기가 끝나기 전에 예측해야하지 경기가 끝난 정보들을 가지고 예측하는 것은 논리적으로 맞지 않다고 생각이 되었습니다.
가령, 승리한 팀과 패배한 팀의 패턴 및 특성을 파악하기 위해서는 사후적 데이터(경기가 끝나고 난 뒤의 데이터)로 분석하면 충분히 승리팀과 패배팀의 패턴을 알 수 있겠지만 결국 승리 / 패배를 예측하기 위해서는 경기가 끝난 사후데이터가 아닌 경기 중의 사전 데이터가 있어야 예측하는 논리에 부합할 것이라고 생각이 되었습니다.
해당 데이터를 수집하기 위해서 찾아보던 중 Riot API에 timeline api가 있어서 이 API를 이용하여 경기가 시작한 후 n분 까지의 경기 데이터를 수집해보도록 하겠습니다.
1. Timeline API
Riot Developer API의 Match-V4 카테고리의 3번째에 해당 api가 존재합니다.
데이터를 수집하는 방법은 기본적으로 Match데이터를 수집하는 방법과 동일합니다. 아래 제가 포스팅한 블로그를 한번 확인해주세요. 아니면 위에서 제 캐글 데이터셋 부분에서 3번 데이터 셋을 다운로드 받아 MatchId만 받아놔주세요!!
그래서 저희는 지금 MatchId가 있다고 가정을 하고 본격적으로 수집하고 전처리를 해보겠습니다.
2. Timeline 데이터 불러오기
사실 Timeline 데이터만 불러왔으면 자신이 분석할 방향에 맞게 구축할 데이터셋을 정의하고 다양한 방법론에 의거해서 데이터를 구축하시면 되는데요!
API를 처음 사용하시거나 불러온 데이터에 대한 설명을 Riot API description을 봐도 이해가 안되시는 분들이 꽤 있으실 것이라 생각이 되므로 해당 API를 통해 불러온 데이터는 어떤 데이터가 존재하며, 저는 어떻게 구축했는지에 대해서 설명해드리도록 하겠습니다!
일단 저도 마찬가지로 제 데이터셋에서 Challenger_Ranked_Games.csv를 불러오도록 하겠습니다.
challenger_game = pd.read_csv('/Users/sinmin-yong/GitHub/studies/Riot_DB/Challenger_Ranked_Games.csv') my_api = your api key
여기까지 준비가 완료 되셨나요?
그럼 이제 먼저 데이터 자체를 불러와보겠습니다.
timeline api를 불러오기 위한 url은 아래와 같습니다.
match_url = "https://kr.api.riotgames.com/lol/match/v4/timelines/by-match/{}?api_key=' + my_api
그럼 requests 모듈을 이용하여 데이터를 확인해볼까요
req = requests.get(match_url.format(4241538868)) req.json()
게임ID 4241538868의 데이터를 json형태로 쭉 확인해보면 무수히 많은 json형태의 데이터들이 나열될 것입니다.
일단 json 데이터에 어떤 키값들이 존재하는지 확인해보겠습니다.
Key값의 정보로는 frames와 frameInterval라는 정보가 있습니다.
이제부터 저희가 사용할 정보는 모두 frames 안에 있는 정보들이므로 frameInterval에 대해서는 굳이 신경쓰지 않으셔도 됩니다.
그럼 frames에는 어떤 정보가 있는지 확인해보겠습니다.
각 frames의 key값으로는 participantFrames, events, timestamp값이 있습니다. 각 변수에 대해서 설명하자면
- participantFrames : 게임에 참여한 10명의 소환사에 대한 정보
- events : 각 소환사들이 행동한 events log
- timestamp : 소환사들의 정보나 행동한 기록이 게임이 시작한 후 얼마만큼 지나서 이뤄졌는지 알 수 있는 시간기록(Timeline)
데이터 셋을 구축하기 앞서 어떤 데이터가 존재하는지 알아야지 원하는 방향으로 데이터를 구축할 수 있기 때문에 위의 각 frames 내부에 있는 데이터에 대해서 설명드리도록 하겠습니다.
2-1. participantFrames
저희가 잘 알다시피 소환사 협곡에 참여하는 유저들은 총 10명으로 아래의 participantFrames로 정보화 되어 있는 유저들도 10명인 것을 확인 가능합니다.
그럼 각 소환사별로 어떤 정보들을 담고 있을까요?
participantFrames에 key값으로 걸려있는 1~10 유저는 실제 participantId를 뜻하지 않으며 participantId는 해당 json내부 데이터에 존재하고 있습니다.
- participantId : 인게임 참여한 소환사 id
- position : 해당 소환사의 현 timestamp 위치
- currentGold : 해당 소환사가 지니고 있는 골드
- totalGold : 해당 소환사가 총 벌어들인 골드
- level : 해당 챔피언의 레벨
- xp : 해당 챔피언의 경험치
- minionskilled : 해당 소환사가 죽인 미니언 수
- jungleMinionsKilled : 해당 소환사가 죽인 정글 미니언 수
- dominionScore : 중요하지 않은 정보
- teamScore : 중요하지 않은 정보
자 participantFrames이 가지고 있는 정보에 대해서 확인해봤는데 대략적으로 어떤 정보를 이용해서 데이터셋을 구축하겠다라는 생각이 구상되시나요??
저 같은 경우에는 "중요하지 않은 정보", "xp", "position"을 뺀 나머지 데이터를 모두 이용했습니다.
제가 구축한 방법에 대한 전체 코드는 마지막 부분에 올려놓도록 하겠습니다.
2-2. events
이벤트 같은 경우 각 events별로 timestamp까지 유저가 어떤 행동을 보였는지 알 수 있는 데이터입니다.
변수에 대한 설명을 듣기전에 게임 내에서 어떤 events 종류들이 있는지 확인해봐야겠죠?
type_ls = [] for i in range(len(req.json()['frames'])): for j in range(len(req.json()['frames'][i]['events'])): type_ls.append((req.json()['frames'][i]['events'][j]['type'])) set(type_ls)
보시는 것처럼 10개의 event가 있는 것을 확인할 수 있습니다.
- Building_kill : 포탑(타워),억제기, 넥서스 파괴
- Champion_kill : 상대 챔피언 처치
- Elite_monster_kill : 엘리트 몬스터(전령, 드래곤, 바론) 처치
- Item_destroyed : 아이템 파괴
- Item_purchase : 아이템 구매
- Item_sold : 아이템 판매
- Item_undo : 중요하지 않은 정보
- Skill_level_up : 스킬 레벨업
- Ward_kill : 와드 파괴
- Ward_placed : 와드 설치
이렇게 다양한 이벤트들이 존재하니 수집시 유의해서 데이터를 구축해야합니다.
유의해야한다는 것이 예를들어 Building_kill이 있으면 억제기인지, 타워인지, 넥서스인지에 대한 정보를 보여주며, 또한 어느라인(탑, 미드, 봇)의 오브젝트인지에 대한 데이터도 함께 있으므로 구분해서 구축해주셔도 될 것 같습니다. 예를들면 이런식입니다.
오브젝트를 파괴했는데 그 오브젝트가 어떤 것이며, 어떤 유저가 파괴했는지, 어떤 유저가 어시스트 했는지, 어느 라인을 파괴했는지 등에 대한 정보가 자세히 설명됩니다.
그리고 모든 events마다 timestamp가 존재하는데 이는 아래의 timestamp에 대해서 설명하면서 다시 한번 강조드리겠습니다.
2-3. Timestamp
Timestamp는 위의 유저들의 정보, 이벤트가 게임 시작 후 얼마나 지났을 때까지의 정보인지를 알려주는 데이터입니다.
현재 Riot API Timeline 데이터의 Timestamp단위는 밀리세컨(ms)단위로 초단위로 바꾸기 위해서는 1000을 곱해주거나 ms단위 그대로 사용해주시면 됩니다.
예를들어 Timestamp가 600000일 때 초단위로 환산하면 600초 즉, 게임 시작 후 10분까지의 데이터라고 보시면 됩니다.
현재 보시는 시간은 frames가 1의 timestamp가 60000인 뜻은 frames 1에 있는 participantFrames, events가 게임 시작 후 1분까지의 데이터라는 뜻입니다.
아까 위에서 각 events마다 timestamp가 존재한다고 말씀드렸는데요.
보시면 봇라인의 포탑을 파괴한 시간(timestamp)이 662265라고 나오죠? 그렇다는 것은 약 11분정도에 봇 라인의 타워를 파괴했다는 것으로 해당 정보들을 이용해서 시계열적인 events 기록을 데이터로 만들 수 있습니다.
3. 데이터 셋 구축
여기서부터는 아마 많은 분들의 다양한 방법론이 존재할테니 서로 다를 수 있지만 저는 데이터를 어떻게 구축했는지 공유드리고자 합니다.
앞서 언급한 것처럼 단순 Match API를 이용해서는 게임이 종료된 후의 모든 데이터를 받아오는 것이여서 사후적인 데이터로 작용할 수 밖에 없어 승리와 패배한 팀의 패턴은 알 수 있지만 n시간까지 주로 어떤 데이터의 패턴을 보여야 승리할 확률이 높아지거나, 패배할 확률이 높아지거나 즉, 승/패를 예측함에 있어서 논리적인 데이터가 아니게됩니다.
따라서 게임 시작 후 n Minute 까지의 정보를 미리 알고(사전적 데이터) 승/패를 예측해보고자 경기 시작 후 n Minute까지의 데이터를 구축해봤습니다. 코드는 아래와 같습니다.
def riot_timeline_dataset(pre_dataset,api_key,timeline): challenger_game = pre_dataset my_api = api_key #수집할 컬럼 정의 use_columns = ['gameId','blueWins','blueTotalGolds','blueCurrentGolds','blueTotalLevel'\ ,'blueAvgLevel','blueTotalMinionKills','blueTotalJungleMinionKills' ,'blueFirstBlood','blueKill','blueDeath','blueAssist'\ ,'blueWardPlaced','blueWardKills','blueFirstTower','blueFirstInhibitor'\ ,'blueFirstTowerLane'\ ,'blueTowerKills','blueMidTowerKills','blueTopTowerKills','blueBotTowerKills'\ ,'blueInhibitor','blueFirstDragon','blueDragnoType','blueDragon','blueRiftHeralds'\ ,'redWins','redTotalGolds','redCurrentGolds','redTotalLevel'\ ,'redAvgLevel','redTotalMinionKills','redTotalJungleMinionKills' ,'redFirstBlood','redKill','redDeath','redAssist'\ ,'redWardPlaced','redWardKills','redFirstTower','redFirstInhibitor'\ ,'redFirstTowerLane'\ ,'redTowerKills','redMidTowerKills','redTopTowerKills','redBotTowerKills'\ ,'redInhibitor','redFirstDragon','redDragnoType','redDragon','redRiftHeralds'] challenger_timeline_df1 = pd.DataFrame() challenger_timeline_df = pd.DataFrame() error_list = [] match_url = \ 'https://kr.api.riotgames.com/lol/match/v4/timelines/by-match/{}?api_key=' + my_api for b in range(len(challenger_game)): try: game_id = challenger_game['gameId'].iloc[b] req = requests.get(match_url.format(game_id)) print('status : ' + str(req.status_code) + ' loop location : ' + str(b)) if req.status_code == 200: pass elif req.status_code == 429: print('api cost full : infinite loop start') print('loop location : ',b) start_time = time.time() while True: if req.status_code == 429: print('try 10 second wait time') time.sleep(10) req = requests.get(match_url.format(game_id)) print(req.status_code) elif req.status_code == 200: print('total wait time : ', time.time() - start_time) print('recovery api cost') break elif req.status_code == 503: print('service available error') start_time = time.time() while True: if req.status_code == 503 or req.status_code == 429: print('try 10 second wait time') time.sleep(10) req = requests.get(match_url.format(game_id)) print(req.status_code) elif req.status_code == 200: print('total error wait time : ', time.time() - start_time) print('recovery api cost') break elif req.status_code == 403: # api갱신이 필요 print('you need api renewal') print('break') break ''' pre_data0 = json_normalize(req.json()) pre_data0['gameId'] = game_id pre_data = pre_data.append(pre_data0) ''' #json data에서 필요한 frames 데이터만 frames = req.json()['frames'] #시작하고 n분 즉, 수집하고 싶은 시간까지의 인덱스가 어디있을지 추출하는 코드 lc0 = 0 # 위치 while True: try: timestamps = frames[lc0]['timestamp'] if timestamps <= timeline: # n Minute를 설정(Ms단위의 timeline) lc0 += 1 else: lc = lc0-1 break except: lc = lc0 - 1 break # participants 1~5 까지는 blueteam, 6~10까지는 redteam participant = frames[lc]['participantFrames'] bluetotal_gold, bluecurrent_gold, bluetotal_level, \ bluetotal_minionkill, bluetotal_jungleminionkill = [],[],[],[],[] redtotal_gold, redcurrent_gold, redtotal_level, \ redtotal_minionkill, redtotal_jungleminionkill = [],[],[],[],[] for i in range(len(participant)): i = i+1 if 1 <=participant[str(i)]['participantId'] <= 5: bluetotal_gold.append(participant[str(i)]['totalGold']) bluecurrent_gold.append(participant[str(i)]['currentGold']) bluetotal_level.append(participant[str(i)]['level']) bluetotal_minionkill.append(participant[str(i)]['minionsKilled']) bluetotal_jungleminionkill.append(participant[str(i)]['jungleMinionsKilled']) else: redtotal_gold.append(participant[str(i)]['totalGold']) redcurrent_gold.append(participant[str(i)]['currentGold']) redtotal_level.append(participant[str(i)]['level']) redtotal_minionkill.append(participant[str(i)]['minionsKilled']) redtotal_jungleminionkill.append(participant[str(i)]['jungleMinionsKilled']) #timestamp별로 독립적인 변수들을 나타내므로 n분까지의 데이터를 수집하기 위해서는 계속 중첩해서 #더해줘야 함 blue_kill, red_kill = 0,0 blue_firstkill, red_firstkill = 0,0 blue_assist, red_assist = 0,0 red_death, blue_death = 0,0 blue_wardplace, red_wardplace = 0,0 blue_wardkill, red_wardkill = 0,0 blue_elite, red_elite = 0,0 blue_rift, red_rift = 0,0 blue_dragon, red_dragon = 0,0 blue_baron, red_baron = 0,0 blue_firstdragon, red_firstdragon = 0,0 blue_dragontype, red_dragontype = [],[] blue_firstbaron, red_firstbaron = 0,0 blue_tower,red_tower = 0,0 blue_firsttower, red_firsttower = 0,0 blue_firsttowerlane, red_firsttowerlane = [],[] blue_midtower, red_midtower = 0,0 blue_toptower, red_toptower = 0,0 blue_bottower, red_bottower = 0,0 blue_inhibitor, red_inhibitor = 0,0 blue_firstinhibitor, red_firstinhibitor = 0,0 for y in range(1,lc+1): events = frames[y]['events'] for x in range(len(events)): if events[x]['type'] == 'WARD_KILL': if 1 <= events[x]['killerId'] <= 5: blue_wardkill += 1 else: red_wardkill += 1 elif events[x]['type'] == 'WARD_PLACED': if 1 <= events[x]['creatorId'] <= 5: blue_wardplace += 1 else: red_wardplace += 1 elif events[x]['type'] == 'CHAMPION_KILL': if 1 <= events[x]['killerId'] <= 5: if red_kill ==0 and blue_kill ==0: blue_firstkill += 0 else: pass blue_kill += 1 blue_assist += len(events[x]['assistingParticipantIds']) red_death += 1 else: if red_kill ==0 and blue_kill ==0: red_firstkill += 0 else: pass red_kill += 1 red_assist += len(events[x]['assistingParticipantIds']) blue_death += 1 elif events[x]['type'] == 'ELITE_MONSTER_KILL': if 1 <= events[x]['killerId'] <= 5: blue_elite += 1 if events[x]['monsterType']== 'DRAGON': if red_dragon ==0 and blue_dragon == 0: blue_firstdragon += 1 else: pass blue_dragontype.append(events[x]['monsterSubType']) blue_dragon += 1 elif events[x]['monsterType']== 'RIFTHERALD': blue_rift += 1 elif events[x]['monsterType']== 'BARON_NASHOR': if red_baron ==0 and blue_dragon == 0: blue_firstbaron += 1 else: pass blue_baron += 1 else: red_elite += 1 if events[x]['monsterType']== 'DRAGON': if red_dragon ==0 and blue_dragon == 0: red_firstdragon += 1 else: pass red_dragontype.append(events[x]['monsterSubType']) red_dragon += 1 elif events[x]['monsterType']== 'RIFTHERALD': red_rift += 1 elif events[x]['monsterType']== 'BARON_NASHOR': if red_baron ==0 and blue_dragon == 0: red_firstbaron += 1 else: pass red_baron += 1 elif events[x]['type'] == 'BUILDING_KILL': if 1 <= events[x]['killerId'] <= 5: if events[x]['buildingType'] == 'TOWER_BUILDING': if red_tower == 0 and blue_tower ==0: blue_firsttower += 1 blue_firsttowerlane.append(events[x]['laneType']) else: pass blue_tower += 1 if events[x]['laneType'] == 'MID_LANE': blue_midtower += 1 elif events[x]['laneType'] == 'TOP_LANE': blue_toptower += 1 elif events[x]['laneType'] == 'BOT_LANE': blue_bottower += 1 elif events[x]['buildingType'] == 'INHIBITOR_BUILDING': if red_inhibitor == 0 and blue_inhibitor ==0: blue_firstinhibitor += 1 else: pass blue_inhibitor += 1 else: if events[x]['buildingType'] == 'TOWER_BUILDING': if red_tower == 0 and blue_tower ==0: red_firsttower += 1 red_firsttowerlane.append(events[x]['laneType']) else: pass red_tower += 1 if events[x]['laneType'] == 'MID_LANE': red_midtower += 1 elif events[x]['laneType'] == 'TOP_LANE': red_toptower += 1 elif events[x]['laneType'] == 'BOT_LANE': red_bottower += 1 elif events[x]['buildingType'] == 'INHIBITOR_BUILDING': if red_inhibitor == 0 and blue_inhibitor ==0: red_firstinhibitor += 1 else: pass red_inhibitor += 1 data_list = [game_id,challenger_game['blueWins'].iloc[b],np.sum(bluetotal_gold)\ ,np.sum(bluecurrent_gold),np.sum(bluetotal_level),np.mean(bluetotal_level)\ ,np.sum(bluetotal_minionkill),np.sum(bluetotal_jungleminionkill)\ ,blue_firstkill,blue_kill,blue_death,blue_assist,blue_wardplace,blue_wardkill\ ,blue_firsttower,blue_firstinhibitor,blue_firsttowerlane,blue_tower\ ,blue_midtower,blue_toptower,blue_bottower,blue_inhibitor,blue_firstdragon\ ,blue_dragontype,blue_dragon,blue_rift\ ,challenger_game['redWins'].iloc[b],np.sum(redtotal_gold)\ ,np.sum(redcurrent_gold),np.sum(redtotal_level),np.mean(redtotal_level)\ ,np.sum(redtotal_minionkill),np.sum(redtotal_jungleminionkill)\ ,red_firstkill,red_kill,red_death,red_assist,red_wardplace,red_wardkill\ ,red_firsttower,red_firstinhibitor,red_firsttowerlane,red_tower\ ,red_midtower,red_toptower,red_bottower,red_inhibitor,red_firstdragon\ ,red_dragontype,red_dragon,red_rift] challenger_timeline_df0 = pd.DataFrame(np.array([data_list]), columns = use_columns) challenger_timeline_df1 = challenger_timeline_df1.append(challenger_timeline_df0) print('data crawling success') if b != 0 and b % 2000 == 0 : #feature가 많다보니 반복문 2000씩 끊어서 수집 challenger_timeline_df = challenger_timeline_df.append(challenger_timeline_df1) challenger_timeline_df1 = pd.DataFrame() except: #에러발생 시 바로 다음 반복문으로 넘어가게끔 print('error visual') error_list.append(b) pass challenger_timeline_df = challenger_timeline_df.append(challenger_timeline_df1) return challenger_timeline_df
만약 위의 코드를 사용하실 분들은 최대한 직관적으로 적어놨긴 했으나 아마 왜 이렇게 짜여졌는지, 이건 무슨 함수인지 모르시는 분들도 있으실테니 만약 효율적인 코드가 있다고 하면 자신의 코드에 맞게 고쳐서 사용해주시면 되고 어떤 함수인지, 왜 이렇게 짜여졌는지에 대해서 궁금하신 분들은 언제든지 댓글 달아주시면 성심성의껏 답변해드리겠습니다!!
- 챌린저, 그마, 마스터 경기 시작 후 10분, 15분까지의 게임 데이터(바로 분석할 수 있도록 정교하게 데이터 셋 구축한 버전)
'ML, DL & Python > Riot API를 활용환 데이터 분석' 카테고리의 다른 글
Riot API를 활용한 리그오브레전드(lol) 데이터 셋 구축(kaggle dataset) - User League/ item / champion / Ranked Games(랭겜) (5) 2020.03.28 리그오브레전드 데이터 분석 - Match Data Analytics(3) (3) 2020.03.15 리그오브레전드 데이터 분석 - match data EDA(2) (3) 2020.03.15 리그오브레전드 데이터 분석 - match data EDA(1) (20) 2020.03.08 리그오브레전드 데이터를 활용한 승/패 예측 (6) 2019.06.02