Numerando linhas dentro de grupos em um quadro de dados

163

Trabalhando com um quadro de dados semelhante 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 

Estou tentando adicionar uma coluna com numeração dentro de cada grupo. Fazer dessa maneira obviamente não está usando os poderes do 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  

Qual seria uma boa maneira de fazer isso?

eli-k
fonte
1
Gostaria de sugerir que adicionar algo como "seq ao longo níveis" ou "contagem ao longo repetições" no título da pergunta como este é como eu encontrei esta questão e é exatamente o que eu estava procurando
crazysantaclaus
2
@crazysantaclaus Se esse fosse o título, eu não teria encontrado o que estava procurando :-( Eu estava literalmente procurando "como numerar linhas dentro de grupos em um quadro de dados"
Zimano

Respostas:

280

Use ave, ddply, dplyrou data.table:

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

ou:

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

ou:

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

ou (o mais eficiente em memória, conforme designado por referência dentro DT):

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

DT[, id := seq_len(.N), by = cat]
DT[, id := rowid(cat)]
mnel
fonte
2
Vale a pena mencionar que avefornece um float em vez de um int aqui. Como alternativa, pode mudar df$valpara seq_len(nrow(df)). Eu apenas corri para este aqui: stackoverflow.com/questions/42796857/...
Frank
1
Curiosamente esta data.tablesolução parece ser mais rápido do 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
4
Obrigado! A dplyrsolução é boa. Mas se, como eu, você foi ficando erros estranhos ao tentar essa abordagem, certifique-se de que você não está recebendo os conflitos entre plyre dplyrcomo explicado neste post Ela pode ser evitada por explicitamente chamandodplyr::mutate(...)
EcologyTom
2
outro data.tablemétodo ésetDT(df)[, id:=rleid(val), by=.(cat)]
chinsoon12
Como modificar library(plyr)e library(dplyr)respostas para tornar a coluna val de classificação em ordem decrescente?
Przemyslaw Remin
26

Por fazer isso pergunta mais completa, uma alternativa R básica com sequencee rle:

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

que fornece o resultado pretendido:

> 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

Se df$caté uma variável de fator, você precisa envolvê-la as.characterprimeiro:

df$num <- sequence(rle(as.character(df$cat))$lengths)
Jaap
fonte
Acabei de notar, esta solução requer que a catcoluna seja classificada?
Zx8754
@ zx8754 sim, a menos que queira número de ocorrências consecutivas decat
Jaap
9

Aqui está uma opção usando um forloop por grupos e não por linhas (como o OP fez)

for (i in unique(df$cat)) df$num[df$cat == i] <- seq_len(sum(df$cat == i))
alittleboy
fonte
9

Aqui está um pequeno truque de melhoria que permite classificar 'val' dentro dos 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
fonte
Você não pode classificar após o group_by?
Zcoleman 9/01/19
6

Gostaria de adicionar uma data.tablevariante usando a rank()função que fornece a possibilidade adicional de alterar a ordem e, assim, a torna um pouco mais flexível que a seq_len()solução e é bastante semelhante às funções row_number no 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
fonte
5

Outra dplyrpossibilidade poderia 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
fonte
3
Em alguns casos, em vez de 1:n()usar, seq_len(n())é mais seguro, caso sua sequência de operações tenha uma situação em que n()possa retornar 0, porque 1:0fornece um vetor de comprimento dois e, ao mesmo tempo, seq_len(0)um vetor de comprimento zero, evitando assim um erro de incompatibilidade de comprimento mutate().
Brian Stamper
0

Usando a rowid()função em 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
fonte
1
Obrigado pela sua resposta, mas parece que já foi abordado na última sugestão na resposta do @ mnel
eli-k