6
votes

Comptage du nombre de lignes depuis la dernière observation qui remplit la condition

Mon Data Frame ressemble aux trois premières colonnes de cet exemple:

id    obs   value   newCol
a     1     uncool  NA
a     2     cool    1
a     3     uncool  NA
a     4     uncool  NA
a     5     cool    2
a     6     uncool  NA
a     7     cool    1
a     8     uncool  NA
b     1     cool    0

Ce dont j'ai besoin est une colonne ( newCol ci-dessus) qui compte le nombre de "non refroidis" entre les observations avec la valeur "cool" ou la première ligne du groupe (regroupées par id).

Comment puis-je faire cela (en utilisant dplyr idéalement)?


0 commentaires

4 Réponses :


1
votes

Outre id , vous avez besoin d'une autre variable de regroupement, donnée par grp = cumsum (dat $ value == "cool") - (dat $ value == "cool") code > qui est illustré ci-dessous.

Ensuite, vous pouvez utiliser mutate où nous attribuons sum (value == "uncool") aux observations où value == " cool " et NA sinon dans chaque groupe.

dat <- structure(list(id = c("a", "a", "a", "a", "a", "a", "a", "a", 
"b"), obs = c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 1L), value = c("uncool", 
"cool", "uncool", "uncool", "cool", "uncool", "cool", "uncool", 
"cool"), newCol = c(NA, 1L, NA, NA, 2L, NA, 1L, NA, 0L)), .Names = c("id", 
"obs", "value", "newCol"), class = "data.frame", row.names = c(NA, 
-9L))

library(dplyr)
dat %>%
  group_by(id, grp = cumsum(dat$value == "cool") - (dat$value == "cool")) %>% 
  mutate(newCool = if_else(value == "cool", sum(value == "uncool"), NA_integer_))
# A tibble: 9 x 6
# Groups:   id, grp [5]
  id      obs value  newCol   grp newCool
  <chr> <int> <chr>   <int> <int>   <int>
1 a         1 uncool     NA     0      NA
2 a         2 cool        1     0       1
3 a         3 uncool     NA     1      NA
4 a         4 uncool     NA     1      NA
5 a         5 cool        2     1       2
6 a         6 uncool     NA     2      NA
7 a         7 cool        1     2       1
8 a         8 uncool     NA     3      NA
9 b         1 cool        0     3       0


0 commentaires

1
votes

Ecrire une fonction simple pour résoudre votre problème:

# Running function
library(dplyr)
data <- data %>%
  group_by(id) %>%
  mutate(newCol = cool_counter(value))

# Results
data
  id      obs value  newCol
  <chr> <dbl> <chr>   <dbl>
1 a         1 uncool     NA
2 a         2 cool        1
3 a         3 uncool     NA
4 a         4 uncool     NA
5 a         5 cool        2
6 a         6 uncool     NA
7 a         7 cool        1
8 a         8 uncool     NA
9 b         1 cool       NA

Cela donne:

# Your data
data <- data.frame(id = c("a", "a", "a", "a", "a", "a" ,"a" ,"a", "b"), 
                   obs = c(1,2,3,4,5,6,7,8,1), 
                   value = c("uncool", "cool", "uncool", "uncool", "cool", "uncool" ,"cool" ,"uncool", "cool"), 
                   stringsAsFactors = FALSE)

# Function for solving problem      
cool_counter <- function(vector) {
  uncool <- FALSE
  count <- 0
  results <- list()

  for(i in 1:length(vector)) {
    if(i == 1) {
      uncool <- vector[i] == "uncool"
      results[[i]] <- NA

      if(uncool) {
        count <- 1
      }
    }
    if(i > 1) {
      uncool <- vector[i] == "uncool"
      if(uncool) {
        count <- count + 1
        results[[i]] <- NA
      }
      if(!uncool) {
        results[[i]] <- count
        count <- 0
      }
    }
  }

  return(unlist(results))
} 


0 commentaires

1
votes

Nous pouvons créer une fonction d'assistance qui regroupera la valeur basée sur cool / uncool , et compter les cool s, c'est-à-dire

# A tibble: 9 x 5
  id      obs value  newCol   new
  <fct> <int> <fct>   <int> <int>
1 a         1 uncool     NA    NA
2 a         2 cool        1     1
3 a         3 uncool     NA    NA
4 a         4 uncool     NA    NA
5 a         5 cool        2     2
6 a         6 uncool     NA    NA
7 a         7 cool        1     1
8 a         8 uncool     NA    NA
9 b         1 cool        0     0

qui donne,

library(tidyverse)

f1 <- function(x) {
    i1 <- which(x == 'cool')
    v1 <- rep(seq_along(i1), c(i1[1], diff(i1)))
    if (tail(x, 1) != 'cool') {
        return(c(v1, tail(v1, 1) + 1))
    } else {
        return(v1)
    }
}

df %>% 
 group_by(id) %>% 
 mutate(new_grp = f1(value)) %>% 
 group_by(id, new_grp) %>% 
 mutate(new = length(value[value != 'cool']), 
        new = replace(new, value != 'cool', NA)) %>% 
 ungroup() %>% 
 select(-new_grp)


0 commentaires

1
votes

Nous pouvons définir des groupes en faisant un cumsum en commençant par le bas, puis utiliser ave pour construire un vecteur pour chaque groupe:

dat %>%
  group_by(id,temp = rev(cumsum(rev(value=="cool")))) %>%
  mutate(newCol = ifelse(value=="cool", n()-1, NA)) %>%
  ungroup() %>%
  select(-temp)
# # A tibble: 9 x 4
# id   obs  value newCol
#   <chr> <int>  <chr>  <dbl>
# 1     a     1 uncool     NA
# 2     a     2   cool      1
# 3     a     3 uncool     NA
# 4     a     4 uncool     NA
# 5     a     5   cool      2
# 6     a     6 uncool     NA
# 7     a     7   cool      1
# 8     a     8 uncool     NA
# 9     b     1   cool      0

Avec dplyr :

transform(dat, newCol = ave(
  value, id, rev(cumsum(rev(value=="cool"))),
  FUN = function(x) ifelse(x=="cool", length(x)-1, NA)))
#   id obs  value newCol
# 1  a   1 uncool   <NA>
# 2  a   2   cool      1
# 3  a   3 uncool   <NA>
# 4  a   4 uncool   <NA>
# 5  a   5   cool      2
# 6  a   6 uncool   <NA>
# 7  a   7   cool      1
# 8  a   8 uncool   <NA>
# 9  b   1   cool      0


0 commentaires