将嵌套列表安全地转换为数据帧,其中某些值是奇异的,而某些是向量

问题描述 投票:1回答:2

我有一个列表,其中每个元素本身都是一个命名的属性列表,这是您从典型的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
  )
)

某些属性具有相同类型的single值(例如attr1attr2),某些属性本身是向量,长度不同(例如attr3)。

我想使用purrr将此列表转换为数据帧,其中attr1attr2是“常规”列,而attr3list列:

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]>

A,如上所述,如果我有许多属性,并且我事先不知道哪些是单数的,哪些是列表,该怎么办? (可以安全地假设所有元素都存在)]

我当然可以总是返回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]>

我不知道如何(轻松)简化,但这也是一个很好的方向。

r purrr
2个回答
2
投票

您可以检查列表中每个元素的长度,如果所有元素均为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
投票

在基数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
© www.soinside.com 2019 - 2024. All rights reserved.