What is the best way to apply a function to a range of values from another column in R data.frame so it remains vectorized?

Viewed 117

I have several columns in R data.frame, and I want to create a new column based on ranges of values from some already existing column. Those ranges are not regular and are determined by start and end values written in first two columns. I want the calculation to remain vectorized. I don't want a for loop underneath.
required result, achieved with a for loop:

df = data.frame(start=c(2,1,4,4,1), end=c(3,3,5,4,2), values=c(1:5))
for (i in 1:nrow(df)) {
  df[i, 'new'] <- sum(df[df[i, 'start']:df[i, 'end'], 'values'])
}
df
2 Answers

here is an option with map2

library(purrr)
library(dplyr)
df %>%
     mutate(new = map2_dbl(start, end, ~ sum(values[.x:.y])))

-output

#  start end values new
#1     2   3      1   5
#2     1   3      2   6
#3     4   5      3   9
#4     4   4      4   4
#5     1   2      5   3

Or with rowwise

df %>% 
    rowwise %>% 
    mutate(new =sum(.$values[start:end])) %>%
    ungroup

-output

# A tibble: 5 x 4
#  start   end values   new
#  <dbl> <dbl>  <int> <int>
#1     2     3      1     5
#2     1     3      2     6
#3     4     5      3     9
#4     4     4      4     4
#5     1     2      5     3

Or using data.table

library(data.table)
setDT(df)[, new := sum(df$values[start:end]), seq_len(nrow(df))]

Here is a base R one-liner.

mapply(function(x1, x2, y){sum(y[x1:x2])}, df[['start']], df[['end']], MoreArgs = list(y = df[['values']]))
#[1] 5 6 9 4 3

And another one.

sapply(seq_len(nrow(df)), function(i) sum(df[['values']][df[i, 'start']:df[i, 'end']]))
#[1] 5 6 9 4 3
Related