R cummax function with NA

Viewed 373

data

data=data.frame("person"=c(1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2),
                 "score"=c(1,2,1,2,3,1,3,NA,4,2,1,NA,2,NA,3,1,2,4),
                  "want"=c(1,2,1,2,3,3,3,3,4,2,1,1,2,2,3,3,3,4))

attempt

library(dplyr)

data = data %>%
  group_by(person) %>%
  mutate(wantTEST = ifelse(score >= 3 | (row_number() >= which.max(score == 3)), 
                        cummax(score), score), 
         wantTEST = replace(wantTEST, duplicated(wantTEST == 4) & wantTEST == 4, NA))

i am basically working to use the cummax function but only under specific circumstances. i want to keep any values (1-2-1-1) except if there is a 3 or 4 (1-2-1-3-2-1-4) should be (1-2-1-3-3-4). if there is NA value i want to carry forward previous value. thank you.

3 Answers

Here's one way with tidyverse. You may want to use fill() after group_by() but that's somewhat unclear.

data %>% 
  fill(score) %>% 
  group_by(person) %>% 
  mutate(
    w = ifelse(cummax(score) > 2, cummax(score), score)
  ) %>%
  ungroup()

# A tibble: 18 x 4
   person score  want     w
    <dbl> <dbl> <dbl> <dbl>
 1      1     1     1     1
 2      1     2     2     2
 3      1     1     1     1
 4      1     2     2     2
 5      1     3     3     3
 6      1     1     3     3
 7      1     3     3     3
 8      1     3     3     3
 9      1     4     4     4
10      2     2     2     2
11      2     1     1     1
12      2     1     1     1
13      2     2     2     2
14      2     2     2     2
15      2     3     3     3
16      2     1     3     3
17      2     2     3     3
18      2     4     4     4

One way to do this is to first fill NA values and then for each row check if anytime the score of 3 or more is passed in the group. If the score of 3 is reached till that point we take the max score until that point or else return the same score.

library(tidyverse)

data %>%
  fill(score) %>%
   group_by(person) %>%
   mutate(want1 = map_dbl(seq_len(n()), ~if(. >= which.max(score == 3))
                                    max(score[seq_len(.)]) else score[.]))

#   person score  want want1
#    <dbl> <dbl> <dbl> <dbl>
# 1      1     1     1     1
# 2      1     2     2     2
# 3      1     1     1     1
# 4      1     2     2     2
# 5      1     3     3     3
# 6      1     1     3     3
# 7      1     3     3     3
# 8      1     3     3     3
# 9      1     4     4     4
#10      2     2     2     2
#11      2     1     1     1
#12      2     1     1     1
#13      2     2     2     2
#14      2     2     2     2
#15      2     3     3     3
#16      2     1     3     3
#17      2     2     3     3
#18      2     4     4     4

Another way is to use accumulate from purrr. I use if_else_ from hablar for type stability:

library(tidyverse)
library(hablar)

data %>% 
  fill(score) %>% 
  group_by(person) %>% 
  mutate(wt = accumulate(score, ~if_else_(.x > 2, max(.x, .y), .y)))
Related