【问题标题】:Speeding up the processing of large data frames in R加快 R 中大数据帧的处理速度
【发布时间】:2014-01-27 00:18:42
【问题描述】:

上下文

我一直在尝试实现最近在this paper 中提出的算法。给定大量文本(语料库),该算法应该返回语料库的特征 n-grams(即 n 个单词的序列)。用户可以决定适当的 n,目前我正在尝试使用 n = 2-6,就像在原始论文中一样。换句话说,使用该算法,我想提取表征语料库的 2 到 6 克。

我能够实现根据识别出的特征 n-gram 来计算分数的部分,但一直在努力消除非特征性的部分。

数据

我有一个名为token.df 的列表,其中包含五个数据帧,其中包括出现在语料库中的所有 n-gram。每个数据帧对应于 n-grams 中的每个 n。例如,token.df[[2]] 包括按字母顺序排列的所有二元组(2 元组)及其分数(以下称为 mi)。

> head(token.df[[2]])
w1    w2      mi
_      eos  17.219346
_   global   7.141789
_     what   8.590394
0        0   2.076421
0       00   5.732846
0      000   3.426785

这里,二元组 0 0(尽管它们本身并不完全是单词)的得分为 2.076421。由于数据帧包含所有出现在语料库中的 n-gram,它们每个都有超过一百万行。

> sapply(token.df, nrow)
[[1]]
NULL

[[2]]
[1] 1006059  # number of unique bigrams in the corpus

[[3]]
[1] 2684027  # number of unique trigrams in the corpus

[[4]]
[1] 3635026  # number of unique 4-grams in the corpus

[[5]]
[1] 3965120  # number of unique 5-grams in the corpus

[[6]]
[1] 4055048  # number of unique 6-grams in the corpus

任务

我想确定哪些 n-gram 要保留,哪些要丢弃。为此,该算法执行以下操作。

  1. 二元组
    • 它保留了得分高于前两个单词匹配bigrams的trigrams的bigrams。
  2. 3-5克
    • 对于每个 n-gram 其中 n = {3, 4, 5},它查看
      • n-gram 的前 n-1 个单词匹配的 n-1 个词和
      • n 个单词与 n-gram 匹配的 n+1 个gram。
    • 仅当 n-gram 的分数高于 n-1 个和 n+1 个的分数时,该算法才会保留> 克以上。
  3. 6克
    • 保留与 6-gram 的前五个单词匹配的分数高于 5-gram 的 6-gram。

示例

> token.df[[2]][15, ]
 w1  w2       mi
  0 001 10.56292
> token.df[[3]][33:38, ]
 w1  w2       w3        mi
  0 001     also  3.223091
  0 001 although  5.288097
  0 001      and  2.295903
  0 001      but  4.331710
  0 001 compared  6.270625
  0 001      dog 11.002312
> token.df[[4]][46:48, ]
 w1  w2            w3      w4        mi
  0 001      compared      to  5.527626
  0 001           dog walkers 10.916028
  0 001 environmental concern 10.371769

这里,不保留二元组0 001,因为前两个词与二元组匹配的三元组之一(0 001 dog)的得分高于二元组(11.002312 > 10.56292)。 trigram 0 001 dog 被保留,因为它的分数 (11.002312) 高于匹配 trigram 前两个单词的 bigram (0 001; score = 10.56292 ) 和前三个词与三元组匹配的 4-gram 的结果 (0 001 dog walkers; score = 10.916028)。

问题和失败尝试

我想知道的是实现上述目标的有效方法。例如,为了确定要保留哪些二元组,我需要为token.df[[2]] 的每一行找出token.df[[3]] 中的哪些行的前两个单词与所关注的二元组相同。但是,由于行数很大,我下面的迭代方法需要很长时间才能运行。他们专注于二元组的情况,因为这项任务看起来比 3-5 克的情况更简单。

  1. for 循环方法。
    由于下面的代码在每次迭代中都会遍历token.df[[3]] 的所有行,因此估计需要几个月的时间才能运行。虽然稍微好一点,但 by() 的情况类似。

    # for loop
    retain <- numeric(nrow(token.df[[2]]))
    for (i in 1:nrow(token.df[[2]])) {
        mis <- token.df[[3]]$mi[token.df[[2]][i, ]$w1 == token.df[[3]][ , 1] & token.df[[2]][i, ]$w2 == token.df[[3]][ , 2]]
        retain[i] <- ifelse(token.df[[2]]$mi[i] > max(mis), TRUE, FALSE)
    }
    
    # by
    mis <- by(token.df[[2]], 1:nrow(token.df[[2]]), function(x) token.df[[3]]$mi[x$w1 == token.df[[3]]$w1 & x$w2 == token.df[[3]]$w2])
    retain <- sapply(seq(mis), function(i) token.df[[2]]$mi[i] > max(mis[[i]]))
    
  2. 指针方法。
    上述问题是(垂直)长数据帧上的大量迭代。为了缓解这个问题,我想我可以利用 n-gram 在每个数据帧中按字母顺序排序的事实,并使用一种指针来指示从哪一行开始查找。但是,这种方法也需要很长时间才能运行(至少几天)。

    retain <- numeric(nrow(token.df[[2]]))
    nrow <- nrow(token.df[[3]]) # number of rows of the trigram data frame
    pos <- 1 # pointer
    for (i in seq(nrow(token.df[[2]]))) {
        j <- 1
        target.rows <- numeric(10)
        while (TRUE) {
            if (pos == nrow + 1 || !all(token.df[[2]][i, 1:2] == token.df[[3]][pos, 1:2])) break
            target.rows[j] <- pos
            pos <- pos + 1
            if (j %% 10 == 0) target.rows <- c(target.rows, numeric(10))
            j <- j + 1
        }
        target.rows <- target.rows[target.rows != 0]
        retain[i] <- ifelse(token.df[[2]]$mi[i] > max(token.df[[3]]$mi[target.rows]), TRUE, FALSE)
    }
    

有没有办法在合理的时间内(例如,一夜之间)完成这项任务?既然迭代方法是徒劳的,我想知道是否可以进行任何矢量化。但我愿意采取任何方式加快这一进程。

数据具有树形结构,即一个二元组被划分为一个或多个三元组,每个三元组又被划分为一个或多个四元组,依此类推。我不确定如何最好地处理此类数据。

可重现的示例

我曾想过将我正在使用的部分真实数据放上去,但减少数据会破坏问题的全部意义。我假设人们不想为此下载整个 250MB 的数据集,我也无权上传它。下面是随机数据集,它仍然比我使用的要小,但有助于解决问题。使用上面的代码(指针方法),我的计算机需要 4-5 秒来处理下面的 token.df[[2]] 的前 100 行,而处理所有的二元组大概需要 12 小时。

token.df <- list()
types <- combn(LETTERS, 4, paste, collapse = "")
set.seed(1)
data <- data.frame(matrix(sample(types, 6 * 1E6, replace = TRUE), ncol = 6), stringsAsFactors = FALSE)
colnames(data) <- paste0("w", 1:6)
data <- data[order(data$w1, data$w2, data$w3, data$w4, data$w5, data$w6), ]
set.seed(1)
for (n in 2:6) token.df[[n]] <- cbind(data[ , 1:n], mi = runif(1E6))

非常感谢任何加快代码速度的想法。

【问题讨论】:

    标签: r dataframe corpus


    【解决方案1】:

    对于所有二元组,以下内容在我的机器上运行不到 7 秒:

    library(dplyr)
    res <- inner_join(token.df[[2]],token.df[[3]],by = c('w1','w2'))
    res <- group_by(res,w1,w2)
    bigrams <- filter(summarise(res,keep = all(mi.y < mi.x)),keep)
    

    dplyr 没有什么特别之处。使用 data.table 或直接在 SQL 中肯定可以完成同样快速(或更快)的解决方案。您只需要切换到使用连接(如在 SQL 中)而不是自己遍历所有内容。事实上,如果简单地在基础 R 中使用 merge 然后 aggregate 不会比你现在正在做的快几个数量级,我不会感到惊讶。 (但您确实应该使用 data.tabledplyr 或直接在 SQL 数据库中执行此操作。

    的确如此:

    library(data.table)
    dt2 <- setkey(data.table(token.df[[2]]),w1,w2)
    dt3 <- setkey(data.table(token.df[[3]]),w1,w2)
    dt_tmp <- dt3[dt2,allow.cartesian = TRUE][,list(k = all(mi < mi.1)),by = c('w1','w2')][(k)]
    

    甚至更快(~2x)。老实说,我什至不确定我是否已经充分利用了这两个软件包的所有速度。


    (来自 Rick 的编辑。尝试作为评论,但语法变得混乱)
    如果使用data.table,这应该会更快,因为data.table 具有by-without-by 功能(有关更多信息,请参阅?data.table):

     dt_tmp <- dt3[dt2,list(k = all(mi < i.mi)), allow.cartesian = TRUE][(k)]
    

    请注意,在加入data.tables 时,您可以在列名前加上i.,以指示使用i= 参数中专门来自data.table 的列。

    【讨论】:

    • 感谢您的及时回复。这正是我想要的!
    猜你喜欢
    • 1970-01-01
    • 2011-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-12
    • 2014-02-19
    • 2012-11-07
    • 2010-12-08
    相关资源
    最近更新 更多