Sim*_*Sim 6 grouping r aggregation data.table
我正在使用一个包含深层嵌套列表的列表列的大型(数百万行)数据表,这些列表没有统一的结构,大小或元素顺序(list(x=1,y=2)
并且list(y=2,x=1)
可能都存在且应该被视为相同).我需要重复执行任意分组,其中包括数据表中的某些列以及列表列中的数据子集.并非所有行都具有与子集匹配的值.
我提出的方法感觉过于复杂.以下是要点:
识别嵌套列表结构中的值.我的方法是使用ul <- unlist(list_col)
,"展平"嵌套数据结构并构建层次结构名称,以便直接访问每个元素,例如address.country.code
.
从分组的角度来看,确保相同的未列出数据的排列被认为是相等的.我的方法是通过其值的名称对未列出的向量进行排序,ul[order(names(ul))]
并通过引用将结果分配为新的字符向量列.
对展平值的子集执行分组.我无法以by=
任何方式使用值为列表或向量的列.因此,我必须找到一种方法将唯一的字符向量映射到简单的值.我这样做了digest
.
以下是两个主力函数:
# Flatten list column in a data.table
flatten_list_col <- function(dt, col_name, flattened_col_name='props') {
flatten_props <- function(d) {
if (length(d) > 0) {
ul <- unlist(d)
names <- names(ul)
if (length(names) > 0) {
ul[order(names)]
} else {
NA
}
} else {
NA
}
}
flattened <- lapply(dt[[col_name]], flatten_props)
dt[, as.character(flattened_col_name) := list(flattened), with=F]
}
# Group by properties in a flattened list column
group_props <- function(prop_group, prop_col_name='props') {
substitute({
l <- lapply(eval(as.name(prop_col_name)), function(x) x[names(x) %in% prop_group])
as.character(lapply(l, digest))
}, list(prop_group=prop_group, prop_col_name=prop_col_name))
}
Run Code Online (Sandbox Code Playgroud)
这是一个可重复的例子:
library(data.table)
dt <- data.table(
id=c(1,1,1,2,2,2),
count=c(1,1,2,2,3,3),
d=list(
list(x=1, y=2),
list(y=2, x=1),
list(x=1, y=2, z=3),
list(y=5, abc=list(a=1, b=2, c=3)),
NA,
NULL
)
)
flatten_list_col(dt, 'd')
dt[, list(total=sum(count)), by=list(id, eval(group_props(c('x', 'y'))))]
Run Code Online (Sandbox Code Playgroud)
输出是:
> flatten_list_col(dt, 'd')
id count d props
1: 1 1 <list> 1,2
2: 1 1 <list> 1,2
3: 1 2 <list> 1,2,3
4: 2 2 <list> 1,2,3,5
5: 2 3 NA NA
6: 2 3 NA
> dt[, list(total=sum(count)), by=list(id, eval(group_props(c('x', 'y'))))]
id group_props total
1: 1 325c6bbb2c33456d0301cf3909dd1572 4
2: 2 7aa1e567cd0d6920848d331d3e49fb7e 2
3: 2 ee7aa3b9ffe6bffdee83b6ecda90faac 6
Run Code Online (Sandbox Code Playgroud)
这种方法有效但效率很低,因为需要压缩和排序列表,因为需要计算摘要.我想知道以下内容:
是否可以通过直接从列表列中检索值来创建扁平列来完成此操作?这可能需要将所选属性指定为表达式而不是简单名称.
有没有办法绕过需要digest
?
这里有很多问题。最重要的(由于其他人的原因,您还没有意识到这一点)是您正在通过引用进行分配,但尝试用比您有空间通过引用执行此操作的更多值进行替换。
举这个非常简单的例子
DT <- data.table(x=1, y = list(1:5))
DT[,new := unlist(y)]
Warning message:
In `[.data.table`(DT, , `:=`(new, unlist(y))) :
Supplied 5 items to be assigned to 1 items of column 'new' (4 unused)
Run Code Online (Sandbox Code Playgroud)
您将丢失nrow(DT)
新创建的列表中除第一个项目之外的所有项目。它们不会对应于 data.table 的行
因此,您必须创建一个data.table
足够大的新变量来分解这些列表变量。通过引用这是不可能的。
newby <- dt[,list(x, props = as.character(unlist(data))), by = list(newby = seq_len(nrow(dt)))][,newby:=NULL]
newby
x props
1: 1 1
2: 1 2
3: 1 2
4: 1 1
5: 1 10
6: 2 1
7: 2 2
8: 2 3
9: 2 5
10: 2 1
11: 2 2
12: 2 3
13: 3 NA
14: 3 NA
Run Code Online (Sandbox Code Playgroud)
请注意,需要使用 as.character 来确保所有值都是相同的类型,并且类型不会在转换中丢失数据。目前,您NA
在数字/整数数据列表中拥有一个逻辑值。
另一项编辑强制所有组件都是角色(甚至是 NA)。props 现在是一个列表,每行有 1 个字符向量。
flatten_props <- function(data) { if (is.list(data)){ ul <- unlist(data) if (length(ul) > 1) { ul <- ul[order(names(ul))] } as .character(ul) } else { as.character(unlist(data))}}
dt[, props := lapply(data, flatten_props)]
dt
x data props
1: 1 <list> 1,2
2: 1 <list> 10,1,2
3: 2 <list> 1,2,3
4: 2 <list> 1,2,3,5
5: 3 NA NA
6: 3
dt[,lapply(props,class)]
V1 V2 V3 V4 V5 V6
1: character character character character character character
Run Code Online (Sandbox Code Playgroud)