Kumpei Shiraishi's blog

物理、プログラミング、クライミングに関する雑記

SKKの統計をグラフにする

概要

SKKは、個人辞書に登録されている候補数の統計を記録することができる。この統計情報のグラフ化を試みる中で、以下のことを勉強することができた:

  • sedの使い方
  • pandasでcsvファイルを取り扱う方法
  • datetimeで日時データを取り扱う方法
  • matplotlib.datesで時系列データを表示する方法

この記事は、その個人的な記録である。グラフ化の元ネタはこのページ:~/.skk-record « 余談ですが……

結論

グラフ

Candidates in my SKK

コード・手順

  1. % cp ~/.skk-record ~/temp/rec.csvで、作業フォルダに統計情報をコピーする
  2. % sed -i '' -e 's/ 登録.*語数: /,/g' rec.csvで、日時と語数の列からなるcsvファイルに整形する(BSD sed)
  3. 以下のPythonコードを実行する:
import datetime as dt
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

# データフレーム
df = pd.read_csv('rec.csv',header=None)
df.columns = ['datetime','words']

# x軸時系列データのリストを整える
dates = [None]*df.shape[0]

for i in range(df.shape[0]):
    dstr = df.ix[i,'datetime']
    ddate = dt.datetime.strptime(dstr, '%a %b %d %H:%M:%S %Y')
    dates[i] = dt.datetime(ddate.year,ddate.month,ddate.day,ddate.hour,ddate.minute,ddate.second)
    # 当たり前だが、ここで時、分、秒を格納しなければ、もっと粗いグラフになる

# 以下、オブジェクト指向なmatplotlibの使い方をしている
# まず基本のFigureクラスのインスタンス。キャンバスだと思っておけばよい。
fig = plt.figure()

# 次にAxesクラスのインスタンス。白い背景と軸くらいに思っておこう。
ax = fig.add_subplot(1,1,1)

# axにプロットする。labelは凡例の名前を指定するため。
ax.plot(dates,df.ix[:,'words'],label='SKK jisyo candidates')

# 以下でグラフの体裁を整える
# 凡例の位置はlegend関数で指定できる
ax.legend(loc='upper left')

# axのx軸について、locatorとformatterを指定する
ax.xaxis.set_major_locator(mdates.DayLocator())
ax.xaxis.set_minor_locator(mdates.HourLocator(interval=12))
ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %d'))

# autofmt_xdate()で良い感じに回転させる
fig.autofmt_xdate(bottom=0.125,ha='center')

# 描画範囲を指定
start = min(dates)
end = max(dates)
ax.set_xlim(dt.date(start.year,start.month,start.day),dt.date(end.year,end.month,end.day+1))

# axにy軸のラベルを付加する
ax.set_ylabel('Number of words')

plt.savefig('a.png',dpi=300)

勉強したこと

sed

sedは置換、挿入、削除などのテキスト処理ができるコマンド。使い方は

を参照。ここでは今回使ったものだけを書いておく。

-eオプションは、その後に処理内容が来ることを表す。

-iオプションは、処理結果をファイルに上書きすることを表す。GNU sedとBSD sedで挙動が異なるので、後述する注意を参照。

特殊文字は、

  • .:任意の一文字
  • *:直前の文字が任意の個数続く文字列(0個も含む)
  • <space><space>*:連続するSPACEを表す
  • <tab>:TAB
  • .*:任意の文字列
  • \^:行頭
  • \$:行末

コマンドの前に来る数字をアドレスと呼び、その行のみに対して処理を行う。例:'3d'ならば3行目のみを削除する。

置換処理はs(Substitution)コマンドで、's/処理前文字列/処理後文字列/g'のように用いる。g(Global)オプションを渡さない場合、各行の最初に登場した当該文字列にのみ処理を行う。区切り文字はsコマンドの後に来たものなら何でも良い。

さて、sedにはGNU系とBSD系があり、それぞれで挙動が違うことに注意。Mac OS XにはBSD sedが入っているが、Homebrewでgnu-sedを入れればGNU sedを使うこともできる。BSD sedを使う上で注意が必要なのは、

  • -iオプションを付けて上書きする場合
  • 改行\nを出力する場合

である。

-iオプションに関してBSD sedのマニュアルには

Edit files in-place, saving backups with the specified extension.

と書いてある。つまりBSD sedにおいては、-iオプションの後ろで指定した文字列をファイル名の末尾に付けて元ファイルのバックアップを取るのである。例:sed -i 'boke' -e 's/foo/bar/g' hoge.txtを実行したならば、hoge.txtに置換済みの内容(欲しい内容)が保存され、hoge.txtbokeに置換前の内容(バックアップ)が保存される。GNU sedと同じように、余計なファイルを生成せずに上書きしたいなら-i ''と空の文字列を指定すればよい。

改行出力についてはこのページを参考にした。結論として、例えば1行目に文字列を挿入して上書きするには、

sed -i '' 1s/\^/foobar\\$'\n'/ input.txt

を実行しよう(今回はpandasでヘッダーを付加したので、BSD sedでの改行出力は不要なのだが)。

pandas

pandasはPythonでデータ分析をする際の定番ライブラリ。インストールしたものの、碌に使っていなかったので、ちょうど良い機会だった。

以下のページを主に参照した:

ここでは、今回のコードを読むのに必要なこと等を書いておく。

csvデータを読み込むには、read_csv('ファイル名')とする。第1行をヘッダーとして認識するので、これが不要ならheader=Noneを与える。また、読み込み時にヘッダーを指定するにはnames='ABC'を与える。そうすれば、第1列をAなどと名付けられる。

read_csv()でデータフレームdfを生成した後から、ヘッダーを指定して列名を名付ける(上書き)にはdf.columns = ['columnA','columnB','columnC']とする。

shape(行数, 列数)を、describe()で平均、分散などの基本的な統計量を得ることができる。

各要素にアクセスするには、ix[]を用いる。例:df.ix[2,'columnB']で第3行、第2列の要素を得る。列は列名でも列番号でも指定できる。複数選択は:を使う。

ソートはsort()。引数に列名を渡して、昇順でソートする。降順はascending=False

行の追加には、まず追加するデータフレームadd = DataFrame([['foobar',1]])を用意する。列名は追加先のデータフレームdfと同じものを、indexは追加先の末尾に合うものを指定する(然もなくばデータフレームの列が拡張され、形が変わる)。そしてconcat([df,add],axis=0)が追加後のデータフレームである。axis=1とすれば列の追加になる。

datetime

datetimeはPythonで日時データを扱うモジュール。使い方は

がよくまとまっている。

文字列から日時にするには

tstr = 'Sat Apr 23 07:12:25 2016'
tdatetime = dt.datetime.strptime(tstr, '%a %b %d %H:%M:%S %Y')

とする。日時から文字列にするには

tdatetime = dt.datetime(year=2015, month=10, day=21, hour=16, minute=29)
tstr = tdatetime.strftime('%Y/%m/%d')

とする。

指定子を知るにはここを参照。現在時刻を取得する.now()などの使い方も載っている。

matplotlib.dates

詳しくは以下を参照:

matplotlibは、オブジェクト指向なプロットを提供する。そのようにプロットする際の流れはコードのコメント、またはここに挙げたページを見てほしい。ここでは、matplotlib.datesの簡単な使い方について書いておく。

手作業で日時を目盛tickに指定するのはかなり大変だ。しかしmatplotlib.datesを使えば、tickの位置を制御するlocatorと、tick毎につける名前の書式を指定するformatterを、簡単に指定できる。

locatorでは分、時、日、週、月、年などの単位でインターバルを指定できる。例:matplotlib.dates.HourLocator(interval=6)は6時間毎にtickを記すlocatorである。他にも、毎週月曜日や、3年毎のイースターのようなtickを簡単に指定できる(公式ドキュメント参照)。

fotmatterでは、matplotlib.dates.DateFormatter()を使うことがほとんどだろう。例:matplotlib.dates.DateFormatter('%b-%d')はApr-24という形式で出力するformatterである。

これらをxaxis.set_major_locator()、xaxis.set_major_formatter()などに渡せばよい。

ちなみに、x軸のformatに日時を指定したが、多くの場合「2016/04/24」や「08:31:54」は横に長すぎて重なってしまう。autofmt_xdate()は、それを良い感じ回転、位置移動してくれる。デフォルトはbottom=0.2, rotation=30, ha='right'で、それぞれ下の余白、回転角、tickの相対位置を表す。また、今回はset_xlim()で描画範囲を指定した。指定しないと原点の下にx軸のtickが表示されず、ちょっと気持ち悪かったからだ。具体的には、x軸データの最大最小を取得し、両者の年月日のみを抜き出して、それを描画範囲とした。これならば、各日付の0時から24時まで描画されるので、欲しいtickになる。