e-Stat APIバージョン2.1をRから使う

e-Stat APIがちょっとバージョンアップしていて、統計データ取得APIデータ形式CSVが追加されました。

前にRからe-Stat APIを使ったときはちょっとJSONをdata.frameに直すのが大変すぎて、

estatapiというパッケージをつくったんですが、

…ひょっとしてもうパッケージいらない?と思ったので試してみました。

変更点

JSON形式のパスはこうでした。

http://api.e-stat.go.jp/rest/<バージョン>/app/json/getStatsData?<パラメータ群>

CSV形式のはこうなります。2.1以降でしか使えないので今のところ<バージョン>には2.1一択です。

http://api.e-stat.go.jp/rest/<バージョン>/app/getSimpleStatsData?<パラメータ群>

参考:政府統計の総合窓口(e-Stat)のAPI 仕様 2.1版 | 政府統計の総合窓口(e-Stat)−API機能

バージョン2.1をRから使ってみる

使ってみます。

library(httr)

appId <- "XXXXXXXX"

res_data <- GET(
  "http://api.e-stat.go.jp/", path = "rest/2.1/app/getSimpleStatsData",
  query = list(
    appId      = appId,
    statsDataId = "0003103532",
    cdCat01     = "010800130,010800140"
  ))

res <- content(res_data)

あっさり取れました。

が、なんかこのresを表示しようとするとRのコンソールが固まります。あれ? データがでかすぎる?とか思って、

head(res)

とやっても同じく固まります。むうう…

何かがおかしい

元のレスポンスのContent-Typeヘッダを見てみます。

res_data$headers$`content-type`
#> [1] "text/plain;charset=utf-8"

あれ、これがtext/csvとかになってないせい?とか思ったんですが、むりやりread_csv()してみてもなんか様子が変です。

res_csv <- readr::read_csv(res)
#> Warning: 13097 parsing failures.
#> row col  expected    actual
#>   1  -- 1 columns 2 columns
#>   2  -- 1 columns 2 columns
#>   3  -- 1 columns 2 columns
#>   5  -- 1 columns 2 columns
#>   6  -- 1 columns 2 columns
#> ... ... ......... .........
#> .See problems(...) for more details.

dim(res_csv)
#> [1] 13099     1

head(res_csv)
#> # A tibble: 6 x 1
#>         RESULT
#>          <chr>
#> 1       STATUS
#> 2    ERROR_MSG
#> 3         DATE
#> 4   RESULT_INF
#> 5 TOTAL_NUMBER
#> 6  FROM_NUMBER

おかしい。と思って生データを見てみると…

library(stringr)

cat(str_sub(res, 0, 1000))
#> "RESULT"
#> "STATUS","0"
#> "ERROR_MSG","正常に終了しました。"
#> "DATE","2016-07-16T23:14:46.597+09:00"
#> "RESULT_INF"
#> "TOTAL_NUMBER","13072"
#> "FROM_NUMBER","1"
#> "TO_NUMBER","13072"
#> "TABLE_INF","0003103532"
#> "STAT_NAME","00200561","家計調査"
#> "GOV_ORG","00200","総務省"
#> "STATISTICS_NAME","家計調査 家計収支編 二人以上の世帯"
#> "TITLE","010","品目分類 品目分類(平成27年改定)(総数:金額)"
#> "CYCLE","月次"
#> "SURVEY_DATE","0"
#> "OPEN_DATE","2016-07-01"
#> "SMALL_AREA","0"
#> "MAIN_CATEGORY","07","企業・家計・経済"
#> "SUB_CATEGORY","04","家計"
#> "OVERALL_TOTAL_NUMBER","4498164"
#> "UPDATED_DATE","2016-06-29"
#> "STATISTICS_NAME_SPEC","家計調査","家計収支編","二人以上の世帯","","",""
#> "TITLE_SPEC","品目分類","品目分類(平成27年改定)(総数:金額)","","",""
#> "NOTE","***","調査又は集計していないもの"
#> "NOTE","-","該当数字がないもの"
#> "NOTE","X","数値が秘匿されているもの"
#> "VALUE"
#> "tab_code","表章項目","cat01_code","品目分類(27年改定)","cat02_code","世帯区分","area_code","地域区分","time_code","時間軸(月次)","unit","value"
#> "01","金額","010800130","352 チョコレート","03","二人以上の世帯(2000年~)","00000","全国","2016000505","2016年5月","円","346"
#> "01","金額","010800130","352 チョコレート","03","二人以上の世帯(2000年~)","00000","全国

グワー!!!

この書き方から察するに、フィールド数がひとつの行は見出しとして使われているようです。メタデータをどこに書くんだろう?とは思ってたんですが、この発想はなかったです。これをCSVと呼んでいいんだろうか...

がんばって読む

こうなれば取りうる戦略はひとつ。"VALUE"までの行を無視することです。readr::read_csv()skip引数を指定するのも手ですが、"VALUE"が何行目なのかを調べるのがめんどくさそうです。 そこで、stringr::str_replace()を使います。先頭から"VALUE"までを空白と置換することで不要な部分を取り除きます。

改行をまたいでマッチさせるには、検索文字列をregex(..., dotall = TRUE)でラップして指定します。あと"VALUE"までだと改行が残ってしまうので"VALUE"\nまで指定する点にも注意です。

library(dplyr)

readr::read_csv(
  str_replace(
    res,
    regex('^.*"VALUE"\n', dotall = TRUE),
    ''
  )
)

#> # A tibble: 13,072 x 12
#>    tab_code 表章項目 cat01_code 品目分類(27年改定) cat02_code                   世帯区分 area_code 地域区分  time_code 時間軸(月次)  unit value
#>       <chr>    <chr>      <chr>                <chr>      <chr>                      <chr>     <chr>    <chr>      <int>          <chr> <chr> <dbl>
#> 1        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000     全国 2016000505      2016年5月    円   346
#> 2        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000     全国 2016000404      2016年4月    円   403
#> 3        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000     全国 2016000303      2016年3月    円   580
#> 4        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000     全国 2016000202      2016年2月    円  1376
#> 5        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000     全国 2016000101      2016年1月    円   665
#> 6        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000     全国 2015001212     2015年12月    円   585
#> 7        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000     全国 2015001111     2015年11月    円   451
#> 8        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000     全国 2015001010     2015年10月    円   413
#> 9        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000     全国 2015000909      2015年9月    円   326
#> 10       01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000     全国 2015000808      2015年8月    円   212
#> # ... with 13,062 more rows

何とか読めました。しかしまあ、メタデータの紐付けを自分でやらなくていいのは楽ですね。

感想

メタデータの紐付けを考えなくていいのでestatapiパッケージは内部的にはCSVを使うと思いますが、普通の人に勧められるかというと微妙ですね。。 CSVが使える!と油断して飛びつくと痛い目に遭います。備えよう。


追記(2016/07/27):

APIの仕様を見ていると、sectionHeaderFlgというパラメータがありました。これに2を指定すると、問題のメタデータ的な部分の出力が抑えられます。

res_data <- GET(
    "http://api.e-stat.go.jp/", path = "rest/2.1/app/getSimpleStatsData",
    query = list(
        appId      = appId,
        statsDataId = "0003103532",
        cdCat01     = "010800130,010800140",
        sectionHeaderFlg  = 2
    ))

cat(str_sub(content(res_data, as = "text"), 0, 100))
#> "VALUE"
#> "tab_code","表章項目","cat01_code","品目分類(27年改定)","cat02_code","世帯区分","area_code","地域区分","time_co

とはいえ"VALUE"だけは残るんですけど、行数が変わらなければread_csv()skipが使えます。ちょっとだけ楽になりました。

readr::read_csv(content(res_data, as = "text"), skip = 1)
#> # A tibble: 13,072 x 12
#>    tab_code 表章項目 cat01_code 品目分類(27年改定) cat02_code                   世帯区分 area_code
#>       <chr>    <chr>      <chr>                <chr>      <chr>                      <chr>     <chr>
#> 1        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000
#> 2        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000
#> 3        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000
#> 4        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000
#> 5        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000
#> 6        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000
#> 7        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000
#> 8        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000
#> 9        01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000
#> 10       01     金額  010800130     352 チョコレート         03 二人以上の世帯(2000年~)     00000
#> # ... with 13,062 more rows, and 5 more variables: 地域区分 <chr>, time_code <int>, 時間軸(月次) <chr>,
#> #   unit <chr>, value <dbl>