简体   繁体   中英

Is there a way in R to make an ifelse on defined consecutive rows?

If I have:

df<-data.frame(group=c(1, 1,1, 1,1, 2, 2, 2, 4,4,4,4), 
              value=c("A","B","C","B","A","A","A","B","D","A","A","B"))

I want to make an ifelse statement or equivalent for whether any "3 in a row" starting from the first row within a group has certain values. So for example, starting in group 1 I want to scan ABC, then BCB, then CBA, and maybe making a 'want' column of if 'C' shows in every scan or not. Something like this:


  group value want_any_c want_any_b
1      1     A        yes        yes
2      1     B        yes        yes
3      1     C        yes        yes
4      1     B        yes        yes
5      1     A        yes        yes
6      2     A         no        yes
7      2     A         no        yes
8      2     B         no        yes
9      4     D         no        yes
10     4     A         no        yes
11     4     A         no        yes
12     4     B         no        yes

follow up: I want to also see if EVERY scan of 3 contains a value, starting from the first row in a group then the second group etc. (ie group 1 scan ABC, BCB, CBA, group 2 scan AAB, and group 4 scan DAA, AAB.) (ty akrun):

  group value want_any_c want_any_b want_every_c want_every_b
1      1     A        yes        yes          yes          yes
2      1     B        yes        yes          yes          yes
3      1     C        yes        yes          yes          yes
4      1     B        yes        yes          yes          yes
5      1     A        yes        yes          yes          yes
6      2     A         no        yes           no          yes
7      2     A         no        yes           no          yes
8      2     B         no        yes           no          yes
9      4     D         no        yes           no           no
10     4     A         no        yes           no           no
11     4     A         no        yes           no           no
12     4     B         no        yes           no           no

We can use any or %in%

library(dplyr)
df %>% 
   group_by(group) %>%
   mutate(want_any_c = c('no', 'yes')[('C' %in% value) + 1],
           want_any_b = c('no', 'yes')[('B' %in% value) + 1])
# A tibble: 12 x 4
# Groups:   group [3]
#   group value want_any_c want_any_b
#   <dbl> <fct> <chr>      <chr>     
# 1     1 A     yes        yes       
# 2     1 B     yes        yes       
# 3     1 C     yes        yes       
# 4     1 B     yes        yes       
# 5     1 A     yes        yes       
# 6     2 A     no         yes       
# 7     2 A     no         yes       
# 8     2 B     no         yes       
# 9     4 D     no         yes       
#10     4 A     no         yes       
#11     4 A     no         yes       
#12     4 B     no         yes       

If it is every scan of 3 values, create another group with gl

library(zoo)
df %>%
 group_by(group) %>%
  mutate(want_any_c = c('no', 'yes')[('C' %in% value) + 1],
        want_any_b = c('no', 'yes')[('B' %in% value) + 1],
        want_every_c = c('no', 'yes')[(all(rollapply(value, 3,
             FUN = function(x) 'C' %in% x))) + 1],
        want_every_b = c('no', 'yes')[(all(rollapply(value, 3, 
             FUN = function(x) 'B' %in% x))) + 1])
# A tibble: 12 x 6
# Groups:   group [3]
#   group value want_any_c want_any_b want_every_c want_every_b
#   <dbl> <fct> <chr>      <chr>      <chr>        <chr>       
# 1     1 A     yes        yes        yes          yes         
# 2     1 B     yes        yes        yes          yes         
# 3     1 C     yes        yes        yes          yes         
# 4     1 B     yes        yes        yes          yes         
# 5     1 A     yes        yes        yes          yes         
# 6     2 A     no         yes        no           yes         
# 7     2 A     no         yes        no           yes         
# 8     2 B     no         yes        no           yes         
# 9     4 D     no         yes        no           no          
#10     4 A     no         yes        no           no          
#11     4 A     no         yes        no           no          
#12     4 B     no         yes        no           no          

As it is done on multiple values, a function would be more useful

f1 <- function(colNm, val){
          c('no', 'yes')[(val %in% {{colNm}}) + 1]
 }


f2 <- function(colNm, val){
        c('no', 'yes')[(all(rollapply({{colNm}}, 3, 
             FUN = function(x) val %in% x))) + 1]
 }

df %>%
    group_by(group) %>%
    mutate(want_any_c = f1(value, "C"), 
           want_any_b = f1(value, "B"),
           want_every_c = f2(value, "C"),
           want_every_b = f2(value, "B"))

Here's a data.table solution

library(zoo)
library(data.table)
setDT(df)

to_check <- c('C', 'B')

df[, paste0('want_any_', to_check) := lapply(to_check, '%in%', value),
   by = group]


df[, paste0('want_every_', to_check) := 
      lapply(to_check, function(x) all(rollapply(value, 3, '%in%', x = x))),
   by = group]

df
#     group value want_any_C want_any_B want_every_C want_every_B
#  1:     1     A       TRUE       TRUE         TRUE         TRUE
#  2:     1     B       TRUE       TRUE         TRUE         TRUE
#  3:     1     C       TRUE       TRUE         TRUE         TRUE
#  4:     1     B       TRUE       TRUE         TRUE         TRUE
#  5:     1     A       TRUE       TRUE         TRUE         TRUE
#  6:     2     A      FALSE       TRUE        FALSE         TRUE
#  7:     2     A      FALSE       TRUE        FALSE         TRUE
#  8:     2     B      FALSE       TRUE        FALSE         TRUE
#  9:     4     D      FALSE       TRUE        FALSE        FALSE
# 10:     4     A      FALSE       TRUE        FALSE        FALSE
# 11:     4     A      FALSE       TRUE        FALSE        FALSE
# 12:     4     B      FALSE       TRUE        FALSE        FALSE

Or as yes/no

want_cols <- grep('want', names(df), value = T)

df[,  (want_cols) := lapply(mget(want_cols), ifelse, 'yes', 'no')]

df
#     group value want_any_C want_any_B want_every_C want_every_B
#  1:     1     A        yes        yes          yes          yes
#  2:     1     B        yes        yes          yes          yes
#  3:     1     C        yes        yes          yes          yes
#  4:     1     B        yes        yes          yes          yes
#  5:     1     A        yes        yes          yes          yes
#  6:     2     A         no        yes           no          yes
#  7:     2     A         no        yes           no          yes
#  8:     2     B         no        yes           no          yes
#  9:     4     D         no        yes           no           no
# 10:     4     A         no        yes           no           no
# 11:     4     A         no        yes           no           no
# 12:     4     B         no        yes           no           no

If you have millions of rows the rollapply approach might be slow. I don't think it's necessarry, there's probably a solution in checking diff(which(value == 'C')) (which I can't figure out at the moment).

Here is a base R solution, where you first define function want as below

want <- function(v,key,f) {
    u <- sapply(seq(length(v)-2),function(k) key %in% v[k+0:2])
    switch (f,
            "any" = rep(ifelse(any(u),"Yes","No"),length(v)),
            "every" = rep(ifelse(all(u),"Yes","No"),length(v))
    )
}

and then you will get the desired output through the following code:

dfout <- cbind(df,do.call(rbind, c(make.row.names = F,
                                   lapply(split(df,df$group), function(v) data.frame(
                                       want_any_c = want(v$value,"C","any"),
                                       want_any_b = want(v$value,"B","any"),
                                       want_every_c = want(v$value,"C","every"),
                                       want_every_b = want(v$value,"B","every"))))))

such that

> dfout
   group value want_any_c want_any_b want_every_c want_every_b
1      1     A        Yes        Yes          Yes          Yes
2      1     B        Yes        Yes          Yes          Yes
3      1     C        Yes        Yes          Yes          Yes
4      1     B        Yes        Yes          Yes          Yes
5      1     A        Yes        Yes          Yes          Yes
6      2     A         No        Yes           No          Yes
7      2     A         No        Yes           No          Yes
8      2     B         No        Yes           No          Yes
9      4     D         No        Yes           No           No
10     4     A         No        Yes           No           No
11     4     A         No        Yes           No           No
12     4     B         No        Yes           No           No

Base R, but doesn't require hardcoding indvidual values as vectors, and matching them etc:

    # Create a group of each grouping var every three rows: 
    n = 3

    df$group2 <- paste0(df$group, 

                       " - ",

                       ave(rep(1:n, ceiling(nrow(df)/n)),

                           rep(1:n, ceiling(nrow(df)/n)), 

                           FUN = seq.int)[1:nrow(df)])


    # Row-wise concatenate the unique values per group: 

    values_by_group <- aggregate(value~group2, df, FUN =

                                   function(x){

                                     paste0(unique(sort(x)),

                                            collapse = ", ")})

 # Add a vector per each unique value in df's value vector: 

values_by_group <- cbind(values_by_group, 

      setNames(data.frame(matrix(NA, nrow = nrow(values_by_group), 

                        ncol = length(unique(df$value)))), 

               c(unique(sapply(df$value, as.character)))))

# Store a vector of indices of values_by_group table 
# matching the values in the original dataframe: 

vec_idx <- names(values_by_group) %in% unique(sapply(df$value, as.character))

# Match vector names with values in value vector:

values_by_group[,vec_idx] <- 

  t(vapply(strsplit(as.character(values_by_group$value), ', '),

         function(x){

           names(values_by_group)[c(vec_idx)] %in% x

           },

          logical(ncol(values_by_group)-sum(!(vec_idx)))

         )
      )


# Merge with the original dataframe, drop unwanted grouping vec:

final_df <- within(merge(df, 

                  values_by_group[,names(values_by_group) != "value"], 

                  by = "group2",

                  all.x = TRUE), rm("group2"))

Data:

df <- data.frame(group = c(1, 1,1, 1,1, 2, 2, 2, 4,4,4,4),
                value = c("A","B","C","B","A","A","A","B","D","A","A","B"))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM