R - Сумма нескольких критериев по кадрам данных различной длины - PullRequest
0 голосов
/ 30 апреля 2018

Первый пост, долгое время пользователь.

Я пытаюсь эффективно суммировать столбец на основе 2 критериев для каждого идентификатора в другом кадре данных различной длины. Ниже приведен пример:

   ID
1  A 
2  B
3  C

ID   Color   Type  Price
A  Green   1     5
A  Blue    2     6
B  Green   3     7
B  Blue    2     2
C  Green   2     4
C  Blue    4     5

Для каждого идентификатора я хотел бы суммировать цену, если цвет синий, а тип равен 2. Результат, надеюсь, будет следующим:

   ID  Price
1  A   6
2  B   2
3  C   0

Это кажется легкой задачей, но я почему-то не могу понять это. Кроме того, мне нужно будет выполнить эту операцию на 2 больших наборах данных (> 1 000 000 строк каждый). Я создал функцию и использовал ее в цикле для предыдущих задач, подобных этой, но это решение не работает из-за большого количества информации. Я чувствую, что функция из apply, вероятно, будет лучшей, но я не могу заставить их работать.

Ответы [ 2 ]

0 голосов
/ 30 апреля 2018

A sapply версия. Возможно, существуют более изящные способы ее написания, но если у вас большие таблицы, как вы сказали, вы можете легко распараллелить их.

Используя данные, предложенные @denis:

df1 <- data.frame(ID = c("A","B","C"))

df2 <- read.table(text = "
                  ID   Color   Type  Price
                  A  Green   1     5
                  A  Blue    2     6
                  A  Blue    2     4
                  B  Green   3     7
                  B  Blue    2     2
                  C  Green   2     4
                  C  Blue    4     5
                  D  Green   2     2
                  D  Blue    4     8
                  ",header = T)

Вот простая функция, которая делает то, что вы хотите с sapply:

 getPrices <- function(tableid=df1,tablevalues=df2,color="Blue",type=2){
     filteredtablevalues <- droplevels(tablevalues[ tablevalues$Color == "Blue" & tablevalues$Type == 2 & tablevalues$ID %in% df1$ID,])
     #droplevels could be skipped by using unique(as.character(filteredtablevalues$ID)) in the sapply, not sure what would be the quickest 
     sapply(levels(filteredtablevalues$ID),function(id,tabval)
            {
            sum(tabval$Price[tabval$ID == id])
        },tabval=filteredtablevalues)
 }

Как вы видите, я добавил два параметра, которые позволяют вам выбрать для пары цвет / тип. И вы можете добавить это:

 tmp=getPrices(df1,df2)
 finaltable=cbind.data.frame(ID=names(tmp),Price=tmp)

Если вам абсолютно необходим фрейм данных с идентификатором столбца и ценой столбца.

Я попробую провести тестирование, когда у меня будет время, но написанное таким образом, вы сможете легко распараллелить это с library(parallel) и library(Rmpi), что может спасти вам жизнь, если у вас очень и очень большие наборы данных.

РЕДАКТИРОВАТЬ:

Benchmark:

Мне не удалось воспроизвести пример dplyr, предложенный @denis, но я мог сравнить версию data.table:

#Create a bigger dataset
nt=10000 #nt as big as you want
df2=rbind.data.frame(df2,
                     list(ID= sample(c("A","B","C"),nt,replace=T),
                          Color=sample(c("Blue","Green"),nt,replace=T),
                          Type=sample.int(5,nt,replace=T),
                          Price=sample.int(5,nt,replace=T)
                          )
                     )

Вы можете тестировать, используя library(microbenchmark):

library(microbenchmark)
microbenchmark(sply=getPrices(df1,df2),dtbl=setDT(df2)[ID %in% unique(df1$ID), .(sum = sum(Price[ Type == 2 & Color == "Blue"])),by = ID],dplyr=df2 %>%  filter(ID %in% unique(df1$ID)) %>%  group_by(ID) %>%  summarize(sum = sum(Price[Type==2 & Color=="Blue"])))

На моем компьютере выдает:

Unit: milliseconds
  expr      min       lq      mean    median        uq      max neval
  sply 78.37484 83.89856  97.75373  89.17033 118.96890 131.3226   100
  dtbl 75.67642 83.44380  93.16893  85.65810  91.98584 137.2851   100
 dplyr 90.67084 97.58653 114.24094 102.60008 136.34742 150.6235   100

Edit2:

sapply выглядит немного быстрее, чем data.table, хотя и незначительно. Но использование sapply может быть очень полезным, если у вас огромный стол ID. Затем вы используете library(parallel) и получаете еще больше времени.

Теперь подход data.table кажется самым быстрым. Но все же, преимущество sapply в том, что вы можете легко распараллелить его. Хотя в этом случае и с учетом того, как я написал функцию getPrices, она будет эффективной, только если ваша таблица ID огромна.

0 голосов
/ 30 апреля 2018

Я немного изменил ваш пример данных, чтобы он учитывал тот факт, что не все идентификаторы находятся в первом кадре данных, и есть два значения для суммирования в единственном числе:

df1 <- data.frame(ID = c("A","B","C"))

df2 <- read.table(text = "
                  ID   Color   Type  Price
                  A  Green   1     5
                  A  Blue    2     6
                  A  Blue    2     4
                  B  Green   3     7
                  B  Blue    2     2
                  C  Green   2     4
                  C  Blue    4     5
                  D  Green   2     2
                  D  Blue    4     8
                  ",header = T)

Два основных пакета, которые делают это быстро и на больших data.frame: dplyr и data.table. Они вполне эквивалентны (почти, см. data.table vs dplyr: один может делать что-то хорошо, другой не может или плохо? ). Вот два решения:

library(data.table)

setDT(df2)[ID %in% unique(df1$ID), .(sum = sum(Price[ Type == 2 & Color == "Blue"])),by = ID]

   ID sum
1:  A  10
2:  B   2
3:  C   0

Вы могли бы сделать

setDT(df2)[ID %in% unique(df1$ID) & Type == 2 & Color == "Blue", .(sum = sum(Price)),by = ID]

но вы отбросите C, так как не выполнено все условие для выбора строки:

   ID sum
1:  A  10
2:  B   2

и с dplyr:

library(dplyr)

df2 %>%
  filter(ID %in% unique(df1$ID)) %>%
  group_by(ID) %>%
  summarize(sum = sum(Price[Type==2 & Color=="Blue"]))

# A tibble: 3 x 2
  ID      sum
  <fct> <int>
1 A        10
2 B         2
3 C         0
...