Penomoran baris dalam grup dalam bingkai data

163

Bekerja dengan bingkai data yang mirip dengan ini:

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 

Saya mencoba menambahkan kolom dengan penomoran dalam setiap grup. Melakukannya dengan cara ini jelas tidak menggunakan kekuatan 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  

Apa cara yang baik untuk melakukan ini?

eli-k
sumber
1
Saya akan menyarankan untuk menambahkan sesuatu seperti "seq sepanjang level" atau "menghitung bersama ulangan" dalam judul pertanyaan karena ini adalah bagaimana saya menemukan pertanyaan ini dan itulah yang saya cari
crazysantaclaus
2
@crazysantaclaus Jika itu judulnya, saya tidak akan menemukan apa yang saya cari :-( Saya benar-benar mencari "bagaimana cara menghitung baris dalam grup dalam kerangka data"
Zimano

Jawaban:

280

Gunakan ave, ddply, dplyratau data.table:

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

atau:

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

atau:

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

atau (memori paling efisien, karena diberikan oleh referensi di dalam DT):

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

DT[, id := seq_len(.N), by = cat]
DT[, id := rowid(cat)]
mnel
sumber
2
Mungkin layak disebutkan bahwa avememberikan pelampung bukan int di sini. Bergantian, bisa berubah df$valmenjadi seq_len(nrow(df)). Saya baru saja menemukan ini di sini: stackoverflow.com/questions/42796857/…
Frank
1
Menariknya data.tablesolusi ini tampaknya lebih cepat daripada menggunakan 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
Terima kasih! The dplyrsolusi yang baik. Tetapi jika, seperti saya, Anda terus mendapatkan kesalahan aneh ketika mencoba pendekatan ini, pastikan bahwa Anda tidak mendapatkan konflik di antara plyrdan dplyrseperti yang dijelaskan dalam posting ini. Hal ini dapat dihindari dengan menelepon secara eksplisitdplyr::mutate(...)
EcologyTom
2
data.tableMetode lain adalahsetDT(df)[, id:=rleid(val), by=.(cat)]
chinsoon12
Bagaimana cara memodifikasi library(plyr)dan library(dplyr)menjawab untuk membuat kolom val peringkat dalam urutan menurun?
Przemyslaw Remin
26

Untuk membuat ini pertanyaan lebih lengkap, alternatif R dasar dengan sequencedan rle:

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

yang memberikan hasil yang diinginkan:

> 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

Jika df$catmerupakan variabel faktor, Anda harus membungkusnya as.characterterlebih dahulu:

df$num <- sequence(rle(as.character(df$cat))$lengths)
Jaap
sumber
Baru diperhatikan, solusi ini membutuhkan catkolom untuk diurutkan?
zx8754
@ zx8754 ya, kecuali jika Anda ingin memberi nomor secara berurutancat
Jaap
9

Berikut adalah opsi menggunakan forloop oleh grup bukan oleh baris (seperti OP lakukan)

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

Berikut ini adalah trik perbaikan kecil yang memungkinkan sort 'val' di dalam grup:

# 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
sumber
Bisakah Anda mengurutkan setelah group_by?
zcoleman
6

Saya ingin menambahkan data.tablevarian menggunakan rank()fungsi yang memberikan kemungkinan tambahan untuk mengubah urutan dan dengan demikian membuatnya sedikit lebih fleksibel daripada seq_len()solusi dan sangat mirip dengan fungsi row_number di 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
sumber
5

dplyrKemungkinan lain bisa:

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
sumber
3
Dalam beberapa kasus, alih-alih 1:n()menggunakan seq_len(n())lebih aman, jika dalam urutan operasi Anda memiliki situasi di mana n()mungkin kembali 0, karena 1:0memberi Anda panjang dua vektor sementara seq_len(0)memberikan panjang vektor nol, sehingga menghindari kesalahan panjang ketidakcocokan dengan mutate().
Brian Stamper
0

Menggunakan rowid()fungsi di 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
sumber
1
Terima kasih atas jawaban Anda tetapi tampaknya sudah tercakup dalam saran terakhir dalam jawaban @ mnel
eli-k