Iterasi melalui kolom untuk menjumlahkan 2 angka sebelumnya dari setiap baris

Aug 15 2020

Di R, saya memiliki kerangka data, dengan kolom 'A', 'B', 'C', 'D'. Kolom memiliki 100 baris.

Saya perlu mengulang melalui kolom untuk melakukan perhitungan untuk semua baris di dataframe yang menjumlahkan 2 baris sebelumnya dari kolom itu, dan kemudian mengatur di kolom baru ('AA', 'AB', dll) berapa jumlahnya:

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

untuk

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

Adakah yang bisa menjelaskan cara membuat fungsi / loop yang memungkinkan saya untuk mengatur kolom yang ingin saya iterasi (kolom yang dipilih, tidak semua kolom) dan kolom yang ingin saya atur?

Jawaban

2 DarrenTsai Aug 15 2020 at 17:50

Satu basebaris:

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

Jika data Anda besar, ini mungkin yang tercepat karena memanipulasi keseluruhan data.frame.


Sebuah dplyrsolusi menggunakan mutate()dengan 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

Jika Anda ingin menjumlahkan 3 baris sebelumnya, argumen kedua across(), yaitu .fns, harus

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

yang setara dengan penggunaan rollsum()di zoo:

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

Tolok ukur

Uji tolok ukur dengan microbenchmarkpaket baru data.framedengan 10.000 baris dan 100 kolom dan mengevaluasi setiap ekspresi sebanyak 10 kali.

# 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

Anda dapat menggunakan dplyr's across(dan mengatur nama opsional) dengan rolling sum (seperti yang diterapkan misalnya dalam zoo):

library(dplyr)
library(zoo)

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

Keluaran:

  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

Dengan A:Dkami telah menentukan rentang nama kolom yang ingin kami terapkan fungsinya. Asumsi di atas dalam .namesargumen adalah Anda ingin menempelkan bersama Asebagai awalan dan nama kolom ( {col}).

1 pseudospin Aug 15 2020 at 19:34

Berikut solusi data.table. Saat Anda memintanya, ini memungkinkan Anda untuk memilih kolom mana yang ingin Anda terapkan daripada hanya untuk semua kolom.

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 Ini 2 atau 3 kali lebih cepat dari jawaban tercepat lainnya.

1 ivan866 Aug 15 2020 at 17:59

Itu adalah pendekatan yang naif dengan forloop bersarang . Berhati-hatilah, ini sangat lambat jika Anda akan mengulang lebih dari ratusan ribu baris.

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
  }
}  

Itu adalah cumsummetode:

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),]))  

Tolok ukur:

#    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  

Saya percaya, sebagian besar waktu metode cumsum membuang-buang alokasi df. Jika diadaptasi dengan benar ke backend data.table, ini bisa menjadi yang tercepat.

1 G.Grothendieck Aug 16 2020 at 03:14

Tentukan kolom yang kita inginkan. Kami menunjukkan beberapa cara berbeda untuk melakukan itu. Kemudian gunakan rollsumruntuk mendapatkan kolom yang diinginkan, atur nama kolom dan ikat DFdengannya.

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)

memberi:

  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

Catatan

Masukan dalam bentuk yang dapat direproduksi:

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