Лучший и самый эффективный способ подсчета токенов слов - PullRequest
2 голосов
/ 10 декабря 2010

У меня есть CSV, который начинается с 3 столбцов.Столбец совокупного процента затрат, столбец затрат и столбец ключевых слов.Скрипт R работает для небольших файлов, но полностью умирает (никогда не завершается), когда я передаю ему фактический файл (который содержит миллион строк).Можете ли вы помочь мне сделать этот скрипт более эффективным?Token.Count - это тот, который у меня возникают проблемы при создании.Спасибо!

# Token Histogram

# Import CSV data from Report Downloader API Feed
Mydf <- read.csv("Output_test.csv.csv", sep=",", header = TRUE, stringsAsFactors=FALSE)

# Helps limit the dataframe according the HTT
# Change number to:
# .99 for big picture
# .8 for HEAD
limitor <- Mydf$CumuCost <= .8
# De-comment to ONLY measure TORSO
#limitor <- (Mydf$CumuCost <= .95 & Mydf$CumuCost > .8)
# De-comment to ONLY measure TAIL
#limitor <- (Mydf$CumuCost <= 1 & Mydf$CumuCost > .95)
# De-comment to ONLY measure Non-HEAD
#limitor <- (Mydf$CumuCost <= 1 & Mydf$CumuCost > .8)

# Creates a column with HTT segmentation labels
# Creates a dataframe
HTT <- data.frame()
# Populates dataframe according to conditions
HTT <- ifelse(Mydf$CumuCost <= .8,"HEAD",ifelse(Mydf$CumuCost <= .95,"TORSO","TAIL"))
# Add the column to Mydf and rename it HTT
Mydf <- transform(Mydf, HTT = HTT)

# Count all KWs in account by using the dimension function
KWportfolioSize <- dim(Mydf)[1]

# Percent of portfolio
PercentofPortfolio <- sum(limitor)/KWportfolioSize

# Length of Keyword -- TOO SLOW
# Uses the Tau package
# My function takes the row number and returns the number of tokens
library(tau)
Myfun = function(n) {
  sum(sapply(Mydf$Keyword.text[n], textcnt, split = "[[:space:][:punct:]]+", method = "string", n = 1L))}
# Creates a dataframe to hold the results
Token.Count <- data.frame()
# Loops until last row and store it in data.frame
for (i in c(1:dim(Mydf)[1])) {Token.Count <- rbind(Token.Count,Myfun(i))}
# Add the column to Mydf
Mydf <- transform(Mydf, Token.Count = Token.Count)
# Not quite sure why but the column needs renaming in this case
colnames(Mydf)[dim(Mydf)[2]] <- "Token.Count"

1 Ответ

2 голосов
/ 11 декабря 2010

Предварительно выделите ваше хранилище , прежде чем заполняет его циклом. Никогда делать то, что вы делаете, и объединять или r | cbind объекты внутри цикла.R должен копировать, выделять больше памяти и т. Д. На каждой итерации цикла, и это накладные расходы, которые наносят вред вашему коду.

Создайте Token.Count с достаточным количеством строк и столбцов и заполните его в цикле.Что-то вроде:

Token.Count <- matrix(ncol = ?, nrow = nrow(Mydf))
for (i in seq_len(nrow(Mydf))) {
    Token.Count[i, ] <- Myfun(i)
}
Token.Count <- data.frame(Token.Count)

Извините, я не могу быть более точным, но я не знаю, сколько столбцов Myfun возвращает.


Обновление 1: Взглянув на textcnt, я думаю, что вы, вероятно, можете вообще избежать цикла.У вас есть что-то вроде этого фрейма данных

DF <- data.frame(CumuCost = c(0.00439, 0.0067), Cost = c(1678, 880),
                 Keyword.text = c("north+face+outlet", "kinect sensor"),
                 stringsAsFactors = FALSE)

Если мы удаляем ключевые слова и преобразуем его в список

keywrds <- with(DF, as.list(Keyword.text))
head(keywrds)

Тогда мы можем вызвать textcnt рекурсивно в этом списке для подсчета слов в каждом компоненте списка;

countKeys <- textcnt(keywrds, split = "[[:space:][:punct:]]+", method = "string",
                     n = 1L, recursive = TRUE)
head(countKeys)

- это почти то, что вы имели, за исключением того, что я добавил recursive = TRUE для обработки каждого из входных векторов отдельно.Последний шаг - от sapply функции sum до countKeys, чтобы получить количество слов:

> sapply(countKeys, sum)
[1] 3 2

Что, по-видимому, является тем, чего вы пытаетесь достичь с помощью цикла и функции.Я правильно понял?


Обновление 2: ОК, если исправлена ​​проблема с предварительным распределением и , использованная textcnt в векторизованном виде, по-прежнему нет 'не так быстро, как вы хотели бы, мы можем исследовать другие способы подсчета слов.Вполне возможно, что вам не нужны все функции textcnt, чтобы делать то, что вы хотите.[Я не могу проверить, сработает ли приведенное ниже решение для всех ваших данных, но это намного быстрее.]

Одним из возможных решений является разделение вектора Keyword.text на слова с использованием встроенного strsplitфункция, например, с использованием keywrds, сгенерированного выше, и только первый элемент:

> length(unlist(strsplit(keywrds[[1]], split = "[[:space:][:punct:]]+")))
[1] 3

Чтобы использовать эту идею, возможно, проще обернуть ее в пользовательскую функцию:

fooFun <- function(x) {
    length(unlist(strsplit(x, split = "[[:space:][:punct:]]+"),
                  use.names = FALSE, recursive = FALSE))
}

который мы затем можем применить к списку keywrds:

> sapply(keywrds, fooFun)
[1] 3 2

Для этого простого примера набора данных мы получаем тот же результат.Как насчет времени вычислений?Сначала для решения используется textcnt, объединяющее два шага из Обновление 1 :

> system.time(replicate(10000, sapply(textcnt(keywrds, 
+                                     split = "[[:space:][:punct:]]+", 
+                                     method = "string", n = 1L, 
+                                     recursive = TRUE), sum)))
   user  system elapsed 
  4.165   0.026   4.285

, а затем решение для Обновление 2 :

> system.time(replicate(10000, sapply(keywrds, fooFun)))
   user  system elapsed 
  0.883   0.001   0.889

Так что даже для этой небольшой выборки при вызове textcnt возникают значительные накладные расходы, но остается ли это различие при применении обоих подходов к полному набору данных.

Наконец, мы должны отметить, что подход strsplit может быть векторизован для работы непосредственно с вектором Keyword.text в DF:

> sapply(strsplit(DF$Keyword.text, split = "[[:space:][:punct:]]+"), length)
[1] 3 2

, который дает те же результаты, что и два других подхода, инезначительно быстрее, чем не-векторизованное использование strsplit:

> system.time(replicate(10000, sapply(strsplit(DF$Keyword.text, 
+                              split = "[[:space:][:punct:]]+"), length)))
   user  system elapsed 
  0.732   0.001   0.734

Является ли что-либо из этого более быстрым для вашего полного набора данных?

Незначительное обновление: репликацияDF для получения 130 строк данных и синхронизации три подхода предполагают, что последний (векторизованный strsplit()) масштабируется лучше:

> DF2 <- rbind(DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF,DF)
> dim(DF2)
[1] 130   3
> system.time(replicate(10000, sapply(textcnt(keywrds2, split = "[[:space:][:punct:]]+", method = "string", n = 1L, recursive = TRUE), sum)))
   user  system elapsed 
238.266   1.790 241.404
> system.time(replicate(10000, sapply(keywrds2, fooFun)))
   user  system elapsed 
 28.405   0.007  28.511
> system.time(replicate(10000, sapply(strsplit(DF2$Keyword.text,split = "[[:space:][:punct:]]+"), length)))
   user  system elapsed 
  7.497   0.011   7.528
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...