ほぼほぼこの記事に書いてある内容なんですけど、httrでやってみたのでメモ。
e-Stat APIとは
政府統計の総合窓口(e-Stat)で提供している統計データを入手できるAPIです。ユーザ登録してAPIキーを生成すると使えます。
今のところ、以下の種類のAPIが用意されています。形式はXMLとJSONとJSONPがありますが、今回はJSON形式のでやってみるので、jsonが返ってくるリクエストURLを書き添えています。
- 統計表情報取得(
getStatsList
):統計表の検索 - メタ情報取得(
getMetaInfo
):統計表のメタ情報を取得 - 統計データ取得(
getStatsData
):統計表のデータを取得 - データセット登録(
postDataset
):統計データの取得条件を登録しておける - データセット参照(
refDataset
):登録されている取得条件を参照 - データカタログ情報取得(
getDataCatalog
):統計表ファイルと統計データベースの検索
あんまりよく理解できてないんですが、
- 「統計表情報取得」で関係ありそうな統計表を検索
- データを取得する統計表を選ぶ
- 「メタ情報取得」で統計表のメタ情報を取得(絞り込みに使う)
- 3.で取得した情報を絞り込み条件にして「統計データ取得」で実際のデータを取得
という感じの流れになるみたいです。絞り込みは、しなくてもいいんですけどした方が取ってくるデータが少ないので早かったです。
(詳しいAPIのパラメータは公式ドキュメントを参照してください:政府統計の総合窓口(e-Stat)のAPI 仕様 | 政府統計の総合窓口(e-Stat)−API機能)
統計表情報取得
例えば、「チョコレート」に関する統計表を検索するときはこんな感じです。
library(httr) library(dplyr) # APIキー key <- "XXXXXX" res <- GET( "http://api.e-stat.go.jp/", path = "rest/2.0/app/json/getStatsList", add_headers(`Accept-Encoding` = "gzip"), query = list( appId = key, searchWord = "チョコレート" ))
返ってくる結果はこんな感じになります。入り組んだ構造になっていますが、TABLE_INF
というやつが探している結果です。
j <- content(res) str(j, max.level = 3) #> List of 1 #> $ GET_STATS_LIST:List of 3 #> ..$ RESULT :List of 3 #> .. ..$ STATUS : int 0 #> .. ..$ ERROR_MSG: chr "正常に終了しました。" #> .. ..$ DATE : chr "2015-10-28T19:33:32.509+09:00" #> ..$ PARAMETER :List of 3 #> .. ..$ LANG : chr "J" #> .. ..$ SEARCH_WORD: chr "チョコレート" #> .. ..$ DATA_FORMAT: chr "J" #> ..$ DATALIST_INF:List of 3 #> .. ..$ NUMBER : int 166 #> .. ..$ RESULT_INF:List of 2 #> .. ..$ TABLE_INF :List of 166 #> .. .. .. [list output truncated]
要素をひとつ覗いてみると、一階層ネストした要素があります。これをなんとかしないといけないので、purrr
のflatten()
という関数を使います。(たぶんjsonlite
のflatten()
とか、rlist
のlist.flatten()
も同じ)
str(j$GET_STATS_LIST$DATALIST_INF$TABLE_INF[[1]], list.len = 6) #> List of 13 #> $ @id : chr "0000100087" #> $ STAT_NAME :List of 2 #> ..$ @code: chr "00200572" #> ..$ $ : chr "全国物価統計調査" #> $ GOV_ORG :List of 2 #> ..$ @code: chr "00200" #> ..$ $ : chr "総務省" #> $ STATISTICS_NAME : chr "平成9年全国物価統計調査 大規模店舗編" #> $ TITLE :List of 2 #> ..$ @no: chr "009" #> ..$ $ : chr "価格分布 品目・銘柄(307),集計価格数・平均・四分位等における価格・標準偏差,全国・都市階級(10)・都道府県(47)・県内"| __truncated__ #> $ CYCLE : chr "-" #> [list output truncated] library(purrr) str(flatten(j$GET_STATS_LIST$DATALIST_INF$TABLE_INF[[1]]), list.len = 6) #> List of 18 #> $ @id : chr "0000100087" #> $ STAT_NAME.@code : chr "00200572" #> $ STAT_NAME.$ : chr "全国物価統計調査" #> $ GOV_ORG.@code : chr "00200" #> $ GOV_ORG.$ : chr "総務省" #> $ STATISTICS_NAME : chr "平成9年全国物価統計調査 大規模店舗編"
ネストされていた要素がフラットになりました。これでbind_rows()
すればdata.frameにできそうです。
ところが、やってみるとエラーが出ます。。
lapply(j$GET_STATS_LIST$DATALIST_INF$TABLE_INF, flatten) %>% bind_rows #> Error: incompatible type (data index: 12, column: 'SURVEY_DATE', was collecting: integer (dplyr::Collecter_Impl<13>), incompatible with data of type: character
なぜかというと"SURVEY_DATE"というカラムに文字列と数値が入り混じっているからです。
j$GET_STATS_LIST$DATALIST_INF$TABLE_INF[11:13] %>% map("SURVEY_DATE") #> [[1]] #> [1] 200211 #> #> [[2]] #> [1] "201201-201212" #> #> [[3]] #> [1] 0
この問題を避けるために、flatten()
したあとにas.character()
を適用してすべてcharacterになるようにします。
as_flattened_character <- function(x) lapply(flatten(x), as.character) lapply(j$GET_STATS_LIST$DATALIST_INF$TABLE_INF, as_flattened_character) %>% bind_rows #> Source: local data frame [166 x 19] #> #> @id STAT_NAME.@code STAT_NAME.$ GOV_ORG.@code GOV_ORG.$ STATISTICS_NAME TITLE.@no #> (chr) (chr) (chr) (chr) (chr) (chr) (chr) #> 1 0000100087 00200572 全国物価統計調査 00200 総務省 平成9年全国物価統計調査 大規模店舗編 009 #> 2 0000100104 00200572 全国物価統計調査 00200 総務省 平成9年全国物価統計調査 小規模店舗編 009 #> 3 0000100125 00200572 全国物価統計調査 00200 総務省 平成9年全国物価統計調査 消費者物価地域差指数編 007 #> 4 0000100126 00200572 全国物価統計調査 00200 総務省 平成9年全国物価統計調査 消費者物価地域差指数編 008 #> 5 0000100127 00200572 全国物価統計調査 00200 総務省 平成9年全国物価統計調査 消費者物価地域差指数編 009 #> 6 0000100128 00200572 全国物価統計調査 00200 総務省 平成9年全国物価統計調査 消費者物価地域差指数編 010 #> 7 0000100129 00200572 全国物価統計調査 00200 総務省 平成9年全国物価統計調査 消費者物価地域差指数編 011 #> 8 0000100130 00200572 全国物価統計調査 00200 総務省 平成9年全国物価統計調査 消費者物価地域差指数編 012 #> 9 0000100146 00200572 全国物価統計調査 00200 総務省 平成14年全国物価統計調査 大規模店舗編 009 #> 10 0000100162 00200572 全国物価統計調査 00200 総務省 平成14年全国物価統計調査 小規模店舗編 009 #> .. ... ... ... ... ... ... ... #> Variables not shown: TITLE.$ (chr), CYCLE (chr), SURVEY_DATE (chr), OPEN_DATE (chr), SMALL_AREA (chr), MAIN_CATEGORY.@code (chr), #> MAIN_CATEGORY.$ (chr), SUB_CATEGORY.@code (chr), SUB_CATEGORY.$ (chr), OVERALL_TOTAL_NUMBER (chr), UPDATED_DATE (chr), TITLE (chr)
横幅が長いので、見たいカラムだけをサブセットするか、RStudioならView()
で見ると見やすいと思います。
lapply(j$GET_STATS_LIST$DATALIST_INF$TABLE_INF, as_flattened_character) %>% bind_rows %>% View
この表の@id
が統計表のIDです。調べる統計表が決まったら、今度はそれを使ってメタ情報を取得します。
メタ情報取得
先ほどの@id
をstatsDataId
に指定します。
res_meta <- GET( "http://api.e-stat.go.jp/", path = "rest/2.0/app/json/getMetaInfo", add_headers(`Accept-Encoding` = "gzip"), query = list( appId = key, statsDataId = "0003103532" ))
返ってくる結果はこんな感じです。またもや入り組んでてわかりにくいですが、CLASS_INF
というのがほしい部分です。
j_meta <- content(res_meta) str(j_meta, max.level = 4, list.len = 3) #> List of 1 #> $ GET_META_INFO:List of 3 #> ..$ RESULT :List of 3 #> .. ..$ STATUS : int 0 #> .. ..$ ERROR_MSG: chr "正常に終了しました。" #> .. ..$ DATE : chr "2015-10-28T19:56:57.481+09:00" #> ..$ PARAMETER :List of 3 #> .. ..$ LANG : chr "J" #> .. ..$ STATS_DATA_ID: chr "0003103532" #> .. ..$ DATA_FORMAT : chr "J" #> ..$ METADATA_INF:List of 2 #> .. ..$ TABLE_INF:List of 13 #> .. .. ..$ @id : chr "0003103532" #> .. .. ..$ STAT_NAME :List of 2 #> .. .. ..$ GOV_ORG :List of 2 #> .. .. .. [list output truncated] #> .. ..$ CLASS_INF:List of 1 #> .. .. ..$ CLASS_OBJ:List of 5 #> .. .. .. .. [list output truncated]
CLASS_OBJ
の中のCLASS
という項目が各項目のメタデータになっているので、それぞれをdata.frameにします。
ここでちょっと厄介なのは、ネストのレベルが違うものが混じっていることです。ひとつめのCLASS
はリストですが、ふたつめ以降のCLASS
はリストのリストです。
(とか書いてもたぶん伝わらないと思うので、「ふーん」くらいに思ってもらえれば...)
str(j_meta$GET_META_INFO$METADATA_INF$CLASS_INF$CLASS_OBJ[[1]]) #> List of 3 #> $ @id : chr "tab" #> $ @name: chr "表章項目" #> $ CLASS:List of 3 #> ..$ @code : chr "01" #> ..$ @name : chr "金額" #> ..$ @level: chr "" str(j_meta$GET_META_INFO$METADATA_INF$CLASS_INF$CLASS_OBJ[[2]], list.len = 3) #> List of 3 #> $ @id : chr "cat01" #> $ @name: chr "品目分類(27年改定)" #> $ CLASS:List of 703 #> ..$ :List of 4 #> .. ..$ @code : chr "000100000" #> .. ..$ @name : chr "世帯数分布(抽出率調整)" #> .. ..$ @level: chr "1" #> .. .. [list output truncated] #> ..$ :List of 4 #> .. ..$ @code : chr "000200000" #> .. ..$ @name : chr "集計世帯数" #> .. ..$ @level: chr "1" #> .. .. [list output truncated] #> ..$ :List of 4 #> .. ..$ @code : chr "000300000" #> .. ..$ @name : chr "世帯人員" #> .. ..$ @level: chr "1" #> .. .. [list output truncated] #> .. [list output truncated]
ここは、リストのリストならbind_rows()
を、リストならas_dafa_frame()
を適用するような関数を作って何とかします。後で参照しやすいように@id
をリストの要素名にしておきます。
(@name
はそれぞれがどんなメタデータか日本語で書いてあるのでわかりやすいんですが、プログラム中で使いづらいので@id
にしています)
force_bind_rows <- function(x) { if(is.list(x[[1]])) bind_rows(x) else as_data_frame(x) } class_obj <- j_meta$GET_META_INFO$METADATA_INF$CLASS_INF$CLASS_OBJ class_df <- lapply(class_obj, function(x) force_bind_rows(x$CLASS)) names(class_df) <- sapply(class_obj, function(x) x$"@id") class_df #> $tab #> Source: local data frame [1 x 3] #> #> @code @name @level #> (chr) (chr) (chr) #> 1 01 金額 #> #> $cat01 #> Source: local data frame [703 x 5] #> #> @code @name @level @unit @parentCode #> (chr) (chr) (chr) (chr) (chr) #> 1 000100000 世帯数分布(抽出率調整) 1 一万分比 NA #> 2 000200000 集計世帯数 1 世帯 NA #> 3 000300000 世帯人員 1 人 NA #> ..(長いので以下省略).. # @nameはこんな感じ。 sapply(class_obj, function(x) x$"@name") #> [1] "表章項目" "品目分類(27年改定)" "世帯区分" "地域区分" "時間軸(月次)"
目的は品目分類なので、そこから「チョコレート」を探してみましょう。
filter(class_df$cat01, stringr::str_detect(`@name`, "チョコレート")) #> Source: local data frame [2 x 5] #> #> @code @name @level @unit @parentCode #> (chr) (chr) (chr) (chr) (chr) #> 1 010800130 352 チョコレート 5 円 010800000 #> 2 010800140 353 チョコレート菓子 5 円 010800000
2つがヒットしました。これを絞り込み条件に使って、実際のデータを取ってみます。
統計データ取得
分類事項01(cat01
)のコードは、cdCat01
というパラメータに指定します。複数指定するときは,
で区切ります。
res_data <- GET( "http://api.e-stat.go.jp/", path = "rest/2.0/app/json/getStatsData", query = list( appId = key, statsDataId = "0003103532", cdCat01 = "010800130,010800140" ))
返ってくるデータは以下のようになっています。これまたわかりにくいですが、DATA_INF
の中のVALUE
というのが求める結果です。
RESULT_INF
には、トータルの件数と今回取得した件数が書かれています。NEXT_KEY
が存在すれば、その値をstartPosition
に指定すると続きのデータを取得できます。デフォルトだと一度に取得できるレコード数は10万件です。
ちなみに、CLASS_INF
にはさっき取得したメタ情報と同じもの(たぶん)が入っています。metaGetFlg=F
というのを指定すれば省略されるらしいです。
j_data <- content(res_data) str(j_data, max.level = 4, list.len = 4) #> List of 1 #> $ GET_STATS_DATA:List of 3 #> ..$ RESULT :List of 3 #> .. ..$ STATUS : int 0 #> .. ..$ ERROR_MSG: chr "正常に終了しました。" #> .. ..$ DATE : chr "2015-10-28T20:34:19.927+09:00" #> ..$ PARAMETER :List of 6 #> .. ..$ LANG : chr "J" #> .. ..$ STATS_DATA_ID : chr "0003103532" #> .. ..$ NARROWING_COND:List of 1 #> .. .. ..$ CODE_CAT01_SELECT: chr "010800130,010800140" #> .. ..$ DATA_FORMAT : chr "J" #> .. .. [list output truncated] #> ..$ STATISTICAL_DATA:List of 4 #> .. ..$ RESULT_INF:List of 3 #> .. .. ..$ TOTAL_NUMBER: int 12064 #> .. .. ..$ FROM_NUMBER : int 1 #> .. .. ..$ TO_NUMBER : int 12064 #> .. ..$ TABLE_INF :List of 13 #> .. .. ..$ @id : chr "0003103532" #> .. .. ..$ STAT_NAME :List of 2 #> .. .. ..$ GOV_ORG :List of 2 #> .. .. ..$ STATISTICS_NAME : chr "家計調査 家計収支編 二人以上の世帯" #> .. .. .. [list output truncated] #> .. ..$ CLASS_INF :List of 1 #> .. .. ..$ CLASS_OBJ:List of 5 #> .. .. .. .. [list output truncated] #> .. ..$ DATA_INF :List of 2 #> .. .. ..$ NOTE :List of 3 #> .. .. ..$ VALUE:List of 12064 #> .. .. .. .. [list output truncated]
これはすんなりbind_rows()
できます。ただし、データはまだちょっと加工が必要です。
value_df <- j_data$GET_STATS_DATA$STATISTICAL_DATA$DATA_INF$VALUE %>% bind_rows glimpse(value_df) #> Observations: 12,064 #> Variables: 7 #> $ @tab (chr) "01", "01", "01", "01", "01", "01", "01", "01", "01", "01", "01", "01", "01", "01", "01", "01", "01", "01", "01", "01"... #> $ @cat01 (chr) "010800130", "010800130", "010800130", "010800130", "010800130", "010800130", "010800130", "010800130", "010800130", "... #> $ @cat02 (chr) "03", "03", "03", "03", "03", "03", "03", "03", "03", "03", "03", "03", "03", "03", "03", "03", "03", "03", "03", "03"... #> $ @area (chr) "00000", "00000", "00000", "00000", "00000", "00000", "00000", "00000", "00000", "00000", "00000", "00000", "00000", "... #> $ @time (chr) "2015000808", "2015000707", "2015000606", "2015000505", "2015000404", "2015000303", "2015000202", "2015000101", "20140... #> $ @unit (chr) "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円", "円"... #> $ $ (chr) "212", "213", "268", "286", "334", "530", "1324", "559", "548", "425", "417", "274", "201", "208", "227", "305", "341"...
データ加工
まず、なにはともあれ値を数値にしておきましょう。値が入っていないところは"-"
という文字になっているので、NA
になります。ワーニングが出ますがまあ意図通りなので無視しましょう。
value_df <- value_df %>% mutate(value = as.numeric(`$`)) #> Warning message: #> In eval(substitute(expr), envir, enclos) : NAs introduced by coercion
次に、各カテゴリのデータをメタ情報とくっつけます。とりあえず必要そうなやつだけ。
df_time <- class_df$time %>% select(`@time` = `@code`, date = `@name`) df_cat01 <- class_df$cat01 %>% select(`@cat01` = `@code`, item = `@name`) df_area <- class_df$area %>% select(`@area` = `@code`, city = `@name`) value_df %>% left_join(df_time, by = "@time") %>% left_join(df_cat01, by = "@cat01") %>% left_join(df_area, by = "@area") #> Source: local data frame [12,064 x 11] #> #> @tab @cat01 @cat02 @area @time @unit $ value date item city #> (chr) (chr) (chr) (chr) (chr) (chr) (chr) (dbl) (chr) (chr) (chr) #> 1 01 010800130 03 00000 2015000808 円 212 212 2015年8月 352 チョコレート 全国 #> 2 01 010800130 03 00000 2015000707 円 213 213 2015年7月 352 チョコレート 全国 #> 3 01 010800130 03 00000 2015000606 円 268 268 2015年6月 352 チョコレート 全国 #> 4 01 010800130 03 00000 2015000505 円 286 286 2015年5月 352 チョコレート 全国 #> 5 01 010800130 03 00000 2015000404 円 334 334 2015年4月 352 チョコレート 全国 #> 6 01 010800130 03 00000 2015000303 円 530 530 2015年3月 352 チョコレート 全国 #> 7 01 010800130 03 00000 2015000202 円 1324 1324 2015年2月 352 チョコレート 全国 #> 8 01 010800130 03 00000 2015000101 円 559 559 2015年1月 352 チョコレート 全国 #> 9 01 010800130 03 00000 2014001212 円 548 548 2014年12月 352 チョコレート 全国 #> 10 01 010800130 03 00000 2014001111 円 425 425 2014年11月 352 チョコレート 全国 #> .. ... ... ... ... ... ... ... ... ... ... ...
あとは、ちょっと邪魔そうな文字とか削ったりとかですが、そのへんはまた次回。
...次回?
勘のいい方はもうお気づきかと思いますが、これは↓の記事を「それchoroplethrでできますよ」と言うための前準備です。
というのは、こんなつぶやきを見かけて、そ、そんなAriさんの手を煩わせるなんて申し訳ない!という露払い的な。
Ariさんがお気に入りにいれたということは日本全国チョコレートマップがchoroplethrで描かれる日も近い
— 山廃おじさん (@dichika) October 27, 2015
しかし長い前準備だった。。パッケージ化したい。