見出し画像

ポケモンの大会に参加したので対戦相手のデータを集計した話

※この記事は投げ銭方式を採用しており、全文無料で読めます。

1. 概要

はじめましての方ははじめまして。既にご存じの方はお久しぶりです。クラルティです。今回例のウイルスがtier1に君臨し、世界大会の中止が発表されていますがオンライン大会は予定通り行うようで、2020 International Challenge April(以下INC4月)が開催されました。そこで使った構築や環境考察の記事を書こうと思い、せっかく相手の構築をメモしてあったので環境を数字で考察しようと考えました。しかし41試合分のデータを手作業で集計するのはめんどくさい。とりあえず相手の情報をExcelに打ちこんだはいいものの、KP(被りポイント、要するに使用率)を調べるために「登場したポケモンの種族名を重複を省いて取得」する関数がパッと見でわからない。これを手作業で集計するのもめんどくさい。標準入力でデータ与えられればpython等で処理するくらいできるのに...

「Excelのデータをpythonに渡してあげて処理すれば良くない!?」

ということでこれまでやったことが無かったのですが適当にpythonでデータと遊んでみることにしました。この記事はその格闘の記録です。

なお、想定している読者は次のような人です。

- ポケモンについての知識は(ほぼ)不要
- プログラミングについては最低限の知識がある

自分は該当しないな、という方もとりあえず最後まで読んでみるかいいねを押してからブラウザバックしてください。いいねされると嬉しいので()

また記事中に参考文献を含むいくらかのリンクが貼ってありますが、問題があったら削除しますのでお手数ですがTwitter(@clarty_ )まで一報ください。

2. KPを求めてみた

事前に手入力したExcelのデータを用意しました。

こんな感じです。TN(トレーナーネーム、ゲーム内での名前)については一応加工してあります。無意識にやっていたのですが、Excelって最初の行は見出しとして扱うのがデフォルトの設定のようで、読み込んだ時にここだけ処理が違うみたいですね。詳しくは後述します。

さて、これを Party_data.xlsx とかで保存して、あわよくばこの端っこに適当な関数を突っ込んでババっと使用率を集計しちゃおうかと思ったのですが、普段Excelを使わない私にはどんな関数があるのかさっぱり。正直SumとAVERAGEくらいしかわかんないし、LOOKUPとかCOUNTとかはggったんですがどうも違うみたいなんですよね。詳しい方いたら教えてください。

そこで「python Excel 読み込み」みたいなワードで検索して出てきたこちらのブログを参考にさせていただきながらとりあえずさっきのデータをsheet_Lという2重配列にすることに成功しました。

import pandas as pd
if __name__ == "__main__":
   sheet_D=pd.read_excel(r'Party_data.xlsxまでのパス',sheet_index=1)
   sheet_L=sheet_D.values.tolist()

成功しましたとかしれっと書いてますがpandasが入ってなかったのでpip installしました。pip installについては各自でggってください。

データを取り込めたところで集計作業に移ります。まずはどのポケモンが何回登場したかが知りたいのですが、どの種族のポケモンがリストにいるのかを過不足なく数え上げる必要があります。Excelを使ってて最初に躓いたポイントですね。とりあえず打ちこんだはいいものの相手の名前はどうでもいいためポケモンの部分の情報だけ取り出したい。今sheet_L[x][y]でExcelのx行y列目の情報にアクセスできるわけだから、ポケモンの名前だけ取ってきた配列はこれでいいはず…

match = len(sheet_L)
pokemon = list()
for i in range(match):
   for j in range(6):
       pokemon.append(sheet_L[i+1][j+1])

良くなかったです。はい。

これが最初の罠で、配列にした時点でExcelの最初の1行の情報は全て消えてきます。つまり見出しの「TN」とか「ポケモンn」の部分はそもそも入ってないんですね。なので正しくはこうでした。

match = len(sheet_L)
pokemon = list()
for i in range(match):
   for j in range(6):
       pokemon.append(sheet_L[i][j+1])

一番左のTNについての情報は別に抜け落ちてないのでj+1はそのままなことには注意です。

ポケモンの情報だけ一重配列に抜き出したので後は普通にその登場回数を求めるだけです。競プロも初心者なのでもっと上手い方法があるかもしれませんが、次のように求めました。上手い方法あったら教えてください。

def get_KP(pokemon):
   pokemon.sort()
   
   KP = list()
   length = len(pokemon)
   counter = 0
   for i in range(length-1):
       counter += 1
       if pokemon[i] != pokemon[i+1]:
           KP.append([pokemon[i], counter])
           counter = 0
   KP.append([pokemon[-1], counter+1])
   KP.sort(key=lambda x:x[1], reverse=True)
   return KP

もう慣れましたがfor文の後に同じ処理をもう一回書かないと、「後ろが違うポケモンになったらカウント!」ってif文でやってるので、最後のポケモンの情報が抜け落ちちゃうんですよね。後forの中身でi+1を参照している関係で、ループ回数がlength-1になるので最後のポケモンの登場回数はcounter+1です。これをミスったので最初KPの和が6の倍数になってませんでした。

3.KPをCSVで出力してみた

KPを求めたはいいものの、標準出力に出すだけじゃ見づらいので上手い形で出力してやろうと思いました。読み書きが簡単でExcelでも開けるCSVが良いと思いまた適当にggるとこんな記事がありました。この記事にまんま書いてあったのでとりあえずKPを出力してみました。

with open(r'G:\マイドライブ\python\pokemon\2020INCApril\KP_data.csv', 'w') as f:
   writer = csv.writer(f)
   writer.writerows(KP)

出来た!!けど空行邪魔だし見づらい。

print(KP)で出力しても変な改行は見当たらないし、writerows()が悪さしているのかと思い引数にend=""とかを追加してみたんですが、エラー起こしました。print()と同じだと勝手に決めつけるのは良くないですね。

結局全然わからなかったので友人に相談したところピッタリなブログを見つけてきてくれました。もしかして、私のggり力低すぎ…?!無事解決したことでKPをCSVで吐き出してくれるコードが完成しました。次回以降の大会でもExcelに手打ちさえすれば済むで楽そうです。

import pandas as pd
import csv
import pprint
def get_KP(pokemon):
   pokemon.sort()
   
   KP = list()
   length = len(pokemon)
   counter = 0
   for i in range(length-1):
       counter += 1
       if pokemon[i] != pokemon[i+1]:
           KP.append([pokemon[i], counter])
           counter = 0
   KP.append([pokemon[-1], counter+1])
   KP.sort(key=lambda x:x[1], reverse=True)
   return KP
if __name__ == "__main__":
   sheet_D=pd.read_excel(r'Party_data.xlsxへのパス',sheet_index=1)
   sheet_L=sheet_D.values.tolist()
   match = len(sheet_L)
   pokemon = list()
   for i in range(match): #Excelのデータからポケモン名だけを抽出
       for j in range(6):
           pokemon.append(sheet_L[i][j+1])

   KP = get_KP(pokemon) #ポケモン名の羅列からKPを集計
   #print(KP)
   with open(r'KP_data.csvへのパス', 'w', newline='') as f:
       writer = csv.writer(f)
       writer.writerows(KP)

4.KKPを求めてみた

せっかくデータをpythonでいじれるようにしたんだからもう少し分析をしてみたいお年頃。ポケモン毎の使用率が分かっても、ポケモンは6匹の組み合わせで戦うゲームなのでその組み合わせ単位で対策する必要があります。そこでKKP(被り被りポイント、あるポケモンについて他のポケモンが同時採用されている数を表す)を集計してみることにしました。

正直この章は面白いこと無いんですが、KP_data.csv を読み込んでポケモンのリストを取得。(この時KPデータは2重配列なのでポケモン名を取得するにはl[i][0]としなきゃいけないのですが[0]を忘れてて時間溶かしました。)

各ポケモンにつき、ポケモン種類数だけの長さを持つ配列を用意し0で初期化。sheet_Lのポケモン名を全探索し、そのポケモンを見つけたら同じ構築のポケモンに対応する位置をインクリメント。この対応する位置というのが現状KP順のリストなので、対応する位置を探すにも全探索しています(50音順とかなら2分探索できそうですが、めんどくさいので…)。結果としてfor文が大量に出てきてしまったので実行に時間かかるかなと思っていたので各所にprint文を置いて進捗を可視化したのですが、たかだか41試合分。すぐに終わりました。今後もっとデータ数が増えたらこのアルゴリズムの改善は課題だと思います。

こうしてとりあえずKKPの表を作り、読みづらいのでポケモン名とKKPをセットにした配列を定義しなおしたうえでソートすることでそれなりに見やすくなりました。これをどう環境分析記事に貼るかは考え中です。

import pandas as pd
import csv
import pprint

def add_KKP(ones_KKP, sheet_L, row, place, pokemon_name):
   for index in range(1,7): #同じ構築の6匹について
       the_pokemon = sheet_L[row][index]
       if index != place: #そのポケモン以外の5匹なら
           for i in range(len(pokemon_name)): #ポケモンリストを探索して
               if pokemon_name[i] == the_pokemon: #一致する名前があったら
                   ones_KKP[i] += 1 #その箇所を+1
   
   return 0

def get_ones_KKP(num_pokemon, match, sheet_L, pokemon, ):
   ones_KKP = [0]*num_pokemon

   for row in range(match):
       for column in range(1,7):
           if sheet_L[row][column] == pokemon:
               place = column
               add_KKP(ones_KKP, sheet_L, row, place, pokemon_name)
   
   return ones_KKP

def get_KKP(pokemon_name, match, sheet_L):
   num_pokemon = len(pokemon_name)

   KKP = list()
   for i in range(num_pokemon):
       pokemon = pokemon_name[i]

       ones_KKP = get_ones_KKP(num_pokemon, match, sheet_L, pokemon)        

       KKP.append([pokemon, ones_KKP])
       print(pokemon, "'s KKP is finished")

   return KKP

def translate_KKP(pokemon_name, KKP):
   length = len(pokemon_name)
   print_KKP = list()
   for i in range(length):
       pokemon = pokemon_name[i]
       KKP_tmp = KKP[i][1]
       ones_KKP = list()
       for j in range(length):
           if KKP_tmp[j] != 0:
               ones_KKP.append([pokemon_name[j], int(KKP_tmp[j])])
       ones_KKP.sort(key=lambda x: x[1], reverse = True)
       print_KKP.append([pokemon, ones_KKP])

   return print_KKP

if __name__ == "__main__":
   sheet_D=pd.read_excel(r'Party_data.xlsxまでのパス',sheet_index=1)
   sheet_L=sheet_D.values.tolist()

   match = len(sheet_L)


   pokemon_name = list()

   with open(r'KP_data.csvまでのパス', 'r') as f:
       reader = csv.reader(f)
       l = [row for row in reader]
       for i in range(len(l)):
           pokemon_name.append(l[i][0])
   print("Party_data is read")


   KKP = get_KKP(pokemon_name, match, sheet_L)

   print_KKP = translate_KKP(pokemon_name, KKP)
   

   with open(r'KKP_data.csvまでのパス', 'w', newline='') as f:
       writer = csv.writer(f)
       writer.writerows(print_KKP)

   print("all clear")

5.終わりに

結構簡単にそれっぽいことが出来たので楽しくなって余計な記事の筆を執ってしまいました。いいねやPV数次第ではまた似たような記事を書くかもしれません。

あとここに書くべき話かは分かりませんがヘッダーの画像はフリ素を適当に取ってきたものなので私はWindowsを使ってます。

今回はデータをpythonで読み書きすることが目的だったので細かいアルゴリズムについては全然考察していませんが、特にKKPを求めるときのようなforの連打は改善の余地がありそうですね。ここはこうするべき、みたいなのがありましたら是非@clarty_までお願いします。

ここから先は

0字

¥ 100

この記事が気に入ったらチップで応援してみませんか?