R 创建具有可变维度的嵌套数据框
我有一个数据框,其中一列R 创建具有可变维度的嵌套数据框,r,dplyr,nested,purrr,R,Dplyr,Nested,Purrr,我有一个数据框,其中一列keys描述了所有剩余列的格式。在下面的示例中,有两个这样的值列,但通常可能还有更多 库(tidyverse) dat=tribble( ~id,~keys,~vals1,~vals2, 1、“A/B”、“1/2”、“11/12”, 3、“C/D/E”、“6/7/8”、“16” ) 我想将这些列转换为一列嵌套的数据帧:在每一行中,值应在“/”上拆分,并形成数据帧的行,标题取自键条目 值列中的条目可能会被截断,在这种情况下,NA应用于缺少的值(即,示例中的条目“16”应解
keys
描述了所有剩余列的格式。在下面的示例中,有两个这样的值列,但通常可能还有更多
库(tidyverse)
dat=tribble(
~id,~keys,~vals1,~vals2,
1、“A/B”、“1/2”、“11/12”,
3、“C/D/E”、“6/7/8”、“16”
)
我想将这些列转换为一列嵌套的数据帧:在每一行中,值应在“/”
上拆分,并形成数据帧的行,标题取自键
条目
值列中的条目可能会被截断,在这种情况下,NA应用于缺少的值(即,示例中的条目“16”
应解释为“16/NA/NA”
)
以下代码生成此特定情况下需要的列:
res=dat%>%
mutate_at(vars(key:last_col()),str_split,pattern=fixed(“/”)%>%
变异(df=pmap(选择(,键:last_col()),
~bind_行(集合名(…2,…1[1:长度(…2)]),
集合名(…3,…1[1:长度(…3)])
res$df
#> [[1]]
#>#tibble:2x2
#>A B
#>
#> 1 1 2
#> 2 11 12
#>
#> [[2]]
#>#tibble:2 x 3
#>C D E
#>
#> 1 6 7 8
#> 2 16
我的问题是如何推广到更多(未知)的列。另外,我使用的setNames
感觉相当笨拙,我希望它能更优雅一点
我主要是寻找一个tidyverse解决方案,但也欢迎其他方法
更新
我应该强调的是,我正在寻找的输出是一个单一的数据帧,其中包含列id
(未更改)和df
(嵌套数据帧的列表)
(原始键/值列并不重要;它们可能会被删除。)
以下是上述示例中所需的结构:
str(res%>%select(id,df))
#>类“tbl_df”、“tbl”和“data.frame”:2个obs。共有2个变量:
#>$id:num13
#>$df:2人名单
#>..$:类“tbl_df”、“tbl”和“data.frame”:2个obs。共有2个变量:
#> .. ..$ A:chr“1”“11”
#> .. ..$ B:chr“2”“12”
#>..$:类“tbl_df”、“tbl”和“data.frame”:2个obs。共有3个变量:
#> .. ..$ C:chr“6”“16”
#> .. ..$ D:chr“7”NA
#> .. ..$ E:chr“8”NA
重塑后,这里有另一个选项
library(dplyr)
library(tidyr)
library(purrr)
dat %>%
pivot_longer(matches("vals\\d+")) %>%
select(-id) %>%
pivot_wider(names_from = keys, values_from = value) %>%
select(-name) %>%
split.default(seq_along(.)) %>%
map(~ .x %>%
separate(names(.), into = str_split(names(.), fixed("/")) %>%
unlist, sep="[/]"))
对于每行,可以将最后3列转换为单个字符元素,其中列值由换行符分隔。然后您基本上有一个csv,但是使用了
/
s而不是逗号,因此您可以使用read.table或其他东西来读取它。我使用data.table::fread是因为它的fill
选项,但也可以通过read\u table或read.table来实现这一点
res <-
dat %>%
mutate(df = apply(dat[-1], 1, function(x)
data.table::fread(paste(x, collapse = '\n'),
sep = '/', fill = TRUE)))
res$df
# [[1]]
# A B
# 1: 1 2
# 2: 11 12
#
# [[2]]
# C D E
# 1: 6 7 8
# 2: 16 NA NA
您也可以使用split
,如下所示
split(dat[-1], dat[1]) %>%
map(~ fread(paste0(.x, collapse="\n"), sep="/", fill = TRUE))
# $`1`
# A B
# 1: 1 2
# 2: 11 12
#
# $`3`
# C D E
# 1: 6 7 8
# 2: 16 NA NA
这是我自己最初尝试的一个改进,它至少适用于任意数量的列 在定义了一个小的效用函数之后
set\u names\u pad=函数(x,y){
长度(x)=长度(y)
集合名(x,y)
}
以下基于pmap
的代码给出了所需的结果:
dat%>%
mutate_at(vars(key:last_col()),str_split,pattern=fixed(“/”)%>%
在(vars(matches(“val”)),~map2(,key,set_names_pad))处进行变异%>%
变异(df=pmap(选择(,匹配(“val”)),绑定_行))
#>#A tible:2 x 5
#>id键vals1 vals2 df
#>
#> 1 1
#> 2 3
当输入有很多行时,这似乎表现得相当好。下面是对@IceCreamToucan的两个建议的比较:
#pmap解决方案
g=函数(x){
x%>%
mutate_at(vars(key:last_col()),str_split,pattern=fixed(“/”)%>%
在(vars(matches(“val”)),~map2(,key,set_names_pad))处进行变异%>%
变异(df=pmap(选择(,匹配(“val”)),绑定_行))
}
#我能吃冰淇淋吗
f1=函数(x){
x%>%
变异(df=apply(.[-1],1,函数(x)
data.table::fread(粘贴(x,collapse='\n'),sep='/',fill=TRUE)))
}
#冰激凌巨嘴鸟II
f2=函数(x){
x%>%
mutate(df=lappy(do.call(粘贴,c(.[1],sep='\n')),
data.table::fread,sep='/',fill=TRUE)
}
基准:标记(f1(dat),f2(dat),g(dat),检查=F)
#>#tibble:3 x 6
#>表达式最小中位数`itr/sec`mem_alloc`gc/sec`
#>
#>1 f1(dat)1.87ms 1.94ms 483。1.93MB 9.38
#>2 f2(dat)1.59ms 1.66ms 573。34.79KB 11.0
#>3g(dat)9.26ms 9.56ms 98.215.13KB 12.3
#增加到10000行
dat2=list(dat)%%>%rep(5000)%%>%bind\u rows%%>%mutate(id=row\u number())
基准:标记(f1(dat2),f2(dat2),g(dat2),检查=F)
#>警告:某些表达式在每次迭代中都有GC;所以过滤是
#>残疾人。
#>#tibble:3 x 6
#>表达式最小中位数`itr/sec`mem_alloc`gc/sec`
#>
#>1 f1(dat2)5.58s 5.58s 0.179 164MB 2.87
#>2 f2(dat2)4.88s 4.88s 0.205 163MB 3.07
#>3 g(dat2)407.51ms 422.89ms 2.36 484KB 5.91
#增加到50000行
dat3=list(dat)%%>%rep(25000)%%>%bind\u rows%%>%mutate(id=row\u number())
基准:标记(f1(dat3),f2(dat3),g(dat3),检查=F)
#>警告:有些表达式在每次迭代中都有GC;所以过滤是
#>残疾人。
#>#tibble:3 x 6
#>表达式最小中位数`itr/sec`mem_alloc`gc/sec`
#>
#>1 f1(dat3)30.56s 30.56s 0.0327 825.7MB 1.64
#>2 f2(dat3)26.84S26.84S0.0373816.7MB 1.49
#>3g(dat3)3.63s3.63s0.2752.3Mb2.20
尽管如此,我仍然觉得使用
tidyr
的旋转功能可以更优雅地完成此操作。非常感谢,我现在知道我没有
split(dat[-1], dat[1]) %>%
map(~ fread(paste0(.x, collapse="\n"), sep="/", fill = TRUE))
# $`1`
# A B
# 1: 1 2
# 2: 11 12
#
# $`3`
# C D E
# 1: 6 7 8
# 2: 16 NA NA