Calculate sum of a column if the difference between consecutive rows meets a condition

时间秒杀一切 提交于 2020-01-24 13:33:38

问题


This is a continued question from the post Remove the first row from each group if the second row meets a condition

Below is a sample dataset:

df <- data.frame(id=c("9","9","9","5","5","4","4","4","4","4","20","20"),
       Date=c("11/29/2018","11/29/2018","11/29/2018","2/13/2019","2/13/2019",
       "6/15/2018","6/20/2018","8/17/2018","8/20/2018","8/23/2018","12/25/2018","12/25/2018"), 
Buyer= c("John","John","John","Maria","Maria","Sandy","Sandy","Sandy","Sandy","Sandy","Paul","Paul"), 
Amount= c("959","1158","596","922","922","1849","4193","4256","65","100","313","99"), stringsAsFactors = F) %>% 
group_by(Buyer,id) %>% mutate(diffs = c(NA, diff(as.Date(Date, format = "%m/%d/%Y")))) 

which would look like:

| id |    Date    | Buyer | diff | Amount |
|----|:----------:|------:|------|--------|
| 9  | 11/29/2018 |  John | NA   | 959    |
| 9  | 11/29/2018 |  John | 0    | 1158   |
| 9  | 11/29/2018 |  John | 0    | 596    |
| 5  | 2/13/2019  | Maria | 76   | 922    |
| 5  | 2/13/2019  | Maria | 0    | 922    |
| 4  | 6/15/2018  | Sandy | -243 | 1849   |
| 4  | 6/20/2018  | Sandy | 5    | 4193   |
| 4  | 8/17/2018  | Sandy | 58   | 4256   |
| 4  | 8/20/2018  | Sandy | 3    | 65     |
| 4  | 8/23/2018  | Sandy | 3    | 100    |
| 20 | 12/25/2018 | Paul  | 124  | 313    |
| 20 | 12/25/2018 | Paul  | 0    | 99     |

I need to retain those records where based on each buyer and id, the sum of amount between consecutive rows >5000 if the difference between two consecutive rows <=5. So, for example, Buyer 'Sandy' with id '4' has two transactions of 1849 and 4193 on '6/15/2018' and '6/20/2018' within a gap of 5 days, and since the sum of these two amounts>5000, the output would have these records. Whereas, for the same Buyer 'Sandy' with id '4' has another transactions of 4256, 65 and 100 on '8/17/2018', '8/20/2018' and '8/23/2018' within a gap of 3 days each, but the output will not have these records as the sum of this amount <5000. The final output would look like:

| id |    Date   | Buyer | diff | Amount |
|----|:---------:|------:|------|--------|
| 4  | 6/15/2018 | Sandy | -243 | 1849   |
| 4  | 6/20/2018 | Sandy | 5    | 4193   |

回答1:


df <- data.frame(id=c("9","9","9","5","5","4","4","4","4","4","20","20"),
                 Date=c("11/29/2018","11/29/2018","11/29/2018","2/13/2019","2/13/2019",
                        "6/15/2018","6/20/2018","8/17/2018","8/20/2018","8/23/2018","12/25/2018","12/25/2018"), 
                 Buyer= c("John","John","John","Maria","Maria","Sandy","Sandy","Sandy","Sandy","Sandy","Paul","Paul"), 
                 Amount= c("959","1158","596","922","922","1849","4193","4256","65","100","313","99"), stringsAsFactors = F) %>% 
  group_by(Buyer,id) %>% mutate(diffs = c(NA, diff(as.Date(Date, format = "%m/%d/%Y")))) 

Changing Date from character to Date and Amount from character to numeric:

df$Date<-as.Date(df$Date, '%m/%d/%y')
df$Amount<-as.numeric(df$Amount)

Now here I group the dataset by id, arrange it with Date, and create a rank within each id (so for example Sandy is going to have rank from 1 through 5 for 5 different days in which she has shopped), then I define a new variable called ConsecutiveSum which adds the Value of each row to it's previous row's Value (lag gives you the previous row). The ifelse statement forces consecutive sum to output a 0 if the previous row's Value doesn't exists. The next step is just enforcing your conditions:

df %>%
  group_by(id) %>%
    arrange(Date) %>%
      mutate(rank=dense_rank(Date)) %>% 
        mutate(ConsecutiveSum = ifelse(is.na(lag(Amount)),0,Amount  + lag(Amount , default = 0)))%>%
         filter(diffs<=5 & ConsecutiveSum>=5000 | ConsecutiveSum==0 & lead(ConsecutiveSum)>=5000)


# id    Date      Buyer Amount diffs  rank ConsecutiveSum
#   <chr> <chr>     <chr>  <dbl> <dbl> <int>          <dbl>
# 1 4     6/15/2018 Sandy   1849    NA     1              0
# 2 4     6/20/2018 Sandy   4193     5     2           6042



回答2:


I would use a combination of techniques available in tidyverse:

First create a grouping variable (new_id) and use the original id and new_id in combination to add together based on a grouping. Then we can filter by the criteria of the sum of the Amount > 5000. We can take this and filter then join or semi_join to filter based on the criteria.

ids is a dataset that finds the total Amount based on id and new_id and filters for when Dollars > 5000. This gives you the id and new_id that meets your criteria

df <- data.frame(id=c("9","9","9","5","5","4","4","4","4","4","20","20"),
                 Date=c("11/29/2018","11/29/2018","11/29/2018","2/13/2019","2/13/2019",
                        "6/15/2018","6/20/2018","8/17/2018","8/20/2018","8/23/2018","12/25/2018","12/25/2018"), 
                 Buyer= c("John","John","John","Maria","Maria","Sandy","Sandy","Sandy","Sandy","Sandy","Paul","Paul"), 
                 Amount= c(959,1158,596,922,922,1849,4193,4256,65,100,313,99), stringsAsFactors = F) %>% 
  group_by(Buyer,id) %>% mutate(diffs = c(NA, diff(as.Date(Date, format = "%m/%d/%Y")))) 


library(tidyverse)

df1 <- df %>% mutate(Date      = as.Date(Date , format = "%m/%d/%Y"), 
                     tf1       = (id != lag(id, default = 0)),
                     tf2       = (is.na(diffs) | diffs > 5))

df1$new_id <- cumsum(df1$tf1 + df1$tf2 > 0)

>df1
       id    Date       Buyer Amount diffs days_post  tf1   tf2   new_id
       <chr> <date>     <chr>  <dbl> <dbl> <date>     <lgl> <lgl>  <int>
     1 9     2018-11-29 John     959    NA 2018-12-04 TRUE  TRUE       1
     2 9     2018-11-29 John    1158     0 2018-12-04 FALSE FALSE      1
     3 9     2018-11-29 John     596     0 2018-12-04 FALSE FALSE      1
     4 5     2019-02-13 Maria    922    NA 2019-02-18 TRUE  TRUE       2
     5 5     2019-02-13 Maria    922     0 2019-02-18 FALSE FALSE      2
     6 4     2018-06-15 Sandy   1849    NA 2018-06-20 TRUE  TRUE       3
     7 4     2018-06-20 Sandy   4193     5 2018-06-25 FALSE FALSE      3
     8 4     2018-08-17 Sandy   4256    58 2018-08-22 FALSE TRUE       4
     9 4     2018-08-20 Sandy     65     3 2018-08-25 FALSE FALSE      4
    10 4     2018-08-23 Sandy    100     3 2018-08-28 FALSE FALSE      4
    11 20    2018-12-25 Paul     313    NA 2018-12-30 TRUE  TRUE       5
    12 20    2018-12-25 Paul      99     0 2018-12-30 FALSE FALSE      5

ids <- df1 %>% 
       group_by(id, new_id) %>% 
       summarise(dollar = sum(Amount)) %>% 
       ungroup() %>% filter(dollar > 5000)
  id   new_id  dollar
 <chr>  <int>   <dbl>
1 4         3    6042
df1 %>% semi_join(ids)


来源:https://stackoverflow.com/questions/57928394/calculate-sum-of-a-column-if-the-difference-between-consecutive-rows-meets-a-con

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!