Lặp lại các cột để tính tổng 2 số trước đó của mỗi hàng

Aug 15 2020

Trong R, tôi có một khung dữ liệu, với các cột 'A', 'B', 'C', 'D'. Các cột có 100 hàng.

Tôi cần phải lặp qua các cột để thực hiện phép tính cho tất cả các hàng trong khung dữ liệu tính tổng 2 hàng trước đó của cột đó, sau đó đặt trong các cột mới ('AA', 'AB', v.v.) tổng đó là:

A  B  C  D 
1  2  3  4
2  3  4  5
3  4  5  6
4  5  6  7 
5  6  7  8
6  7  8  9

đến

A   B   C   D   AA   AB   AC   AD
1   2   3   4   NA   NA   NA   NA
2   3   4   5   3    5    7    9
3   4   5   6   5    7    9    11
4   5   6   7   7    9    11   13
5   6   7   8   9    11   13   15
6   7   8   9   11   13   15   17

Ai đó có thể giải thích cách tạo một hàm / vòng lặp cho phép tôi đặt các cột tôi muốn lặp lại (các cột đã chọn, không phải tất cả các cột) và các cột tôi muốn đặt không?

Trả lời

2 DarrenTsai Aug 15 2020 at 17:50

Một baselớp lót:

cbind(df, setNames(df + df[c(NA, 1:(nrow(df)-1)), ], paste0("A", names(df))))

Nếu dữ liệu của bạn lớn, dữ liệu này có thể là nhanh nhất vì nó thao tác toàn bộ data.frame.


Một dplyrgiải pháp sử dụng mutate()với across().

library(dplyr)

df %>%
  mutate(across(A:D,
                ~ .x + lag(.x),
                .names = "A{col}"))

#   A B C D AA AB AC AD
# 1 1 2 3 4 NA NA NA NA
# 2 2 3 4 5  3  5  7  9
# 3 3 4 5 6  5  7  9 11
# 4 4 5 6 7  7  9 11 13
# 5 5 6 7 8  9 11 13 15
# 6 6 7 8 9 11 13 15 17

Nếu bạn muốn tính tổng 3 hàng trước đó, thì đối số thứ hai của across(), tức là .fns, phải là

~ .x + lag(.x) + lag(.x, 2)

tương đương với việc sử dụng rollsum()trong zoo:

~ zoo::rollsum(.x, k = 3, fill = NA, align = 'right')

Điểm chuẩn

Một bài kiểm tra điểm chuẩn với microbenchmarkgói trên mới data.framevới 10000 hàng và 100 cột và đánh giá mỗi biểu thức trong 10 lần.

# Unit: milliseconds
#                     expr        min         lq       mean     median         uq        max neval
#              darren_base   18.58418   20.88498   35.51341   33.64953   39.31909   80.24725    10
#         darren_dplyr_lag   39.49278   40.27038   47.26449   42.89170   43.20267   76.72435    10
# arg0naut91_dplyr_rollsum  436.22503  482.03199  524.54800  516.81706  534.94317  677.64242    10
#    Grothendieck_rollsumr 3423.92097 3611.01573 3650.16656 3622.50895 3689.26404 4060.98054    10
1 arg0naut91 Aug 15 2020 at 17:49

Bạn có thể sử dụng dplyr's across(và đặt tên tùy chọn) với tổng cuộn (như được triển khai, ví dụ: trong zoo):

library(dplyr)
library(zoo)

df %>%
  mutate(
    across(
      A:D,
      ~ rollsum(., k = 2, fill = NA, align = 'right'), 
      .names = 'A{col}'
    )
  )

Đầu ra:

  A B C D AA AB AC AD
1 1 2 3 4 NA NA NA NA
2 2 3 4 5  3  5  7  9
3 3 4 5 6  5  7  9 11
4 4 5 6 7  7  9 11 13
5 5 6 7 8  9 11 13 15
6 6 7 8 9 11 13 15 17

Với việc A:Dchúng tôi đã chỉ định phạm vi tên cột mà chúng tôi muốn áp dụng hàm. Giả định ở trên trong .namesđối số là bạn muốn dán cùng nhau Adưới dạng tiền tố và tên cột ( {col}).

1 pseudospin Aug 15 2020 at 19:34

Đây là một giải pháp data.table. Khi bạn yêu cầu, nó cho phép bạn chọn những cột bạn muốn áp dụng nó thay vì chỉ cho tất cả các cột.

library(data.table)
x <- data.table(A=1:6, B=2:7, C=3:8, D=4:9)
selected_cols <- c('A','B','D')
new_cols <- paste0("A",selected_cols)
x[, (new_cols) := lapply(.SD, function(col) col+shift(col, 1)), .SDcols = selected_cols]
x[]

NB Câu trả lời này nhanh hơn gấp 2 hoặc 3 lần so với câu trả lời nhanh nhất khác.

1 ivan866 Aug 15 2020 at 17:59

Đó là một cách tiếp cận ngây thơ với các forvòng lặp lồng nhau . Hãy coi chừng nó rất chậm nếu bạn lặp đi lặp lại hàng trăm nghìn hàng.

i <- 1
n <- 5
df <- data.frame(A=i:(i+n), B=(i+1):(i+n+1), C=(i+2):(i+n+2), D=(i+3):(i+n+3))
for (col in colnames(df)) {
  for (ind in 1:nrow(df)) {
    if (ind-1==0) {next}
    s <- sum(df[c(ind-1, ind), col])
    df[ind, paste0('S', col)] <- s
  }
}  

Đó là một cumsumphương pháp:

na.df <- data.frame(matrix(NA, 2, ncol(df)))
colnames(na.df) <- colnames(df)
cs1 <- cumsum(df)
cs2 <- rbind(cs1[-1:-2,], na.df)
sum.diff <- cs2-cs1
cbind(df, rbind(na.df[1,], cs1[2,], sum.diff[1:(nrow(sum.diff)-2),]))  

Điểm chuẩn:

#    Unit: milliseconds  
#                      expr     min       lq     mean   median       uq     max neval  
#          darrentsai.rbind 11.5623 12.28025 23.38038 16.78240 20.83420 91.9135   100  
#     darrentsai.rbind.rev1  8.8267  9.10945 15.63652  9.54215 14.25090 62.6949   100  
#             pseudopsin.dt  7.2696  7.52080 20.26473 12.61465 17.61465 69.0110   100  
#            ivan866.cumsum 25.3706 30.98860 43.11623 33.78775 37.36950 91.6032   100  

Tôi tin rằng, hầu hết thời gian phương pháp cumsum lãng phí vào việc phân bổ df. Nếu được điều chỉnh một cách chính xác với phần phụ trợ data.table, nó có thể là nhanh nhất.

1 G.Grothendieck Aug 16 2020 at 03:14

Chỉ định các cột chúng tôi muốn. Chúng tôi chỉ ra một số cách khác nhau để làm điều đó. Sau đó, sử dụng rollsumrđể có được các cột mong muốn, đặt tên cột và gắn DFkết với nó.

library(zoo)

# jx <- names(DF)  # if all columns wanted
# jx <- sapply(DF, is.numeric)  # if all numeric columns
# jx <- c("A", "B", "C", "D")  # specify columns by name
jx <- 1:4   # specify columns by position

r <- rollsumr(DF[jx], 2, fill = NA)
colnames(r) <- paste0("A", colnames(r))
cbind(DF, r)

cho:

  A B C D AA AB AC AD
1 1 2 3 4 NA NA NA NA
2 2 3 4 5  3  5  7  9
3 3 4 5 6  5  7  9 11
4 4 5 6 7  7  9 11 13
5 5 6 7 8  9 11 13 15
6 6 7 8 9 11 13 15 17

Ghi chú

Đầu vào ở dạng có thể tái tạo:

DF <- structure(list(A = 1:6, B = 2:7, C = 3:8, D = 4:9), 
  class = "data.frame", row.names = c(NA, -6L))