多次元データ解析ライブラリ xarray
この記事では、多次元データ解析を支援するPythonのライブラリxarrayを紹介します。
さらに詳しい情報は本家の情報を参照してください。
xarrayの特徴
背景
科学計測データは往々にして多次元になります。
例えば、複数の位置に設置したセンサで時系列データを計測する場合、
計測データは 空間チャンネル方向 × 時間方向 の二次元データになります。
さらにそのデータに短時間フーリエ変換を施す場合は 空間チャンネル方向 × 時間方向 × 周波数方向 の三次元データになったりします。
一般的にこういうデータを扱う場合、numpy の np.ndarray を使うことが多いと思います。
しかし、np.ndarray は単純な行列(もしくはテンソル)なので他の情報は別途置いておく必要があります。
上の例だと、
- 次元の順番:二次元データのうち1次元目が空間チャンネル、二次元目が時間に対応すること
- 各次元の座標
などがここで言う「他の情報」にあたります。
そのため、例えばその中のデータからある時間範囲の一部切り出して使う、というような場合は
切り出したデータの他に、時間軸のデータも同時に切り出しておく必要があります。
素の np.ndarray を使ってそれをきっちりやっていく、ってことももちろんできるんですが、
複雑なプログラムだとこのような煩雑な操作はミスの元になります。
xarray はそういった操作を簡単にするためのライブラリです。
(ちなみに内部では np.ndarray を用いているので、np.ndarrayの高速な演算性能をほとんど犠牲にしません)
なお、一次元のデータを扱うライブラリとしてpandasがあります。
pandasは多次元のデータを(簡単には)扱えません。xarrayはそれを補間するライブラリになっています。
特徴としては上記の他に
-
__str__
メソッドがオーバーロードされていて、print したときにその概要を表示してくれる。 - 位置インデクシング・スライシング(例えば、ある時刻に最も近いときのデータを探すなど)が可能。
その結果もxarrayオブジェクトになり、軸に関する情報も正しく保有してくれる。 - 簡単な統計処理(移動平均など)が可能。軸に関する情報も正しく保有してくれる。
- pandas との相互変換が可能
- メモリに載り切らない巨大なデータにも対応している(らしい)
などがあります。
ちなみに、
import numpy as np
import xarray as xr
xr.__version__
'0.9.1'
と言う風に、xr
と略すことが一般的なようです。
データタイプ
主には xr.DataArray, xr.Dataset の2つのデータタイプをサポートしています。
xr.DataArray
xr.DataArray は上記で述べた多次元のデータです。
内部には軸の値とラベルのペアである順序付き辞書型 coords
および、その他の情報を格納するattrs
という順序付き辞書型を有しています。
__get_item__
メソッドをオーバーロードしているので、da[i,j]というようにnp.ndarrayと同じようにアクセスすることができます。
ただし戻り値も xr.DataArray オブジェクトになるので、軸情報などを受け継いでくれます。
xr.Dataset
xr.DataArray を複数保持するオブジェクトです。
複数の軸を有することができて、各データがどの軸に対応するかの情報を保有しておいてくれます。
辞書オブジェクトのようにアクセスすることができます。
例えば、内部に温度 T と密度 N の情報を有するxr.Datasetでは
data['T'] とすると、温度 T がxr.DataArrayとして戻ってきます。
xr.DataArray の使い方
これはpandas
におけるDataSeries
に似た役割のものです。
データの値自体と、軸のデータを有しています。
インスタンス化
data = xr.DataArray(np.random.randn(2, 3))
とすると、軸情報のない2x3の xr.DataArray オブジェクトを作成できます。
print
メソッドで作成したオブジェクトの概要を表示できます。
print(data)
<xarray.DataArray (dim_0: 2, dim_1: 3)>
array([[ 0.32853 , -1.010702, 1.220686],
[ 0.877681, 1.180265, -0.963936]])
Dimensions without coordinates: dim_0, dim_1
今回のように軸を明示的に指定しなかった場合、dim_0, dim_1が自動的に割り振られます。
例えば、あるデータdata_np
の一次元目が空間位置x、二次元目が時刻tに対応する場合を考えます。
#例データ
data_np = np.random.randn(5,4)
x_axis = np.linspace(0.0, 1.0, 5)
t_axis = np.linspace(0.0, 2.0, 4)
data = xr.DataArray(data_np, dims=['x','t'],
coords={'x':x_axis, 't':t_axis},
name='some_measurement')
というように、
-
dims
にdata_npの各次元に対応するラベルをリスト(もしくはタプル)で、 -
coords
に軸ラベルとそれに対応するデータを辞書型で与えます。
print(data)
<xarray.DataArray 'some_measurement' (x: 5, t: 4)>
array([[ 1.089975, 0.343039, -0.521509, 0.02816 ],
[ 1.117389, 0.589563, -1.030908, -0.023295],
[ 0.403413, -0.157136, -0.175684, -0.743779],
[ 0.814334, 0.164875, -0.489531, -0.335251],
[ 0.009115, 0.294526, 0.693384, -1.046416]])
Coordinates:
* t (t) float64 0.0 0.6667 1.333 2.0
* x (x) float64 0.0 0.25 0.5 0.75 1.0
表示された概要のうち
<xarray.DataArray 'some_measurement' (x: 5, t: 4)>
は, このDataArray はsome_measurement
という名前の 5x4 の行列で、1次元目の軸ラベルが'x'に、2次元目の軸ラベルが't'に対応していることを示しています。
また、
Coordinates:
以下は、軸データの一覧を示しています。
軸情報
軸一覧にはdims
によりアクセスすることができます。
なお、ここで表示される順序が、本来のデータの何時限目の軸に対応しているかを示しています。
data.dims
('x', 't')
軸の値にアクセスするには、ラベル名を引数にします。
data['x']
<xarray.DataArray 'x' (x: 5)>
array([ 0. , 0.25, 0.5 , 0.75, 1. ])
Coordinates:
* x (x) float64 0.0 0.25 0.5 0.75 1.0
インデクシング
xarray は複数の種類のインデクシングをサポートしています。pandasの仕組みを用いているのでpandasと同様に高速です。
numpy 風のアクセス
data[0,1]
<xarray.DataArray 'some_measurement' ()>
array(0.3430393695918721)
Coordinates:
t float64 0.6667
x float64 0.0
array-likeなので通常の行列のようにアクセスできます。
その時の軸情報を引き継がれます。
positional インデクシング
.loc
メソッドを使うことで、軸データに沿った位置を指定してアクセスできます。
data.loc[0:0.5, :1.0]
<xarray.DataArray 'some_measurement' (x: 3, t: 2)>
array([[ 1.089975, 0.343039],
[ 1.117389, 0.589563],
[ 0.403413, -0.157136]])
Coordinates:
* t (t) float64 0.0 0.6667
* x (x) float64 0.0 0.25 0.5
.loc[0:0.5, :1.0]
は、1次元目の軸に沿って0 < x < 0.5 の範囲、2次元目の軸に沿って t < 1.0 の範囲のデータを切り出してくる操作です。
軸ラベル名を指定したアクセス
軸ラベル名を指定したアクセスには.isel
や .sel
メソッドを使います。
.isel
は軸ラベルを指定し、そのインデックスを整数で指定します。
data.isel(t=1)
<xarray.DataArray 'some_measurement' (x: 5)>
array([ 0.343039, 0.589563, -0.157136, 0.164875, 0.294526])
Coordinates:
t float64 0.6667
* x (x) float64 0.0 0.25 0.5 0.75 1.0
.sel
は軸ラベルと、その軸値を指定します。
data.sel(t=slice(0.5,2.0))
<xarray.DataArray 'some_measurement' (x: 5, t: 3)>
array([[ 0.343039, -0.521509, 0.02816 ],
[ 0.589563, -1.030908, -0.023295],
[-0.157136, -0.175684, -0.743779],
[ 0.164875, -0.489531, -0.335251],
[ 0.294526, 0.693384, -1.046416]])
Coordinates:
* t (t) float64 0.6667 1.333 2.0
* x (x) float64 0.0 0.25 0.5 0.75 1.0
計算
np.ndarray 風の操作を数多くサポートしています。
ブロードキャストを含めた基本的な演算をサポートしています。
data+10
<xarray.DataArray 'some_measurement' (x: 5, t: 4)>
array([[ 11.089975, 10.343039, 9.478491, 10.02816 ],
[ 11.117389, 10.589563, 8.969092, 9.976705],
[ 10.403413, 9.842864, 9.824316, 9.256221],
[ 10.814334, 10.164875, 9.510469, 9.664749],
[ 10.009115, 10.294526, 10.693384, 8.953584]])
Coordinates:
* t (t) float64 0.0 0.6667 1.333 2.0
* x (x) float64 0.0 0.25 0.5 0.75 1.0
要素ごとの計算では、これらの情報を引き継げます。
np.sin(data)
<xarray.DataArray 'some_measurement' (x: 5, t: 4)>
array([[ 0.886616, 0.336351, -0.498189, 0.028156],
[ 0.89896 , 0.555998, -0.857766, -0.023293],
[ 0.39256 , -0.15649 , -0.174781, -0.677074],
[ 0.727269, 0.164129, -0.470212, -0.329006],
[ 0.009114, 0.290286, 0.639144, -0.865635]])
Coordinates:
* t (t) float64 0.0 0.6667 1.333 2.0
* x (x) float64 0.0 0.25 0.5 0.75 1.0
xr.Dataset の使い方
xr.Dataset
は、複数のxr.DataArray
をひとまとめにしたオブジェクトです。
特に、軸を共有するxr.DataArray
に対して、一気にインデクシングやスライシングを施すことができます。
1つの計測器が複数種類の信号を出力することもあると思いますが、
そのような多次元情報を扱うのに適したものとなっています。
これはpandas
におけるDataFrame
に似た役割のものです。
インスタンス化
ひとつ目の引数はdata_vars
はdict
-like です。
key には格納する data の名前、要素には 2要素のタプルを渡します。
タプルのひとつ目の要素は、そのデータに対応する軸ラベルを、ふたつ目の要素にはデータ(array
-like)を渡します。
軸データを格納するにはcoords
にdict
-like を渡します。
キーには軸のラベル、要素には軸の値を渡します。
ds = xr.Dataset({'data1': (['x','t'], np.random.randn(5,4)), 'data2': (['x','t'], np.random.randn(5,4))},
coords={'x': x_axis, 't': t_axis})
ds
<xarray.Dataset>
Dimensions: (t: 4, x: 5)
Coordinates:
* t (t) float64 0.0 0.6667 1.333 2.0
* x (x) float64 0.0 0.25 0.5 0.75 1.0
Data variables:
data1 (x, t) float64 -1.091 -1.851 0.343 2.077 1.477 0.0009389 1.358 ...
data2 (x, t) float64 0.4852 -0.5463 -0.22 -1.357 -1.416 -0.4929 ...
中身にアクセスするためには、ラベル名を[]
内に渡します。
その場合、戻り値はxr.DataArray
になります。
ds['data1']
<xarray.DataArray 'data1' (x: 5, t: 4)>
array([[ -1.091230e+00, -1.851416e+00, 3.429677e-01, 2.077113e+00],
[ 1.476765e+00, 9.389425e-04, 1.358136e+00, -1.627471e+00],
[ -2.007550e-01, 1.008126e-01, 7.177067e-01, 8.893402e-01],
[ -1.813395e-01, -3.407015e-01, -9.673550e-01, 1.135727e+00],
[ 2.423873e-01, -1.198268e+00, 1.650465e+00, -1.923102e-01]])
Coordinates:
* t (t) float64 0.0 0.6667 1.333 2.0
* x (x) float64 0.0 0.25 0.5 0.75 1.0
軸にもラベルでアクセスできます。
ds['x']
<xarray.DataArray 'x' (x: 5)>
array([ 0. , 0.25, 0.5 , 0.75, 1. ])
Coordinates:
* x (x) float64 0.0 0.25 0.5 0.75 1.0
xr.Dataset のインデクシング
指標アクセスにはisel
を用います。
x 軸に沿った1番目の要素にアクセスするには、以下のように、軸ラベル名とそれに対応するインデックスを指定します。
ds.isel(x=1)
<xarray.Dataset>
Dimensions: (t: 4)
Coordinates:
* t (t) float64 0.0 0.6667 1.333 2.0
x float64 0.25
Data variables:
data1 (t) float64 1.477 0.0009389 1.358 -1.627
data2 (t) float64 -1.416 -0.4929 0.4926 -0.7186
もちろん 複数の軸も指定できて
ds.isel(x=1, t=2)
<xarray.Dataset>
Dimensions: ()
Coordinates:
t float64 1.333
x float64 0.25
Data variables:
data1 float64 1.358
data2 float64 0.4926
スライスにも対応しています。
ds.isel(x=slice(0,2,1), t=2)
<xarray.Dataset>
Dimensions: (x: 2)
Coordinates:
t float64 1.333
* x (x) float64 0.0 0.25
Data variables:
data1 (x) float64 0.343 1.358
data2 (x) float64 -0.22 0.4926
位置インデクシング
位置インデクシングには.sel
メソッドを使います。
.isel
と同じように、軸ラベル名と今度は軸の値を指定します。
ds.sel(x=0.0)
<xarray.Dataset>
Dimensions: (t: 4)
Coordinates:
* t (t) float64 0.0 0.6667 1.333 2.0
x float64 0.0
Data variables:
data1 (t) float64 -1.091 -1.851 0.343 2.077
data2 (t) float64 0.4852 -0.5463 -0.22 -1.357
デフォルトでは厳密に値が一致したものが帰ってきますが、method
オプションで指定できます。
最寄値が欲しい場合はmethod='nearest'
とします。
# x = 0.4 に最もxが近い値を返す。
ds.sel(x=0.4, method='nearest')
<xarray.Dataset>
Dimensions: (t: 4)
Coordinates:
* t (t) float64 0.0 0.6667 1.333 2.0
x float64 0.5
Data variables:
data1 (t) float64 -0.2008 0.1008 0.7177 0.8893
data2 (t) float64 -0.03163 0.6942 0.8194 -2.93
スライスオブジェクトを渡すことも可能です。
ds.sel(x=slice(0,0.5))
<xarray.Dataset>
Dimensions: (t: 4, x: 3)
Coordinates:
* t (t) float64 0.0 0.6667 1.333 2.0
* x (x) float64 0.0 0.25 0.5
Data variables:
data1 (x, t) float64 -1.091 -1.851 0.343 2.077 1.477 0.0009389 1.358 ...
data2 (x, t) float64 0.4852 -0.5463 -0.22 -1.357 -1.416 -0.4929 ...