Calculare for each row the percentage of a variable by another in data.table

Viewed 76

I'm looking for a way to optimize an aggregate in data.table, I have several million data and my current implementation is slow.

Reproducible example:

library(data.table)
df <- data.table(Factor = as.factor(rep(LETTERS[1:3], 3)),
                 Variable = 1:9)

Current implementation:

aux <- df[, .(sumVar = sum(Variable)/sum(df$Variable)), by = .(Factor)]
df[aux, sumVar := sumVar, on = .(Factor = Factor)]

Desired output:

> df
   Factor Variable    sumVar
1:      A        1 0.2666667
2:      B        2 0.3333333
3:      C        3 0.4000000
4:      A        4 0.2666667
5:      B        5 0.3333333
6:      C        6 0.4000000
7:      A        7 0.2666667
8:      B        8 0.3333333
9:      C        9 0.4000000

I think my problem is in the merge, but I do not know how to improve it, I am not familiar with dplyr and I have not found any way to do the operation in one step with data.table.

Any help is appreciated!

3 Answers

There's a lot of repetition in your example correctly, so not sure I interpreted it. Nonetheless, it seems best to just calculate the denominator once and using gsum:

BigTotal <- df[, sum(Variable)]
df[, sumVar1 := sum(Variable), by = .(Factor)][, propVar := sumVar1 / BigTotal]

Around half the time of Ben's fastest solution.

df <- data.table(
  Factor = as.factor(sample(LETTERS, size = 10^8, replace = T)),
  Variable = sample(10^3, size = 10^8, replace = T)
)

microbenchmark::microbenchmark(dt1 = {
  aux <- df[, .(sumVar = sum(Variable)/sum(df$Variable)), keyby = .(Factor)]
  df[aux, sumVar := sumVar, on = .(Factor = Factor)]
},
dt2 = {
BigTotal <- df[, sum(Variable)]
df[, sumVar1 := sum(Variable), by = .(Factor)][, propVar := sumVar1 / BigTotal]
}, 
times = 2)


Unit: seconds
 expr      min       lq     mean   median       uq      max neval cld
  dt1 9.523696 9.523696 9.567555 9.567555 9.611414 9.611414     2   b
  dt2 3.996581 3.996581 4.521274 4.521274 5.045967 5.045967     2  a 

something like

df[ , ':='(sumVar = sum(Variable)/sum(df$Variable)), by = .(Factor)] 

What kind of data do you have and timing do you expect? On the following example with 100M rows, I get the following timings

library(data.table)

df <- data.table(
  Factor = as.factor(sample(LETTERS, size = 10^8, replace = T)),
  Variable = sample(10^3, size = 10^8, replace = T)
)

# data.table solution 1
system.time({
  aux <- df[, .(sumVar = sum(Variable)/sum(df$Variable)), by = .(Factor)]
  df[aux, sumVar := sumVar, on = .(Factor = Factor)]
})  # ~10.5 seconds

# data.table solution 2
system.time({
  df[, sumVar := sum(Variable)/sum(df$Variable), by = Factor]
})  # ~8.3 seconds

# dplyr solution 1
system.time({
  df %>% dplyr::group_by(Factor) %>% dplyr::mutate(A=sum(Variable)/sum(df$Variable))
})  # ~10.0 seconds

Note that the speedup of data.table becomes more impressive as the cardinality of Factor increases..

df <- data.table(
  Factor = as.factor(sample(as.character(10^6), size = 10^8, replace = T)),
  Variable = as.numeric(sample(10^3, size = 10^8, replace = T))
)

# data.table solution 1
system.time({
  aux <- df[, .(sumVar = sum(Variable)/sum(df$Variable)), by = .(Factor)]
  df[aux, sumVar := sumVar, on = .(Factor = Factor)]
})  # ~5.0 seconds

# data.table solution 2
system.time({
  df[, sumVar := sum(Variable)/sum(df$Variable), by = Factor]
})  # ~3.1 seconds

# dplyr solution 1
system.time({
  df %>% dplyr::group_by(Factor) %>% dplyr::mutate(A=sum(Variable)/sum(df$Variable))
})  # ~6.9 seconds
Related