読者です 読者をやめる 読者になる 読者になる

StatsFragments

Python, R, Rust, 統計, 機械学習とか

Python pandas 日本語環境向けのちょっとしたパッケージ作った

最近の空き時間は GitHub で草植えをしている。まずは pandas を日本語環境で使う場合に たまに必要になる処理をまとめた パッケージを作った。

インストール

pip install japandas

機能

機能の一覧はこちら。

  • 日時処理
    • 日本語日付のパース
    • 日本の祝日カレンダー
  • 文字列処理
    • Unicode 正規化
    • 全角/半角変換
  • リモートデータアクセス

詳細はドキュメントを。

日時処理

日本語日付のパース

pandas には 日付らしい入力を適切に処理してくれる pandas.to_datetime があるが、これは日本語の日付 ( "XX年XX月XX日" とか ) に対応していない。例えば 以下のような入力は日時としてパースされず 文字列のまま残ってしまう。

import pandas as pd

pd.to_datetime(u'2014年11月30日')
# u'2014年11月30日'

japandas.to_datetimepd.to_datetime を軽くラップし、以下のような日付をパースできるようにする。

  • 'XX年XX月XX日'
  • 'XX年XX月'
  • 'XX年XX月XX日XX時XX分'
  • 'XX年XX月XX日XX時XX分XX秒'
import japandas as jpd

jpd.to_datetime(u'2015年3月1日')
# Timestamp('2015-03-01 00:00:00')

jpd.to_datetime([u'2014年11月30日13時25分', u'2014年11月30日14時38分'])
# <class 'pandas.tseries.index.DatetimeIndex'>
# [2014-11-30 13:25:00, 2014-11-30 14:38:00]
# Length: 2, Freq: None, Timezone: None

補足 リスト-like な入力を処理する場合、日付のフォーマットは全て同じでなければならない。

参考 標準の pd.to_datetime で任意のフォーマットをパースするには、以下のように format オプションで書式を与えてやればよい。pd.to_datetime については、 Python pandas で日時関連のデータ操作をカンタンに - StatsFragments を。

pd.to_datetime(u'2014年11月30日', format=u'%Y年%m月%d日')
# Timestamp('2014-11-30 00:00:00')

日本の祝日カレンダー

japandas.JapaneseHolidayCalendar は日本の祝日を定義したクラス。

calendar = jpd.JapaneseHolidayCalendar()
calendar.holidays()
# <class 'pandas.tseries.index.DatetimeIndex'>
# [1970-01-01, ..., 2030-12-23]
# Length: 969, Freq: None, Timezone: None

このカレンダー定義を使うと、定義に応じた営業日計算が以下のとおりできる。

参考 Time Series / Date functionality — pandas 0.16.2 documentation

import datetime

cday = pd.offsets.CDay(calendar=calendar)

# 4/29は祝日(昭和の日)なので無視されて加算
datetime.datetime(2014, 4, 28) + cday
# Timestamp('2014-04-30 00:00:00')

# 4/26は土曜日, 4/27は日曜日なので無視されて減算
datetime.datetime(2014, 4, 28) - cday
# Timestamp('2014-04-25 00:00:00')

# 5/4は日曜日, 5/5は祝日(こどもの日), 5/6は祝日(みどりの日/振替休日)なので無視されて加算
datetime.datetime(2014, 5, 3) + cday
# Timestamp('2014-05-07 00:00:00')

# 5/3は土曜日、前日は営業日なので通常の減算
datetime.datetime(2014, 5, 3) - cday
# Timestamp('2014-05-02 00:00:00')

また、作成した Custom Business Day インスタンスは 指定した範囲の営業日を含む Index の生成にも使える。japandas.date_rangepd.date_range の日本語日付対応版。

indexer = jpd.date_range(u'2014年5月1日', u'2014年5月10日', freq=cday)
indexer
# <class 'pandas.tseries.index.DatetimeIndex'>
# [2014-05-01, ..., 2014-05-09]
# Length: 5, Freq: C, Timezone: None

この Index を使うと、カレンダー定義に応じてデータを抽出する処理が簡単に書ける。

import numpy as np

# 処理したいデータを作成
df = pd.DataFrame(np.random.randn(10, 3),
                  index=jpd.date_range(u'2014年5月1日', u'2014年5月10日', freq='D'))
df
#                    0         1         2
# 2014-05-01  0.762453 -1.418762 -0.150073
# 2014-05-02  0.966500 -0.473888  0.272871
# 2014-05-03  0.473370 -1.282504  0.380449
# 2014-05-04  0.215411  0.220587 -1.088699
# 2014-05-05  0.286348 -1.069165 -1.471871
# 2014-05-06 -0.665438 -0.402046 -1.008051
# 2014-05-07  1.173935  2.080087 -2.279285
# 2014-05-08 -0.957195  0.746798  0.092214
# 2014-05-09 -0.259276 -0.775489  0.572525
# 2014-05-10 -0.910188  0.294136  0.020730

# カレンダー上 営業日のレコードを抽出
df.loc[indexer]
#                    0         1         2
# 2014-05-01  0.762453 -1.418762 -0.150073
# 2014-05-02  0.966500 -0.473888  0.272871
# 2014-05-07  1.173935  2.080087 -2.279285
# 2014-05-08 -0.957195  0.746798  0.092214
# 2014-05-09 -0.259276 -0.775489  0.572525

# カレンダー上 休日のレコードを抽出
df[~df.index.isin(indexer)]
#                    0         1         2
# 2014-05-03  0.473370 -1.282504  0.380449
# 2014-05-04  0.215411  0.220587 -1.088699
# 2014-05-05  0.286348 -1.069165 -1.471871
# 2014-05-06 -0.665438 -0.402046 -1.008051
# 2014-05-10 -0.910188  0.294136  0.020730

文字列処理

Unicode 正規化

Series.str.normalize を使うと、標準の unicodedata.normalize と同じ処理を 各値に対して適用できる。

s = pd.Series([u'アイウエオ', u'カキクケコ', u'ガギグゲゴ', u'ABCDE'])
s
# 0         アイウエオ
# 1         カキクケコ
# 2    ガギグゲゴ
# 3         ABCDE
# dtype: object

s.str.normalize()
# 0    アイウエオ
# 1    カキクケコ
# 2    ガギグゲゴ
# 3    ABCDE
# dtype: object

文字列以外の値は np.nan になる。これは str アクセサの他のメソッドと一緒。また、 Python 2.x 系の str ( unicode ではないもの) も処理できる。

s = pd.Series([u'アイウエオ', 23, u'ガギグゲゴ', None, 'AAA'])
s.str.normalize()
# 0    アイウエオ
# 1      NaN
# 2    ガギグゲゴ
# 3      NaN
# 4      AAA
# dtype: object

全角/半角変換

正規化があればいらない気もするのだが、全角/半角変換用のメソッドもつけてみた。

  • Series.str.h2z: 半角 -> 全角へ変換
  • Series.str.z2h: 全角 -> 半角へ変換
s = pd.Series([u'アイウエオ', u'ABC01', u'DE345'])
z = s.str.h2z()
z
# 0    アイウエオ
# 1    ABC01
# 2    DE345
# dtype: object

z.str.z2h()
# 0    アイウエオ
# 1    ABC01
# 2    DE345
# dtype: object

参考 文字列処理全般については、 Python pandas で日時関連のデータ操作をカンタンに - StatsFragments を。

補足 全角/半角変換について、既存パッケージの実装は ベクトル化したとき少し遅くなりそうだったので再発明したのだが、速度的にはあまり意味はなかった。今後 既存パッケージに置き換えるか、Cython 化するかしたい 。また、str.normalize標準にのせたい

リモートデータアクセス

以下の記事の内容を pandas.io.data.DataReader と同じ形式でラップしたもの。加えて .plot(kind='ohlc') も可能。

参考 Remote Data Access — pandas 0.16.2 documentation

jpd.DataReader(7203, 'yahoojp', start='2014-10-01', end='2014-10-05')
#               始値    高値    安値    終値       出来高  調整後終値*
# 日付
# 2014-10-01  6450  6559  6435  6500  14482100    6500
# 2014-10-02  6370  6423  6256  6275  15240200    6275
# 2014-10-03  6231  6309  6217  6290  10280100    6290

補足 ページ取得の際は毎回ウェイトを入れている。あまり長期間のデータ取得には使わないよう。

最後に

不具合、もしくは日本固有の機能要望があれば GitHub からください。

Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理

Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理