多次元データ解析ライブラリ　xarray


# 多次元データ解析ライブラリ　xarray

この記事では、多次元データ解析を支援するPythonのライブラリxarrayを紹介します。
さらに詳しい情報は本家の[情報](http://xarray.pydata.org/en/stable/)を参照してください。

# xarrayの特徴
## 背景
科学計測データは往々にして多次元になります。
例えば、複数の位置に設置したセンサで時系列データを計測する場合、
計測データは 空間チャンネル方向 × 時間方向 の二次元データになります。
さらにそのデータに短時間フーリエ変換を施す場合は 空間チャンネル方向 × 時間方向 × 周波数方向 の三次元データになったりします。

一般的にこういうデータを扱う場合、numpy の np.ndarray を使うことが多いと思います。
しかし、np.ndarray は単純な行列（もしくはテンソル）なので他の情報は別途置いておく必要があります。
上の例だと、
+ 次元の順番：二次元データのうち1次元目が空間チャンネル、二次元目が時間に対応すること
+ 各次元の座標

などがここで言う「他の情報」にあたります。

そのため、例えばその中のデータからある時間範囲の一部切り出して使う、というような場合は
切り出したデータの他に、時間軸のデータも同時に切り出しておく必要があります。

素の np.ndarray を使ってそれをきっちりやっていく、ってことももちろんできるんですが、
複雑なプログラムだとこのような煩雑な操作はミスの元になります。

xarray はそういった操作を簡単にするためのライブラリです。  
（ちなみに内部では np.ndarray を用いているので、np.ndarrayの高速な演算性能をほとんど犠牲にしません）
なお、一次元のデータを扱うライブラリとしてpandasがあります。
pandasは多次元のデータを（簡単には）扱えません。xarrayはそれを補間するライブラリになっています。

特徴としては上記の他に
+ `__str__` メソッドがオーバーロードされていて、print したときにその概要を表示してくれる。
+ 位置インデクシング・スライシング（例えば、ある時刻に最も近いときのデータを探すなど）が可能。
その結果もxarrayオブジェクトになり、軸に関する情報も正しく保有してくれる。
+ 簡単な統計処理（移動平均など）が可能。軸に関する情報も正しく保有してくれる。
+ pandas との相互変換が可能
+ メモリに載り切らない巨大なデータにも対応している（らしい）

などがあります。

ちなみに、


```python
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`に似た役割のものです。
データの値自体と、軸のデータを有しています。

## インスタンス化


```python
data = xr.DataArray(np.random.randn(2, 3))
```

とすると、軸情報のない2x3の xr.DataArray オブジェクトを作成できます。

`print` メソッドで作成したオブジェクトの概要を表示できます。


```python
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に対応する場合を考えます。


```python
#例データ
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)
```


```python
data = xr.DataArray(data_np, dims=['x','t'], 
                    coords={'x':x_axis, 't':t_axis}, 
                    name='some_measurement')
```

というように、
+ `dims` にdata_npの各次元に対応するラベルをリスト（もしくはタプル）で、
+ `coords` に軸ラベルとそれに対応するデータを辞書型で与えます。


```python
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 の行列で、１次元目の軸ラベルが'x'に、２次元目の軸ラベルが't'に対応していることを示しています。

また、
> `Coordinates:`

以下は、軸データの一覧を示しています。

## 軸情報

軸一覧には`dims`によりアクセスすることができます。  
なお、ここで表示される順序が、本来のデータの何時限目の軸に対応しているかを示しています。


```python
data.dims
```




    ('x', 't')



軸の値にアクセスするには、ラベル名を引数にします。


```python
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 風のアクセス


```python
data[0,1]
```




    <xarray.DataArray 'some_measurement' ()>
    array(0.3430393695918721)
    Coordinates:
        t        float64 0.6667
        x        float64 0.0



array-likeなので通常の行列のようにアクセスできます。
その時の軸情報を引き継がれます。

### positional インデクシング

`.loc`メソッドを使うことで、軸データに沿った位置を指定してアクセスできます。


```python
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]`
は、１次元目の軸に沿って0 < x < 0.5  の範囲、２次元目の軸に沿って t < 1.0 の範囲のデータを切り出してくる操作です。

### 軸ラベル名を指定したアクセス

軸ラベル名を指定したアクセスには`.isel`や `.sel` メソッドを使います。

`.isel`は軸ラベルを指定し、そのインデックスを整数で指定します。


```python
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`は軸ラベルと、その軸値を指定します。


```python
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 風の操作を数多くサポートしています。

ブロードキャストを含めた基本的な演算をサポートしています。


```python
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



要素ごとの計算では、これらの情報を引き継げます。


```python
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 を渡します。
キーには軸のラベル、要素には軸の値を渡します。


```python
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})
```


```python
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`になります。


```python
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



軸にもラベルでアクセスできます。


```python
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番目の要素にアクセスするには、以下のように、軸ラベル名とそれに対応するインデックスを指定します。


```python
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



もちろん 複数の軸も指定できて


```python
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



スライスにも対応しています。


```python
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`と同じように、軸ラベル名と今度は軸の値を指定します。


```python
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'`とします。


```python
# 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



スライスオブジェクトを渡すことも可能です。


```python
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 ...




```python

```