PSM

python する man

Pythonで麻雀の戦績を集計してみた

いつか集計しようと思って,趣味の麻雀の記録をexcelでとっていたのですが,この度ついに実行に移すことになりました.

データ読み込み

exelで集計していたデータをpythonで読み込みます.
使用環境は3系(python3.6.0)でJupyter Notebookを利用しています.

今回はpandasのread_csv()を利用して読み込みました.
エクセルで作成したファイルを[cmd+shift+s]でファイル形式をcsvに変えて別名保存しておきます.

pd.read_csv("../Data/mahjong_log.csv")

としてみると,

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8c in position 1: invalid start byte

と怒られます.
read_table()を試してみても,やはり同じエラー.
文字コードをencoding="sjis"で指定する必要がありました.

import pandas as pd
pd.read_csv("hoge.csv", encoding="sjis")

データ加工

こんな感じのデータを読み込みます.
f:id:muromura:20170510223606p:plain
列が各対局者,行が半荘ごとの成績(得点)となっています.
対局に参加していない人の欄は空白になっており,pythonではNaNとして読み込まれます.

3行目から6行目はエクセルで順位つけようとして途中で面倒になってやめた名残です.
不要行削除の例示に都合良いのでそのままにしておきます.

1行を削除するときには,配列のスライスと同じように指定すれば良いです.

#不要部分削除
hoge[:5]

列を削除するときにはdrop()を使います.

#不要部分削除
data = data.drop("削除したい列名", axis=1)

データ集計

対局数をカウントするのには,NaNの数を利用することにしました.
各列ごとにis.null()でNaNの数を調べるとTrue/Falseで結果が帰ってきます.
pythonにおいてはTrue/Falseは数値計算すると1/0に等しくなるので,結果を1から引いてやることで各列で対局に参加した部分(=得点が入力されている部分)を1,それ以外を0とすることができました.
あとはそれを合計するだけです.

#対局数
(1-data.isnull()).sum()


順位の集計にはちょっと手間取りました.
rank()を使えば良いのですが,このとき行ごとに順位を出すには,引数でaxis=1を指定します.
ちなみにこのときNaNはNaNのまま無視してくれました.便利!

rank()を使うと数が少ない順に1から数字が振られるので,本来の順位とは逆になってしまいます.
それをひっくり返すためには5から順位を引けば良いことになります.
これはpythonのブロードキャストを使って簡単に書けました.

#順位づけ
rank = data.rank(axis=1)
rank= 5 - rank

次に,1位から4位までそれぞれの順位を何回とったか数えます.
たぶん他にもっと良い方法があると思いますが,思いつかなかったので,各列ごとに抜き出してvalue_counts()で集計し,後でそれを全部くっつけるという方法をとることにしました,
最初に空のDataFrameを用意し,colmuns()でとってきた列名でfor文を回して集計結果をくっつけています.
DataFrameの結合にはconcat()を使い,列方向にくっつけたいときにはaxis=1を指定します.

#各順位を何回とったかの集計
#列名を取得
players = rank.columns
#空のデータフレームを用意
ranks = pd.DataFrame(index=[1,2,3,4], columns=[])
#for文で回してranksに突っ込んでいく.
for player in players:
    r = rank[player].value_counts()
    ranks = pd.concat([ranks, r], axis=1)

サンプルコード

というわけで長くなってしまいましたが,サンプルコードは以下.

import numpy as np
import pandas as pd

#データ読み込み
raw_data = pd.read_csv("sample.csv", encoding="sjis")

#不要な部分を切り捨てる
data = pd.DataFrame(raw_data[5:])
data = data.drop("day", axis=1)
#型をチェック
#data.dtypes

#順位づけ
rank = data.rank(axis=1)
rank= 5 - rank

#対局数
(1-data.isnull()).sum()

#平均順位算
rank.mean()

#合計計算
data.sum()

#平均得点計算
data.mean()

#順位合計
players = rank.columns
players
ranks = pd.DataFrame(index=[1,2,3,4], columns=[])
for player in players:
    r = rank[player].value_counts()
    ranks = pd.concat([ranks, r], axis=1)

#集計
result = pd.concat([(1-data.isnull()).sum(),
                    ranks.T,
                    rank.mean(),
                    data.sum(), 
                    data.mean()],
                   axis=1, join='inner').T

result.index=['対局数',"1位","2位","3位","4位",'平均順位','合計得点','平均得点']
result = result.round(2)

結果はこんな感じになります.
f:id:muromura:20170510224213p:plain


また時間のあるときに,次は各日ごとの集計をしたり,player同士の相性を計算したりなどやっていきたいと思います.
適当ですが,得点の相関係数とかでも相性の指標になりうるかもしれませんね.

ファイルを一括で検索して移動

1.はじめに

 ダウンロードしてきたフリーペーパーがPDFなのはいいんだけど,各章ごとに別フォルダに分割されちゃっていました.
f:id:muromura:20170426013039p:plain
 この中に1つずつバラバラに各章のPDFが入っている感じです.

 印刷したり読んだりするのに,いちいちフォルダを開いたり閉じたりして一箇所にまとめなくちゃいけないのが面倒だなーと思っていたところ,むかしbashでそういうファイル管理を触ったことがあるのを思い出したのでメモ.

*ちなみにMacならディレクトリだろ?とかbashLinux?とかよくわかんないのでスルー


2.相対パス

 相対パスの書き方って言語ごとに微妙に違うようですね.困りますね.
 とりあえず,bash書くときは ../ で親ディレクトリが取れます.
 さらに遡りたいときは ../../ のように続けていけば大丈夫そうです.


3.mv

mv [現在のpath] [移動したいpath]

ディレクトリやファイルが移動できます.このときに例えば

mv [現在のpath] ~/hoge/piyo.pdf

とすれば,hogeというディレクトリの下のpiyo.pdfと名前を変更しつつ保存できるし,

mv [現在のpath] ~/hoge/

とすれば,hogeというディレクトリの下に名前はそのままで保存してくれます.

基本的にbashのmv(移動)やcp(コピー)は,移動先に指定された名前のディレクトリが存在すればそこに,なければ勝手に作ってそこに移動してくれるようです.


4.正規表現

~/hoge/*

のように * を使うと,hogeディレクトリにある全てのファイル,ディレクトリのことを指してくれます.これはちなみに,

~/hoge/*.pdf

のように部分指定をかけることも可能です.

5.スクリプトbashとして動かす

 vimでもsublimeでもatomでもいいのですが,sample.shのように.shという拡張子のファイルを作ります.拡張子もべつに何でもいいのですが,シェルスクリプトであるということを表すために.shを用いるのが普通です.bashというのはシェルスクリプトの中の一つです.

 中身は以下のようにします.

#!/bin/sh

[ここに命令を書く]

 スクリプトが完成したら,ターミナルに

bash [.shファイルのパス]

 と打ち込むと実行できます.
 例えばカレントディレクトリにsample.shがあれば以下です.

bash sample.sh

6.サンプルスクリプト

ということで,以上全ての知識を生かして今回使ったスクリプトが下です.

#!/bin/sh

pdfs="../paper/*/*"
for pdf in $pdfs; do
    echo "Moving: " $pdf
    mv $pdf ../pdf/
done

ここでは,paperというディレクトリの下に
f:id:muromura:20170426013039p:plain
このようにディレクトリが並んでいて,その中にさらにpdfファイルが入っている状態から,paperのとなりのpdfというディレクトリに,一括でpdfファイルを移動する操作を行いました.

ちなみにbashでは,変数は頭に$をつけないと呼び出せません.
echoは標準出力してくれる関数です.他の言語のprintのようなものですかね.


5.まとめ

何を簡単なことをだらだら書いてんねんという話ですが,こういう簡単なことも,やらないと半年ぐらいですぐに忘れるのでメモしておきました.
初心者はありとあらゆるところにつまづきます.

それでは.

Pythonで実行ファイルからデータの相対パスを取得する

Pythonを触っていて,相対パス絶対パスの取得まわりで手間取ったので,メモしておきます.

環境
MacOS
Python3.6.0

はじめに

pythonでデータを読み込む際,データの絶対パスが必要なときがあります.
そんなとき,スクリプトとデータの相対関係(パス)さえわかっていれば,データの絶対パスが取得できます.

相対パスの取得のしかた

手順としては,

 0. osをインポートする
 1.スクリプトが置いてあるディレクトリの絶対パスを取得する
 2.「1で取得した絶対パス」と「スクリプトとデータの相対パス」をくっつける
 3.正規化する

の順に行います.
ただし,1で取得されるのは,スクリプトそのものの絶対パスではなく,スクリプトがあるディレクトリの絶対パスであることに注意が必要です.


ディレクトリ構造

hoge/
 ├ dataset/
 │ └ data
 └ script
   └ script.py

dataがcsvなどの読み込みたいデータ,script.pyが実行スクリプトです.


コード

import os
#スクリプトのあるディレクトリの絶対パスを取得
name = os.path.dirname(os.path.abspath(__name__)) 

#絶対パスと相対パスをくっつける
joined_path = os.path.join(name, '../dataset/data')

#正規化して絶対パスにする
data_path = os.path.normpath(joined_path)