web-dev-qa-db-fra.com

Calculer la moyenne et l'écart type par groupe pour plusieurs variables dans un data.frame

Modifier - Cette question était initialement intitulée << Remodelage des données longues à larges dans R >>


J'apprends juste le R et j'essaie de trouver des moyens de l'appliquer pour aider les autres dans ma vie. En tant que test, je travaille à remodeler certaines données et j'ai du mal à suivre les exemples que j'ai trouvés en ligne. Ce que je commence ressemble à ceci:

ID  Obs 1   Obs 2   Obs 3
1   43      48      37
1   27      29      22
1   36      32      40
2   33      38      36
2   29      32      27
2   32      31      35
2   25      28      24
3   45      47      42
3   38      40      36

Et ce que je veux finir ressemblera à ceci:

ID  Obs 1 mean  Obs 1 std dev   Obs 2 mean  Obs 2 std dev
1   x           x               x           x
2   x           x               x           x
3   x           x               x           x

Et ainsi de suite. Ce dont je ne suis pas sûr, c'est de savoir si j'ai besoin d'informations supplémentaires dans mes données détaillées ou quoi. J'imagine que la partie mathématique (trouver la moyenne et les écarts-types) sera la partie facile, mais je n'ai pas été en mesure de trouver un moyen qui semble fonctionner pour remodeler correctement les données pour commencer ce processus.

Merci beaucoup pour toute aide.

25
user2348358

Il s'agit d'un problème d'agrégation, pas d'un problème de remodelage comme la question l'avait suggéré à l'origine - nous souhaitons agréger chaque colonne en une moyenne et un écart type par ID. Il existe de nombreux packages qui gèrent ces problèmes. Dans la base de R, cela peut être fait en utilisant aggregate comme ceci (en supposant que DF est la trame de données d'entrée):

ag <- aggregate(. ~ ID, DF, function(x) c(mean = mean(x), sd = sd(x)))

Note 1: Un intervenant a souligné que ag est un bloc de données pour lequel certaines colonnes sont des matrices. Bien que cela puisse sembler étrange au départ, en fait, cela simplifie l'accès. ag a le même nombre de colonnes que l'entrée DF. Sa première colonne ag[[1]] Est ID et la ième colonne du reste ag[[i+1]] (Ou de façon équivalente ag[-1][[i]]) Est la matrice de statistiques de la ième colonne d'observation d'entrée . Si l'on souhaite accéder à la jième statistique de la ième observation c'est donc ag[[i+1]][, j] Qui peut aussi s'écrire ag[-1][[i]][, j].

Par contre, supposons qu'il y ait k colonnes statistiques pour chaque observation dans l'entrée (où k = 2 dans la question). Ensuite, si nous aplatissons la sortie, pour accéder à la jième statistique de la ième colonne d'observation, nous devons utiliser la ag[[k*(i-1)+j+1]] ou l'équivalent ag[-1][[k*(i-1)+j]] plus complexe.

Par exemple, comparez la simplicité de la première expression par rapport à la seconde:

ag[-1][[2]]
##        mean      sd
## [1,] 36.333 10.2144
## [2,] 32.250  4.1932
## [3,] 43.500  4.9497

ag_flat <- do.call("data.frame", ag) # flatten
ag_flat[-1][, 2 * (2-1) + 1:2]
##   Obs_2.mean Obs_2.sd
## 1     36.333  10.2144
## 2     32.250   4.1932
## 3     43.500   4.9497

Note 2: L'entrée sous forme reproductible est:

Lines <- "ID  Obs_1   Obs_2   Obs_3
1   43      48      37
1   27      29      22
1   36      32      40
2   33      38      36
2   29      32      27
2   32      31      35
2   25      28      24
3   45      47      42
3   38      40      36"
DF <- read.table(text = Lines, header = TRUE)
31
G. Grothendieck

Voici probablement la façon la plus simple de s'y prendre (avec un exemple reproductible ):

library(plyr)
df <- data.frame(ID=rep(1:3, 3), Obs_1=rnorm(9), Obs_2=rnorm(9), Obs_3=rnorm(9))
ddply(df, .(ID), summarize, Obs_1_mean=mean(Obs_1), Obs_1_std_dev=sd(Obs_1),
  Obs_2_mean=mean(Obs_2), Obs_2_std_dev=sd(Obs_2))

   ID  Obs_1_mean Obs_1_std_dev  Obs_2_mean Obs_2_std_dev
1  1 -0.13994642     0.8258445 -0.15186380     0.4251405
2  2  1.49982393     0.2282299  0.50816036     0.5812907
3  3 -0.09269806     0.6115075 -0.01943867     1.3348792

EDIT: L'approche suivante vous permet d'économiser beaucoup de frappe lorsque vous traitez plusieurs colonnes.

ddply(df, .(ID), colwise(mean))

  ID      Obs_1      Obs_2      Obs_3
1  1 -0.3748831  0.1787371  1.0749142
2  2 -1.0363973  0.0157575 -0.8826969
3  3  1.0721708 -1.1339571 -0.5983944

ddply(df, .(ID), colwise(sd))

  ID     Obs_1     Obs_2     Obs_3
1  1 0.8732498 0.4853133 0.5945867
2  2 0.2978193 1.0451626 0.5235572
3  3 0.4796820 0.7563216 1.4404602
18
Carson

Il existe plusieurs façons de procéder. reshape2 est un package utile. Personnellement, j'aime utiliser data.table

Ci-dessous, une étape par étape

Si myDF est votre data.frame:

library(data.table)
DT <- data.table(myDF)

DT

# this will get you your mean and SD's for each column
DT[, sapply(.SD, function(x) list(mean=mean(x), sd=sd(x)))]

# adding a `by` argument will give you the groupings
DT[, sapply(.SD, function(x) list(mean=mean(x), sd=sd(x))), by=ID]

# If you would like to round the values: 
DT[, sapply(.SD, function(x) list(mean=round(mean(x), 3), sd=round(sd(x), 3))), by=ID]

# If we want to add names to the columns 
wide <- setnames(DT[, sapply(.SD, function(x) list(mean=round(mean(x), 3), sd=round(sd(x), 3))), by=ID], c("ID", sapply(names(DT)[-1], paste0, c(".men", ".SD"))))

wide

   ID Obs.1.men Obs.1.SD Obs.2.men Obs.2.SD Obs.3.men Obs.3.SD
1:  1    35.333    8.021    36.333   10.214      33.0    9.644
2:  2    29.750    3.594    32.250    4.193      30.5    5.916
3:  3    41.500    4.950    43.500    4.950      39.0    4.243

En outre, cela peut être utile ou non

> DT[, sapply(.SD, summary), .SDcols=names(DT)[-1]]
        Obs.1 Obs.2 Obs.3
Min.    25.00 28.00 22.00
1st Qu. 29.00 31.00 27.00
Median  33.00 32.00 36.00
Mean    34.22 36.11 33.22
3rd Qu. 38.00 40.00 37.00
Max.    45.00 48.00 42.00
15
Ricardo Saporta

J'ajoute la solution dplyr.

set.seed(1)
df <- data.frame(ID=rep(1:3, 3), Obs_1=rnorm(9), Obs_2=rnorm(9), Obs_3=rnorm(9))

library(dplyr)
df %>% group_by(ID) %>% summarise_each(funs(mean, sd))

#      ID Obs_1_mean Obs_2_mean Obs_3_mean  Obs_1_sd  Obs_2_sd  Obs_3_sd
#   (int)      (dbl)      (dbl)      (dbl)     (dbl)     (dbl)     (dbl)
# 1     1  0.4854187 -0.3238542  0.7410611 1.1108687 0.2885969 0.1067961
# 2     2  0.4171586 -0.2397030  0.2041125 0.2875411 1.8732682 0.3438338
# 3     3 -0.3601052  0.8195368 -0.4087233 0.8105370 0.3829833 1.4705692
9
Joe

Voici une autre version du data.table réponses, en utilisant les données de @ Carson, c'est un peu plus lisible (et aussi un peu plus rapide, grâce à l'utilisation de lapply au lieu de sapply):

library(data.table)
set.seed(1)
dt = data.table(ID=c(1:3), Obs_1=rnorm(9), Obs_2=rnorm(9), Obs_3=rnorm(9))

dt[, c(mean = lapply(.SD, mean), sd = lapply(.SD, sd)), by = ID]
#   ID mean.Obs_1 mean.Obs_2 mean.Obs_3  sd.Obs_1  sd.Obs_2  sd.Obs_3
#1:  1  0.4854187 -0.3238542  0.7410611 1.1108687 0.2885969 0.1067961
#2:  2  0.4171586 -0.2397030  0.2041125 0.2875411 1.8732682 0.3438338
#3:  3 -0.3601052  0.8195368 -0.4087233 0.8105370 0.3829833 1.4705692
8
eddi