TECH MEMO

技術的な内容に関する個人的なメモ

世界カーリング選手権データの可視化①(pandasによるデータ集計・可視化)

前回の記事からだいぶ経ってしまいましたが、pandas,matplotlibを使って、少しデータを集計・可視化したいと思います。
可視化するデータは以下で取得したCSV形式の試合結果データ(xxx_result_score.csv)です。

curlyst.hatenablog.com

なお、今回対象とするデータは、2002年〜2016年までの15年間の男女世界選手権のデータです。
(ただし、女子の2009年と2011年のデータに関しては、各試合の詳細データが取得できなかったため、今回は対象外としています。)

1. データの読み込み

まずはデータが格納されたディレクトリから再帰的にファイルを探索して、各世界選手権データのファイルパスを取得します。

# 入力ディレクトリ
input_dir_path = "<ファイルを格納してあるディレクトリパスを指定>"
input_file_ext = "results_score.csv"

input_file_path_list = []
for root, dirs, files in os.walk(input_dir_path):
    for file in files:
        if input_file_ext in file:
            input_file_path_list.append(os.path.join(root, file))


上記で取得した各ファイルパスに対して、1ファイルずつ順に読み込んでいき、データをDataFrame(result_score_pd)に格納していきます。
なお、以下では読み込んだデータをDataFrameに格納する前に、以下の2つの処理を実施しています。

  • 勝敗フラグ(勝ち:1, 引き分け:0, 負け:-1)の付与
  • 年、性別カラムの追加


# 各試合のスコアデータを格納するDataFrame
result_score_pd = None

# 各ファイルを順に読み込み、DataFrameに格納する
for file_path in input_file_path_list:
    # CSVファイルの読み込み
    tmp_result_score_pd = pd.read_csv(file_path)
    
    # 勝敗フラグの付与(勝ち:1, 引き分け:0, 負け:-1)
    result_score_list = []
    for row_idx in range(0, tmp_result_score_pd.shape[0], 2):
        score1 = tmp_result_score_pd["score(total)"][row_idx]
        score2 = tmp_result_score_pd["score(total)"][row_idx+1]
        
        if tmp_result_score_pd["draw"][row_idx] != tmp_result_score_pd["draw"][row_idx+1]:
            print "draw is different: row {0} and {1}".format(tmp_result_score_pd["draw"][row_idx], tmp_result_score_pd["draw"][row_idx+1])
        elif tmp_result_score_pd["draw"][row_idx] != tmp_result_score_pd["draw"][row_idx+1]:
            print "sheet is different: row {0} and {1}".format(tmp_result_score_pd["draw"][row_idx], tmp_result_score_pd["draw"][row_idx+1])
        
        if score1 < score2:
            result_score_list.append(-1)
            result_score_list.append(1)
        elif score1 == score2:
            result_score_list.append(0)
            result_score_list.append(0)
        else:
            result_score_list.append(1)
            result_score_list.append(-1)

        # Hammerの修正(先行/後攻がわからない場合)
        hammer1 = tmp_result_score_pd["hammer"][row_idx]
        hammer2 = tmp_result_score_pd["hammer"][row_idx+1]
        if hammer1 == hammer2:
            tmp_result_score_pd["hammer"][row_idx] = -1
            tmp_result_score_pd["hammer"][row_idx+1] = -1
    
    tmp_result_score_pd["result"] = result_score_list
    
    # 年度、性別の取得
    tmp_list = re.findall(r"[0-9]+", file_path)
    if len(tmp_list) != 1 and len(tmp_list[0]) != 4:
        print "Year is missing: {0} {1}".format(file_path, year)
    year = tmp_list[0]
    
    gender = None
    if "Women" in file_path:
        gender = "Women"
    elif "Men" in file_path:
        gender = "Men"
    else:
        print "Gender is missing: row {0}.".format(row_idx)
    
    tmp_result_score_pd["year"] = [year] * tmp_result_score_pd.shape[0]
    tmp_result_score_pd["gender"] = [gender] * tmp_result_score_pd.shape[0]
    
    # DataFrameに追加
    if result_score_pd is None:
        result_score_pd = tmp_result_score_pd
    else:
        result_score_pd = pd.concat([result_score_pd, tmp_result_score_pd])

# データの年数
year_list = list(set(result_score_pd["year"]))
year_list.sort()
print "the number of years : {0}".format(len(year_list))
print year_list

# 年×男or女
year_gender_pd = result_score_pd[["year", "gender"]]
year_gender_pd = year_gender_pd.drop_duplicates()
year_gender_pd = year_gender_pd.sort_values(by=["year", "gender"])
print "\nthe number of competition : {0}".format(year_gender_pd.shape[0])
year_gender_list = []
for value in year_gender_pd.values:
    year_gender_tuple = tuple(value)
    year_gender_list.append(year_gender_tuple)
print year_gender_list

上記を実行すると以下のように出力されます。

the number of years : 15
['2002', '2003', '2004', '2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016']

the number of competition : 28
[('2002', 'Men'), ('2002', 'Women'), ('2003', 'Men'), ('2003', 'Women'), ('2004', 'Men'), ('2004', 'Women'), ('2005', 'Men'), ('2005', 'Women'), ('2006', 'Men'), ('2006', 'Women'), ('2007', 'Men'), ('2007', 'Women'), ('2008', 'Men'), ('2008', 'Women'), ('2009', 'Men'), ('2010', 'Men'), ('2010', 'Women'), ('2011', 'Men'), ('2012', 'Men'), ('2012', 'Women'), ('2013', 'Men'), ('2013', 'Women'), ('2014', 'Men'), ('2014', 'Women'), ('2015', 'Men'), ('2015', 'Women'), ('2016', 'Men'), ('2016', 'Women')]

	finish file readeing: 0.87944817543 [sec]

なお、DataFrameには以下のようにデータが格納されます。

f:id:curlyst:20170106003330p:plain


2. データの集計・可視化

データの読み込みができたので、まずはデータの概要を把握するために、簡単な集計・可視化を行っていきます。

2-1. 全試合数

2002年〜2016年までの全28大会で、合計何試合が行われたかを集計します。

# 試合数
game_num = result_score_pd.shape[0] / 2
print "全試合数: {0}".format(game_num)

実行結果

全試合数: 1873

15年間で男女合わせて1873試合が世界選手権で行われています。カーリングの世界選手権では予選はリーグ戦なので、試合数は多いですね。

2-2. 出場チーム(国)数の推移

各大会で何カ国が出場しているかを集計します。

# 各大会のチーム数
print "\n各大会の出場国数: "
team_num_pd = pd.pivot_table(data=result_score_pd, index="gender", columns="year", values="team", aggfunc=lambda x: len(x.unique()))
print team_num_pd

実行結果

各大会の出場国数: 
year    2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016 
gender                                                                     
Men     10.0  10.0  10.0  12.0  12.0  12.0  12.0  12.0  12.0  12.0  12.0  12.0  12.0  12.0  12.0  
Women   10.0  10.0  10.0  12.0  12.0  12.0  12.0   NaN  12.0   NaN  12.0  12.0  12.0  12.0  12.0 

男女共、各年での出場チーム数は同じで、2002年〜2004年までは10チーム、2005年からは12チームが出場しています。

2-3. 各国の出場回数と勝率

各国が男女合わせて何回世界選手権に出場しているか、また各国の勝率を可視化してみます。

# 各国の出場回数
team_entry_num_pd = pd.pivot_table(data=result_score_pd, index="team", columns="gender", values="year", aggfunc=lambda x: len(x.unique()))
team_entry_num_pd = team_entry_num_pd.fillna(0)
team_entry_num_pd["total"] = team_entry_num_pd["Men"] + team_entry_num_pd["Women"]
team_entry_num_pd = team_entry_num_pd.sort_values(by="total", ascending=False)

ax = team_entry_num_pd.plot.bar(y=["Men", "Women"], alpha=0.8, stacked=True)
ax.set_yticks(range(0, 30, 1), minor=True)
ax.grid('on', which='minor', axis='y')
ax.set_ylabel("num of entry", fontsize=13)
ax.set_xlabel("country", fontsize=13)
ax.tick_params(labelsize=13)

実行結果
f:id:curlyst:20170106005547p:plain

カーリングの盛んなカナダや、発祥地であるスコットランドは全大会に出場しています。
日本も全体の9番目に位置しており、がんばっていますね。

次に各国の勝率を可視化します。

# 各国の勝率の計算
win_lose_num_pd = pd.pivot_table(data=result_score_pd, index="team", columns="result", values="draw", aggfunc="count")
win_lose_num_pd = win_lose_num_pd.fillna(0)
win_lose_num_pd.columns = ["lose", "win"]
win_lose_num_pd["total"] =  win_lose_num_pd["lose"] + win_lose_num_pd["win"]
win_lose_num_pd["winning percentage"] = win_lose_num_pd["win"] / win_lose_num_pd["total"]
win_lose_num_pd = win_lose_num_pd.sort_values(by="winning percentage", ascending=False)

# 勝ち数・負け数の棒グラフ
ax = win_lose_num_pd.plot.bar(y=["win", "lose"], alpha=0.8, figsize=(10, 5))
ax.set_ylabel("num of games", fontsize=13)
ax.set_xlabel("country", fontsize=13)
ax.tick_params(labelsize=13)
# 勝率の折れ線グラフ(2軸)
ax2 = ax.twinx()
win_lose_num_pd.plot(ax=ax2, y="winning percentage", color="red", marker="o", markersize=5, legend=False)
ax2.set_ylabel("winning percentage", fontsize=13)
ax2.set_ylim([0.0, 1.0])
ax2.tick_params(labelsize=13)
ax.grid()
ax2.set_xlim([-1, win_lose_num_pd.shape[0]])

実行結果
f:id:curlyst:20170110002021p:plain

赤い折れ線が勝率を表しています。
勝率を見るとカナダが圧倒的で約7割7分と、やはり強いですね。
日本は負け越し。。。日本がんばれ!!

3. まとめ

今回は最初なので、まずはpadasを用いてデータの読み込みから簡単な可視化を実施しました。
次回はもう少し掘り下げた集計・可視化ができればと思います。