1. Qiita
  2. Items
  3. Python

「サザエさんのじゃんけん データ分析」のR言語からPythonに移植

  • 4
    Like
  • 2
    Comment

はじめに

もともと2013年に静岡Developers勉強会で機械学習を学んだ際にR言語を覚えたのですが、その後に1年に1回だけ「サザエさんのじゃんけん データ分析」のためだけにR Studioを起動するだけになっていて、操作も思い出す感じでやっている状態です。
昨年、Visual Studio CodeでPythonの開発環境を整えることが出来たので、R言語からPythonに移植してみました。とはいっても、Pythonを本格的に意識して組むこと自体は今回初めてなので、そこそこ苦労しました。

開発環境

  • OS:Windows10 Home(64bit)
  • Python:Python 3.5.2 :: Anaconda 4.2.0.0 (64bit)
  • エディタ:Visual Studio Code version 1.8.1

インストール

pip install beautifulsoup4
pip install pandasql

Webスクレイピング

PythonでWebスクレイピングを組むのは初めてだったので、検索したらサザエさん(とプリキュア)のジャンケンデータのダウンロードとしてGit Hubにスクリプトが公開されていたので参考にさせて頂きました。
今回は、Pandasを使用してCSVファイルに出力するようになっています。

GetSazaeData.py
# -*- coding: utf-8 -*-

#get data from http://www.asahi-net.or.jp/~tk7m-ari/sazae_ichiran.html

'''
サザエさんのじゃんけん予想データ取得
'''
import urllib.request
import datetime as dt
import bs4 as bs
import pandas as pd

def normalized_year(year):
    '''
    2桁年を4桁年に編集
    '''
    return int(year) + 2000 if year < 91 else int(year) + 1900

def read_data():
    '''
    Webスクレイピングによるデータ取得
    '''
    result = []
    response = urllib.request.urlopen('http://www.asahi-net.or.jp/~tk7m-ari/sazae_ichiran.html')
    soup = bs.BeautifulSoup(response, "lxml")

    for i in soup.body.contents[9].contents:
        if i.string and len(i.string.strip()) != 0:
            split = i.strip().split()
            seq = int(split[0][1:-1])
            year, month, day = map(int, split[1].split('.'))
            year = normalized_year(year)
            #the data contain illegal date: 91.11.31 -> 91.12.01
            if year == 1991 and month == 11 and day == 31:
                date = dt.date(year, month + 1, 1)
            else:
                date = dt.date(year, month, day)

            kind, idx = ('-', 9)
            hand = split[2]
            if hand == 'グー':
                kind, idx = ('G', 1)
            if hand == 'チョキ':
                kind, idx = ('C', 2)
            if hand == 'パー':
                kind, idx = ('P', 3)

            result.append((seq, year, date, kind, idx))
    result.reverse()

    return result

def main():
    '''
    メイン
    '''
    df_data = pd.DataFrame(read_data(), columns=['seq', 'year', 'date', 'kind', 'idx'])
    df_data.to_csv('SazaeData.csv', index=False)

if __name__ == '__main__':
    main()

サザエさんのじゃんけん予想

次の手の予測アルゴリズム

  • チョキが多いので、グー > チョキ > パーの優先順位とする
  • 前回と違う手を出すので、上記の優先順位で勝手を選ぶ
  • 二手前と一手前が違う手なら、残りの手を出すので勝手を選ぶ
  • 三手の中に同手がある場合、 残りの手を出すので勝手を選ぶ
  • 二手前と一手前が同じ手なら、勝手を出すので負手を選ぶ

GetSazaeData.pyによって出力した「SazaeData.csv」を読み込んで2006年~2016年の10年間の勝敗結果を出力します。
pandasのデータ操作に慣れそうにないので、「pandasql」を使用してSQLによるデータ操作にしています。あと、pandasqlをフォークしてユーザー定義関数を使えるようにした「pysqldf 」も良さげですね。

main.py
# -*- coding: utf-8 -*-
'''
サザエさんのじゃんけん予想
'''
import pandas as pd
from pandasql import sqldf

def get_guess(fstkind, sndkind, thrkind):
    '''
    次手の予測
    '''

    guess = 'G'

    if fstkind == 'G':
        guess = 'C'
    elif fstkind == 'C':
        guess = 'G'
    elif fstkind == 'P':
        guess = 'G'

    #2手前が在る場合
    if sndkind != '':
        if sndkind != fstkind:
            #違う組み合わせ 残りの手が出ると予想するので残りの手の勝手にする
            ptn = fstkind + sndkind
            if ptn in('GC', 'CG'):
                guess = 'C' #Pの予想でCにする
            elif ptn in('CP', 'PC'):
                guess = 'P' #Gの予想でPにする
            elif ptn in('PG', 'GP'):
                guess = 'G' #Cの予想でGにする
        else:
            #同一なら勝手と予想するので負手にする
            if fstkind == 'G':
                guess = 'C' #Pの予想でCにする
            elif fstkind == 'C':
                guess = 'P' #Gの予想でPにする
            elif fstkind == 'P':
                guess = 'G' #Cの予想でGにする

        #3手前が在る場合
        if thrkind != '':
            #違う組み合わせ 残りの手が出ると予想するので残りの手の勝手にする
            ptn = fstkind + sndkind
            if ptn in('GC', 'CG'):
                guess = 'C' #Pが出るのでCにする
            ptn = fstkind + sndkind + thrkind
            if ptn in('GCG', 'CGC'):
                guess = 'C' #Pの予想でCにする
            elif ptn in('CPC', 'PCP'):
                guess = 'P' #Gの予想でPにする
            elif ptn in('PGP', 'GPG'):
                guess = 'G' #Cの予想でGにする
            elif ptn in('GGC', 'CCG', 'GCC', 'CGG'):
                guess = 'C' #Pの予想でCにする
            elif ptn in('CCP', 'PPC', 'PCC', 'CPP'):
                guess = 'P' #Gの予想でPにする
            elif ptn in('PPG', 'GGP', 'GPP', 'PGG'):
                guess = 'G' #Cの予想でGにする

    return guess   #戻り値

def get_fight(kind, guess):
    '''
    勝敗 関数作成
    '''

    ptn = kind + guess
    if ptn in('GP', 'CG', 'PC'):
        result = 'win'
    elif kind == guess:
        result = 'draw'
    else:
        result = 'lose'

    return result   #戻り値


def get_fight_result(df_data):
    '''
    年別の過去データとの勝敗
    '''
    result = []
    i = 0
    oldyear = 0
    row = len(df_data)
    while i < row:
        if oldyear != df_data.ix[i, 'year']:
            oldyear = df_data.ix[i, 'year']
            thrkind, sndkind, fstkind = ['', '', '']

        seq = df_data.ix[i, 'seq']
        year = df_data.ix[i, 'year']
        date = df_data.ix[i, 'date']
        kind = df_data.ix[i, 'kind']

        #次の手の勝手を取得
        guess = get_guess(fstkind, sndkind, thrkind)
        fight = get_fight(kind, guess)
        thrkind, sndkind, fstkind = [sndkind, fstkind, kind]

        result.append((seq, year, date, kind, guess, fight))
        i = i + 1

    return pd.DataFrame(result, columns=['seq', 'year', 'date', 'kind', 'guess', 'fight'])

def get_winning_percentage(df_data):
    '''
    年別の勝率計算
    '''
    result = []
    i = 0
    oldyear = 0
    row = len(df_data)
    while i < row:
        if oldyear != df_data.ix[i, 'year']:
            oldyear = df_data.ix[i, 'year']
            year = oldyear
            draw = df_data.ix[i, 'cnt']
            lose = df_data.ix[i+1, 'cnt']
            win = df_data.ix[i+2, 'cnt']
            rate = round(win / (win + lose), 2)
            result.append((year, win, lose, draw, rate))

        i = i + 1

    return pd.DataFrame(result, columns=['year', 'win', 'lose', 'draw', 'rate'])

def main():
    '''
    メイン
    '''
    #サザエさんのじゃんけんデータの読み込み
    ytbl = pd.read_csv('SazaeData.csv')

    #10年分の過去データとの勝敗
    pd.set_option("display.max_rows", 100)
    query = "SELECT seq, year, date, kind, idx FROM ytbl WHERE idx<>9 AND year BETWEEN 2006 AND 2016;"
    ytblptn = sqldf(query, locals())
    fighttbl = get_fight_result(ytblptn)
    print(fighttbl)

    #10年分の年別の勝率計算
    query = "SELECT year,fight,COUNT(fight) AS cnt FROM fighttbl GROUP BY year,fight ORDER BY year;"
    fightcnt = sqldf(query, locals())
    ratetbl = get_winning_percentage(fightcnt)
    print(ratetbl)

if __name__ == '__main__':
    main()

出力結果

長いので2016年のサザエさんの手の予想と勝敗結果

seq year date kind guess fight
497 1263 2016 2016-01-10 C G win
498 1264 2016 2016-01-17 G G draw
499 1265 2016 2016-01-24 C C draw
500 1266 2016 2016-01-31 G C lose
501 1267 2016 2016-02-07 P C win
502 1268 2016 2016-02-14 C G win
503 1269 2016 2016-02-21 C P lose
504 1270 2016 2016-02-28 G P win
505 1271 2016 2016-03-06 P C win
506 1272 2016 2016-03-13 P G lose
507 1273 2016 2016-03-20 G G draw
508 1274 2016 2016-03-27 C G win
509 1275 2016 2016-04-03 P C win
510 1276 2016 2016-04-10 G P win
511 1277 2016 2016-04-17 G G draw
512 1278 2016 2016-04-24 C G win
513 1279 2016 2016-05-01 C C draw
514 1280 2016 2016-05-08 P C win
515 1281 2016 2016-05-15 P P draw
516 1282 2016 2016-05-22 G P win
517 1283 2016 2016-05-29 P G lose
518 1284 2016 2016-06-05 C G win
519 1285 2016 2016-06-12 G P win
520 1286 2016 2016-06-19 P C win
521 1287 2016 2016-06-26 G G draw
522 1288 2016 2016-07-03 C G win
523 1289 2016 2016-07-10 C C draw
524 1290 2016 2016-07-17 P C win
525 1291 2016 2016-07-24 C P lose
526 1292 2016 2016-07-31 G P win
527 1293 2016 2016-08-07 G C lose
528 1294 2016 2016-08-14 C C draw
529 1295 2016 2016-08-21 P C win
530 1296 2016 2016-08-28 C P lose
531 1297 2016 2016-09-04 P P draw
532 1298 2016 2016-09-11 G P win
533 1299 2016 2016-09-18 G G draw
534 1300 2016 2016-09-25 P G lose
535 1301 2016 2016-10-02 C G win
536 1302 2016 2016-10-09 G P win
537 1303 2016 2016-10-16 C C draw
538 1305 2016 2016-10-30 P C win
539 1306 2016 2016-11-06 C P lose
540 1307 2016 2016-11-13 G P win
541 1308 2016 2016-11-20 G C lose
542 1309 2016 2016-11-27 P C win
543 1310 2016 2016-12-04 P G lose
544 1311 2016 2016-12-11 C G win
545 1312 2016 2016-12-18 G P win
546 1313 2016 2016-12-25 P C win

2006年~2016年の勝敗結果

year win lose draw rate
0 2006 23 13 14 0.64
1 2007 22 17 12 0.56
2 2008 29 13 9 0.69
3 2009 31 6 12 0.84
4 2010 27 7 13 0.79
5 2011 30 8 12 0.79
6 2012 27 12 10 0.69
7 2013 24 13 12 0.65
8 2014 30 10 11 0.75
9 2015 32 9 9 0.78
10 2016 27 11 12 0.71

最後に

R言語でのデータフレームもPythonのpandasを使うことで簡単に移植できました。sqldfもpandasqlで代用できたしね。
どちらかというとVisual Studio CodeによるPylintでの警告エラーを減らす方が苦労しました。エラー内容から検索しても日本語で書かれたサイトに辿り着かないので英文を理解しながら直していきました。Pythonの命名規則などを理解しないといけないですね。

参照