我正在寻找一种干净的方式从表中取一行并将其扩展为多行,除了其中一列之外,其中包含几乎相同的信息。
以下是我从这个开始的例子:
sex cat status pairs
1 F 6,10 Cancer 6,10
2 F 8,10 Cancer 8,10
3 F 12,13 NoCancer 12,13
4 F 3,4,5,10 Cancer
5 F 7,10 Cancer 7,10
6 F 4,8 NoCancer 4,8
并希望最终得到这个:
sex cat status pairs
1 F 6,10 Cancer 6,10
2 F 8,10 Cancer 8,10
3 F 12,13 NoCancer 12,13
4 F 3,4,5,10 Cancer 3,4
4 F 3,4,5,10 Cancer 3,5
4 F 3,4,5,10 Cancer 3,10
4 F 3,4,5,10 Cancer 4,5
4 F 3,4,5,10 Cancer 4,10
4 F 3,4,5,10 Cancer 5,10
5 F 7,10 Cancer 7,10
6 F 4,8 NoCancer 4,8
现在,我知道我可以拿一个字符串并轻松将它拆分,然后找到所有可能的大小为m的组合。
这样的事情:
combn(x,2, simplify=F, function(x){ paste(x, collapse=",")} )
虽然我做了类似的事情,我将字符串分解为单个元素,然后使用plyr
(由才华横溢的@recology_通过this gist建议)
在我之前的例子中(可以在要点中看到)解决方案最终得到类似于以下内容:
df <- data.frame(id =c(11,32,37),
name=c("rick","tom","joe"),
stringsAsFactors = FALSE)
library(plyr)
foo <- function(x){
strsplit(x, "")[[1]]
}
ddply(df, .(id, name), summarise, letters=foo(name))
我没有成功将combn()函数合并到这个模式中。任何建议都将受到高度赞赏。
答案 0 :(得分:3)
这是使用data.tables
的方法library(data.table)
DT <- as.data.table(df)
result <- DT[,combn(unlist(strsplit(cat,",")),2,paste,collapse=","),
by=list(sex,cat,status)]
setnames(result,"V1","pairs")
result
# sex cat status pairs
# 1: F 6,10 Cancer 6,10
# 2: F 8,10 Cancer 8,10
# 3: F 12,13 NoCancer 12,13
# 4: F 3,4,5,10 Cancer 3,4
# 5: F 3,4,5,10 Cancer 3,5
# 6: F 3,4,5,10 Cancer 3,10
# 7: F 3,4,5,10 Cancer 4,5
# 8: F 3,4,5,10 Cancer 4,10
# 9: F 3,4,5,10 Cancer 5,10
# 10: F 7,10 Cancer 7,10
# 11: F 4,8 NoCancer 4,8
请注意,我使用df
导入stringsAsFacctors=F
,F
的{{1}}被解释为Female
,因此我需要FALSE
,但这不应该影响你。
答案 1 :(得分:1)
我试着把它编辑成@jlhoward的答案,但它太长了。所以单独写它。这个答案基本上建立在他漂亮而紧凑的解决方案(+1)之上,以解决可能的速度增强问题。
首先,strsplit
被矢量化。因此,我们可以通过利用data.table
还允许轻松创建和操作list
类型的列的事实,首先将它们分开在每一行上来避免分裂:
DT[, splits := strsplit(cat, ",", fixed=TRUE)]
其次,如果拆分的长度是&lt; = 2L,那么我们不必使用combn
- 因为什么都不会改变。这应该导致更多的加速与这些列的数量成比例。
DT[, { tmp = splits[[1L]];
if (length(tmp) <= 2L)
list(pairs=pairs)
else
list(pairs=as.vector(combn(tmp, 2L, paste, collapse=",")))
},
by=list(sex, cat, status)]
以下是一些基准:
## data.table solution from @jlhoward's
f1 <- function(DT) {
result <- DT[,combn(unlist(strsplit(cat,",")),2,paste,collapse=","),
by=list(sex,cat,status)]
setnames(result,"V1","pairs")
}
## slightly more efficient in terms of speed
f2 <- function(DT) {
DT[, splits := strsplit(cat, ",", fixed=TRUE)]
ans <- DT[, { tmp = splits[[1L]];
if (length(tmp) <= 2L)
list(pairs=cat)
else
list(pairs=as.vector(combn(tmp, 2L, paste, collapse=",")))
},
by=list(sex, cat, status)]
}
dplyr
解决方案也会针对每个组进行拆分。此外,每个组的do.call(rbind, .)
和data.frame(.)
调用效率非常低。我已将其简化为删除一些函数调用,包括do.call(rbind, .)
。
data.frame(.)
呼叫无法避免,IIUC,do(.)
需要它。无论如何,将简化版本添加到基准测试中:
f3 <- function(df) {
twosplit <- function(df,varname = "cat"){
strsplit(df[[varname]],split = ",")[[1L]] %>%
combn(2, paste, collapse=",") %>%
data.frame(pairs = .)
}
df %>% group_by(sex, cat, status) %>% do(twosplit(.))
# the results are not in the same order..
}
f4 <- function(d) {
pairs <- lapply(strsplit(d$cat, ','), function(x) apply(combn(x, 2), 2, paste, collapse=','))
new.rows <- mapply(function(row, ps) as.data.frame(c(as.list(row), list(pairs=ps))),
row=split(d, 1:nrow(d)), ps=pairs, SIMPLIFY=FALSE)
do.call(rbind, new.rows)
}
DT <- rbindlist(replicate(1e4L, df, simplify=FALSE))[, status := 1:nrow(DT)]
DF <- as.data.frame(DT)
system.time(ans2 <- f2(DT)) ## 1.3s
system.time(ans1 <- f1(DT)) ## 4.9s
system.time(ans3 <- f3(DF)) ## 212s!
system.time(ans4 <- f4(DF)) ## stopped after 8 mins.
最后一点:你可以避免在这里使用combn
(这真的很慢),如果你总是需要nC2
,你自己的自定义功能,我会留下它给你。
答案 2 :(得分:0)
以下是通过dplyr
的王位继承人plyr
的方法:
library(dplyr)
twosplit <- function(df,varname = "V2"){
strsplit(df[[varname]],split = ",") %>%
unlist %>%
combn(2, simplify=FALSE, function(x){ paste(x, collapse=",")} ) %>%
do.call(rbind,.) %>%
unname %>%
data.frame(unname(df),pairs = .)
}
df %>%
group_by(V2) %>%
do(twosplit(.))
V2 X1 X2 X3 X4 pairs
1 12,13 FALSE 12,13 NoCancer 12,13 12,13
2 3,4,5,10 FALSE 3,4,5,10 Cancer NA 3,4
3 3,4,5,10 FALSE 3,4,5,10 Cancer NA 3,5
4 3,4,5,10 FALSE 3,4,5,10 Cancer NA 3,10
5 3,4,5,10 FALSE 3,4,5,10 Cancer NA 4,5
6 3,4,5,10 FALSE 3,4,5,10 Cancer NA 4,10
7 3,4,5,10 FALSE 3,4,5,10 Cancer NA 5,10
8 4,8 FALSE 4,8 NoCancer 4,8 4,8
9 6,10 FALSE 6,10 Cancer 6,10 6,10
10 7,10 FALSE 7,10 Cancer 7,10 7,10
11 8,10 FALSE 8,10 Cancer 8,10 8,10
答案 3 :(得分:0)
这是一个基础R解决方案:
# define sample data
d <- read.table(text=" sex cat status pairs
1 F 6,10 Cancer 6,10
2 F 8,10 Cancer 8,10
3 F 12,13 NoCancer 12,13
4 F 3,4,5,10 Cancer ''
5 F 7,10 Cancer 7,10
6 F 4,8 NoCancer 4,8", as.is=TRUE)
# add pairs column
pairs <- lapply(strsplit(d$cat, ','), function(x) apply(combn(x, 2), 2, paste, collapse=','))
new.rows <- mapply(function(row, ps) as.data.frame(c(as.list(row), list(pairs=ps))),
row=split(d, 1:nrow(d)), ps=pairs, SIMPLIFY=FALSE)
do.call(rbind, new.rows)
# sex cat status pairs pairs.1
# 1 FALSE 6,10 Cancer 6,10 6,10
# 2 FALSE 8,10 Cancer 8,10 8,10
# 3 FALSE 12,13 NoCancer 12,13 12,13
# 4.1 FALSE 3,4,5,10 Cancer 3,4
# 4.2 FALSE 3,4,5,10 Cancer 3,5
# 4.3 FALSE 3,4,5,10 Cancer 3,10
# 4.4 FALSE 3,4,5,10 Cancer 4,5
# 4.5 FALSE 3,4,5,10 Cancer 4,10
# 4.6 FALSE 3,4,5,10 Cancer 5,10
# 5 FALSE 7,10 Cancer 7,10 7,10
# 6 FALSE 4,8 NoCancer 4,8 4,8