Parcourez les colonnes pour additionner les 2 nombres précédents de chaque ligne

Aug 15 2020

Dans R, j'ai un dataframe, avec les colonnes 'A', 'B', 'C', 'D'. Les colonnes ont 100 lignes.

Je dois parcourir les colonnes pour effectuer un calcul pour toutes les lignes de la trame de données qui additionne les 2 lignes précédentes de cette colonne, puis définir dans de nouvelles colonnes ("AA", "AB", etc.) quelle est cette somme :

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

à

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

Quelqu'un peut-il expliquer comment créer une fonction/boucle qui me permet de définir les colonnes sur lesquelles je souhaite itérer (colonnes sélectionnées, pas toutes les colonnes) et les colonnes que je souhaite définir ?

Réponses

2 DarrenTsai Aug 15 2020 at 17:50

Un baseone-liner :

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

Si vos données sont volumineuses, celle-ci pourrait être la plus rapide car elle manipule l'intégralité du fichier data.frame.


Une dplyrsolution utilisant mutate()avec 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

Si vous souhaitez additionner les 3 lignes précédentes, le deuxième argument de across(), c'est-à-dire .fns, doit être

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

ce qui équivaut à l'utilisation de rollsum()dans zoo:

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

Référence

Un test de référence avec microbenchmarkpackage sur un nouveau data.frameavec 10000 lignes et 100 colonnes et évaluer chaque expression 10 fois.

# 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

Vous pouvez utiliser dplyr's across(et définir des noms facultatifs) avec une somme glissante (comme implémenté, par exemple dans zoo):

library(dplyr)
library(zoo)

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

Production:

  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

Avec A:Dnous avons spécifié la plage de noms de colonnes à laquelle nous voulons appliquer la fonction. L'hypothèse ci-dessus dans .namesl'argument est que vous souhaitez coller ensemble Acomme préfixe et le nom de la colonne ( {col}).

1 pseudospin Aug 15 2020 at 19:34

Voici une solution data.table. Comme vous le demandez, il vous permet de sélectionner les colonnes auxquelles vous souhaitez l'appliquer plutôt que pour toutes les colonnes.

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'est 2 ou 3 fois plus rapide que l'autre réponse la plus rapide.

1 ivan866 Aug 15 2020 at 17:59

C'est une approche naïve avec des forboucles imbriquées. Attention, c'est sacrément lent si vous parcourez des centaines de milliers de lignes.

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

C'est une cumsumméthode :

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

Référence:

#    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  

Je crois que la plupart du temps, la méthode cumsum gaspille des allocations df. S'il est correctement adapté au backend data.table, il pourrait être le plus rapide.

1 G.Grothendieck Aug 16 2020 at 03:14

Spécifiez les colonnes que nous voulons. Nous montrons plusieurs façons différentes de le faire. Utilisez ensuite rollsumrpour obtenir les colonnes souhaitées, définissez les noms de colonne et reliez-les DFavec.

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)

donnant:

  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

Noter

L'entrée sous forme reproductible :

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