Numeración de filas dentro de grupos en un marco de datos

163

Trabajar con un marco de datos similar a este:

set.seed(100)  
df <- data.frame(cat = c(rep("aaa", 5), rep("bbb", 5), rep("ccc", 5)), val = runif(15))             
df <- df[order(df$cat, df$val), ]  
df  

   cat        val  
1  aaa 0.05638315  
2  aaa 0.25767250  
3  aaa 0.30776611  
4  aaa 0.46854928  
5  aaa 0.55232243  
6  bbb 0.17026205  
7  bbb 0.37032054  
8  bbb 0.48377074  
9  bbb 0.54655860  
10 bbb 0.81240262  
11 ccc 0.28035384  
12 ccc 0.39848790  
13 ccc 0.62499648  
14 ccc 0.76255108  
15 ccc 0.88216552 

Estoy tratando de agregar una columna con numeración dentro de cada grupo. Hacerlo de esta manera obviamente no está usando los poderes de R:

 df$num <- 1  
 for (i in 2:(length(df[,1]))) {  
   if (df[i,"cat"]==df[(i-1),"cat"]) {  
     df[i,"num"]<-df[i-1,"num"]+1  
     }  
 }  
 df  

   cat        val num  
1  aaa 0.05638315   1  
2  aaa 0.25767250   2  
3  aaa 0.30776611   3  
4  aaa 0.46854928   4  
5  aaa 0.55232243   5  
6  bbb 0.17026205   1  
7  bbb 0.37032054   2  
8  bbb 0.48377074   3  
9  bbb 0.54655860   4  
10 bbb 0.81240262   5  
11 ccc 0.28035384   1  
12 ccc 0.39848790   2  
13 ccc 0.62499648   3  
14 ccc 0.76255108   4  
15 ccc 0.88216552   5  

¿Cuál sería una buena manera de hacer esto?

eli-k
fuente
1
Yo sugeriría que añadir algo como "ss" a lo largo de los niveles de "contar lo largo de réplicas" o en el título de la pregunta ya que esto es lo que encontramos esta pregunta y es exactamente lo que estaba buscando
crazysantaclaus
2
@crazysantaclaus Si ese fuera el título, no habría encontrado lo que estaba buscando :-( Estaba literalmente buscando "cómo numerar filas dentro de grupos en un marco de datos"
Zimano

Respuestas:

280

Uso ave, ddply, dplyro data.table:

df$num <- ave(df$val, df$cat, FUN = seq_along)

o:

library(plyr)
ddply(df, .(cat), mutate, id = seq_along(val))

o:

library(dplyr)
df %>% group_by(cat) %>% mutate(id = row_number())

o (la memoria más eficiente, ya que se asigna por referencia dentro DT):

library(data.table)
DT <- data.table(df)

DT[, id := seq_len(.N), by = cat]
DT[, id := rowid(cat)]
mnel
fuente
2
Vale la pena mencionar que aveda un flotador en lugar de un int aquí. Alternativamente, podría cambiar df$vala seq_len(nrow(df)). Me encontré con esto por aquí: stackoverflow.com/questions/42796857/…
Frank
1
Curiosamente, esta data.tablesolución parece ser más rápida que usar frank: library(microbenchmark); microbenchmark(a = DT[, .(val ,num = frank(val)), by = list(cat)] ,b =DT[, .(val , id = seq_len(.N)), by = list(cat)] , times = 1000L)
hannes101
44
¡Gracias! La dplyrsolucion es buena. Pero si, como yo, seguiste recibiendo errores extraños al probar este enfoque, asegúrate de no tener conflictos plyry, dplyrcomo se explica en esta publicación , se puede evitar llamando explícitamentedplyr::mutate(...)
EcologyTom
2
otro data.tablemétodo essetDT(df)[, id:=rleid(val), by=.(cat)]
chinsoon12
¿Cómo modificar library(plyr)y library(dplyr)responder para hacer la columna val de clasificación en orden descendente?
Przemyslaw Remin
26

Por hacer esto pregunta más completa, una alternativa base R con sequencey rle:

df$num <- sequence(rle(df$cat)$lengths)

que da el resultado deseado:

> df
   cat        val num
4  aaa 0.05638315   1
2  aaa 0.25767250   2
1  aaa 0.30776611   3
5  aaa 0.46854928   4
3  aaa 0.55232243   5
10 bbb 0.17026205   1
8  bbb 0.37032054   2
6  bbb 0.48377074   3
9  bbb 0.54655860   4
7  bbb 0.81240262   5
13 ccc 0.28035384   1
14 ccc 0.39848790   2
11 ccc 0.62499648   3
15 ccc 0.76255108   4
12 ccc 0.88216552   5

Si df$cates una variable de factor, as.characterprimero debe envolverla :

df$num <- sequence(rle(as.character(df$cat))$lengths)
Jaap
fuente
Acabo de notar, esta solución requiere catcolumna para ser ordenado?
zx8754
@ zx8754 sí, a menos que desee numerar por ocurrencias consecutivas decat
Jaap
9

Aquí hay una opción que usa un forbucle por grupos en lugar de por filas (como lo hizo OP)

for (i in unique(df$cat)) df$num[df$cat == i] <- seq_len(sum(df$cat == i))
un pequeño chico
fuente
9

Aquí hay un pequeño truco de mejora que permite ordenar 'val' dentro de los grupos:

# 1. Data set
set.seed(100)
df <- data.frame(
  cat = c(rep("aaa", 5), rep("ccc", 5), rep("bbb", 5)), 
  val = runif(15))             

# 2. 'dplyr' approach
df %>% 
  arrange(cat, val) %>% 
  group_by(cat) %>% 
  mutate(id = row_number())
Andrii
fuente
¿No puedes ordenar después de group_by?
zcoleman el
6

Me gustaría agregar una data.tablevariante usando la rank()función que proporciona la posibilidad adicional de cambiar el orden y, por lo tanto, la hace un poco más flexible que la seq_len()solución y es bastante similar a las funciones row_number en RDBMS.

# Variant with ascending ordering
library(data.table)
dt <- data.table(df)
dt[, .( val
   , num = rank(val))
    , by = list(cat)][order(cat, num),]

    cat        val num
 1: aaa 0.05638315   1
 2: aaa 0.25767250   2
 3: aaa 0.30776611   3
 4: aaa 0.46854928   4
 5: aaa 0.55232243   5
 6: bbb 0.17026205   1
 7: bbb 0.37032054   2
 8: bbb 0.48377074   3
 9: bbb 0.54655860   4
10: bbb 0.81240262   5
11: ccc 0.28035384   1
12: ccc 0.39848790   2
13: ccc 0.62499648   3
14: ccc 0.76255108   4

# Variant with descending ordering
dt[, .( val
   , num = rank(-val))
    , by = list(cat)][order(cat, num),]
hannes101
fuente
5

Otra dplyrposibilidad podría ser:

df %>%
 group_by(cat) %>%
 mutate(num = 1:n())

   cat      val   num
   <fct>  <dbl> <int>
 1 aaa   0.0564     1
 2 aaa   0.258      2
 3 aaa   0.308      3
 4 aaa   0.469      4
 5 aaa   0.552      5
 6 bbb   0.170      1
 7 bbb   0.370      2
 8 bbb   0.484      3
 9 bbb   0.547      4
10 bbb   0.812      5
11 ccc   0.280      1
12 ccc   0.398      2
13 ccc   0.625      3
14 ccc   0.763      4
15 ccc   0.882      5
tmfmnk
fuente
3
En algunos casos, en lugar de 1:n()usar seq_len(n())es más seguro, en el caso de que en su secuencia de operaciones tenga una situación en la que n()podría regresar 0, porque 1:0le da un vector de longitud dos mientras que seq_len(0)da un vector de longitud cero, evitando así un error de desajuste de longitud con mutate().
Brian Stamper
0

Usando la rowid()función en data.table:

> set.seed(100)  
> df <- data.frame(cat = c(rep("aaa", 5), rep("bbb", 5), rep("ccc", 5)), val = runif(15))
> df <- df[order(df$cat, df$val), ]  
> df$num <- data.table::rowid(df$cat)
> df
   cat        val num
4  aaa 0.05638315   1
2  aaa 0.25767250   2
1  aaa 0.30776611   3
5  aaa 0.46854928   4
3  aaa 0.55232243   5
10 bbb 0.17026205   1
8  bbb 0.37032054   2
6  bbb 0.48377074   3
9  bbb 0.54655860   4
7  bbb 0.81240262   5
13 ccc 0.28035384   1
14 ccc 0.39848790   2
11 ccc 0.62499648   3
15 ccc 0.76255108   4
12 ccc 0.88216552   5
AKRosenblad
fuente
1
Gracias por su respuesta, pero parece que ya está cubierto en la última sugerencia en la respuesta de @ mnel
eli-k