Безопасное преобразование вложенного списка в фрейм данных, где некоторые значения являются единственными, а некоторые - векторами - PullRequest
1 голос
/ 29 февраля 2020

У меня есть список, в котором каждый элемент сам по себе является именованным списком атрибутов, вид вывода, который вы получите из типичного JSON:

l <- list(
  list(
    "attr1" = 1,
    "attr2" = "x",
    "attr3" = 3:4
  ),
  list(
    "attr1" = 5,
    "attr2" = "y",
    "attr3" = 7:9
  ),
  list(
    "attr1" = 10,
    "attr2" = "z",
    "attr3" = 12
  )
)

Некоторые из атрибутов имеют одиночное значение того же типа (например, attr1 и attr2), а некоторые сами являются векторами, различной длины (например, attr3).

Я хотел бы использовать purrr для преобразования этого списка во фрейм данных, где attr1 и attr2 - это «обычные» столбцы, а attr3 - это список столбец:

tibble(
  attr1 = c(1, 5, 10),
  attr2 = c("x", "y", "z"),
  attr3 = list(c(3:4), c(7:9), 12)
)
# A tibble: 3 x 3
  attr1 attr2 attr3    
  <dbl> <chr> <list>   
1     1 x     <int [2]>
2     5 y     <int [3]>
3    10 z     <dbl [1]>

Суть в том, что у меня много атрибутов, и я понятия не имею, какие из них являются единственными и являются списками

Этот простой подход, конечно, потерпит неудачу из-за attr3:

attrs <- names(l[[1]])

get_element_details <- function(element, attrs) {
  element_list <- map(attrs, function(attr) pluck(element, attr))
  names(element_list) <- attrs
  element_list
}

df <- l %>% map_dfr(get_element_details, attrs)

Ошибка: аргумент 3 должен иметь длину 1, а не 2

Этот подход работает, но я должен заранее знать, какие атрибуты должны быть единичными, а какие списки. Я использую именованный вектор attrs_dict, что-то вроде словаря Python, с которым функция pluck_wrapper обращается, чтобы вернуть единственное значение или список:

attrs_dict <- c("attr1" = FALSE, "attr2" = FALSE, "attr3" = TRUE)
pluck_wrapper <- function(element, attr, attrs_dict) {
  res <- pluck(element, attr)
  if (attrs_dict[attr]) {
    return(list(res))
  }
  return(res)
}
get_element_details <- function(element, attrs_dict) {
  attrs <- names(attrs_dict)
  element_list <- map(attrs, function(attr) pluck_wrapper(element, attr, attrs_dict))
  names(element_list) <- attrs
  element_list
}
df <- l %>% map_dfr(get_element_details, attrs_dict)
df
# A tibble: 3 x 3
  attr1 attr2 attr3    
  <dbl> <chr> <list>   
1     1 x     <int [2]>
2     5 y     <int [3]>
3    10 z     <dbl [1]>

Увы, как я уже сказал, что мне делать, если у меня много атрибутов, и я заранее не знаю, которые являются единичными, а какие списками? (хотя можно предположить, что все они существуют)

Я, конечно, всегда могу вернуть list(pluck(...)), но я получу:

get_element_details <- function(element, attrs) {
  element_list <- map(attrs, function(attr) list(pluck(element, attr)))
  names(element_list) <- attrs
  element_list
}

df <- l %>% map_dfr(get_element_details, attrs)
df
# A tibble: 3 x 3
  attr1     attr2     attr3    
  <list>    <list>    <list>   
1 <dbl [1]> <chr [1]> <int [2]>
2 <dbl [1]> <chr [1]> <int [3]>
3 <dbl [1]> <chr [1]> <dbl [1]>

Что я понятия не имею, как упростить (легко), но это также было бы хорошим направлением.

Ответы [ 2 ]

3 голосов
/ 29 февраля 2020

Вы можете проверить длину каждого элемента в списке, и если все они равны 1, мы можем unlist их.

library(purrr)
transpose(l) %>% map_dfc(~if(all(lengths(.x) ==1)) unlist(.x) else .x)

# A tibble: 3 x 3
#  attr1 attr2 attr3    
#  <dbl> <chr> <list>   
#1     1 x     <int [2]>
#2     5 y     <int [3]>
#3    10 z     <dbl [1]>

Если вы хотите, чтобы полные данные были неопубликованными, вы можете сделать

transpose(l) %>% map_dfc(list) %>% tidyr::unnest(cols = V1:V3)

# A tibble: 6 x 3
#     V1 V2       V3
#  <dbl> <chr> <dbl>
#1     1 x         3
#2     1 x         4
#3     5 y         7
#4     5 y         8
#5     5 y         9
#6    10 z        12
1 голос
/ 29 февраля 2020

В базе R вы можете использовать cbind.

res <- do.call(rbind, lapply(l, function(x) data.frame(t(cbind(x)))))

res
#    attr1 attr2   attr3
# x      1     x    3, 4
# x1     5     y 7, 8, 9
# x2    10     z      12

str(res)
'data.frame':   3 obs. of  3 variables:
 $ attr1:List of 3
  ..$ : num 1
  ..$ : num 5
  ..$ : num 10
 $ attr2:List of 3
  ..$ : chr "x"
  ..$ : chr "y"
  ..$ : chr "z"
 $ attr3:List of 3
  ..$ : int  3 4
  ..$ : int  7 8 9
  ..$ : num 12
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...