turtlechanのブログ

無知の私がLinuxをいじりながら書いていくブログ

【Python】numpyでpandasのrolling的な動作をさせるには

株価データなどをnumpyでいじっているときに pandas の rolling() 的なことをしたいときありませんか?例えば5日間のデータをずらしながら取得したいとか。forループ で良さそうですが遅いのでなしの方向で。
素直に pandas ライブラリを使用すれば解決なんですが、私はひねくれものなので numpy で行いたいのです。
今回はそういった方の参考になるかもしれない記事を書こうと思ってます。

※python2で書いています。

numpyでrolling()的なことをする

私はいい案が浮かばなかったのでググりました。
そしたら こちら が見つかりました。ありがとうございます。

上記のサイトで乗っている以下のコードで良さそうです。勝手に転載してごめんなさい。

def rolling_window(a, window):
    shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
    strides = a.strides + (a.strides[-1],)
    return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)

以上で解決です。

おまけ

numpy.lib.stride_tricks.as_strided() が何をしているのかおまけ程度に動かしてみようと思います。
上記のものだけで終わらせる勇気が私にはないので。。。

そもそも numpy.lib.stride_tricks.as_strided() なんて深いところにあったら気付かないよなぁ。とか強がりたいですが、私は numpy の基本的なメソッドすらよく分かっていません。

 

本題に戻ります。
numpy の公式ドキュメントを見てみましょう。
URL: https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.lib.stride_tricks.as_strided.html#numpy.lib.stride_tricks.as_strided
横文字ですが、いじいじしながら見れば私でも理解できるだろう。

as_strided() の形式(定義)は以下。

as_strided(x, shape=None, strides=None, subok=False, writeable=True)

引数の意味

  • x: 元になるndarray型の配列
  • shape: 新しい配列(view)の形状
  • strides: 新しい配列(view)の縦・横方向の進み具合
  • subok: サブクラスを保持するか
  • writeable: 書き込み可能にするか

今回は subok と writeable はどうでもいいですね、シカトします。
また、上記だけではピンとこないので少し触ってみます。

以下、間違ったコードを書きます。

as_strided.py
#! /usr/bin/env python
# coding: utf-8

import numpy as np
from numpy.lib.stride_tricks import as_strided


xArr = np.arange(25, dtype='uint16')
print(xArr)
print(as_strided(xArr, shape=(5, 5)))
print(as_strided(xArr, shape=(5, 5), strides=(1, 1)))

np.arange() で 0~24 の配列(ndarray)を 変数xArr に用意(代入)しています。各要素の型は usigned int16 している。
はじめに、用意した配列(xArr)を出力。次に as_strided() に x と shape を指定して出力(5×5で出力する想定)。次に x, shape, strides を指定して出力(縦横1要素ずつ進める想定)。

実行結果
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 24]
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]
[[   0  256    1  512    2]
 [ 256    1  512    2  768]
 [   1  512    2  768    3]
 [ 512    2  768    3 1024]
 [   2  768    3 1024    4]]

最初の出力は問題なし。
2つめの出力は shape=(5, 5) と指定しているので 5×5 の配列になっています。予想通りの出力ですね。
3つめの出力は、、、256 とか 512 とか どこから来たんだよって数が出力されています。

これは strides の値が間違っているからです。strides が受け取る値は縦横で進むバイト数を指定しないといけません。今回は unsigned int16 にしているので、配列(xArr)内の要素は1つあたり 16bit で格納されています。つまり 2byte なので strides=(2, 2) としてあげなければいけなかったのです。

以下、修正したコード。

as_strided.py
#! /usr/bin/env python
# coding: utf-8

import numpy as np
from numpy.lib.stride_tricks import as_strided


xArr = np.arange(25, dtype='uint16')
print(xArr)
print(as_strided(xArr, shape=(5, 5)))
print(as_strided(xArr, shape=(5, 5), strides=(2, 2)))

strides=(2, 2) に変更しただけ。

実行結果
[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 24]
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]
[[0 1 2 3 4]
 [1 2 3 4 5]
 [2 3 4 5 6]
 [3 4 5 6 7]
 [4 5 6 7 8]]

先程のわけの分からない数字はなくなり、縦・横ともに1要素ずつ進んだ値になっているのが確認できるかと思います。

ちなみに、要素を2つずつ進めたい場合は 2byte × 2 なので strides=(4, 4) とすればいいです。また、今回は 2byte でしたが、ndarray の生成時に dtype を指定しなかった場合は大体自動で int64 か float64 になると思うので、要素1つあたり 64bit → 8byte です。

なんとなく分かったでしょうか?
日本語が不自由なんで分かりにくいでしょうが、色々動かしてみると分かると思います。

おわりに

株価データの分析の話だけど、n日間の最大値・最小値とかを計算したいときに使うと便利ですね。

何かの参考になれば幸いです。