Rのこと。

記事は引っ越し作業中。2023年中までに引っ越しを完了させてブログは削除予定

JSONとNDJSONをRで扱う

はじめに

ここではJSONとNDJSONをRで扱う方法をまとめておく。

JSONとは

そもそもJSONとはなにか。JSON(JavaScript Object Notation)は、軽量のデータ交換フォーマットで、人間にも機械にも読み書きが容易な形式のデータのことらしい。

基本的には下記のようなKey-Value形式でデータを保持する。business_idvcNAWiLM4dR7D2nwwJ7nCAで、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_idmVHrayjG3uZ_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)

使い方

詳しくは下記のサイトも参考にしてください。

PDFは帝国データバンクのPDF。

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のデータを読み込みたい。

f:id:AZUMINO:20200827023556p:plain

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>