Durchlaufen Sie die Spalten, um die vorherigen 2 Zahlen jeder Zeile zu summieren

Aug 15 2020

In R habe ich einen Datenrahmen mit den Spalten 'A', 'B', 'C', 'D'. Die Spalten haben 100 Zeilen.

Ich muss durch die Spalten iterieren, um eine Berechnung für alle Zeilen im Datenrahmen durchzuführen, die die vorherigen 2 Zeilen dieser Spalte summiert, und dann in neuen Spalten ('AA', 'AB' usw.) festlegen, was diese Summe ist:

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

zu

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

Kann jemand erklären, wie man eine Funktion/Schleife erstellt, mit der ich die Spalten festlegen kann, über die ich iterieren möchte (ausgewählte Spalten, nicht alle Spalten) und die Spalten, die ich festlegen möchte?

Antworten

2 DarrenTsai Aug 15 2020 at 17:50

Ein baseEinzeiler:

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

Wenn Ihre Daten groß sind, ist diese möglicherweise die schnellste, da sie die gesamte data.frame.


Eine dplyrLösung mutate()mit 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

Wenn Sie die vorherigen 3 Zeilen summieren möchten, sollte das zweite Argument von across(), dh .fns, sein

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

was der Verwendung von rollsum()in entspricht zoo:

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

Benchmark

Ein Benchmark-Test mit microbenchmarkeinem neuen Paket data.framemit 10000 Zeilen und 100 Spalten und 10-maliger Bewertung jedes Ausdrucks.

# 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

Sie können dplyr's verwenden across(und optionale Namen festlegen) mit fortlaufender Summe (wie z. B. in implementiert zoo):

library(dplyr)
library(zoo)

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

Ausgabe:

  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

Mit A:Dhaben wir den Bereich von Spaltennamen angegeben, auf den wir die Funktion anwenden möchten. Die Annahme oben im Argument ist, dass Sie als Präfix und den Spaltennamen ( ) .nameszusammenfügen möchten .A{col}

1 pseudospin Aug 15 2020 at 19:34

Hier ist eine data.table-Lösung. Wenn Sie danach fragen, können Sie auswählen, auf welche Spalten Sie es anwenden möchten, anstatt nur auf alle Spalten.

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: Dies ist 2- oder 3-mal schneller als die schnellste andere Antwort.

1 ivan866 Aug 15 2020 at 17:59

Das ist ein naiver Ansatz mit verschachtelten forSchleifen. Passen Sie auf, dass es verdammt langsam ist, wenn Sie Hunderttausende Zeilen durchlaufen.

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

Das ist eine cumsumMethode:

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

Benchmark:

#    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  

Ich glaube, die Cumsum-Methode verschwendet die meiste Zeit an df-Zuweisungen. Bei korrekter Anpassung an das data.table-Backend könnte es das schnellste sein.

1 G.Grothendieck Aug 16 2020 at 03:14

Geben Sie die gewünschten Spalten an. Wir zeigen verschiedene Möglichkeiten, dies zu tun. Verwenden Sie dann rollsumr, um die gewünschten Spalten zu erhalten, legen Sie die Spaltennamen fest und cbind DFdamit.

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)

Geben:

  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

Notiz

Die Eingabe in reproduzierbarer Form:

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