3

I have a dataframe (myDF) with 700.000+ rows, each row has two columns, id and text. The text has 140 character texts (tweets) and I would like to run a sentiment analysis that I got off the web on them. However, no matter what I try, I have memory problems on a macbook with 4gb ram.

I was thinking that maybe I could loop through rows, e.g. do the first 10, and then the second 10...etc. (I run into problems even with batches of 100) Would this solve the problem? What is the best way to loop in such way?

I am posting my code here:

library(plyr)
library(stringr)

# function score.sentiment
score.sentiment = function(sentences, pos.words, neg.words, .progress='none')
{
   # Parameters
   # sentences: vector of text to score
   # pos.words: vector of words of postive sentiment
   # neg.words: vector of words of negative sentiment
   # .progress: passed to laply() to control of progress bar

   # create simple array of scores with laply
   scores = laply(sentences,
   function(sentence, pos.words, neg.words)
   {

      # split sentence into words with str_split (stringr package)
      word.list = str_split(sentence, "\\s+")
      words = unlist(word.list)

      # compare words to the dictionaries of positive & negative terms
      pos.matches = match(words, pos.words)
      neg.matches = match(words, neg.words)

      # get the position of the matched term or NA
      # we just want a TRUE/FALSE
      pos.matches = !is.na(pos.matches)
      neg.matches = !is.na(neg.matches)

      # final score
    score = sum(pos.matches)- sum(neg.matches)
      return(score)
      }, pos.words, neg.words, .progress=.progress )

   # data frame with scores for each sentence
   scores.df = data.frame(text=sentences, score=scores)
   return(scores.df)
}

# import positive and negative words
pos = readLines("positive_words.txt")
neg = readLines("negative_words.txt")

# apply function score.sentiment


myDF$scores = score.sentiment(myDF$text, pos, neg, .progress='text') 
4

3 回答 3

5

4 GB 的内存听起来足以容纳 700,000 个 140 个字符的句子。计算情绪分数的另一种方法可能更节省内存和时间和/或更容易分成块。不是处理每个句子,而是将整组句子分解成单词

words <- str_split(sentences, "\\s+")

然后确定每个句子中有多少个单词,并创建一个单词向量

len <- sapply(words, length)
words <- unlist(words, use.names=FALSE)

通过重新使用words变量,我释放了以前用于重新循环的内存(无需显式调用垃圾收集器,这与 @cryo111 中的建议相反!)。您可以pos.words使用words %in% pos.words. 但是我们可以稍微聪明一点,计算这个逻辑向量的累积和,然后在每个句子的最后一个词处对累积和进行子集化

cumsum(words %in% pos.words)[len]

并计算单词的数量作为这个的导数

pos.match <- diff(c(0, cumsum(words %in% pos.words)[len]))

这是pos.match你分数的一部分。所以

scores <- diff(c(0, cumsum(words %in% pos.words)[len])) - 
          diff(c(0, cumsum(words %in% neg.words)[len]))

就是这样。

score_sentiment <-
    function(sentences, pos.words, neg.words)
{
    words <- str_split(sentences, "\\s+")
    len <- sapply(words, length)
    words <- unlist(words, use.names=FALSE)
    diff(c(0, cumsum(words %in% pos.words)[len])) - 
      diff(c(0, cumsum(words %in% neg.words)[len]))
}

这里的目的是一次性处理你的所有句子

myDF$scores <- score_sentiment(myDF$text, pos, neg)

这避免了 for 循环,虽然与@joran 所指示的正确实施时lapply的朋友相比,其本质上不是低效的,但与矢量化解决方案相比效率非常低。可能不会在这里被复制,并且返回(只是)分数不会浪费内存返回我们已经知道的信息(句子)。最大的记忆将是和。sentencessentenceswords

如果内存仍然是一个问题,那么我会创建一个索引,可用于将文本分成更小的组,并计算每个组的分数

nGroups <- 10 ## i.e., about 70k sentences / group
idx <- seq_along(myDF$text)
grp <- split(idx, cut(idx, nGroups, labels=FALSE))
scorel <- lapply(grp, function(i) score_sentiment(myDF$text[i], pos, neg))
myDF$scores <- unlist(scorel, use.names=FALSE)

首先确保它myDF$text实际上是一个字符,例如,myDF$test <- as.character(myDF$test)

于 2013-04-29T02:18:04.730 回答
1

我认为很难对您的问题给出明确的答案,但这里有一些指示。对我有帮助的是频繁使用垃圾收集器gc()以及从内存中删除不再需要的对象rm(obj_name)。您还可以考虑将数据传输到 MySQL 等数据库中。如果您将数据框导出为 csv 并使用,这将相当容易LOAD DATA INFILE ...。然后应该可以遍历比 100 行更大的块(RODBC 包是从 R 访问 SQL 数据库的好工具)。另一种选择是将数据保存在外部文件中并逐块读取数据,但我不知道如何在 R 中有效地做到这一点。密切关注资源监视器(任务管理器 - 性能- 资源监视器 - 内存)。

顺便说一句:据我所知,一条 twitter 消息可以是 560 字节长(最大)。700k 个条目提供大约 400MB 的数据。虽然这是一个相当大的数据量,但对于 4GB RAM 应该没有问题。你的记忆中还有其他数据吗?你有其他程序在运行吗?

于 2013-04-29T01:39:58.050 回答
0

如果我理解正确,您想使用循环将函数应用于十行的集合。这是一种通用的方法。我首先使用split. 它们没有订购,但没关系,因为您可以在最后重新订购。然后,您在循环中应用您的函数,并使用 将结果添加到“输出”向量中rbind

x <-matrix(1:100,ncol=1)
parts.start <-split(1:100,1:10) #creates list: divide in 10 sets of 10 lines

out <-NULL
for (i in 1:length(parts.start)){
res <- x[parts.start[[i]],,drop=FALSE]*2 #your function applied to elements of the list.
out <-rbind(out,res)
}
head(out)

     [,1]
[1,]    2
[2,]   22
[3,]   42
[4,]   62
[5,]   82
[6,]  102
于 2013-04-28T22:11:54.037 回答