rowSums sur plusieurs colonnes d'une table de façon automatique

Postez ici vos questions, réponses, commentaires ou suggestions - Les sujets seront ultérieurement répartis dans les archives par les modérateurs

Modérateur : Groupe des modérateurs

Arnaud de Coninck
Messages : 24
Enregistré le : 17 Fév 2015, 13:31
Contact :

rowSums sur plusieurs colonnes d'une table de façon automatique

Messagepar Arnaud de Coninck » 15 Sep 2021, 18:55

Bonjour,
J'ai une table de 1024 lignes et plusieurs milliers de colonnes . Chaque colonne correspond à un spectre d'analyse pour un pas de mesure donné. Pour plus de facilité, j'ai simplifié le tout par la table suivante:

Code : Tout sélectionner

spe1 = c(1:5)
spe2 = c(1:5*2)
spe3 = c(1:5*3)
spe4 = c(1:5/2)
spe5 = c(1:5*2.6)
spe6= c(1:5/11)
spe7= c(1:5*6)
spe8= c(1:5-1.2)
spe9= c(1:5*3.3)
 x = data.table(spe1,spe2, spe3, spe4,spe5,spe6,spe7,spe8,spe9)
x


Le but de ma démarche est de pouvoir sommer et/ou moyenner (selon besoin) les colonnes avec un pas qui peut être changé au besoin. C'est-à-dire je voudrais qu'il soit possible par exemple de faire la somme des lignes des 3 premières colonnes, puis des 3 suivante, ...et ainsi de suite jusqu'à la fin. Mais j'aimerais avoir la possibilité de modifier le pas si besoin (ex: faire la somme des colonnes de 1à 5, 6 à 10 .....). Le résultats de la première somme me donne un fichier texte nommé spe1 qui est structuré comme suit:
spe1
channel content
1 0
2 2
3 10
4 1
5 23
........
Donc pour ma table "x" seront compilés dans un nouveau dossier les nouveaux fichiers issus des différentes somme de colonne: nommé spe1 (somme des colonnes 1 à 3), spe2 (somme des col de 4 à 6) , spe3 ( col 7 à 9 ).

J'arrive manuellement à avoir ce que je veux pour la première somme (ici ex: colonne 1 à 3), mais je n'arrive pas automatiser le tout pour que le calcul continu avec les autres colonnes.

Code : Tout sélectionner

spe1<-rowSums(x[,1:3]) ## somme 1-3 et création d'une df
dfspe1<-as.data.frame(spe1)
names(dfspe1) <- c("content") ## on renomme spe1 : content
colnames(dfspe1)

# Création d'un nouveau fichier spectre
channel=c(1:9) ## Nouvelle colonne pour channel numérotation des lignes de 1 à 9
spe1 <- cbind(channel, dfspe1)
names(spe1) <- c("channel","content")
colnames(spe1)


J'ai essayé avec apply, lapply et boucle for, mais j'ai souvent un message d'erreur m'indiquant que le nombre de dimension est incorrect, ou "x doit être un tableau ayant au moins 2 dimensions, etc . C'est une histoire de matrice ou de vecteur, mais je ne suis pas assez avancé pour voir quel est le problème, ni même trouver la bonne méthode.
Voici ce que j'ai essayé,

Code : Tout sélectionner

# Vecteur qui contiendra la somme de tous les éléments
spee <- rep(NA, length(x))
# Boucle sur la table
for (i in 1:ncol(x)) {
  spee [i] <- rowSums(x[i,1:3])
}



Si quelqu'un peut m'aider, ça serait fort apprécié.
Cordialement
Arnaud

Pierre-Yves Berrard
Messages : 1029
Enregistré le : 12 Jan 2016, 23:30

Re: rowSums sur plusieurs colonnes d'une table de façon automatique

Messagepar Pierre-Yves Berrard » 15 Sep 2021, 19:56

Bonjour,

Il y a un problème manifeste dans la boucle for : x[i,1:3]

Si i parcourt les colonnes, pourquoi le retrouve ton dans le crochet dans la sélection par ligne (à gauche de la virgule) ?
PY

Arnaud de Coninck
Messages : 24
Enregistré le : 17 Fév 2015, 13:31
Contact :

Re: rowSums sur plusieurs colonnes d'une table de façon automatique

Messagepar Arnaud de Coninck » 16 Sep 2021, 14:23

Bonjour,
En effet c'est mieux que i parcourt les lignes puisqu'il faut sommer les valeurs d'une même ligne dans les colonne 1, 2 et 3 .
Peut-être ceci serait mieux comme base de départ:

Code : Tout sélectionner

for (i in 1:NROW(x)) {
  spe [i] <- rowSums(x[i,1:3])
}


Je voudrais aussi que R lance le calcul de la somme pour les colonnes suivantes 3 à 6, puis 7 à 9 automatiquement. Simplement, je ne vois pas comment le transcrire dans le script.
Merci
Arnaud

Pierre-Yves Berrard
Messages : 1029
Enregistré le : 12 Jan 2016, 23:30

Re: rowSums sur plusieurs colonnes d'une table de façon automatique

Messagepar Pierre-Yves Berrard » 16 Sep 2021, 18:57

Une proposition :

Code : Tout sélectionner

agrege_colonnes <- function(donnees,
                            pas = 3,
                            agreg_fun = sum) {

  prem_cols <- seq(1, ncol(donnees), by = pas)
  
  res 
<- apply(
    donnees,
    MARGIN = 1,
    function(ligne)
      sapply(
        prem_cols,
        function(prem_col) {
          dern_col <- prem_col + pas - 1
          cols 
<- seq(prem_col, dern_col)
          agreg_fun(ligne[cols], na.rm = TRUE)
        }
      )
  )

  as.data.frame(t(res))
  
}


Code : Tout sélectionner

agrege_colonnes(x, pas = 3, agreg_fun = sum)
#>   V1        V2   V3
#> 1  6  3.190909  9.1
#> 2 12  6.381818 19.4
#> 3 18  9.572727 29.7
#> 4 24 12.763636 40.0
#> 5 30 15.954545 50.3
agrege_colonnes(x, pas = 2, agreg_fun = mean)
#>    V1   V2       V3   V4   V5
#> 1 1.5 1.75 1.345455  2.9  3.3
#> 2 3.0 3.50 2.690909  6.4  6.6
#> 3 4.5 5.25 4.036364  9.9  9.9
#> 4 6.0 7.00 5.381818 13.4 13.2
#> 5 7.5 8.75 6.727273 16.9 16.5
agrege_colonnes(x, pas = 5, agreg_fun = median)
#>   V1        V2
#> 1  2  1.695455
#> 2  4  3.700000
#> 3  6  5.850000
#> 4  8  8.000000
#> 5 10 10.150000  
PY

Mickael Canouil
Messages : 1315
Enregistré le : 04 Avr 2011, 08:53
Contact :

Re: rowSums sur plusieurs colonnes d'une table de façon automatique

Messagepar Mickael Canouil » 17 Sep 2021, 08:55

Bonjour,

comme vous semblez utilisez data.table, voici une implémentation :

Code : Tout sélectionner

df<-structure(list(spe1=1:5,spe2=c(2,4,6,8,10),spe3=c(3,
6,9,12,15),spe4=c(0.5,1,1.5,2,2.5),spe5=c(2.6,5.2,
7.8,10.4,13),spe6=c(0.0909090909090909,0.181818181818182,
0.272727272727273,0.363636363636364,0.454545454545455),spe7=c(6,
12,18,24,30),spe8=c(-0.2,0.8,1.8,2.8,3.8),spe9=c(3.3,
6.6,9.9,13.2,16.5)),row.names=c(NA,-5L),class="data.frame")

library("data.table")
fun_by <- function(x, by, fun) {
  transpose(setDT(x), keep.names = "variable")[
    j = list(
      variable = paste(variable, collapse = ";"),
      value = fun(.SD[= -c("variable")])
    ),
    by = ceiling(1:/ by)
  ][
    j = rowid := seq_len(.N),
    by = "variable"
  ][
    j = -c("ceiling")
  ]


Somme des lignes des colonnes 3 par 3 :

Code : Tout sélectionner

res_dt <- fun_by(df, 3, colSums)
res_dt
#>           variable     value rowid
#>  1: spe1;spe2;spe3  6.000000     1
#>  2: spe1;spe2;spe3 12.000000     2
#>  3: spe1;spe2;spe3 18.000000     3
#>  4: spe1;spe2;spe3 24.000000     4
#>  5: spe1;spe2;spe3 30.000000     5
#>  6: spe4;spe5;spe6  3.190909     1
#>  7: spe4;spe5;spe6  6.381818     2
#>  8: spe4;spe5;spe6  9.572727     3
#>  9: spe4;spe5;spe6 12.763636     4
#> 10: spe4;spe5;spe6 15.954545     5
#> 11: spe7;spe8;spe9  9.100000     1
#> 12: spe7;spe8;spe9 19.400000     2
#> 13: spe7;spe8;spe9 29.700000     3
#> 14: spe7;spe8;spe9 40.000000     4
#> 15: spe7;spe8;spe9 50.300000     5    

Code : Tout sélectionner

dcast.data.table(res_dt, formula = rowid ~ variable, value.var = "value")
#>    rowid spe1;spe2;spe3 spe4;spe5;spe6 spe7;spe8;spe9
#> 1:     1              6       3.190909            9.1
#> 2:     2             12       6.381818           19.4
#> 3:     3             18       9.572727           29.7
#> 4:     4             24      12.763636           40.0
#> 5:     5             30      15.954545           50.3     


Somme des lignes des colonnes 2 par 2 :

Code : Tout sélectionner

res_dt <- fun_by(df, 2, colSums)
res_dt
#>      variable     value rowid
#>  1: spe1;spe2  3.000000     1
#>  2: spe1;spe2  6.000000     2
#>  3: spe1;spe2  9.000000     3
#>  4: spe1;spe2 12.000000     4
#>  5: spe1;spe2 15.000000     5
#>  6: spe3;spe4  3.500000     1
#>  7: spe3;spe4  7.000000     2
#>  8: spe3;spe4 10.500000     3
#>  9: spe3;spe4 14.000000     4
#> 10: spe3;spe4 17.500000     5
#> 11: spe5;spe6  2.690909     1
#> 12: spe5;spe6  5.381818     2
#> 13: spe5;spe6  8.072727     3
#> 14: spe5;spe6 10.763636     4
#> 15: spe5;spe6 13.454545     5
#> 16: spe7;spe8  5.800000     1
#> 17: spe7;spe8 12.800000     2
#> 18: spe7;spe8 19.800000     3
#> 19: spe7;spe8 26.800000     4
#> 20: spe7;spe8 33.800000     5
#> 21:      spe9  3.300000     1
#> 22:      spe9  6.600000     2
#> 23:      spe9  9.900000     3
#> 24:      spe9 13.200000     4
#> 25:      spe9 16.500000     5
#>      variable     value rowid    

Code : Tout sélectionner

dcast.data.table(res_dt, formula = rowid ~ variable, value.var = "value")
#>    rowid spe1;spe2 spe3;spe4 spe5;spe6 spe7;spe8 spe9
#> 1:     1         3       3.5  2.690909       5.8  3.3
#> 2:     2         6       7.0  5.381818      12.8  6.6
#> 3:     3         9      10.5  8.072727      19.8  9.9
#> 4:     4        12      14.0 10.763636      26.8 13.2
#> 5:     5        15      17.5 13.454545      33.8 16.5     


Moyenne des lignes des colonnes 2 par 2 :

Code : Tout sélectionner

dcast.data.table(fun_by(df, 2, colMeans), formula = rowid ~ variable, value.var = "value")
#>    rowid spe1;spe2 spe3;spe4 spe5;spe6 spe7;spe8 spe9
#> 1:     1       1.5      1.75  1.345455       2.9  3.3
#> 2:     2       3.0      3.50  2.690909       6.4  6.6
#> 3:     3       4.5      5.25  4.036364       9.9  9.9
#> 4:     4       6.0      7.00  5.381818      13.4 13.2
#> 5:     5       7.5      8.75  6.727273      16.9 16.5     


Cordialement,
Mickaël
mickael.canouil.fr | rlille.fr

Arnaud de Coninck
Messages : 24
Enregistré le : 17 Fév 2015, 13:31
Contact :

Re: rowSums sur plusieurs colonnes d'une table de façon automatique

Messagepar Arnaud de Coninck » 28 Sep 2021, 02:09

Pierre-Yves Berrard et Mickael Canouil, merci pour vos réponses . Vous m'avez donné deux façons de procéder que je n'ai pas encore eu le temps de tester sur mes vraies données, mais dès que j'en ai fini avec le labo je m'y mets.
Encore un merci beaucoup, ça va grandement me simplifier la vie si ça marche.
Cordialement
Arnaud

Logez Maxime
Messages : 3138
Enregistré le : 26 Sep 2006, 11:35

Re: rowSums sur plusieurs colonnes d'une table de façon automatique

Messagepar Logez Maxime » 29 Sep 2021, 07:44

Bonjour,

Si tu ne cherches a faire que des sommes ou des moyennes alors tu peux aussi passer par des fonctions de base qui seront probablement plus efficaces sur des gros jeux de données. Elles le seront d'autant plus que les données sont stockées dans une matrice, si elles sont toutes de même nature (numérique par exemple) :

Code : Tout sélectionner

mat <- matrix(rnorm(1024*3000), 1024, 3000)
# un facteur regroupant les colonnes
fac <- gl(1000, 3)
# pour les sommes
t(rowsum(t(mat), fac))

# pour des moyennes
t(rowsum(t(mat), fac)/c(table(fac)))

Cordialement,
Maxime

Mickael Canouil
Messages : 1315
Enregistré le : 04 Avr 2011, 08:53
Contact :

Re: rowSums sur plusieurs colonnes d'une table de façon automatique

Messagepar Mickael Canouil » 29 Sep 2021, 10:38

Logez Maxime a écrit :Bonjour,

Si tu ne cherches a faire que des sommes ou des moyennes alors tu peux aussi passer par des fonctions de base qui seront probablement plus efficaces sur des gros jeux de données. Elles le seront d'autant plus que les données sont stockées dans une matrice, si elles sont toutes de même nature (numérique par exemple) :

Code : Tout sélectionner

mat <- matrix(rnorm(1024*3000), 1024, 3000)
# un facteur regroupant les colonnes
fac <- gl(1000, 3)
# pour les sommes
t(rowsum(t(mat), fac))

# pour des moyennes
t(rowsum(t(mat), fac)/c(table(fac)))

Cordialement,
Maxime


Je ne connaissais pas "rowsum", pratique (et performant) ^^

Petite généralisation pour coller aux autres propositions :

Code : Tout sélectionner

df<-structure(list(spe1=1:5,spe2=c(2,4,6,8,10),spe3=c(3,
6,9,12,15),spe4=c(0.5,1,1.5,2,2.5),spe5=c(2.6,5.2,
7.8,10.4,13),spe6=c(0.0909090909090909,0.181818181818182,
0.272727272727273,0.363636363636364,0.454545454545455),spe7=c(6,
12,18,24,30),spe8=c(-0.2,0.8,1.8,2.8,3.8),spe9=c(3.3,
6.6,9.9,13.2,16.5)),row.names=c(NA,-5L),class="data.frame")

sum_by <- function(x, by) {
  t(rowsum(t(x), gl(ceiling(ncol(x) / by), by)))
}

sum_by(df, 3)
#>       1         2    3
#> [1,]  6  3.190909  9.1
#> [2,] 12  6.381818 19.4
#> [3,] 18  9.572727 29.7
#> [4,] 24 12.763636 40.0
#> [5,] 30 15.954545 50.3  
Mickaël
mickael.canouil.fr | rlille.fr

Facundo Muñoz
Messages : 156
Enregistré le : 04 Juil 2019, 09:58
Contact :

Re: rowSums sur plusieurs colonnes d'une table de façon automatique

Messagepar Facundo Muñoz » 04 Oct 2021, 08:29

Bonjour,

Belles solutions. Une alternative est d'utiliser la structure de "array" à 3 dimensions pour capturer le "pas de calcul" dans la deuxième dimension, sur laquelle on "applique" la fonction sum ou mean.
Ainsi, on évite la variable auxiliaire supplémentaire, et les deux transpositions.

Code : Tout sélectionner

sum_by <- function(x, by) {
  arr <- array(as.matrix(x), dim = c(nrow(x), by, ncol(x)/by))
  apply(arr, c(1, 3), sum)
}
ƒacu.-

Logez Maxime
Messages : 3138
Enregistré le : 26 Sep 2006, 11:35

Re: rowSums sur plusieurs colonnes d'une table de façon automatique

Messagepar Logez Maxime » 04 Oct 2021, 18:53

Bonjour,

C'est une alternative intéressante, mais l'utilisation du apply est en général à proscrire parce que bien moins efficace que les fonctions rowSums, colSums, rowMeans, colMeans, etc. (quand il est question de sommes ou de moyennes).
Enfin tout est relatif :-), mais comme on parle optimisation de temps de calcul :

Code : Tout sélectionner

# pour coller à la demande initiale
mat <- matrix(rnorm(1024*3000), 1024)
sum_by <- function (x, by)
{
    t(rowsum(t(x), gl(ceiling(ncol(x)/by), by)))
}

sum_by_arr <- function (x, by)
{
    arr <- array(as.matrix(x), dim = c(nrow(x), by, ncol(x)/by))
    apply(arr, c(1, 3), sum)
}

microbenchmark(sum_by(mat, 3), sum_by_arr(mat, 3))
Unit: milliseconds
               expr       min        lq      mean     median        uq       max neval cld
     sum_by(mat, 3)   77.8557   79.3633  111.4477   80.38205  100.5184  329.2517    10  a
 sum_by_arr(mat, 3) 1416.4618 1924.2234 1951.3747 1959.26170 2057.4288 2301.0677    10   b
 
 # le profiling de la fonction apply array
 $by.self
                self.time self.pct total.time total.pct
"apply"              1.04    76.47       1.36    100.00
"FUN"                0.22    16.18       0.22     16.18
"aperm.default"      0.04     2.94       0.04      2.94
"unlist"             0.04     2.94       0.04      2.94
"lengths"            0.02     1.47       0.02      1.47

$by.total
                total.time total.pct self.time self.pct
"apply"               1.36    100.00      1.04    76.47
"sum_by_arr"          1.36    100.00      0.00     0.00
"FUN"                 0.22     16.18      0.22    16.18
"aperm.default"       0.04      2.94      0.04     2.94
"unlist"              0.04      2.94      0.04     2.94
"aperm"               0.04      2.94      0.00     0.00
"lengths"             0.02      1.47      0.02     1.47
 
La fonction apply est bien celle qui mange le temps de calcul ici. De plus la fonction sum qui est intrinsèquement rapide, perd de son intérêt en passant par le apply, qui l'a fait appliquer plein de fois à des toutes petites série de données.
On peut partir sur des array mais différemment :

Code : Tout sélectionner

sum_arr <- function (x, by)
{
    dim(x) <- c(nrow(x), by, ncol(x)/by)
    x <- aperm(x, c(1, 3, 2))
    res <- rowSums(x, dims = 2)
    res
} # ou
sum_arr2 <- function(x, by) {
y <- t(x)
dim(y) <- c(by, ncol(x)/by, nrow(x))
res <- colSums(y)
res <- t(res)
res
}
microbenchmark(sum_by(mat, 3), sum_arr(mat, 3), sum_arr2(mat, 3))
Unit: milliseconds
             expr     min       lq      mean   median       uq      max neval cld
   sum_by(mat, 3) 56.6063 70.88230  76.00375 76.98065 81.02200  93.7218   100  a
  sum_arr(mat, 3) 75.2635 82.76865 106.03232 87.47710 95.53550 248.8842   100   b
 sum_arr2(mat, 3) 54.2322 65.92415  74.44021 75.24425 78.69955 187.0590   100  a
On retombe sur des temps de calcul beaucoup plus efficaces parce que tout le calcul se fait dans les fonctions rowSums ou colSums sans passer par un apply.
Dans le cas de cette dernière fonction, c'est la fonction transposée qui prend le plus de temps mais comme elle est extrêmement rapide ça ne pose pas problème.

On peut aussi utiliser le apply avec rowSums :

Code : Tout sélectionner

sum_arr3 <- function (x, by)
{
    dim(x) <- c(nrow(x), by, ncol(x)/by)
    res <- apply(x, 3, rowSums)
    res
    }


Finalement le mieux que j'ai trouvé c'est encore la boucle !

Code : Tout sélectionner

sum_arr4 <- function (x, by)
{
    nr <- nrow(x)
    nc <- ncol(x)
    dim(x) <- c(nr, by, nc/by)
    res <- array(numeric(), dim = c(nr, nc/by))
    for (i in seq_len(ncol(res))) {
        res[, i] <- rowSums(x[, , i])
    }
    res
}

microbenchmark(sum_by(mat, 3), sum_arr2(mat, 3), sum_arr3(mat, 3), sum_arr4(mat, 3))
             expr     min       lq      mean    median       uq      max neval cld
   sum_by(mat, 3) 56.4047 66.56695  74.23903  71.70035  78.1705 194.6760   100  b
 sum_arr2(mat, 3) 53.3039 63.81625  70.14915  68.89870  75.0045  96.3015   100  b
 sum_arr3(mat, 3) 89.7877 96.22660 124.95617 104.06355 114.6987 244.7114   100   c
 sum_arr4(mat, 3) 41.2810 45.19000  52.34429  51.55855  55.6232 158.4412   100 a


Cordialement,
Maxime

Facundo Muñoz
Messages : 156
Enregistré le : 04 Juil 2019, 09:58
Contact :

Re: rowSums sur plusieurs colonnes d'une table de façon automatique

Messagepar Facundo Muñoz » 04 Oct 2021, 20:23

Excellent ! merci pour cet analyse de performance. Et bonus point pour la (ré-)découverte (pour moi) de la fonction aperm() que j'avais cherché mais pas trouvé :)
ƒacu.-


Retourner vers « Questions en cours »

Qui est en ligne

Utilisateurs parcourant ce forum : Google [Bot] et 1 invité