30 votes

Méthode efficace pour rechercher des suites répétées de lignes, supprimer et compter

J'ai un ensemble de données avec des lignes répétitives. Je veux supprimer les répétitions consécutives et les compter, mais uniquement si elles sont consécutives. Je cherche un moyen efficace de le faire. Je ne vois pas comment dans dplyr ou data.table .

MWE

 dat <- data.frame(
    x = c(6, 2, 3, 3, 3, 1, 1, 6, 5, 5, 6, 6, 5, 4),
    y = c(7, 5, 7, 7, 7, 5, 5, 7, 1, 2, 7, 7, 1, 7),
    z = c(rep(LETTERS[1:2], each=7))
)

##        x     y     z
## 1      6     7     A
## 2      2     5     A
## 3      3     7     A
## 4      3     7     A
## 5      3     7     A
## 6      1     5     A
## 7      1     5     A
## 8      6     7     B
## 9      5     1     B
## 10     5     2     B
## 11     6     7     B
## 12     6     7     B
## 13     5     1     B
## 14     4     7     B
 

Sortie désirée

        x     y     z   n
1      6     7     A   1
2      2     5     A   1
3      3     7     A   3
4      1     5     A   2
5      6     7     B   1
6      5     1     B   1
7      5     2     B   1
8      6     7     B   2
9      5     1     B   1 
10     4     7     B   1
 

26voto

Frank Points 51885

Avec data.table:

 library(data.table)
setDT(dat)

dat[, c(.SD[1L], .N), by=.(g = rleidv(dat))][, g := NULL]

    x y z N
 1: 6 7 A 1
 2: 2 5 A 1
 3: 3 7 A 3
 4: 1 5 A 2
 5: 6 7 B 1
 6: 5 1 B 1
 7: 5 2 B 1
 8: 6 7 B 2
 9: 5 1 B 1
10: 4 7 B 1
 

15voto

Frank Points 51885

Semblable à la réponse de Ricky, voici une autre solution de base:

 with(rle(do.call(paste, dat)), cbind(dat[ cumsum(lengths), ], lengths))
 

Si paste ne le coupe pas pour les classes de colonnes que vous avez, vous pouvez le faire

 ud     = unique(dat)
ud$r   = seq_len(nrow(ud))
dat$r0 = seq_len(nrow(dat))
newdat = merge(dat, ud)

with(rle(newdat[order(newdat$r0), ]$r), cbind(dat[cumsum(lengths), ], lengths))
 

... Bien que je devine qu'il y a une meilleure façon.

11voto

alistaire Points 5898

Avec dplyr, vous pouvez emprunter data.table::rleid de faire une ID d'exécution de la colonne, puis utilisez n pour compter les lignes et unique de annulerait répète:

dat %>% group_by(run = data.table::rleid(x, y, z)) %>%  mutate(n = n()) %>% 
    distinct() %>% ungroup() %>% select(-run)

Vous pouvez remplacer rleid avec seulement de la base de R, si vous voulez, mais il n'est pas joli:

dat %>% group_by(run = rep(seq_along(rle(paste(x, y, z))$len), 
                           times = rle(paste(x, y, z))$len)) %>%  
    mutate(n = n()) %>% distinct() %>% ungroup() %>% select(-run)

De toute façon, vous obtenez:

Source: local data frame [10 x 4]

       x     y      z     n
   (dbl) (dbl) (fctr) (int)
1      6     7      A     1
2      2     5      A     1
3      3     7      A     3
4      1     5      A     2
5      6     7      B     1
6      5     1      B     1
7      5     2      B     1
8      6     7      B     2
9      5     1      B     1
10     4     7      B     1

Modifier

Par @Frank commentaire, vous pouvez également utiliser summarise pour insérer n et de l'effondrement au lieu de mutate et unique si vous group_by toutes les variables que vous souhaitez conserver avant d' run, comme summarise s'effondre le dernier groupe. Un avantage de cette approche est que vous n'avez pas à ungroup pour se débarrasser de l' run, comme summarise fait pour vous:

dat %>% group_by(x, y, z, run = data.table::rleid(x, y, z)) %>% 
    summarise(n = n()) %>% select(-run)

10voto

Ricky Points 2518

Une solution de base ci-dessous

 idx <- rle(with(dat, paste(x, y, z)))
d <- cbind(do.call(rbind, strsplit(idx$values, " ")), idx$lengths)
as.data.frame(d)  

   V1 V2 V3 V4
1   6  7  A  1
2   2  5  A  1
3   3  7  A  3
4   1  5  A  2
5   6  7  B  1
6   5  1  B  1
7   5  2  B  1
8   6  7  B  2
9   5  1  B  1
10  4  7  B  1
 

7voto

Jota Points 13441

Si vous avez un grand ensemble de données, vous pouvez utiliser une idée similaire à la solution data.table de Frank, mais évitez d'utiliser .SD comme ceci:

 dat[, g := rleidv(dat)][, N := .N, keyby = g
   ][J(unique(g)), mult = "first"
   ][, g := NULL
   ][]
 

C'est moins lisible, et il s'avère que c'est plus lent aussi. La solution de Frank est plus rapide et plus lisible.

 # benchmark on 14 million rows
dat <- data.frame(
    x = rep(c(6, 2, 3, 3, 3, 1, 1, 6, 5, 5, 6, 6, 5, 4), 1e6),
    y = rep(c(7, 5, 7, 7, 7, 5, 5, 7, 1, 2, 7, 7, 1, 7), 1e6),
    z = rep(c(rep(LETTERS[1:2], each=7)), 1e6)
)

setDT(dat)
d1 <- copy(dat)
d2 <- copy(dat)
 

Avec R 3.2.4 et data.table 1.9.7 (sur l’ordinateur de Frank):

 system.time(d1[, c(.SD[1L], .N), by=.(g = rleidv(d1))][, g := NULL])
#    user  system elapsed 
#    0.42    0.10    0.52 
system.time(d2[, g := rleidv(d2)][, N := .N, keyby = g][J(unique(g)), mult = "first"][, g := NULL][])
#    user  system elapsed 
#    2.48    0.25    2.74 
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X