将字符串拆分为2个字符的组合,并扩展为R中的数据框

时间:2014-07-09 19:51:52

标签: r string dataframe combinations plyr

我正在寻找一种干净的方式从表中取一行并将其扩展为多行,除了其中一列之外,其中包含几乎相同的信息。

以下是我从这个开始的例子:

    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()函数合并到这个模式中。任何建议都将受到高度赞赏。

4 个答案:

答案 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=FF的{​​{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.. 
}

更新:(还添加了@ MatthewPlourde的解决方案)

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