【问题标题】:String concatenation using an apply function in R使用 R 中的应用函数进行字符串连接
【发布时间】:2017-12-26 08:02:26
【问题描述】:

我有以下代码,其目的是将一个序列转录为三个元组。它可以正确执行,但在应用于非常大的数据集(即数百万行)时特别慢。

我怀疑罪魁祸首是向量中的“for - 循环”(特别是 for y: 循环),并且觉得应该有一种更有效的方法使用其中一个 apply 函数 - 不幸的是我不太熟悉这种方法,想请求一些帮助(拜托!)。

M.Order <- function(in.vector) {
  return.str <- vector()
  in.vector <- strsplit(in.vector, ' > ', fixed = T)
  for (x in 1:length(in.vector)) {
      output <- NULL
      if(length(in.vector[[x]]) == 1) {
          output <- paste0(in.vector[[x]], '|NULL|NULL')
      } else if(length(in.vector[[x]]) == 2) {
          output <- paste(c(in.vector[[x]][1], in.vector[[x]][2],'NULL'), collapse='|')
      } else if(length(in.vector[[x]]) == 3) {
          output <- paste(in.vector[[x]], collapse = '|')
      } else for (y in 1:(length(in.vector[[x]])-2)) {
          output <- ifelse(length(output) == 0
                          ,paste(in.vector[[x]][y:(y+2)], collapse = '|')
                          ,paste0(output, ' > ', paste(in.vector[[x]][y:(y+2)], collapse = '|'))
                          )
      }
      return.str[x] <- output
  }
return (return.str)
}

orig.str <- rbind.data.frame(
  'A > B > C > B > B > A > B > A > C',
  'A > B',
  'A > C > B',
  'A',
  'A > B > D > C')

colnames(orig.str) <- 'Original'
orig.str$Processed <- M.Order(as.character(orig.str$Original))
orig.str

返回(正确)

                           Original                                             Processed
1 A > B > C > B > B > A > B > A > C A|B|C > B|C|B > C|B|B > B|B|A > B|A|B > A|B|A > B|A|C
2                             A > B                                              A|B|NULL
3                         A > C > B                                                 A|C|B
4                                 A                                           A|NULL|NULL
5                     A > B > D > C                                         A|B|D > B|D|C

【问题讨论】:

  • 旁白:rbind 将行绑定在一起。如果您只想创建一个 data.frame,请使用 data.frame,而不是 rbind.data.frame。您也可以在此调用中直接分配列名。无需单独进行。
  • 我对速度不是特别了解,但我知道bind_rows 的dplyr 包比rbind 快。希望它有所帮助,因为它不是功能的答案

标签: r string loops apply


【解决方案1】:

编辑:删除 rollapply 功能,因为它很慢,并创建了我的 自己的功能。 327,680 行的运行时间:

  • 我的代码:5.62 秒
  • 您的代码:5.66 秒。

所以没有显着差异。

首先,拆分'>'字符上的字符串,如果向量没有至少三个元素,则将NULL添加到向量中。然后,使用 rollapply 连接由三个字符组成的组,用“|”分隔,最后折叠这些组。

# sample data
df  = data.frame(Original=c("A > B > C > B > B > A > B > A > C","A > B","A > C > B","A","A > B > D > C"),stringsAsFactors = FALSE)
for(i in 1:16) df=rbind(df,df)

groups <- function(x)
{
  result <- vector("character", length(x)-2)
  for(k in 1:(length(x)-2) )
  {
    result[k] = paste(x[k:(k+2)],collapse="|")
  }
  return(paste(result,collapse=" > "))
}

array1 = lapply(strsplit(df$Original," > "), function(x) if (length(x) == 1) {c(x[1],"NULL","NULL")} else {if (length(x) == 2) {c(x[1:2],"NULL")} else {x}})
df$modified =  lapply(array1,groups)

输出:(作为易读性列表)

[[1]]
[1] "A|B|C > B|C|B > C|B|B > B|B|A > B|A|B > A|B|A > B|A|C"

[[2]]
[1] "A|B|NULL"

[[3]]
[1] "A|C|B"

[[4]]
[1] "A|NULL|NULL"

[[5]]
[1] "A|B|D > B|D|C"

希望这会有所帮助!

【讨论】:

  • 感谢您的解决方案。我已经计时了,它似乎比 for 循环(超过 300 万个实体)要长 6-7 倍!速度这么慢似乎很奇怪?
  • 我对 rollapply 函数做了一些研究,显然这是一个相当缓慢的函数。我找不到一个好的选择,所以我创建了自己的。代码现在大约和你的 for 循环一样快 - 但可能更清晰;)我会看看我是否可以改进..
  • 实际上 Florian,在我的样本数据(可变长度)上,它的速度是原始数据的两倍 :) 非常感谢。
  • 太好了!我只是对您的样本进行了几次行绑定以供试用。感谢有趣的挑战;)
【解决方案2】:

基本逻辑似乎可以用以下规则来描述:

  1. &gt; 拆分字符串
  2. 对于每个字符串,从每个位置开始,使用 '|' 合并接下来的 3 个字符。
  3. 用空格合并所有生成的元组。

第 2 步是最复杂的。可以用下面的泛化函数来解决:

merge_tuples = function (str, len, sep) {
    start_positions = seq_len(max(length(str) - len + 1, 1))
    tuple_indices = lapply(start_positions, seq, length.out = len)
    lapply(tuple_indices, function (i) paste(str[i], collapse = sep))
}

这已被推广到适用于任何大小(不仅仅是 3)和每个分隔符(不仅仅是'|')。

例子:

> merge_tuples(c('A', 'B', 'C'), 2, ':')
[[1]]
[1] "A:B"

[[2]]
[1] "B:C"

有了这个,res就很容易解决了:

orig = c('A > B > C > B > B > A > B > A > C',
         'A > B',
         'A > C > B',
         'A',
         'A > B > D > C')

tuples = lapply(strsplit(orig, ' > '), merge_tuples, len = 3, sep = '|')
merged = sapply(tuples, paste, collapse = ' ')

这将在没有足够元素的地方输出NA 而不是NULL(如您的代码中所示)。我假设这没什么大不了的。如果是,请将出现的位置替换为 gsub

【讨论】:

  • 非常感谢:我特别喜欢不依赖其他软件包的解决方案,因为它感觉“更整洁”。有趣的是,for 循环在 175-180 秒内完成(使用 system.time 包装器),而第一个 lapply 需要大约 205 秒,第二个 sapply 需要 18 秒(显然在此过程中生成了一个巨大的元组对象 ~ 1.9gb)。知道为什么会慢吗?!
【解决方案3】:

部分解决方案...

以下函数转换一个字符串:

makes = function (S) 
{
    L = strsplit(gsub(" > ", "", S), "")[[1]]
    m = outer(1:3, 0:(length(L) - 3), "+")
    m[] = L[m]
    paste(apply(m, 2, function(x) {
        paste0(x, collapse = "|")
    }), collapse = " > ")
}

它的工作原理是使用outer 制作一个偏移矩阵,然后在将字符串清理为仅字母并拆分为向量后使用它从字符串中取出元素。然后它只是将它们全部粘贴在一起的情况:

> makes(orig.str$Original[1])
[1] "A|B|C > B|C|B > C|B|B > B|B|A > B|A|B > A|B|A > B|A|C"

它对小于 3 的哈希进行哈希处理:

> makes(orig.str$Original[2])
[1] "A|B|NA > A|B|A"
Warning message:
In m[] = L[m] :
  number of items to replace is not a multiple of replacement length
> makes(orig.str$Original[3])
[1] "A|C|B"
> makes(orig.str$Original[4])
Error in L[m] : only 0's may be mixed with negative subscripts
> makes(orig.str$Original[5])
[1] "A|B|D > B|D|C"

可能值得明确地检测这些边缘情况(代码中的length(L) &lt; 3 应该这样做)并单独处理它们。

然后应用你的数据框来做每一个。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-08-08
    • 2012-07-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-07
    相关资源
    最近更新 更多