Đánh số hàng trong các nhóm trong khung dữ liệu


163

Làm việc với khung dữ liệu tương tự như sau:

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 

Tôi đang cố gắng thêm một cột với đánh số trong mỗi nhóm. Làm theo cách này rõ ràng là không sử dụng quyền hạn của 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  

Điều gì sẽ là một cách tốt để làm điều này?


1
Tôi sẽ đề nghị thêm một cái gì đó như "seq dọc theo cấp độ" hoặc "đếm dọc bản sao" trong tiêu đề câu hỏi vì đây là cách tôi tìm thấy câu hỏi này và đó chính xác là những gì tôi đang tìm kiếm
crazysantaclaus

2
@crazysantaclaus Nếu đó là tiêu đề, tôi sẽ không tìm thấy thứ mà tôi đang tìm kiếm :-( Tôi thực sự đang tìm kiếm "cách đánh số hàng trong các nhóm trong khung dữ liệu"
Zimano

Câu trả lời:


280

Sử dụng ave, ddply, dplyrhoặc data.table:

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

hoặc là:

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

hoặc là:

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

hoặc (hiệu quả nhất về bộ nhớ, vì nó chỉ định bởi tham chiếu trong DT):

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

DT[, id := seq_len(.N), by = cat]
DT[, id := rowid(cat)]

2
Có thể đáng nói đến việc aveđưa ra một float thay vì int ở đây. Thay phiên, có thể thay đổi df$valđể seq_len(nrow(df)). Tôi vừa chạy vào đây: stackoverflow.com/questions/42796857/ Kẻ
Frank

1
Điều thú vị là data.tablegiải pháp này dường như nhanh hơn so với sử dụng 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 28/07/17

4
Cảm ơn! Các dplyrgiải pháp là tốt. Nhưng nếu, giống như tôi, bạn tiếp tục gặp phải những lỗi kỳ lạ khi thử phương pháp này, hãy đảm bảo rằng bạn không bị xung đột giữa plyrdplyrnhư được giải thích trong bài đăng này Có thể tránh được bằng cách gọi rõ ràngdplyr::mutate(...)
EcologyTom

2
một data.tablephương pháp khác làsetDT(df)[, id:=rleid(val), by=.(cat)]
chin gió12

Làm thế nào để sửa đổi library(plyr)library(dplyr)trả lời để làm cho cột val xếp hạng theo thứ tự giảm dần?
Przemyslaw Remin

26

Để làm điều này câu hỏi đầy đủ hơn, một thay thế cơ sở R với sequencerle:

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

cung cấp kết quả dự định:

> 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

Nếu df$catlà một biến nhân tố, bạn cần phải bọc nó as.charactertrước:

df$num <- sequence(rle(as.character(df$cat))$lengths)

Chỉ cần lưu ý, giải pháp này yêu cầu catcột được sắp xếp?
zx8754

@ zx8754 có, trừ khi bạn muốn đánh số theo các lần xuất hiện liên tiếp củacat
Jaap

9

Đây là một tùy chọn sử dụng forvòng lặp theo nhóm thay vì theo hàng (như OP đã làm)

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

9

Đây là một mẹo cải tiến nhỏ cho phép sắp xếp 'val' trong các nhóm:

# 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())

Bạn có thể không sắp xếp sau nhóm_by?
zcoleman

6

Tôi muốn thêm một data.tablebiến thể bằng cách sử dụng rank()hàm cung cấp khả năng bổ sung để thay đổi thứ tự và do đó làm cho nó linh hoạt hơn một chút so với seq_len()giải pháp và khá giống với các hàm row_number trong 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),]

5

Một dplyrkhả năng khác có thể là:

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

3
Trong một số trường hợp thay vì 1:n()sử dụng seq_len(n())sẽ an toàn hơn, trong trường hợp trong chuỗi thao tác của bạn, bạn có một tình huống n()có thể quay lại 0, bởi vì 1:0cung cấp cho bạn một vectơ có độ dài hai trong khi seq_len(0)cho vectơ có độ dài bằng 0, do đó tránh được lỗi không khớp chiều dài mutate().
Brian Stamper

0

Sử dụng rowid()chức năng trong 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

1
Cám ơn câu trả lời của bạn, nhưng nó có vẻ là đã được đề cập trong đề nghị cuối cùng trong @ mnel của câu trả lời
eli-k
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.