Отмена вложения данных JSON в `j` data.table

У меня есть данные в файле JSON, которые я хочу вложить в таблицу data.table. JSON структурирован следующим образом:

[
  {
    "version_id": "123456",
    "data": [
      {
        "review_id": "1",
        "rating": 5,
        "review": "This app is great",
        "date": "2024-09-01"
      },
      {
        "review_id": "2",
        "rating": 1,
        "review": "This app is terrible",
        "date": "2024-09-01"
      }
    ]
  },
  {
    "version_id": "789101",
    "data": [
      {
        "review_id": "3",
        "rating": 3,
        "review": "This app is OK",
        "date": "2024-09-01"
      }
    ]
  }
]

Я могу преобразовать это в data.table, выполнив кучу операций, а затем связав результаты вместе, но я хочу знать, можно ли сделать это проще и/или эффективнее с помощью j в data.table.

Что я делаю сейчас:

reviews <- jsonlite::read_json("reviews.json")
version_ids <- purrr::map_chr(reviews, "version_id")
review_data <- purrr::map(reviews, "data")
cbind(
  data.table::data.table(
    version_id = rep(version_ids, lengths(review_data))
  ),
  lapply(
    review_data,
    function(d) {
      data.table::data.table(
        review_id = purrr::map_chr(d, "review_id"),
        rating = purrr::map_int(d, "rating"),
        review = purrr::map_chr(d, "review"),
        date = purrr::map_chr(d, "date")
      )
    }
  ) |> 
    data.table::rbindlist()
)
#>    version_id review_id rating               review       date
#> 1:     123456         1      5    This app is great 2024-09-01
#> 2:     123456         2      1 This app is terrible 2024-09-01
#> 3:     789101         3      3       This app is OK 2024-09-01

Это правильный результат, но я надеюсь сделать что-то вроде:

data.table::data.table(
  version_id = version_ids,
  review_data = review_data
)[
  rep(version_id, lengths(review_data)),
  .(
    review_id = purrr::map_chr(.SD["review_data"], "review_id"),
    rating = purrr::map_int(.SD["review_data"], "rating"),
    review = purrr::map_chr(.SD["review_data"], "review"),
    date = purrr::map_chr(.SD["review_data"], "date")
  ),
  by = version_id
]

Но это дает мне ошибку:

Error in `[.data.table`(data.table::data.table(version_id = version_ids,  : 
  When i is a data.table (or character vector), the columns to join by must be specified using 'on=' argument (see ?data.table), by keying x (i.e. sorted, and, marked as sorted, see ?setkey), or by sharing column names between x and i (i.e., a natural join). Keyed joins might have further speed benefits on very large data due to x being sorted in RAM.

Я получаю ту же ошибку, если полностью опустить i. Может ли кто-нибудь помочь заставить это работать?


52
2

Ответы:

Решено

РЕДАКТИРОВАТЬ с помощью data.table: вместо того, чтобы пытаться заставить таблицу данных рекурсивно проходить через себя, изначально настройте таблицу данных как длинную, а затем вы можете использовать функцию rbindlist() для разделения столбцов:

data.table::data.table(
    version_id = rep(version_ids, map(review_data, length)),
    data.table::rbindlist(unlist(review_data, recursive = FALSE))
)

   version_id review_id rating               review       date
1:     123456         1      5    This app is great 2024-09-01
2:     123456         2      1 This app is terrible 2024-09-01
3:     789101         3      3       This app is OK 2024-09-01

Оригинальный ответ с использованием tidyr, если он будет полезен другим: Если кто-то использовал tidyr, а не data.table, следующее должно работать путем поэтапного разделения списка: сначала для получения индекса version_id с помощью unnest_wider(), затем для создания строки для каждого обзора (с использованием unnest()), а затем путем создания столбцов для характеристики каждого отзыва (используя unnest_wider()):

library(tidyverse)
reviews <- jsonlite::read_json("reviews.json")

tibble(reviews) %>% 
  unnest_wider(col = reviews) %>% 
  unnest(col = data) %>% 
  unnest_wider(col = data) 
# A tibble: 3 × 5
  version_id review_id rating review               date      
  <chr>      <chr>      <int> <chr>                <chr>     
1 123456     1              5 This app is great    2024-09-01
2 123456     2              1 This app is terrible 2024-09-01
3 789101     3              3 This app is OK       2024-09-01

При необходимости тиббл можно преобразовать в data.table.


Быстрое и грязное решение data.table:

reviews <- jsonlite::read_json("reviews.json")
n <- sapply(reviews, \(x) length(x$data))
DT <- data.table(
  version_id = rep(sapply(reviews, \(x) x$version_id), n),
  lapply(reviews, \(x) rbindlist(lapply(x$data, as.data.table))) |>
    rbindlist()
)

#    version_id review_id rating               review       date
#        <char>    <char>  <int>               <char>     <char>
# 1:     123456         1      5    This app is great 2024-09-01
# 2:     123456         2      1 This app is terrible 2024-09-01
# 3:     789101         3      3       This app is OK 2024-09-01