JSONとNDJSONをRで扱う
はじめに
ここではJSONとNDJSONをRで扱う方法をまとめておく。
JSONとは
そもそもJSONとはなにか。JSON(JavaScript Object Notation)は、軽量のデータ交換フォーマットで、人間にも機械にも読み書きが容易な形式のデータのことらしい。
基本的には下記のようなKey-Value形式でデータを保持する。business_id
はvcNAWiLM4dR7D2nwwJ7nCA
で、hours
には、Tuesday
からThursday
まであって、おのおのOPENからCLOSEまでの時間がネストされて保持される。
[ { "business_id":"vcNAWiLM4dR7D2nwwJ7nCA", "full_address":"4840 E Indian School Rd\nSte 101\nPhoenix, AZ 85018", "hours":{ "Tuesday":{ "close":"17:00", "open":"08:00" }, "Friday":{ "close":"17:00", "open":"08:00" }, "Monday":{ "close":"17:00", "open":"08:00" }, "Wednesday":{ "close":"17:00", "open":"08:00" }, "Thursday":{ "close":"17:00", "open":"08:00" } }, "open":true, "categories":[ "Doctors", "Health & Medical" ], "city":"Phoenix", "review_count":9, "name":"Eric Goldberg, MD", "neighborhoods":[ ], "longitude":-111.98375799999999, "state":"AZ", "stars":3.5, "latitude":33.499313000000001, "attributes":{ "By Appointment Only":true }, "type":"business" } ]
JSON形式を読み込むには、jsonlite::fromJSON()
かjsonlite::read_json()
を使うことになる。デフォルトの設定であれば、jsonlite::fromJSON()
はデータフレームで、
library(jsonlite) library(tidyverse) jsonlite::fromJSON("json_fromat.json") business_id full_address hours.Tuesday.close hours.Tuesday.open hours.Friday.close 1 vcNAWiLM4dR7D2nwwJ7nCA 4840 E Indian School Rd\nSte 101\nPhoenix, AZ 85018 17:00 08:00 17:00 2 UsFtqoBl7naz8AVUBZMjQQ 202 McClure St\nDravosburg, PA 15034 <NA> <NA> <NA> hours.Friday.open hours.Monday.close hours.Monday.open hours.Wednesday.close hours.Wednesday.open hours.Thursday.close 1 08:00 17:00 08:00 17:00 08:00 17:00 2 <NA> <NA> <NA> <NA> <NA> <NA> hours.Thursday.open open categories city review_count name neighborhoods longitude state stars 1 08:00 TRUE Doctors, Health & Medical Phoenix 9 Eric Goldberg, MD NULL -111.98376 AZ 3.5 2 <NA> TRUE Nightlife Dravosburg 4 Clancy's Pub NULL -79.88693 PA 3.5 latitude attributes.By Appointment Only attributes.Happy Hour attributes.Accepts Credit Cards attributes.Good For Groups 1 33.49931 TRUE NA NA NA 2 40.35052 NA TRUE TRUE TRUE attributes.Outdoor Seating attributes.Price Range type 1 NA NA business 2 FALSE 1 business
jsonlite::read_json()
はリストで読み込む。simplifyVector = TRUE
とすればデータフレームで返る。
jsonlite::read_json("json_fromat.json") [[1]] [[1]]$business_id [1] "vcNAWiLM4dR7D2nwwJ7nCA" [[1]]$full_address [1] "4840 E Indian School Rd\nSte 101\nPhoenix, AZ 85018" [[1]]$hours [[1]]$hours$Tuesday [[1]]$hours$Tuesday$close [1] "17:00" 【略】 [[2]]$attributes [[2]]$attributes$`Happy Hour` [1] TRUE [[2]]$attributes$`Accepts Credit Cards` [1] TRUE [[2]]$attributes$`Good For Groups` [1] TRUE [[2]]$attributes$`Outdoor Seating` [1] FALSE [[2]]$attributes$`Price Range` [1] 1 [[2]]$type [1] "business"
NDJSON
NDJSON(Newline Delimited JSON)は、下記のような形式でJSONデータを保持している。改行区切りJSONとも呼ばれる。このような形式にする理由として、NDJSONは、1度に1レコードを処理できる構造化データを保存したり、ストリーミングするための便利な形式なんだとか。
{"business_id": "vcNAWiLM4dR7D2nwwJ7nCA", "full_address": "4840 E Indian School Rd\nSte 101\nPhoenix, AZ 85018", "hours": {"Tuesday": {"close": "17:00", "open": "08:00"}, "Friday": {"close": "17:00", "open": "08:00"}, "Monday": {"close": "17:00", "open": "08:00"}, "Wednesday": {"close": "17:00", "open": "08:00"}, "Thursday": {"close": "17:00", "open": "08:00"}}, "open": true, "categories": ["Doctors", "Health & Medical"], "city": "Phoenix", "review_count": 9, "name": "Eric Goldberg, MD", "neighborhoods": [], "longitude": -111.98375799999999, "state": "AZ", "stars": 3.5, "latitude": 33.499313000000001, "attributes": {"By Appointment Only": true}, "type": "business"}
なので、1行目を表示させると、こんな違いがある。NDJSONは、1行で1つのレコードになる。必ずしもこうではないJSONファイルはやまほどある…泣
readLines("/Users/aki/Desktop/json_fromat.json" , n = 1, warn = FALSE) [1] "[" readLines("/Users/aki/Desktop/ndjson_fromat.json", n = 1, warn = FALSE) [1] "{\"business_id\": \"vcNAWiLM4dR7D2nwwJ7nCA\", \"full_address\": \"4840 E Indian School Rd\\nSte 101\\nPhoenix, AZ 85018\", \"hours\": {\"Tuesday\": {\"close\": \"17:00\", \"open\": \"08:00\"}, \"Friday\": {\"close\": \"17:00\", \"open\": \"08:00\"}, \"Monday\": {\"close\": \"17:00\", \"open\": \"08:00\"}, \"Wednesday\": {\"close\": \"17:00\", \"open\": \"08:00\"}, \"Thursday\": {\"close\": \"17:00\", \"open\": \"08:00\"}}, \"open\": true, \"categories\": [\"Doctors\", \"Health & Medical\"], \"city\": \"Phoenix\", \"review_count\": 9, \"name\": \"Eric Goldberg, MD\", \"neighborhoods\": [], \"longitude\": -111.98375799999999, \"state\": \"AZ\", \"stars\": 3.5, \"latitude\": 33.499313000000001, \"attributes\": {\"By Appointment Only\": true}, \"type\": \"business\"}"
なので、ここらへんを利用しつつNDJSONかどうかを判定するヘルパー関数を雑に作るとこんな感じ。
is_ndjson <- function(path) { if(str_detect(string = path, pattern = ".json$") == FALSE){ stop("must be json format") } valid <- readLines(con = path, n = 1, warn = FALSE) res <- jsonlite::validate(valid) if (res == TRUE) { res } else { res[[1]] } } is_ndjson(path = "/Users/aki/Desktop/iris.csv") is_ndjson(path = "/Users/aki/Desktop/iris.csv") でエラー: must be json format is_ndjson(path = "/Users/aki/Desktop/json_fromat.json") [1] FALSE is_ndjson(path = "/Users/aki/Desktop/ndjson_fromat.json") [1] TRUE
NDJSONの読み込みは、stream_in()
を使用する。file()
でコネクションを作る必要があります。
jsonlite::stream_in(file("/Users/aki/Desktop/ndjson_fromat.json")) opening file input connection. Imported 2 records. Simplifying... closing file input connection. business_id full_address hours.Tuesday.close hours.Tuesday.open hours.Friday.close 1 vcNAWiLM4dR7D2nwwJ7nCA 4840 E Indian School Rd\nSte 101\nPhoenix, AZ 85018 17:00 08:00 17:00 2 UsFtqoBl7naz8AVUBZMjQQ 202 McClure St\nDravosburg, PA 15034 <NA> <NA> <NA> hours.Friday.open hours.Monday.close hours.Monday.open hours.Wednesday.close hours.Wednesday.open hours.Thursday.close 1 08:00 17:00 08:00 17:00 08:00 17:00 2 <NA> <NA> <NA> <NA> <NA> <NA> hours.Thursday.open open categories city review_count name neighborhoods longitude state stars 1 08:00 TRUE Doctors, Health & Medical Phoenix 9 Eric Goldberg, MD NULL -111.98376 AZ 3.5 2 <NA> TRUE Nightlife Dravosburg 4 Clancy's Pub NULL -79.88693 PA 3.5 latitude attributes.By Appointment Only attributes.Happy Hour attributes.Accepts Credit Cards attributes.Good For Groups 1 33.49931 TRUE NA NA NA 2 40.35052 NA TRUE TRUE TRUE attributes.Outdoor Seating attributes.Price Range type 1 NA NA business 2 FALSE 1 business
たとえば、'Yelp Academic Dataset Business'データは、Yelpに登録されているお店の情報がNDJSON形式で保存されている。ここからダウンロードできる。
is_ndjson(path = "yelp_academic_dataset_business.json") [1] TRUE df <- jsonlite::stream_in(file("/Users/aki/Desktop/yelp_academic_dataset_business.json")) opening file input connection. Imported 61184 records. Simplifying... closing file input connection. 警告メッセージ: parse_string(txt, bigint_as_char) で: 使われていないコネクション 4 (/Users/aki/Desktop/ndjson_fromat.json) を閉じます df %>% as_tibble() # A tibble: 61,184 x 15 business_id full_address hours$Tuesday$c… $$open $Friday$close $$open $Monday$close $$open $Wednesday$close $$open $Thursday$close $$open $Sunday$close $$open $Saturday$close $$open open categories city <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <lgl> <list> <chr> 1 vcNAWiLM4d… "4840 E Ind… 17:00 08:00 17:00 08:00 17:00 08:00 17:00 08:00 17:00 08:00 NA NA NA NA TRUE <chr [2]> Phoe… 2 UsFtqoBl7n… "202 McClur… NA NA NA NA NA NA NA NA NA NA NA NA NA NA TRUE <chr [1]> Drav… 3 cE27W9VPgO… "1530 Hamil… NA NA NA NA NA NA NA NA NA NA NA NA NA NA FALSE <chr [3]> Beth… 4 HZdLhv6COC… "301 S Hill… 21:00 10:00 21:00 10:00 21:00 10:00 21:00 10:00 21:00 10:00 18:00 11:00 21:00 10:00 TRUE <chr [6]> Pitt… 5 mVHrayjG3u… "414 Hawkin… 19:00 10:00 20:00 10:00 NA NA 19:00 10:00 19:00 10:00 NA NA 16:00 10:00 TRUE <chr [5]> Brad… 6 KayYbHCt-R… "141 Hawtho… NA NA NA NA NA NA NA NA NA NA NA NA NA NA TRUE <chr [4]> Carn… 7 b12U9TFESS… "718 Hope H… NA NA NA NA NA NA NA NA NA NA NA NA NA NA TRUE <chr [2]> Carn… 8 Sktj1eHQFu… "920 Forsyt… NA NA NA NA NA NA NA NA NA NA NA NA NA NA TRUE <chr [2]> Carn… 9 3ZVKmuK2l7… "8 Logan St… NA NA NA NA NA NA NA NA NA NA NA NA NA NA TRUE <chr [2]> Carn… 10 wJr6kSA5dc… "2100 Washi… 02:00 08:00 02:00 08:00 02:00 08:00 02:00 08:00 02:00 08:00 02:00 08:00 02:00 08:00 TRUE <chr [4]> Carn… # … with 61,174 more rows, and 86 more variables: review_count <int>, name <chr>, neighborhoods <list>, longitude <dbl>, state <chr>, stars <dbl>, latitude <dbl>, attributes$`By Appointment Only` <lgl>, $`Happy # Hour` <lgl>, $`Accepts Credit Cards` <list>, $`Good For Groups` <lgl>, $`Outdoor Seating` <lgl>, $`Price Range` <int>, $`Good for Kids` <lgl>, $Alcohol <chr>, $`Noise Level` <chr>, $`Has TV` <lgl>, # $Attire <chr>, $Ambience$romantic <lgl>, $$intimate <lgl>, $$classy <lgl>, $$hipster <lgl>, $$divey <lgl>, $$touristy <lgl>, $$trendy <lgl>, $$upscale <lgl>, $$casual <lgl>, $`Good For Dancing` <lgl>, # $Delivery <lgl>, $`Coat Check` <lgl>, $Smoking <chr>, $`Take-out` <lgl>, $`Takes Reservations` <lgl>, $`Waiter Service` <lgl>, $`Wi-Fi` <chr>, $Caters <lgl>, $`Good For`$dessert <lgl>, $$latenight <lgl>, # $$lunch <lgl>, $$dinner <lgl>, $$breakfast <lgl>, $$brunch <lgl>, $Parking$garage <lgl>, $$street <lgl>, $$validated <lgl>, $$lot <lgl>, $$valet <lgl>, $Music$dj <lgl>, $$background_music <lgl>, # $$karaoke <lgl>, $$live <lgl>, $$video <lgl>, $$jukebox <lgl>, $$playlist <lgl>, $`Drive-Thru` <lgl>, $`Wheelchair Accessible` <lgl>, $BYOB <lgl>, $Corkage <lgl>, $`BYOB/Corkage` <chr>, $`Order at # Counter` <lgl>, $`Good For Kids` <lgl>, $`Dogs Allowed` <lgl>, $`Open 24 Hours` <lgl>, $`Hair Types Specialized In`$coloring <lgl>, $$africanamerican <lgl>, $$curly <lgl>, $$perms <lgl>, $$kids <lgl>, # $$extensions <lgl>, $$asian <lgl>, $$straightperms <lgl>, $`Accepts Insurance` <lgl>, $`Ages Allowed` <chr>, $`Payment Types`$amex <lgl>, $$cash_only <lgl>, $$mastercard <lgl>, $$visa <lgl>, $$discover <lgl>, # $`Dietary Restrictions`$`dairy-free` <lgl>, $$`gluten-free` <lgl>, $$vegan <lgl>, $$kosher <lgl>, $$halal <lgl>, $$`soy-free` <lgl>, $$vegetarian <lgl>, type <chr>
階層構造の整理
JSONを読み込むとき、おおよそはネストしていることが多い。例えば、先程読み込んだデータは一見、データフレームに見える。
df %>% dplyr::select(1:3) %>% dplyr::slice(1:5) business_id full_address hours.Tuesday.close hours.Tuesday.open hours.Friday.close hours.Friday.open 1 vcNAWiLM4dR7D2nwwJ7nCA 4840 E Indian School Rd\nSte 101\nPhoenix, AZ 85018 17:00 08:00 17:00 08:00 2 UsFtqoBl7naz8AVUBZMjQQ 202 McClure St\nDravosburg, PA 15034 <NA> <NA> <NA> <NA> 3 cE27W9VPgO88Qxe4ol6y_g 1530 Hamilton Rd\nBethel Park, PA 15234 <NA> <NA> <NA> <NA> 4 HZdLhv6COCleJMo7nPl-RA 301 S Hills Vlg\nPittsburgh, PA 15241 21:00 10:00 21:00 10:00 5 mVHrayjG3uZ_RLHkLj-AMg 414 Hawkins Ave\nBraddock, PA 15104 19:00 10:00 20:00 10:00 hours.Monday.close hours.Monday.open hours.Wednesday.close hours.Wednesday.open hours.Thursday.close hours.Thursday.open hours.Sunday.close hours.Sunday.open 1 17:00 08:00 17:00 08:00 17:00 08:00 <NA> <NA> 2 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 3 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 4 21:00 10:00 21:00 10:00 21:00 10:00 18:00 11:00 5 <NA> <NA> 19:00 10:00 19:00 10:00 <NA> <NA> hours.Saturday.close hours.Saturday.open 1 <NA> <NA> 2 <NA> <NA> 3 <NA> <NA> 4 21:00 10:00 5 16:00 10:00
これに対して、そのまま変数名を引っ張ろうとするとエラーになる。
df %>% dplyr::select(hours.Tuesday.close) .f(.x[[i]], ...) でエラー: オブジェクト 'hours.Tuesday.close' がありません
理由は、str()
で構造を見てみると、hours >> Tuesday >> open/close
となっておりJSONの階層構造がそのまま反映されているため。
df %>% dplyr::select(1:3) %>% dplyr::slice(1:5) %>% str() 'data.frame': 5 obs. of 3 variables: $ business_id : chr "vcNAWiLM4dR7D2nwwJ7nCA" "UsFtqoBl7naz8AVUBZMjQQ" "cE27W9VPgO88Qxe4ol6y_g" "HZdLhv6COCleJMo7nPl-RA" ... $ full_address: chr "4840 E Indian School Rd\nSte 101\nPhoenix, AZ 85018" "202 McClure St\nDravosburg, PA 15034" "1530 Hamilton Rd\nBethel Park, PA 15234" "301 S Hills Vlg\nPittsburgh, PA 15241" ... $ hours :'data.frame': 5 obs. of 7 variables: ..$ Tuesday :'data.frame': 5 obs. of 2 variables: .. ..$ close: chr "17:00" NA NA "21:00" ... .. ..$ open : chr "08:00" NA NA "10:00" ... ..$ Friday :'data.frame': 5 obs. of 2 variables: .. ..$ close: chr "17:00" NA NA "21:00" ... .. ..$ open : chr "08:00" NA NA "10:00" ... ..$ Monday :'data.frame': 5 obs. of 2 variables: .. ..$ close: chr "17:00" NA NA "21:00" ... .. ..$ open : chr "08:00" NA NA "10:00" ... ..$ Wednesday:'data.frame': 5 obs. of 2 variables: .. ..$ close: chr "17:00" NA NA "21:00" ... .. ..$ open : chr "08:00" NA NA "10:00" ... ..$ Thursday :'data.frame': 5 obs. of 2 variables: .. ..$ close: chr "17:00" NA NA "21:00" ... .. ..$ open : chr "08:00" NA NA "10:00" ... ..$ Sunday :'data.frame': 5 obs. of 2 variables: .. ..$ close: chr NA NA NA "18:00" ... .. ..$ open : chr NA NA NA "11:00" ... ..$ Saturday :'data.frame': 5 obs. of 2 variables: .. ..$ close: chr NA NA NA "21:00" ... .. ..$ open : chr NA NA NA "10:00" ...
値を取り出すために、色々な面倒がある。
df$hours$Tuesday$close[1:5] [1] "17:00" NA NA "21:00" "19:00"
なので、jsonlite::flatten()
で階層構造をフラットにする。見た目は変わらないが、階層構造はなくなっている。すごく便利な関数。
df %>% dplyr::select(1:3) %>% dplyr::slice(1:5) %>% jsonlite::flatten() %>% str() 'data.frame': 5 obs. of 16 variables: $ business_id : chr "vcNAWiLM4dR7D2nwwJ7nCA" "UsFtqoBl7naz8AVUBZMjQQ" "cE27W9VPgO88Qxe4ol6y_g" "HZdLhv6COCleJMo7nPl-RA" ... $ full_address : chr "4840 E Indian School Rd\nSte 101\nPhoenix, AZ 85018" "202 McClure St\nDravosburg, PA 15034" "1530 Hamilton Rd\nBethel Park, PA 15234" "301 S Hills Vlg\nPittsburgh, PA 15241" ... $ hours.Tuesday.close : chr "17:00" NA NA "21:00" ... $ hours.Tuesday.open : chr "08:00" NA NA "10:00" ... $ hours.Friday.close : chr "17:00" NA NA "21:00" ... $ hours.Friday.open : chr "08:00" NA NA "10:00" ... $ hours.Monday.close : chr "17:00" NA NA "21:00" ... $ hours.Monday.open : chr "08:00" NA NA "10:00" ... $ hours.Wednesday.close: chr "17:00" NA NA "21:00" ... $ hours.Wednesday.open : chr "08:00" NA NA "10:00" ... $ hours.Thursday.close : chr "17:00" NA NA "21:00" ... $ hours.Thursday.open : chr "08:00" NA NA "10:00" ... $ hours.Sunday.close : chr NA NA NA "18:00" ... $ hours.Sunday.open : chr NA NA NA "11:00" ... $ hours.Saturday.close : chr NA NA NA "21:00" ... $ hours.Saturday.open : chr NA NA NA "10:00" ... df %>% dplyr::select(1:3) %>% dplyr::slice(1:5) %>% jsonlite::flatten() business_id full_address hours.Tuesday.close hours.Tuesday.open hours.Friday.close hours.Friday.open 1 vcNAWiLM4dR7D2nwwJ7nCA 4840 E Indian School Rd\nSte 101\nPhoenix, AZ 85018 17:00 08:00 17:00 08:00 2 UsFtqoBl7naz8AVUBZMjQQ 202 McClure St\nDravosburg, PA 15034 <NA> <NA> <NA> <NA> 3 cE27W9VPgO88Qxe4ol6y_g 1530 Hamilton Rd\nBethel Park, PA 15234 <NA> <NA> <NA> <NA> 4 HZdLhv6COCleJMo7nPl-RA 301 S Hills Vlg\nPittsburgh, PA 15241 21:00 10:00 21:00 10:00 5 mVHrayjG3uZ_RLHkLj-AMg 414 Hawkins Ave\nBraddock, PA 15104 19:00 10:00 20:00 10:00 hours.Monday.close hours.Monday.open hours.Wednesday.close hours.Wednesday.open hours.Thursday.close hours.Thursday.open hours.Sunday.close hours.Sunday.open 1 17:00 08:00 17:00 08:00 17:00 08:00 <NA> <NA> 2 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 3 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 4 21:00 10:00 21:00 10:00 21:00 10:00 18:00 11:00 5 <NA> <NA> 19:00 10:00 19:00 10:00 <NA> <NA> hours.Saturday.close hours.Saturday.open 1 <NA> <NA> 2 <NA> <NA> 3 <NA> <NA> 4 21:00 10:00 5 16:00 10:00
階層構造をフラットにすれば、そのまま変数名を引っ張れる。
df %>% jsonlite::flatten() %>% dplyr::select(hours.Tuesday.close) %>% dplyr::slice(1:5) hours.Tuesday.close 1 17:00 2 <NA> 3 <NA> 4 21:00 5 19:00
データフレームとティブル
JOSNを扱う上でもう1つ面倒なのでが、ネストしている値の見え方。データフレームとティブルでは色々違うので、基本はティブルを推奨。まずはデータフレームで見て見ると、categories
はカンマ区切りになっているようみ見える。
head(df["categories"], 5) categories 1 Doctors, Health & Medical 2 Nightlife 3 Active Life, Mini Golf, Golf 4 Shopping, Home Services, Internet Service Providers, Mobile Phones, Professional Services, Electronics 5 Bars, American (New), Nightlife, Lounges, Restaurants
なので、これを分割してみると、なんかc()
で結合されているっぽい。
head(df["categories"], 5) %>% tidyr::separate(categories, into = c("col1", "col2", "col3", "col4", "col5", "col6"), sep = ",") col1 col2 col3 col4 col5 col6 1 c("Doctors" "Health & Medical") <NA> <NA> <NA> <NA> 2 Nightlife <NA> <NA> <NA> <NA> <NA> 3 c("Active Life" "Mini Golf" "Golf") <NA> <NA> <NA> 4 c("Shopping" "Home Services" "Internet Service Providers" "Mobile Phones" "Professional Services" "Electronics") 5 c("Bars" "American (New)" "Nightlife" "Lounges" "Restaurants") <NA> 警告メッセージ: Expected 6 pieces. Missing pieces filled with `NA` in 4 rows [1, 2, 3, 5].
その理由は、これはもともとリストなので、データフレームが,
でつないで、表示しているだけ…なんと。as_tibble()
すると、リストのまま表示してくれるので、色々とトラブルは回避できそう。
df_flat <- df %>% jsonlite::flatten() %>% as_tibble() df_flat %>% select(categories) %>% head(5) # A tibble: 5 x 1 categories <list> 1 <chr [2]> 2 <chr [1]> 3 <chr [3]> 4 <chr [6]> 5 <chr [5]>
データフレームのリストであれば、mutate(hoge = map())
で色々できるので、あとはよしなにどうぞ。拙文ではあるが{purrr}で覗くStarWarsの世界とか参考になるかもしれません。
df_flat %>% head(5) %>% dplyr::select(business_id, categories) %>% dplyr::mutate(length = purrr::map_int(.x = categories, .f = function(x){length(x)}), flg_Bars = purrr::map_lgl(.x = categories, .f = function(x){ any(str_detect(string = x, pattern = "Bars")) }) )%>% arrange(business_id) # A tibble: 5 x 4 business_id categories length flg_Bars <chr> <list> <int> <lgl> 1 cE27W9VPgO88Qxe4ol6y_g <chr [3]> 3 FALSE 2 HZdLhv6COCleJMo7nPl-RA <chr [6]> 6 FALSE 3 mVHrayjG3uZ_RLHkLj-AMg <chr [5]> 5 TRUE 4 UsFtqoBl7naz8AVUBZMjQQ <chr [1]> 1 FALSE 5 vcNAWiLM4dR7D2nwwJ7nCA <chr [2]> 2 FALSE
business_id
がmVHrayjG3uZ_RLHkLj-AMg
にはBars
が含まれているようなので、バラして確認する…separate()
のinto
のところが嫌な感じ。なので、本番で仮にいるならコンマの最大数とかで渡す工夫するか。確かにあるっぽいので、問題ない。
df_flat %>% head(5) %>% select(business_id, categories) %>% unnest(cols = c(categories)) %>% group_by(business_id) %>% summarise(categories_list = paste0(categories, collapse = ",")) %>% tidyr::separate(categories_list, into = paste0("cate", 1:6), sep = ",") %>% arrange(business_id) A tibble: 5 x 7 business_id cate1 cate2 cate3 cate4 cate5 cate6 <chr> <chr> <chr> <chr> <chr> <chr> <chr> 1 cE27W9VPgO88Qxe4ol6y_g Active Life Mini Golf Golf NA NA NA 2 HZdLhv6COCleJMo7nPl-RA Shopping Home Services Internet Service Providers Mobile Phones Professional Services Electronics 3 mVHrayjG3uZ_RLHkLj-AMg Bars American (New) Nightlife Lounges Restaurants NA 4 UsFtqoBl7naz8AVUBZMjQQ Nightlife NA NA NA NA NA 5 vcNAWiLM4dR7D2nwwJ7nCA Doctors Health & Medical NA NA NA NA 警告メッセージ:
参照サイト
{tabulizer}でThe R session had a fatal errorを繰り返した話。
20200827追記:pdftoolsパッケージのメモ
はじめに
{tabulizer}
でThe R session had a fatal errorを繰り返した話。
library(tabulizer) ↓ The R session had a fatal erro...
{tabulizer}
{tabulizer}
というPDFを読み込みパッケージがある。PDFなんかはデータソースとしては避けたいが、そうは行かない。データが上手く転記出来ないし、やりたくないし、手作業とか件数が多いと、もう最悪であるが、この{tabulizer}
が助けてくれる。
しかし、{tabulizer}
でThe R session had a fatal errorを繰り返した。つまり、パッケージを読み込むと、落ちる。そこで、下記のページの通り、色々やってみたが上手くいかなかった。
Githubを見るとわかるが、javaが関係している。{h2o}
のときも同様であるが、個人的にjavaが絡んでコケると、バージョンが関係していることが多い。ということで、javaのバージョンを調べたら、java11にパスが通っていたので、java8に切り替えたら上手く行った。ということで、rm -Rf
コマンドでjava11を削除した。削除後のjavaのバージョンは下記の通り。
$ java -version openjdk version "1.8.0_212" OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_212-b03) OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.212-b03, mixed mode)
使い方
詳しくは下記のサイトも参考にしてください。
- 「2016年版このRパッケージがすごい」暫定第一位、tabulizerパッケージを使って、日本で話題のCookpadの有価証券PDFから超簡単にデータを取得してビジュアライズまでしてみた
- ropensci/tabulizer
- tabulizer tutorial
PDFは帝国データバンクのPDF。
- Source | 飲食店の倒産動向調査(2019年)
library(tabulizer) path <- "path to pdf" # pdfの座標を調べる # locate_areas(file = path, widget = "shiny") # [[1]] # top left bottom right # 224.6174 71.5583 423.8356 547.7301 # extract_tables()でも上手くテーブルがひけるなら… # data <- extract_tables(file = path, output = "data.frame") data <- extract_tables(file = path, area = list(c(224, 71, 423, 547)), output = "data.frame") # クラスがリストで返ってくるので、リストのデータフレームが格納されているとこをとる。 data[[1]] 業態.年 X2000 X2001 X2002 X2003 X2004 X2005 X2006 X2007 X2008 X2009 X2010 X2011 X2012 X2013 X2014 X2015 X2016 X2017 X2018 X2019 年換算 1 した場合 NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA 2 酒場・ビヤホール 18 21 32 63 60 58 71 113 97 113 126 125 141 121 126 122 97 133 129 143 156 3 西洋料理店 18 18 23 33 32 44 37 42 68 53 71 58 66 66 61 60 61 86 92 110 120 4 中華・東洋料理店 17 30 33 51 69 40 49 77 83 73 88 97 102 99 84 91 92 104 97 96 104 5 喫茶店 14 14 16 35 28 31 38 57 56 54 52 66 48 35 58 43 50 66 66 60 65 6 一般食堂 19 24 36 34 34 32 51 64 71 88 51 90 85 67 79 50 61 78 45 59 64 7 その他の一般飲食店 14 15 18 19 30 36 41 42 43 56 45 44 53 59 56 52 44 46 48 57 62 8 バー・キャバレーなど 6 17 24 36 48 37 56 70 99 80 75 70 55 52 51 49 45 72 62 57 62 9 日本料理店 12 14 24 43 53 25 34 55 52 72 67 63 72 69 63 51 52 74 65 46 50 10 すし店 17 16 26 35 36 31 33 37 34 30 22 38 30 37 25 25 26 24 25 18 19 11 そば・うどん店 3 7 10 9 13 7 22 24 23 17 14 24 24 17 18 21 22 16 18 15 16 12 料亭 9 7 12 11 13 12 11 12 8 10 12 13 9 9 8 9 7 8 6 7 7 13 合計 147 183 254 369 416 353 443 593 634 646 623 688 685 631 629 573 557 707 653 668 728
以上です。
R version 3.6.1 tabulizer version 0.2.2 $ java -version openjdk version "1.8.0_212" OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_212-b03) OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.212-b03, mixed mode)
pdftoolsパッケージのメモ
こんなPDFのデータを読み込みたい。
pdftoolsパッケージでゴリゴリやれば行けなくもない。
library(pdftools) col_nm <- c("商品名","重量","エネルギー","たんぱく質","脂質","炭水化物","ナトリウム","カリウム", "カルシウム","リン","鉄","ビタミンA","ビタミンB1","ビタミンB2","ナイアシン", "ビタミンC","ビタミンD","ビタミンE","コレステロール","食物繊維","食塩相当量") pdftools::pdf_text(pdf = "~/Desktop/nutrition.pdf")[1] %>% stringr::str_split(., pattern = "\n") %>% tibble::as_tibble(., .name_repair = make.names) %>% dplyr::slice(., 12:48) %>% dplyr::mutate(X = str_replace_all(X, pattern = "\\s+", replacement = " ")) %>% tidyr::separate(., col = X, into = c(paste0("X", 1:21)), sep = " ") %>% purrr::set_names(., nm = col_nm) %>% dplyr::mutate_at(., .vars = col_nm[-1], .funs = as.numeric) %>% print(n = 50) # A tibble: 37 x 21 商品名 重量 エネルギー たんぱく質 脂質 炭水化物 ナトリウム カリウム カルシウム リン 鉄 ビタミンA ビタミンB1 <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> 1 海老天七味… 195. 429 12.3 19.8 50.2 1174 243 42 117 0.8 41 0.08 2 モスバーガ… 209. 366 15.6 15.6 40.9 888 330 32 156 1.2 30 0.1 3 テリヤキバ… 168. 377 14.7 16.8 41.7 1023 255 29 144 1.1 19 0.09 4 テリヤキチ… 149 307 20.1 10.5 32.8 845 286 19 164 0.7 31 0.09 5 モスチーズ… 224. 419 18.6 20 41.3 1077 340 116 297 1.2 74 0.11 6 モス野菜バ… 190. 351 14.4 16.3 36.9 683 355 32 154 1.3 36 0.11 7 海老カツバ… 158. 390 11.7 18.2 45.3 769 116 56 223 0.6 7 0.06 8 ロースカツ… 174. 412 17.9 16.4 48.7 866 301 34 187 0.9 9 0.59 9 フィッシュ… 144 390 16.7 18.9 38.6 814 235 103 275 0.5 49 0.07 10 チキンバー… 152 382 15.4 17.4 41 649 269 24 106 0.7 5 0.13 11 ハンバーガ… 126. 308 14 11.9 36.2 669 259 26 140 1.1 14 0.08 12 チーズバー… 141. 361 17 16.3 36.6 858 269 110 281 1.1 58 0.09 13 ダブルモス… 272. 516 24.4 25.4 47.2 1292 482 43 249 1.9 40 0.14 14 ダブルモス… 287. 569 27.4 29.8 47.6 1481 492 127 390 1.9 84 0.15 15 ダブルテリ… 233. 539 23.6 26.2 52 1551 404 40 236 1.8 29 0.13 16 ダブルモス… 243. 489 22.8 25.7 41.8 1006 502 42 246 2.1 45 0.14 17 ダブルハン… 179. 446 22.4 21.3 41.1 992 406 36 232 1.9 23 0.11 18 ダブルチー… 194. 499 25.4 25.7 41.5 1181 416 120 373 1.9 67 0.12 19 スパイシー… 217. 367 15.6 15.6 41.1 956 361 33 159 1.3 56 0.11 20 スパイシー… 232. 420 18.6 20 41.5 1145 371 117 300 1.3 100 0.12 21 スパイシー… 280. 517 24.4 25.4 47.4 1359 513 43 252 2.1 65 0.14 22 スパイシー… 295. 570 27.4 29.8 47.8 1548 523 127 393 2.1 109 0.15 23 モスの菜摘… 188. 207 9.4 14 11.5 483 411 31 125 1.1 46 0.11 24 モスの菜摘… 140 186 15.1 10.7 7.7 749 313 17 134 0.6 40 0.08 25 モスの菜摘… 130. 244 11.6 16.4 12.7 589 269 100 243 0.3 57 0.06 26 モスの菜摘… 145 244 6.6 15.7 19.4 544 150 53 191 0.4 15 0.05 27 モスの菜摘… 151 264 12.7 13.8 22.2 640 315 27 152 0.7 17 0.580 28 モスの菜摘… 139 236 10.3 15 15.2 432 304 21 74 0.5 13 0.12 29 モスの菜摘… 187. 186 6.8 11.5 14.5 465 264 44 66 0.9 36 0.1 30 ソイモスバ… 208. 345 13 13.1 43.9 870 183 45 97 1 20 0.09 31 ソイモスチ… 223. 398 16 17.5 44.3 1059 193 129 238 1 64 0.1 32 ソイスパイ… 216. 346 13 13.1 44.1 938 214 46 100 1.1 46 0.1 33 ソイスパイ… 231. 399 16 17.5 44.5 1127 224 130 241 1.1 90 0.11 34 ソイテリヤ… 167. 356 12.1 14.3 44.7 1005 108 42 85 0.9 9 0.08 35 ソイモス野… 189. 330 11.8 13.8 39.9 665 208 45 95 1.1 26 0.1 36 ソイハンバ… 125. 287 11.4 9.4 39.2 651 112 39 81 0.9 4 0.07 37 ソイチーズ… 140. 340 14.4 13.8 39.6 840 122 123 222 0.9 48 0.08 # … with 8 more variables: ビタミンB2 <dbl>, ナイアシン <dbl>, ビタミンC <dbl>, ビタミンD <dbl>, ビタミンE <dbl>, # コレステロール <dbl>, 食物繊維 <dbl>, 食塩相当量 <dbl>