Python
R

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


はじめに

もともと2013年に静岡Developers勉強会で機械学習を学んだ際にR言語を覚えたのですが、その後に1年に1回だけ「サザエさんのじゃんけん データ分析」のためだけにR Studioを起動するだけになっていて、操作も思い出す感じでやっている状態です。

昨年、Visual Studio CodeでPythonの開発環境を整えることが出来たので、R言語からPythonに移植してみました。とはいっても、Pythonを本格的に意識して組むこと自体は今回初めてなので、そこそこ苦労しました。

【2018/12/31追記】

2017年冬版 サザエさんじゃんけん白書によるとクール(四半期)の初回(1月、4月、7月、10月の初回)はチョキが出やすいとのことで、今回はこれを取り入れてみました。


開発環境


  • OS:Windows10 Home(64bit)

  • Python:Python 3.5.2 :: Anaconda 4.2.0.0 (64bit)

  • エディタ:Visual Studio Code version 1.24.0


インストール

pip install beautifulsoup4

pip install pandasql


Webスクレイピング

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

今回は、Pandasを使用してCSVファイルに出力するようになっています。

【2018/01/01追記】

サザエさんのジャンケン学」が2017/06/25を以って終了となりましたので、2017/07以降のデータは取得できません。長い間お疲れ様でしたm(_ _)m

それ以降のデータはサザエさんとの勝負結果(年別)から手動で追記しています。


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()



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


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


  • チョキが多いので、グー > チョキ > パーの優先順位とする

  • 前回と違う手を出すので、上記の優先順位で勝手を選ぶ

  • 二手前と一手前が違う手なら、残りの手を出すので勝手を選ぶ

  • 三手の中に同手がある場合、 残りの手を出すので勝手を選ぶ

  • 二手前と一手前が同じ手なら、勝手を出すので負手を選ぶ

  • 1月、4月、7月、10月の第1週目はチョキが出やすいので、グーを選ぶ(追加)

GetSazaeData.pyによって出力した「SazaeData.csv」を読み込んで2009年~2018年の10年間の勝敗結果を出力します。

pandasのデータ操作に慣れそうにないので、「pandasql」を使用してSQLによるデータ操作にしています。あと、pandasqlをフォークしてユーザー定義関数を使えるようにした「pysqldf 」も良さげですね。


main.py

# -*- coding: utf-8 -*-

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

import datetime
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 isFirstWeek(value):
'''
1,4,7,10の第1週目か判定
'''

date = datetime.datetime.strptime(value, '%Y-%m-%d')
if((date.month - 1) % 3 != 0):
return False

day = date.day

weeks = 0
while day > 0:
weeks += 1
day -= 7

return (weeks == 1)

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)
#1,4,7,10の第1週目はチョキが多い
if(isFirstWeek(date)):
guess = 'G'
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), 3)
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 2009 AND 2018;"
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()



出力結果

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

seq
year
date
kind
guess
fight

443
1364
2018
2018-01-07
C
G
win

444
1365
2018
2018-01-14
G
G
draw

445
1366
2018
2018-01-21
C
C
draw

446
1367
2018
2018-01-28
G
C
lose

447
1368
2018
2018-02-04
P
C
win

448
1369
2018
2018-02-11
G
G
draw

449
1370
2018
2018-02-18
C
G
win

450
1371
2018
2018-02-25
C
C
draw

451
1372
2018
2018-03-04
P
C
win

452
1373
2018
2018-03-11
G
P
win

453
1374
2018
2018-03-18
G
G
draw

454
1375
2018
2018-03-25
C
G
win

455
1376
2018
2018-04-01
C
G
win

456
1377
2018
2018-04-08
G
C
lose

457
1378
2018
2018-04-15
P
C
win

458
1379
2018
2018-04-22
P
G
lose

459
1380
2018
2018-04-29
C
G
win

460
1381
2018
2018-05-06
G
P
win

461
1382
2018
2018-05-13
C
C
draw

462
1383
2018
2018-05-20
P
C
win

463
1384
2018
2018-05-27
G
P
win

464
1385
2018
2018-06-03
C
G
win

465
1386
2018
2018-06-10
G
C
lose

466
1387
2018
2018-06-17
P
C
win

467
1388
2018
2018-06-24
P
G
lose

468
1389
2018
2018-07-01
C
G
win

469
1390
2018
2018-07-08
G
P
win

470
1391
2018
2018-07-15
P
C
win

471
1392
2018
2018-07-22
G
G
draw

472
1393
2018
2018-07-29
C
G
win

473
1394
2018
2018-08-05
C
C
draw

474
1395
2018
2018-08-12
P
C
win

475
1396
2018
2018-08-19
G
P
win

476
1397
2018
2018-08-26
G
G
draw

477
1398
2018
2018-09-02
P
G
lose

478
1399
2018
2018-09-09
C
G
win

479
1400
2018
2018-09-16
G
P
win

480
1401
2018
2018-09-23
C
C
draw

481
1402
2018
2018-09-30
P
C
win

482
1403
2018
2018-10-07
C
G
win

483
1404
2018
2018-10-14
G
P
win

484
1405
2018
2018-10-21
P
C
win

485
1406
2018
2018-11-04
P
G
lose

486
1407
2018
2018-11-11
C
G
win

487
1408
2018
2018-11-18
C
P
lose

488
1409
2018
2018-11-25
G
P
win

489
1410
2018
2018-12-02
G
C
lose

490
1411
2018
2018-12-09
P
C
win

491
1412
2018
2018-12-16
C
G
win

2009年~2018年の勝敗結果

year
win
lose
draw
rate

0
2009
32
5
12
0.865

1
2010
27
6
14
0.818

2
2011
30
8
12
0.789

3
2012
27
12
10
0.692

4
2013
26
11
12
0.703

5
2014
32
8
11
0.800

6
2015
34
8
8
0.810

7
2016
26
12
12
0.684

8
2017
34
8
6
0.810

9
2018
30
9
10
0.769


最後に

R言語でのデータフレームもPythonのpandasを使うことで簡単に移植できました。sqldfもpandasqlで代用できたしね。

どちらかというとVisual Studio CodeによるPylintでの警告エラーを減らす方が苦労しました。エラー内容から検索しても日本語で書かれたサイトに辿り着かないので英文を理解しながら直していきました。Pythonの命名規則などを理解しないといけないですね。


参照