R: data.table вычисляет средневзвешенные значения нескольких переменных с несколькими весовыми переменными каждая, по группам

Я все еще новичок в data.table, Мой вопрос похож на этот и этот. Разница в том, что я хочу вычислить взвешенные средние для нескольких переменных по группам, но используя более одного веса для каждого среднего.

Рассмотрим следующее data.table (фактический намного больше):

library(data.table)

set.seed(123456)

mydata <- data.table(CLID = rep("CNK", 10),
                     ITNUM = rep(c("First", "Second", "First", "First", "Second"), 2),
                     SATS = rep(c("Always", "Amost always", "Sometimes", "Never", "Always"), 2),
                     ASSETS = rep(c("0-10", "11-25", "26-100", "101-200", "MORE THAN 200"), 2),
                     AVGVALUE1 = rnorm(10, 10, 2),
                     AVGVALUE2 = rnorm(10, 10, 2),
                     WGT1 = rnorm(10, 3, 1),
                     WGT2 = rnorm(10, 3, 1),
                     WGT3 = rnorm(10, 3, 1))

#I set the key of the table to the variables I want to group by,
#so the output is sorted
setkeyv(mydata, c("CLID", "ITNUM", "SATS", "ASSETS"))

Чего я хочу достичь, так это рассчитать взвешенные средства для AVGVALUE1 а также AVGVALUE2 (и, возможно, больше переменных) по группам, определенным ITNUM, SATS, ASSETS используя каждую из весовых переменных WGT1, WGT2, WGT3 (и, возможно, их больше). Таким образом, для каждой из переменных, которые я хочу вычислить, взвешенные средние значения будут иметь три взвешенных средних по группам (или независимо от того, какое количество весов будет).

Я могу сделать это для каждой переменной в отдельности, например:

all.weights <- c("WGT1", "WGT2", "WGT3")
avg.var <- "AVGVALUE1"
split.vars <- c("ITNUM", "SATS", "ASSETS")

mydata[ , Map(f = weighted.mean, x = .(get(avg.var)), w = mget(all.weights),
na.rm = TRUE), by = c(key(mydata)[1], split.vars)]

Я добавляю первую ключевую переменную в by, хотя это константа, потому что я хотел бы иметь его в качестве столбца в выводе. И я получаю:

   CLID  ITNUM         SATS        ASSETS       V1       V2       V3
1:  CNK  First       Always          0-10 11.66824 11.66819 11.66829
2:  CNK  First        Never       101-200 11.37378 12.21008 11.60182
3:  CNK  First    Sometimes        26-100 12.43004 13.13450 12.01330
4:  CNK Second       Always MORE THAN 200 12.32265 11.81613 12.56786
5:  CNK Second Amost always         11-25 10.76556 11.34669 10.52458

Однако с фактическим data.tableгде у меня гораздо больше столбцов для вычисления взвешенных средних (а также гораздо больше весов для использования), было бы довольно сложно сделать это один за другим. То, что я представляю, это функция, где среднее значение для каждой из переменных (AVGVALUE1, AVGVALUE2 и так далее) вычисляется с каждой из весовых переменных (WGT1, WGT2, WGT3 и т. д.) и выход для каждой переменной, для которой вычисляется взвешенное среднее, добавляется в список. Я думаю, что список будет лучшим вариантом, потому что, если все оценки находятся в одном и том же выводе, количество столбцов может быть бесконечным. Так что-то вроде этого:

[[1]]
   CLID  ITNUM         SATS        ASSETS       V1       V2       V3
1:  CNK  First       Always          0-10 11.66824 11.66819 11.66829
2:  CNK  First        Never       101-200 11.37378 12.21008 11.60182
3:  CNK  First    Sometimes        26-100 12.43004 13.13450 12.01330
4:  CNK Second       Always MORE THAN 200 12.32265 11.81613 12.56786
5:  CNK Second Amost always         11-25 10.76556 11.34669 10.52458

[[2]]
   CLID  ITNUM         SATS        ASSETS        V1        V2        V3
1:  CNK  First       Always          0-10  9.132899  9.060045  9.197005
2:  CNK  First        Never       101-200 12.896584 13.278680 13.000772
3:  CNK  First    Sometimes        26-100 10.972260 11.215390 10.828431
4:  CNK Second       Always MORE THAN 200 11.704404 11.611072 11.749586
5:  CNK Second Amost always         11-25  8.086409  8.225030  8.028928

Что я пробовал до сих пор:

  1. С помощью lapply

    all.weights <- c("WGT1", "WGT2", "WGT3")
    avg.vars <- c("AVGVALUE1", "AVGVALUE2")
    split.vars <- c("ITNUM", "SATS", "ASSETS")
    
    lapply(mydata, function(i) {
    mydata[ , Map(f = weighted.mean, x = mget(avg.vars)[i], w = mget(all.weights),
    na.rm = TRUE), by = c(key(mydata)[1], split.vars)]
    })
    
    Error in weighted.mean.default(x = dots[[1L]][[1L]], w = dots[[2L]][[1L]],  : 
     'x' and 'w' must have the same length
    
  2. С помощью mapply

    myfun <- function(data, spl.v, avg.v, wgts) {
      data[ , Map(f = weighted.mean, x = mget(avg.v), w = mget(all.weights),
      na.rm = TRUE), by = c(key(data)[1], spl.v)]
    }
    
    mapply(FUN = myfun, data = mydata, spl.v = split.vars, avg.v = avg.vars,
    wgts = all.weights)
    
    Error: value for ‘AVGVALUE2’ not found
    

Я пытался обернуть mget(avg.v) как список - .(mget(avg.v)), но затем получите еще одну ошибку:

 Error in mapply(FUN = f, ..., SIMPLIFY = FALSE) : 
  could not find function "." 

Может кто-нибудь помочь?

2 ответа

Решение

Я. lapply решение

all.weights <- c("WGT1", "WGT2", "WGT3")
avg.vars    <- c("AVGVALUE1", "AVGVALUE2")
split.vars  <- c("ITNUM", "SATS", "ASSETS")

myfun <- function(avg.vars){
  tmp <-
    mydata[ , Map(f = weighted.mean, 
                x = .(get(avg.vars)), 
                w = mget(all.weights),
                na.rm = TRUE), 
          by = c(key(mydata)[1], split.vars)]  

  return(tmp) # totally optional, a habit from using C and Java
}

lapply(avg.vars, myfun)

Up-сторон:

  • Использует * применить
  • Избегает петли
  • Намного быстрее, чем делать это один за другим

Down-сторона:

  • Возвращает список
[[1]]
   CLID  ITNUM         SATS        ASSETS       V1       V2       V3
1:  CNK  First       Always          0-10 11.66824 11.66819 11.66829
2:  CNK  First        Never       101-200 11.37378 12.21008 11.60182
3:  CNK  First    Sometimes        26-100 12.43004 13.13450 12.01330
4:  CNK Second       Always MORE THAN 200 12.32265 11.81613 12.56786
5:  CNK Second Amost always         11-25 10.76556 11.34669 10.52458

[[2]]
   CLID  ITNUM         SATS        ASSETS        V1        V2        V3
1:  CNK  First       Always          0-10  9.132899  9.060045  9.197005
2:  CNK  First        Never       101-200 12.896584 13.278680 13.000772
3:  CNK  First    Sometimes        26-100 10.972260 11.215390 10.828431
4:  CNK Second       Always MORE THAN 200 11.704404 11.611072 11.749586
5:  CNK Second Amost always         11-25  8.086409  8.225030  8.028928

II. for решение петли

Используя простой for цикл с примером где avg.vars имеет 2 значения:

all.weights <- c("WGT1", "WGT2", "WGT3")
avg.vars    <- c("AVGVALUE1", "AVGVALUE2")
split.vars  <- c("ITNUM", "SATS", "ASSETS")

result <- data.frame(matrix(nrow=0,ncol=7))
for(i in avg.vars){
  tmp <- 
    mydata[ , Map(f = weighted.mean, 
                x = .(get(i)), 
                w = mget(all.weights),
                na.rm = TRUE), 
          by = c(key(mydata)[1], split.vars)]  

  result <- rbind(result,tmp,use.names=F)
}
colnames(result) <- c("CLID", "ITNUM", "SATS", "ASSETS", "V1", "V2", "V3")
result
    CLID  ITNUM         SATS        ASSETS        V1        V2        V3
 1:  CNK  First       Always          0-10 11.668243 11.668192 11.668287
 2:  CNK  First        Never       101-200 11.373780 12.210083 11.601819
 3:  CNK  First    Sometimes        26-100 12.430039 13.134499 12.013299
 4:  CNK Second       Always MORE THAN 200 12.322651 11.816135 12.567860
 5:  CNK Second Amost always         11-25 10.765557 11.346688 10.524583
 6:  CNK  First       Always          0-10  9.132899  9.060045  9.197005
 7:  CNK  First        Never       101-200 12.896584 13.278680 13.000772
 8:  CNK  First    Sometimes        26-100 10.972260 11.215390 10.828431
 9:  CNK Second       Always MORE THAN 200 11.704404 11.611072 11.749586
10:  CNK Second Amost always         11-25  8.086409  8.225030  8.028928

Up-сторон:

  • Завершается мгновенно в примере
  • Масштабируется до любого количества столбцов без дополнительной обработки данных / кодирования
  • Сэкономит огромное количество времени на прохождение одного за другим
  • Возвращает хороший data.table
  • если вы действительно хотите список, вы можете получить это, инициализируя return как список (return <- list()), создавая переменную счетчика (n <- 1) затем замена rbind заявление с return[n] <- tmp и увеличивая счетчик (n <- n + 1) внутри цикла

Down-сторона:

  • Если ваши данные очень большие (например,> 100000 строк и десятки или более значений avg.var) тогда производительность любого цикла или функции, написанной с помощью циклов, будет плохой

Мы можем использовать outer (которая выполняет функцию для всех комбинаций значений в двух входных векторах), работая с векторизованной функцией взвешенных средних. Определив функцию, используемую outer в рамках таблицы данных, мы можем иметь get оцените столбцы data.table:

wmeans = mydata[, {
  f  = function(X,Y) weighted.mean(get(X), get(Y));
  vf = Vectorize(f);
  outer(avg.var, all.weights, vf)},
  by = split.vars]

Это помещает все средства в один столбец (то есть в "длинный" формат). Мы также можем добавить еще пару столбцов, чтобы указать, к какой комбинации "цена / вес" относится каждая:

wmeans[, mean.v := expand.grid(avg.var, all.weights)[,1]]       
wmeans[, mean.w := expand.grid(avg.var, all.weights)[,2]]
head(wmeans)
#    ITNUM   SATS ASSETS        V1    mean.v mean.w
# 1: First Always   0-10 11.668243 AVGVALUE1   WGT1
# 2: First Always   0-10  9.132899 AVGVALUE2   WGT1
# 3: First Always   0-10 11.668192 AVGVALUE1   WGT2
# 4: First Always   0-10  9.060045 AVGVALUE2   WGT2
# 5: First Always   0-10 11.668287 AVGVALUE1   WGT3
# 6: First Always   0-10  9.197005 AVGVALUE2   WGT3

Мы можем использовать dcast чтобы преобразовать это в data.table, который длинный в avg.var, но широкий в all.weights:

wide.wmeans = dcast(wmeans, mean.v+ITNUM+SATS+ASSETS ~ mean.w, value.var = "V1")  
#       mean.v  ITNUM         SATS        ASSETS      WGT1      WGT2      WGT3
# 1: AVGVALUE1  First       Always          0-10 11.668243 11.668192 11.668287
# 2: AVGVALUE1  First        Never       101-200 11.373780 12.210083 11.601819
# 3: AVGVALUE1  First    Sometimes        26-100 12.430039 13.134499 12.013299
# 4: AVGVALUE1 Second       Always MORE THAN 200 12.322651 11.816135 12.567860
# 5: AVGVALUE1 Second Amost always         11-25 10.765557 11.346688 10.524583
# 6: AVGVALUE2  First       Always          0-10  9.132899  9.060045  9.197005
# 7: AVGVALUE2  First        Never       101-200 12.896584 13.278680 13.000772
# 8: AVGVALUE2  First    Sometimes        26-100 10.972260 11.215390 10.828431
# 9: AVGVALUE2 Second       Always MORE THAN 200 11.704404 11.611072 11.749586
#10: AVGVALUE2 Second Amost always         11-25  8.086409  8.225030  8.028928

Если вам нужно это как список, а не как data.table, вы можете разделить его, используя

lapply(avg.var, function(x) wide.wmeans[mean.v == x])
# [[1]]
#       mean.v  ITNUM         SATS        ASSETS     WGT1     WGT2     WGT3
# 1: AVGVALUE1  First       Always          0-10 11.66824 11.66819 11.66829
# 2: AVGVALUE1  First        Never       101-200 11.37378 12.21008 11.60182
# 3: AVGVALUE1  First    Sometimes        26-100 12.43004 13.13450 12.01330
# 4: AVGVALUE1 Second       Always MORE THAN 200 12.32265 11.81613 12.56786
# 5: AVGVALUE1 Second Amost always         11-25 10.76556 11.34669 10.52458
# 
# [[2]]
#       mean.v  ITNUM         SATS        ASSETS      WGT1      WGT2      WGT3
# 1: AVGVALUE2  First       Always          0-10  9.132899  9.060045  9.197005
# 2: AVGVALUE2  First        Never       101-200 12.896584 13.278680 13.000772
# 3: AVGVALUE2  First    Sometimes        26-100 10.972260 11.215390 10.828431
# 4: AVGVALUE2 Second       Always MORE THAN 200 11.704404 11.611072 11.749586
# 5: AVGVALUE2 Second Amost always         11-25  8.086409  8.225030  8.028928
Другие вопросы по тегам