Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

conditional cumulative sum using dplyr

Tags:

r

dplyr

zoo

My dataframe looks like this and I want two separate cumulative columns, one for fund A and the other for fund B

Name    Event    SalesAmount   Fund    Cum-A(desired)    Cum-B(desired)
John    Webinar   NA            NA        NA               NA
John    Sale     1000           A        1000              NA
John    Sale     2000           B        1000             2000
John    Sale     3000           A        4000             2000
John    Email    NA             NA       4000             2000
Tom     Webinar   NA            NA        NA               NA
Tom     Sale     1000           A        1000              NA
Tom     Sale     2000           B        1000             2000
Tom     Sale     3000           A        4000             2000
Tom     Email    NA             NA       4000             2000

I have tried:
df<-
    df %>%
    group_by(Name)%>%  
    mutate(Cum-A = as.numeric(ifelse(Fund=="A",cumsum(SalesAmount),0)))%>%
    mutate(Cum-B = as.numeric(ifelse(Fund=="B",cumsum(SalesAmount),0)))

but it is totally not what I want as it shows me the runningtotal of both funds,albeit only on the row when the funds match. Kindly help.

like image 791
gibbz00 Avatar asked May 19 '15 22:05

gibbz00


3 Answers

How about:

library(dplyr)

d %>% 
  group_by(Name) %>% 
  mutate(cA=cumsum(ifelse(!is.na(Fund) & Fund=="A",SalesAmount,0))) %>% 
  mutate(cB=cumsum(ifelse(!is.na(Fund) & Fund=="B",SalesAmount,0)))

The output:

Source: local data frame [10 x 8]
Groups: Name

   Name   Event SalesAmount Fund Cum.A.desired. Cum.B.desired.   cA   cB
1  John Webinar          NA   NA             NA             NA    0    0
2  John    Sale        1000    A           1000             NA 1000    0
3  John    Sale        2000    B           1000           2000 1000 2000
4  John    Sale        3000    A           4000           2000 4000 2000
5  John   Email          NA   NA           4000           2000 4000 2000
6   Tom Webinar          NA   NA             NA             NA    0    0
7   Tom    Sale        1000    A           1000             NA 1000    0
8   Tom    Sale        2000    B           1000           2000 1000 2000
9   Tom    Sale        3000    A           4000           2000 4000 2000
10  Tom   Email          NA   NA           4000           2000 4000 2000

Zeroes in the resulting columns can be replaced by NA afterwards if needed:

result$cA[result$cA==0] <- NA
result$cB[result$cB==0] <- NA

Your input data set:

d <- structure(list(Name = structure(c(1L, 1L, 1L, 1L, 1L, 2L, 2L,     2L, 2L, 2L), .Label = c("John", "Tom"), class = "factor"), Event = structure(c(3L,     2L, 2L, 2L, 1L, 3L, 2L, 2L, 2L, 1L), .Label = c("Email", "Sale",     "Webinar"), class = "factor"), SalesAmount = c(NA, 1000L, 2000L,     3000L, NA, NA, 1000L, 2000L, 3000L, NA), Fund = structure(c(NA,     1L, 2L, 1L, NA, NA, 1L, 2L, 1L, NA), .Label = c("A", "B"), class = "factor"),         Cum.A.desired. = c(NA, 1000L, 1000L, 4000L, 4000L, NA, 1000L,         1000L, 4000L, 4000L), Cum.B.desired. = c(NA, NA, 2000L, 2000L,         2000L, NA, NA, 2000L, 2000L, 2000L)), .Names = c("Name",     "Event", "SalesAmount", "Fund", "Cum.A.desired.", "Cum.B.desired."    ), class = "data.frame", row.names = c(NA, -10L))
like image 171
Marat Talipov Avatar answered Oct 21 '22 15:10

Marat Talipov


Here's an approach generalizing to more funds, using zoo and data.table:

# prep
require(data.table)
require(zoo)
setDT(d)
d[,Fund:=as.character(Fund)]         # because factors are the worst
uf  <- unique(d[Event=="Sale"]$Fund) # collect set of funds

First, assign cumulative sales on the relevant subset of observations:

for (f in uf) d[(Event=="Sale"&Fund==f),paste0('c',f):=cumsum(SalesAmount),by=Name]

Then, carry the last observation forward:

d[,paste0('c',uf):=lapply(.SD,na.locf,na.rm=FALSE),.SDcols=paste0('c',uf),by=Name]
like image 3
Frank Avatar answered Oct 21 '22 13:10

Frank


You can shorten @Marat's answer slightly by rolling it all into a single mutate:

df %>%
  group_by(Name) %>% 
  mutate(
    cA = cumsum(ifelse(!is.na(Fund) & Fund == "A", SalesAmount, 0)),
    cB = cumsum(ifelse(!is.na(Fund) & Fund == "B", SalesAmount, 0)),
    cA = ifelse(cA == 0, NA, cA),
    cB = ifelse(cB == 0, NA, cB)
  )
like image 1
Matt Upson Avatar answered Oct 21 '22 15:10

Matt Upson