我有一个列表,其中每个元素本身都是一个命名的属性列表,这是您从典型的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值(例如attr1
和attr2
),某些属性本身是向量,长度不同(例如attr3
)。
我想使用purrr
将此列表转换为数据帧,其中attr1
和attr2
是“常规”列,而attr3
是list列:
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]>
我不知道如何(轻松)简化,但这也是一个很好的方向。
您可以检查列表中每个元素的长度,如果所有元素均为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
在基数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