rlistでリストを整形してデータフレームにする

# 深くネストしたlist

野生のJSONをうっかりRに読み込んだらめっちゃネストした複雑なリストが出来上がってしまった…というのはよくある話です。Rでリストを扱うのは一般に憂鬱な作業ですが、`rlist`パッケージを使えば`dplyr`を使ってデータフレームを扱うが如くの容易さで操作ができます。`rlist`の基本的な機能については[公式のチュートリアル](https://renkun-ken.github.io/rlist-tutorial/index.html)を確認することをお勧めしますが、長かったので[少し短めにまとめたもの](https://qiita.com/nozma/items/d4879bf55e8637c0686e)もあります。

今回は野生のJSONをハンドリングする練習として、`rlist`パッケージに含まれているデータセットの`nyweather`を例に各種項目をデータフレームに整形する作業をやっていきます。

`nyweather`にはOpenWeatherMapのAPIを用いて取得されたニューヨークの毎時気象データが入っています。データは1日ごとにリストになっており、さらにその中に毎時データがリストとして入っており、その中にさらに各測定項目が…という深いネストを伴った構造になっています。

まずトップレベルの名前を確認してみます。

``` r
library(rlist)
head(names(nyweather))
```

    [1] "D20130101" "D20130102" "D20130103" "D20130104" "D20130105" "D20130106"

トップレベルはデータの取得日になっています。このように名前部分にデータが入ってしまっているということもしばしばあります。`nyweather`の場合は内部に別途日時が記録されているのでここからデータを抽出する必要はないのですが、名前からのデータ抽出方法についても後で解説します。

1日目のデータを確認してみましょう。

``` r
str(nyweather$D20130101, max.level = 2, list.len = 6)
```

    List of 6
     $ message : chr ""
     $ cod     : chr "200"
     $ city_id : int 5128581
     $ calctime: num 0.597
     $ cnt     : int 25
     $ list    :List of 25
      ..$ :List of 5
      ..$ :List of 5
      ..$ :List of 5
      ..$ :List of 5
      ..$ :List of 5
      ..$ :List of 5
      .. [list output truncated]

毎時データも確認してみましょう。

``` r
str(nyweather$D20130101$list[[1]])
```

    List of 5
     $ weather:List of 1
      ..$ :List of 4
      .. ..$ id         : int 803
      .. ..$ main       : chr "Clouds"
      .. ..$ description: chr "broken clouds"
      .. ..$ icon       : chr "04d"
     $ main   :List of 5
      ..$ temp    : num 274
      ..$ pressure: int 1013
      ..$ humidity: int 50
      ..$ temp_min: num 272
      ..$ temp_max: num 276
     $ wind   :List of 3
      ..$ speed: num 10.8
      ..$ deg  : int 310
      ..$ gust : num 14.4
     $ clouds :List of 1
      ..$ all: int 75
     $ dt     : int 1356969600

項目によってはさらにネストしているものがあることが分かります。しかもこのデータ構造は取得日によって微妙に違うことがあります。日によって他の日には無いデータが取得されていたり、一部のデータが欠損していたりするのです。データが欠損している場合に`null`が入っているのではなく項目自体が省略されている、というのはJSONだとありがちな構造です。

# 特定項目の抽出

まずは平均気温を抽出することを考えてみましょう。後先考えなければこれは比較的簡単です。ネストした項目にアクセスするには、`list.map()`や`list.mapv()`等の要素にアクセスする関数をネストさせれば良いのです。

``` r
library(rlist)
library(pipeR) # pipeRはrlistと組み合わせて使用した際に問題が起きないように設計されている
nyweather %>>% 
  list.map( # 各日付に対する操作
    list %>>% # $listの各項目に対する操作
      list.mapv(main$temp) # main$tempの中身をベクトルとして抽出
  ) %>>%
  head(3)
```

    $D20130101
     [1] 273.84 274.61 274.99 274.92 274.72 273.85 272.83 271.97 272.03 271.46
    [11] 271.19 271.07 270.83 270.64 269.98 269.76 270.17 271.33 271.75 271.34
    [21] 271.41 271.60 272.14 273.12 274.37
    
    $D20130102
     [1] 274.37 275.37 276.43 276.61 276.27 275.56 275.50 275.83 275.76 275.83
    [11] 276.03 276.03 276.09 276.60 276.80 276.66 276.80 276.73 276.67 276.73
    [21] 276.65 276.60 276.80 277.17 277.17
    
    $D20130103
     [1] 277.17 276.78 276.26 276.33 276.40 276.07 275.17 274.32 273.48 272.95
    [11] 272.11 271.14 270.42 269.48 268.00 268.00 268.00 268.00 268.00 269.39
    [21] 270.33 271.29

しかし、このような抽出だけで十分というケースは稀でしょう。まず時刻がわかりません。

そこで今度は時刻と平均気温を合わせて抽出することを考えてみましょう。

複数の項目を抽出する際には、`list.select()`を使用します。`dplyr`の`select()`のようなものだと考えれば良いでしょう。

先程の`list.map()`でも同じですが、これらの関数は **リスト要素の文脈で式を評価**
することで抽出という操作を行います。つまり、評価する式を操作すれば、単なる抽出だけでなく集計や変換を同時に行うこともできます。

`nyweather`では、時刻がunix timeで記録されていますから、抽出ついでにこれをPOSIXctクラスへ変換してみましょう。unix
timeから日時への変換は、`lubridate`パッケージを使って`lubridate::as_datetime(x, tz =
"UTC")`とするか、組み込みの`as.POSIXct()`を使って`as.POSIXct(x, origin = "1970/1/1",
tz = "UTC")`とすることで行えます。

``` r
nyweather %>>%
  list.map(
    list %>>%
      list.select(
        dt = lubridate::as_datetime(dt, tz = "UTC"),
        temp = main$temp
      )
  ) %>>% head(1) %>>% str(list.len = 3)
```

    List of 1
     $ D20130101:List of 25
      ..$ :List of 2
      .. ..$ dt  : POSIXct[1:1], format: "2012-12-31 16:00:00"
      .. ..$ temp: num 274
      ..$ :List of 2
      .. ..$ dt  : POSIXct[1:1], format: "2012-12-31 17:00:00"
      .. ..$ temp: num 275
      ..$ :List of 2
      .. ..$ dt  : POSIXct[1:1], format: "2012-12-31 18:00:00"
      .. ..$ temp: num 275
      .. [list output truncated]

`list.select()`による抽出結果は、要素数が等しいリストになっています。このようなリストは`list.stack()`でデータフレームに変換できます。データフレームも実質的にはリストなので、`list.stack()`の結果をさらにスタックすることもできます。

``` r
nyweather %>>%
  list.map(
    list %>>%
      list.select(
        dt = lubridate::as_datetime(dt, tz = "UTC"),
        temp = main$temp
      ) %>>%
      list.stack()
  ) %>>%
  list.stack() %>>% head
```

| dt                  |   temp |
| :------------------ | -----: |
| 2012-12-31 16:00:00 | 273.84 |
| 2012-12-31 17:00:00 | 274.61 |
| 2012-12-31 18:00:00 | 274.99 |
| 2012-12-31 19:00:00 | 274.92 |
| 2012-12-31 20:00:00 | 274.72 |
| 2012-12-31 21:00:00 | 273.85 |

無事にデータフレームとしてデータを抽出できました。

# 欠損値がある場合

`nyweather`に含まれる観測項目は均一ではない、つまり、日や時刻によって項目があったりなかったりするということを最初に説明しました。

`$list`以下の毎時データにどのような項目が含まれているのかを集計してみましょう。この用途には`list.table()`が便利です。項目名を抽出するには`names()`を使用します。

``` r
nyweather %>>%
  list.table(
    list %>>% list.map(names(.))
  )
```

``` 

       clouds            dt forecast_rain          main          rain 
         1263          1263            64          1263            30 
      weather          wind 
         1263          1263 
```

一部のデータのみ`forecast_rain`や`rain`が含まれているようです。`forecast_rain`についてどんなデータなのか確認してみましょう。

``` r
nyweather %>>%
  list.map(
    list %>>%
      list.mapv(.$forecast_rain)
  ) %>>%
  list.clean()
```

    $D20130111
    3h 3h 3h 3h 3h 3h 3h 
     0  0  0  0  0  0  0 
    
    $D20130113
     3h  3h  3h  3h  3h  3h  3h  3h  3h  3h  3h  3h  3h  3h  3h  3h 
    0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.8 0.0 0.0 0.0 0.0 
    
    $D20130115
      3h   3h   3h   3h   3h   3h   3h   3h   3h 
    0.07 0.07 0.07 0.07 0.07 0.07 0.04 0.04 0.04 
    
    $D20130116
      3h   3h   3h   3h   3h   3h   3h 
    0.04 0.04 1.10 1.10 1.10 1.10 1.10 
    
    $D20130117
     3h  3h  3h  3h  3h  3h  3h  3h  3h  3h 
    4.5 4.5 4.5 4.5 4.5 4.5 1.2 1.2 1.2 1.2 
    
    $D20130118
     3h  3h  3h  3h  3h  3h  3h  3h  3h  3h  3h  3h  3h  3h  3h 
    1.2 1.2 1.2 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 0.4 

ここで注意すべきは`forecast_rain`ではなく`.$forecast_rain`とアクセスする必要があるという点です。`forecast_rain`はすべてのリスト要素に含まれているわけではないので、そのままアクセスすると`forecast_rain`を含まない要素にぶつかった際に`オブジェクト
'forecast_rain'
がありません`のようなエラーが出て処理が止まってしまいます。

`.$forecast_rain`のように`$`演算子を介したアクセスでは、要素が存在しなければ`NULL`が返るため、すべてのリスト要素に対して抽出処理を実行できます。そのかわりに`NULL`が残ってしまいますが、これが不要なら`list.clean()`で除外できます。

では、先程と同様に時刻と一緒に上記の値を抽出してみましょう。

``` r
nyweather %>>%
  list.map(
    list %>>%
      list.select(
        dt = lubridate::as_datetime(dt, tz = "UTC"),
        forecast_rain = .$forecast_rain$`3h`
      )  %>>%
      list.stack()
  ) %>>%
  list.stack()
```

    data.table::rbindlist(.data, ...) でエラー: NULL に対して属性を設定しようとしました

はい。失敗しました。`list.stack()`は`data.table::rbindlist()`のラッパーなのですが、結果に`NULL`が含まれるリストを`data.table::rbindlist()`はうまく扱うことができません。

`list.clean()`で`NULL`を取り除きましょう。今回取り除くべき`NULL`は`list.select()`の結果の一部として含まれているので、`recursive=TRUE`を指定する必要があります。`NULL`を取り除くことで部分的に列の数が変わってしまいますが、それによって生じる隙間は`list.stack()`に`fill
= TRUE`を指定することで埋めることができます。

今回の例ではデータの密度が低く、内側の処理で1列のデータフレームが生じる可能性があるので、外側で改めて`list.stack(fill =
TRUE)`を実行しています。

なお、隙間は`NA`で埋められるため、`na.omit()`で簡単に不要な行を削除できます。

``` r
nyweather %>>%
  list.map(
    list %>>%
      list.select(
        dt = lubridate::as_datetime(dt, tz = "UTC"),
        forecast_rain = .$forecast_rain$`3h`
      )  %>>%
      list.clean(recursive = TRUE) %>>%
      list.stack(fill = TRUE)
  ) %>>%
  list.stack(fill = TRUE) %>>%
  na.omit() %>>%  head
```

|     | dt                  | forecast\_rain |
| --- | :------------------ | -------------: |
| 227 | 2013-01-10 18:00:00 |              0 |
| 228 | 2013-01-10 22:00:00 |              0 |
| 229 | 2013-01-10 23:00:00 |              0 |
| 230 | 2013-01-11 00:00:00 |              0 |
| 231 | 2013-01-11 01:00:00 |              0 |
| 232 | 2013-01-11 03:00:00 |              0 |

# keyを値として使いたい場合

ときには時刻や日付のような値がkeyとして使用されている場合があります。このようなJSONをRに読み込むと、keyはname属性として取り込まれます。したがって、これを値として使いたい場合は、name属性を取得してやればよいということになります。

`nyweather`では第一レベルのnameがデータ取得日になっているので、これを含むデータフレームを作成してみましょう。

name属性の取得方法はいくつかあります。まず、組み込み関数の`names()`が使えます。

``` r
names(nyweather) %>>% head
```

    [1] "D20130101" "D20130102" "D20130103" "D20130104" "D20130105" "D20130106"

また、`rlist`の関数群の中（正確にはラムダ式の中）で使用できる変数である`.name`を通じてnameを取得することもできます

``` r
nyweather %>>%
  list.map(.name) %>>%
  head
```

    $D20130101
    [1] "D20130101"
    
    $D20130102
    [1] "D20130102"
    
    $D20130103
    [1] "D20130103"
    
    $D20130104
    [1] "D20130104"
    
    $D20130105
    [1] "D20130105"
    
    $D20130106
    [1] "D20130106"

`names(.)`と`.name`の挙動の違いに注意してください。

``` r
nyweather %>>%
  list.map(names(.)) %>>%
  head
```

``` 
$D20130101
[1] "message"  "cod"      "city_id"  "calctime" "cnt"      "list"    

$D20130102
[1] "message"  "cod"      "city_id"  "calctime" "cnt"      "list"    

$D20130103
[1] "message"  "cod"      "city_id"  "calctime" "cnt"      "list"    

$D20130104
[1] "message"  "cod"      "city_id"  "calctime" "cnt"      "list"    

$D20130105
[1] "message"  "cod"      "city_id"  "calctime" "cnt"      "list"    

$D20130106
[1] "message"  "cod"      "city_id"  "calctime" "cnt"      "list"    
```

`.name`は操作の対象となっている要素自体のname属性を取得するのに対し、`name(.)`は操作の対象となっている要素に含まれているname属性を取得します。`.name`は上方向を、`name(.)`は下方向を見ているのです。

# 階層の異なるデータを組み合わせる

日付が入っている`name`属性と時刻や気温の入っている要素は階層が違います。

このようなデータはそのままではstackできません。

``` r
nyweather %>>%
  list.select(
    date = .name,
    dt = list %>>% list.map(.$dt),
    temp = list %>>% list.map(.$main$temp)
  ) %>>%
  list.stack()
```

``` r
data.table::rbindlist(.data, ...) でエラー: Column 2 of item 1 is length 25, inconsistent with first column of that item which is length 1. rbind/rbindlist doesn't recycle as it already expects each item to be a uniform list, data.frame or data.table
```

`datatable::rbindlist()`は要素数の違うリストを結合できないことが原因のようです。これを解決する方法はいくつかあります。一つは`list.select()`の代わりに`data.frame()`を使用して先にデータフレームを作成してしまってから結合する方法です。`data.frame()`の要素にはベクトルを与える必要があるので、`list.mapv()`を使用します。

``` r
nyweather %>>%
  list.map(
    data.frame(
      date = .name,
      dt = list %>>% list.mapv(lubridate::as_datetime(dt, tz = "UTC")),
      temp = list %>>% list.mapv(main$temp)
    )
  ) %>>% 
  list.stack() %>>% head
```

| date      |         dt |   temp |
| :-------- | ---------: | -----: |
| D20130101 | 1356969600 | 273.84 |
| D20130101 | 1356973200 | 274.61 |
| D20130101 | 1356976800 | 274.99 |
| D20130101 | 1356980400 | 274.92 |
| D20130101 | 1356984000 | 274.72 |
| D20130101 | 1356987600 | 273.85 |

結果をみるとわかるように、この方法だとPOSIXctクラスが保持されません（なんでだろ）。

もう一つの方法は`tibble::tibble()`を使用するというものです。この場合、`list.map()`の結果をそのまま含めることができます。ただし結果はリストのままtibbleに含まれることになるので、データフレームと同等の形式にするためには`tidyr::unnest()`でリストを開いてやる必要があります。

``` r
nyweather %>>% 
  list.map(
    tibble::tibble(
      date = .name,
      dt = list %>>% list.map(lubridate::as_datetime(dt, tz = "UTC")),
      temp = list %>>% list.map(main$temp)
    )
  ) %>>% 
  list.stack() %>>% 
  tidyr::unnest() %>>% head
```

| date      | dt                  |   temp |
| :-------- | :------------------ | -----: |
| D20130101 | 2012-12-31 16:00:00 | 273.84 |
| D20130101 | 2012-12-31 17:00:00 | 274.61 |
| D20130101 | 2012-12-31 18:00:00 | 274.99 |
| D20130101 | 2012-12-31 19:00:00 | 274.92 |
| D20130101 | 2012-12-31 20:00:00 | 274.72 |
| D20130101 | 2012-12-31 21:00:00 | 273.85 |

# 複雑にネストしたリストをデータフレームに変換する

tibbleを使う方法では、複雑な構造をデータフレームに変換することもできます。

例えば`$main`の中身の各項目を、項目名と値の2列として取得する例を考えてみましょう。つまり、次のようなデータフレームを作成することを目的とします。

| item      | value  |
| --------- | ------ |
| temp      | 273.84 |
| temp\_min | 272.15 |
| temp\_max | 275.93 |
| …         | …      |

まず次のようにリストを要素として含むtibbleを作成します。

``` r
nyweather %>>% 
  list.map(
    tibble::tibble(
      date = .name,
      item = list %>>% list.map(names(main)),
      value = list %>>% list.map(main)
    )
  ) %>>% 
  list.stack() %>>% head
```

    # A tibble: 6 x 3
      date      item      value     
    * <chr>     <list>    <list>    
    1 D20130101 <chr [5]> <list [5]>
    2 D20130101 <chr [5]> <list [5]>
    3 D20130101 <chr [5]> <list [5]>
    4 D20130101 <chr [5]> <list [5]>
    5 D20130101 <chr [5]> <list [5]>
    6 D20130101 <chr [5]> <list [5]>

これを`tidyr::unnest()`を使って開けばデータフレームが得られます。今回の例では`value`の中身はさらにネストされたリストになっているので、`tidyr::unnest()`は2回実行する必要があります。

``` r
nyweather %>>% 
  list.map(
    tibble::tibble(
      date = .name,
      item = list %>>% list.map(names(main)),
      value = list %>>% list.map(main)
    )
  ) %>>%
  list.stack() %>>% 
  tidyr::unnest() %>>% 
  tidyr::unnest() %>>% head
```

    # A tibble: 6 x 3
      date      item     value
    * <chr>     <chr>    <dbl>
    1 D20130101 temp      274.
    2 D20130101 pressure 1013 
    3 D20130101 humidity   50 
    4 D20130101 temp_min  272.
    5 D20130101 temp_max  276.
    6 D20130101 temp      275.

`tidyr::unnest()`の対象となる列に含まれるリストは、それぞれ要素数が等しい必要があります。つまり、次のようなtibbleはそのままunnestすることができません。

``` r
nyweather %>>% 
  list.map(
    tibble::tibble(
      date = .name,
      item = list %>>% list.map(names(main)),
      value = list %>>% list.map(main),
      wind = list %>>% list.map(wind$speed)
    )
  ) %>>%
  list.stack() %>>% head
```

    # A tibble: 6 x 4
      date      item      value      wind     
    * <chr>     <list>    <list>     <list>   
    1 D20130101 <chr [5]> <list [5]> <dbl [1]>
    2 D20130101 <chr [5]> <list [5]> <dbl [1]>
    3 D20130101 <chr [5]> <list [5]> <dbl [1]>
    4 D20130101 <chr [5]> <list [5]> <dbl [1]>
    5 D20130101 <chr [5]> <list [5]> <dbl [1]>
    6 D20130101 <chr [5]> <list [5]> <dbl [1]>

この場合は`wind$speed`を取得する部分を`list.mapv`にしてベクトルとして取得するようにするか、unnestする際に`.preserve
= wind`を指定して、unnestの対象から外すという方法があります。`.preserve
=`を指定する方法は、順序を間違えると予期せぬデータの重複を招く場合があるので注意が必要です。

``` r
nyweather %>>% 
  list.map(
    tibble::tibble(
      date = .name,
      item = list %>>% list.map(names(main)),
      value = list %>>% list.map(main),
      wind = list %>>% list.map(wind$speed)
    )
  ) %>>%
  list.stack() %>>% 
  tidyr::unnest(.preserve = wind) %>>% head
```

    # A tibble: 6 x 4
      date      wind      item     value    
    * <chr>     <list>    <chr>    <list>   
    1 D20130101 <dbl [1]> temp     <dbl [1]>
    2 D20130101 <dbl [1]> pressure <int [1]>
    3 D20130101 <dbl [1]> humidity <int [1]>
    4 D20130101 <dbl [1]> temp_min <dbl [1]>
    5 D20130101 <dbl [1]> temp_max <dbl [1]>
    6 D20130101 <dbl [1]> temp     <dbl [1]>

ところで、通常のデータフレームでも`I()`を使用すればリストを要素に含めることができ、`tidyr::unnest()`で開くことができます。

ただし、RStudio上のプレビューでは各項目が`<S3:
AsIs>`のように表示されてリストに含まれる要素数がわかりにくくなります。複数回のunnestが必要な複雑なデータを扱う場合は、展開ミスを防ぐためにtibbleを使用したほうが良いでしょう。

``` r
nyweather %>>% 
  list.map(
    data.frame(
      date = .name,
      item = I(list %>>% list.map(names(main))),
      value = I(list %>>% list.map(main))
    )
  ) %>>%
  list.stack() %>>% head
```

    # A tibble: 6 x 3
      date      item      value     
    * <fct>     <I(list)> <I(list)> 
    1 D20130101 <chr [5]> <list [5]>
    2 D20130101 <chr [5]> <list [5]>
    3 D20130101 <chr [5]> <list [5]>
    4 D20130101 <chr [5]> <list [5]>
    5 D20130101 <chr [5]> <list [5]>
    6 D20130101 <chr [5]> <list [5]>