【问题标题】:Split a string into combinations of 2 characters and expand into data frame in R将字符串拆分为 2 个字符的组合并扩展为 R 中的数据框
【发布时间】:2014-07-09 19:51:51
【问题描述】:

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

下面是一个我从这里开始的例子:

    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() 函数合并到此模式中。任何建议将不胜感激。

【问题讨论】:

  • 您可以使用dput 在此处包含您的一些数据吗?

标签: r string dataframe combinations plyr


【解决方案1】:

这是使用 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

请注意,我使用stringsAsFacctors=F 导入了df,而FemaleF 被解释为FALSE,所以我需要df$sex &lt;- "F",但这不会影响您。

【讨论】:

  • 谢谢。虽然我尝试将代码中的依赖关系保持在较低水平,但我已经将 data.table 用于其他用途,这就像一个魅力。
【解决方案2】:

我试图将其编辑为@jlhoward 的答案,但它太长了。所以分开写。这个答案基本上建立在他漂亮而紧凑的解决方案 (+1) 之上,以解决可能的速度增强问题。

首先,strsplit 被矢量化。因此,我们可以利用data.table 还允许轻松创建和操作list 类型的列这一事实,通过预先拆分它们来避免对每一行进行拆分:

DT[, splits := strsplit(cat, ",", fixed=TRUE)]

其次,如果 splits 的长度是 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.

最后一点:如果您总是只需要 nC2 和您自己的自定义函数,您可以避免在此处使用 combn(这真的很慢),我将把它留给您。

【讨论】:

  • 哇。谢谢你。由于这是在不太大的数据集上一次性完成的事情,因此速度并不是我真正关心的问题。但是,感谢您的出色故障。
【解决方案3】:

这是通过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

【讨论】:

    【解决方案4】:

    这是一个基本的 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
    

    【讨论】:

    • 这是一些相当令人印象深刻的应用函数杂耍。谢谢。
    猜你喜欢
    • 1970-01-01
    • 2021-12-28
    • 2019-10-12
    • 2012-04-08
    • 2011-09-24
    • 1970-01-01
    • 1970-01-01
    • 2011-08-28
    • 2016-12-08
    相关资源
    最近更新 更多