cucumber flesh

Rを中心としたデータ分析・統計解析らへんの話題をしていくだけ

⭐️RでJSONパーサーのjqを使って快適なAPI暮らしを

jqというC言語で書かれた軽量のJSONパーサー(コンピュータが読みやすい表示になっているものを人間でも見やすくする感じのもの)の一種であるjqの機能をRに導入した{jqr}パッケージを試したのでメモ。

本家のjqでできることはだいたいできる(対応しているバージョンはjq 1.4)と思うけど、jq自体の経験がないのでよくわかっていない部分がある。ただ、APIラッパーのパッケージや関数を書くときに{jqr}がなかなか良さそうだ。

RubicureAPI政府統計 e-Stat API使って試してみる。

なおJSONのような階層構造のあるデータをR上で可視化するのに{xmlview}{jsonview}が便利である。

uribo.hatenablog.com

🔰 どういうときに使うのか

ウェブAPIに対して有効なリクエストを送ると、JSON形式でコンテンツが帰ってくることが多い。HTTPリクエストをR上で実行する{httr}では、次のようによしなに整形した値を表示してくれてそれはそれでありがたいのだけど、listクラスオブジェクトなので、data.frameクラスオブジェクトに対して処理が面倒だったりする。もっと気楽にお手軽に、JSONの値に対して演算を行ったり必要な情報の抽出や加工をしてみたいことがある。そんなときに{jqr}を使うと良い

library(httr)
GET("https://rubicure.herokuapp.com/series.json") %>% 
  content() %>% 
  str()

# List of 13
#  $ :List of 5
#   ..$ series_name : chr "unmarked"
#   ..$ title       : chr "ふたりはプリキュア"
#   ..$ started_date: chr "2004-02-01"
#   ..$ ended_date  : chr "2005-01-30"
#   ..$ girls       :List of 2
#   .. ..$ : chr "cure_black"
#   .. ..$ : chr "cure_white"
# ... 省略

content()関数の引数にtextを指定すると、文字列としてJSONがそのまま帰ってくる。{jqr}を利用する際はas = 'text'としておくのが良いだろう。

cure.json <- GET("https://rubicure.herokuapp.com/series.json") %>% 
  content(as = "text", encoding = "utf-8")

jqrの関数

library(jqr)

jqのもっとも簡単な使い方は圧縮されたJSONを人間が見やすい形に展開することである。それにはjq()関数を使う。jqのコマンドが利用できるのですでにjqについて理解している場合はこれを使うのがもっとも便利そうだ。

jq(cure.json, ".")
# [
#     {
#         "series_name": "unmarked",
#         "title": "ふたりはプリキュア",
#         "started_date": "2004-02-01",
#         "ended_date": "2005-01-30",
#         "girls": [
#             "cure_black",
#             "cure_white"
#         ]
#     },
# ...
# ]

# 出力する要素の位置を指定する
jq(cure.json, ".[2]")
# {
#     "series_name": "splash_star",
#     "title": "ふたりはプリキュア Splash☆Star",
#     "started_date": "2006-02-05",
#     "ended_date": "2007-01-28",
#     "girls": [
#         "cure_bloom",
#         "cure_egret"
#     ]
# }

# jq(cure.json, ".[2:5]") # 2以上5未満の配列

# jq(cure.json, ".[]|.girls") # girlsキーの値を返す
# jq(cure.json, ".[].girls | @csv")
# jq(cure.json, "map({series_name: .series_name})")

jqのコマンドに慣れていなくても、専用の関数が用意されているので、jqを理解するまではこちらを使いたい。

# index()とjqコマンドの.[]は同じ結果を出力する
all.equal(cure.json %>% index(), cure.json %>% jq(".[]"))
## [1] TRUE

index()によってインデックス化したjqrクラスオブジェクト(jq()ではjqsonクラスオブジェクト)に対しては、さまざまな処理を加えられる。また関数select()を使い、任意のキーを抽出したり、キーの名称を変更することもできる。

cure.json.index <- cure.json %>% index() %>% 
  select(series_name   = .series_name,
         title         = .title,
         started_date  = .started_date,
         ended_date    = .ended_date,
         girls         = .girls)

cure.json %>% lengthj()
## 13

キーの名称を取得するのにkeys()を使う

cure.json.index[2] %>% keys()
## [
##     "ended_date",
##     "girls",
##     "series_name",
##     "started_date",
##     "title"
## ]

各キーの値についてJSONがサポートしている以下のデータ型の判定をする。

  • 数値 numbers
  • 文字列 strings
  • ブール型 booleans
  • 配列 arrays
  • オブジェクト objects
  • "null"
# jq(cure.json.index[1], 'map(type)')
cure.json.index[1] %>% types()
## [
##     "string",
##     "string",
##     "string",
##     "string",
##     "array"
## ]

任意のキーを抽出する方法としては、キーの削除をdel()を使って行っても良いし、type()で指定するのも良いだろう(types()と混同しやすい)。

# jq(cure.json, "del(.[].girls)")
cure.json.index[1] %>% del(girls)
## {
##     "series_name": "unmarked",
##     "title": "ふたりはプリキュア",
##     "started_date": "2004-02-01",
##     "ended_date": "2005-01-30"
## }
# jq(cure.json, '.[].girls|arrays')
cure.json.index[1] %>% index() %>% type(arrays)
## [
##     "cure_black",
##     "cure_white"
## ]

キーの値を取り出すにはdotstr()を使うのが良いだろうか。

# jq(cure.json, ".[].series_name|@text")
cure.json %>% index %>% dotstr(series_name)
## [
##     "unmarked",
##     "max_heart",
##     "splash_star",
##     "yes",
##     "yes_gogo",
##     "fresh",
##     "heart_catch",
##     "suite",
##     "smile",
##     "dokidoki",
##     "happiness_charge",
##     "go_princess",
##     "maho_girls"
## ]
cure.json %>% index() %>% dotstr(title)
## [
##     "ふたりはプリキュア",
##     "ふたりはプリキュア Max Heart",
##     "ふたりはプリキュア Splash☆Star",
##     "Yes! プリキュア5",
##     "Yes! プリキュア5 Go Go!",
##     "フレッシュプリキュア!",
##     "ハートキャッチプリキュア!",
##     "スイートプリキュア♪",
##     "スマイルプリキュア!",
##     "ドキドキ!プリキュア",
##     "ハピネスチャージプリキュア!",
##     "Go!プリンセスプリキュア",
##     "魔法つかいプリキュア!"
## ]
cure.json.index[1] %>% dotstr(title)
## "ふたりはプリキュア"

並び替え

cure.json %>% sortj(series_name)
# [
#   {
#     "series_name": "dokidoki",
#     "title": "ドキドキ!プリキュア",
#     "started_date": "2013-02-03",
#     "ended_date": "2014-01-26",
#     "girls": [
#       "cure_heart",
#       "cure_diamond",
#       "cure_rosetta",
#       "cure_sword",
#       "cure_ace"
#       ]
#   },
# ... 省略
#     {
#         "series_name": "yes_gogo",
#         "title": "Yes! プリキュア5 Go Go!",
#         "started_date": "2008-02-03",
#         "ended_date": "2009-01-25",
#         "girls": [
#             "cure_dream",
#             "cure_rouge",
#             "cure_lemonade",
#             "cure_mint",
#             "cure_aqua",
#             "milky_rose"
#         ]
#     }
# ]

今回の例ではあまり利用する機会がないが、jqの演算用関数も用意されている。

cure.json %>% minj(started_date)
## {
##     "series_name": "unmarked",
##     "title": "ふたりはプリキュア",
##     "started_date": "2004-02-01",
##     "ended_date": "2005-01-30",
##     "girls": [
##         "cure_black",
##         "cure_white"
##     ]
## }
cure.json %>% maxj(started_date)
## {
##     "series_name": "maho_girls",
##     "title": "魔法つかいプリキュア!",
##     "started_date": "2016-02-07",
##     "girls": [
##         "cure_miracle",
##         "cure_magical"
##     ]
## }
cure.json %>% index() %>% 
  do(.started_date >= '2010-02-07')
## [
##     false,
##     false,
##     false,
##     false,
##     false,
##     false,
##     true,
##     true,
##     true,
##     true,
##     true,
##     true,
##     true
## ]

jqでは、アウトプットの形式をいろいろ選べて良いが、{jqr}もその機能をサポートしている。なおcsvにするには対象が配列でないといけないので注意が必要。

# jq(cure.json, ".[].girls|@csv")
cure.json %>% index() %>% select(title = .series_name) %>% at(text)
## [
##     "{\"title\":\"unmarked\"}",
##     "{\"title\":\"max_heart\"}",
##     "{\"title\":\"splash_star\"}",
##     "{\"title\":\"yes\"}",
##     "{\"title\":\"yes_gogo\"}",
##     "{\"title\":\"fresh\"}",
##     "{\"title\":\"heart_catch\"}",
##     "{\"title\":\"suite\"}",
##     "{\"title\":\"smile\"}",
##     "{\"title\":\"dokidoki\"}",
##     "{\"title\":\"happiness_charge\"}",
##     "{\"title\":\"go_princess\"}",
##     "{\"title\":\"maho_girls\"}"
## ]

jsonlite::fromJSON()と同じなのだけど、データフレームに変換するには以下のようにする。

df.cure <- cure.json %>% index() %>% string() %>% jsonlite::fromJSON(txt = .)
all.equal(df.cure, jsonlite::fromJSON(cure.json))
## [1] TRUE
df.cure %>% tail() %>% knitr::kable(format = "markdown")
series_name title started_date ended_date girls
8 suite スイートプリキュア♪ 2011-02-06 2012-01-29 cure_melody, cure_rhythm, cure_beat, cure_muse
9 smile スマイルプリキュア! 2012-02-05 2013-01-27 cure_happy, cure_sunny, cure_peace, cure_march, cure_beauty
10 dokidoki ドキドキ!プリキュア 2013-02-03 2014-01-26 cure_heart, cure_diamond, cure_rosetta, cure_sword, cure_ace
11 happiness_charge ハピネスチャージプリキュア! 2014-02-02 2015-01-25 cure_lovely, cure_princess, cure_honey, cure_fortune
12 go_princess Go!プリンセスプリキュア 2015-02-01 2016-01-31 cure_flora, cure_mermaid, cure_twinkle, cure_scarlet
13 maho_girls 魔法つかいプリキュア 2016-02-07 NA cure_miracle, cure_magical

🔗 参考

💻 実行環境

devtools::session_info() %$% packages %>% 
  dplyr::filter(`*`  == "*") %>% 
  dplyr::select(package, version, source) %>% 
  knitr::kable(format = "markdown")
package version source
httr 1.1.0.9000 Github (<hadley/httr@7261a52>)
jqr 0.2.0 CRAN (R 3.2.4)
magrittr 1.5 Github (<smbache/magrittr@00a1fe3>)
remoji 0.1.0 Github (<richfitz/remoji@dc00779>)