LoginSignup
1
1

More than 3 years have passed since last update.

オンライン家族麻雀をPythonを使って解析してみる(PART 1: DATAを取る)

Posted at

概要

最近家族とオンライン麻雀サイト天鳳で週に1回2時間ほど楽しんでいます。

今回は対戦成績の分析を家族が皆がウェブ上で見れるようにしたのでその解説です。

プロセスとしては:

  1. Pythonで分析
  2. Dashを使って可視化
  3. HerokuとGithubを使ってデブロイ

こんな感じに仕上がりました↓
https://drmahjong.herokuapp.com/

drmahjangsc1.png
drmahjangsc2.png
drmahjangsc3.png

Codeは以下のGithubで見ることができます。
https://github.com/mottoki/mahjan-score

データを取る

天鳳のデータはログから取ることができます。requestというModuleを使用してデータを取ります。

python.py
import requests
import datetime

new_date = datetime.datetime.now().strftime('%Y%m%d')
url = "https://tenhou.net/sc/raw/dat/"+f"sca{new_date}.log.gz"
filename = f"sca{new_date}.log.gz"

# Download gz file from the url
with open(filename, "wb") as f:
    r = requests.get(url)
    f.write(r.content)

PythonとPandasを使ってデータ処理

Rawデータをプレイヤーの名前でフィルターをかけて、splitを使用してプレイヤーの名前とポイントだけのデータフレームを摘出します。

new_data.py
import os
import pickle
import pandas as pd

# プレイヤーの名前
playercol = ['date', 'Mirataro', 'Shinwan', 'ToShiroh', 'yukoron']

# Pandasのデータフレームに変換
df = pd.read_csv(filename, usecols=[0], error_bad_lines=False, header=None)
df[len(df.columns)] = new_date

# プレイヤーの名前でフィルターをかける
df = df[(df[0].str.contains(playercol[1])) & 
    (df[0].str.contains(playercol[2])) & 
    (df[0].str.contains(playercol[3])) &
    (df[0].str.contains(playercol[4]))]

# データフレームの処理を行う
df[['one','two','three','four']] = df[0].str.split('|', 3, expand=True)
df.columns = ['original', 'date', 'room', 'time', 'type', 'name1']
df['date'] = pd.to_datetime(df['date'], format='%Y%m%d')
df[['empty', 'n1', 'n2', 'n3', 'n4']] = df.name1.str.split(" ", n=4, expand=True)
# 重要なコラムだけ使用
df = df[['date', 'n1', 'n2', 'n3', 'n4']]

# スコアと名前についているカギカッコを取り去りデータフレーム化する
new_score = pd.DataFrame(columns=playercol)
k=0
for i, j in df.iterrows():
   dd = j[0]
   new_score.loc[k, 'date'] = dd
   for name in df.columns[1:]:
       s = j[name]
       player = s.split('(')[0]
       score = [p.split(')')[0] for p in s.split('(') if ')' in p][0]
       score = int(float(score.replace('+', '')))
       new_score.loc[k, player] = score
   k += 1

# 古いデータをPickleから呼ぶ
current_dir = os.getcwd()
old_score = pd.read_pickle(f"{current_dir}/players_score.pkl")

# 新しいデータと古いデータを合わせる
concat_score = pd.concat([old_score, new_score], ignore_index=True)
concat_score.to_pickle(f"{current_dir}/players_score.pkl")

Dashを使って可視化する

Dashというライブラリを使って素早くデータの可視化を行います。

Dashのチュートリアルが一番わかりやすいです。(参考:Dash Documentation & User Guide)

Dashで引っかかるところのはCallbackという機能ですが、Pythonの可視化ライブラリDashを使う 2 Callbackをみるなど詳しく説明されている方がいますのでそちらを参照してください。

1. Front側(ウェブに見えるもの)

全てのコードを説明すると長くなってしまうので、コアとなる部分を例として説明します。

基本的に最初の app.layout= の中に書かれているものは全てウェブサイトで表示されるものです。

表示したくないもの(例えば何度も使うデータ「intermediate-values」)は style={'display': 'none'} と入れるとウェブ上で見えなくなります。


# フロントエンドはこのなかに書く
app.layout = html.Div([
    # データに反映される日付をユーザーが選べるようにする
    html.Div([
        html.H2("DR.麻雀"),
        dcc.DatePickerRange(
            id='my-date-picker-range',
            min_date_allowed=dt(2020, 3, 1),
            max_date_allowed=dt.today(),
            end_date=dt.today()
        ),
        ], className="mytablestyle"),

    # 何度も使うデータ:style={'display': 'none'}で見えなくする
    html.Div(id='intermediate-value', style={'display': 'none'}),

    # ポイントの推移(グラフ)
    dcc.Graph(id='mygraph'),

    # 総合ポイント(テーブル)
    html.Div([
            html.Div(html.P('現在の総合ポイント')),
            html.Div(id='totalscore'),
        ], className="mytablestyle"),

])

2. CallbackでデータをJson化する

データをpandasのread_pickleで読み込み、日付でフィルターをかけ、json化して戻します。

こうすることによって同じデータをグラフやテーブルに何度も使えるようになります。

@app.callback(Output("intermediate-value", "children"),
    [Input("my-date-picker-range", "start_date"),
    Input("my-date-picker-range", "end_date")])
def update_output(start_date, end_date):
    players = pd.read_pickle('players_score.pkl')
    if start_date is not None:
        start_date = dt.strptime(re.split('T| ', start_date)[0], '%Y-%m-%d')
        players = players.loc[(players['date'] >= start_date)]
    if end_date is not None:
        end_date = dt.strptime(re.split('T| ', end_date)[0], '%Y-%m-%d')
        players = players.loc[(players['date'] <= end_date)]
    return players.to_json(date_format='iso', orient='split')

3. Callbackでデータをグラフ化&テーブル化

json化したデータをPandasのデータフレームに戻し、グラフ化とテーブル化していきます。

グラフ化はPlotlyの様式に乗っ取り、go.Figure()で表します。

テーブル化はhtml.Tableで表しました。テーブルはdash_tableというライブラリもあるのですが、今回のテーブルは簡易なものだったので必要ないろ思いこのスタイルにしました。

@app.callback([Output('mygraph', 'figure'),
    Output('totalscore', 'children')],
    [Input('intermediate-value', 'children'),
    Input('datatype', 'value')])
def update_fig(jsonified_df, data_type):
    # Json化したデータをPandasでもとに戻す。
    players = pd.read_json(jsonified_df, orient='split')

    # グラフ化
    fig = go.Figure()
    for i, name in enumerate(players.columns[1:]):
        fig.add_trace(go.Scatter(x=players.date, 
                            y=np.array(players[name]).cumsum(),
                            mode='lines',
                            name=name,
                            line=dict(color=colors[i], width=4)))

    fig.update_layout(plot_bgcolor='whitesmoke',
        title='合計ポイントの推移',
        legend=dict(
            orientation="h",
            yanchor="bottom",
            y=1.02,
            xanchor="right",
            x=1,)
    )

    # 総合ポイントを計算
    summed = players.sum()

    # グラフと表を返す
    return fig, html.Table([
        html.Thead(
            html.Tr([html.Th(col) for col in summed.index])
            ),
        html.Tbody(
            html.Tr([html.Td(val) for val in summed])
            ),
        ])

HerokuとGithubを使ってデプロイ

最後にHerokuとGithubを使ってデプロイしていきます。

公式サイト(Deploying Dash Apps)にはGitとHerokuのやりかたが詳しく載っているので、ほとんどやり方は一緒です。

プロセスはこんな感じです:

  1. Githubのアカウントにサインアップする
  2. Githubの新しいレポジトリを作成する
  3. GithubにSSH接続する。(オプショナルですが、やったほうが楽。参照:GitHubにssh接続できるようにする)
  4. デプロイに必要なファイル(.ignore, Procfile, requirements.txt)を作成する。gunicornも必要なので pip install gunicornでインストールする。
  5. Gitコマンドで上記のファイルとapp.pyplayers_score.pklのデータファイルをGithubにプッシュする。

    git init
    git add .
    git commit -m "メッセージ"
    git remote add origin git@github.com:<ユーザー名>/<レポジトリ名>
    git push origin master
    
  6. Githubにプッシュされているのを確認したらHerokuのアカウントを作成してNew > create new appのボタンで新しいアプリを作成(Regionは日本がないので、United Statesを選択)。

  7. 作成されたアプリのDeployのタブをクリックし、Deployment methodをGithubにし、2.で作成したレポジトリに接続します。

  8. 最後にManual deployDeploy Branchという黒いボタンを押すと勝手にデプロイしてくれます。

最後に

いかかでしたでしょうか。

cronとHerokuのAutomatic Deployを使えば天鳳から取ってくる新しいデータのアップデートを自動化することも可能です。(参考:cronでGithubにPushするプロセスを自動化する

 参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1