web-dev-qa-db-fra.com

Agréger un cadre de données sur une colonne donnée et afficher une autre colonne

J'ai un dataframe en R de la forme suivante:

> head(data)
  Group Score Info
1     1     1    a
2     1     2    b
3     1     3    c
4     2     4    d
5     2     3    e
6     2     1    f

Je voudrais l'agréger après la colonne Score en utilisant la fonction max

> aggregate(data$Score, list(data$Group), max)

  Group.1         x
1       1         3
2       2         4

Mais je voudrais aussi afficher la colonne Info associée à la valeur maximale de la colonne Score pour chaque groupe. Je n'ai aucune idée comment faire ça. Ma sortie désirée serait:

  Group.1         x        y
1       1         3        c
2       2         4        d

Un indice?

52
jul635

Tout d'abord, vous divisez les données à l'aide de split:

split(z,z$Group)

Ensuite, pour chaque morceau, sélectionnez la ligne avec le score maximum:

lapply(split(z,z$Group),function(chunk) chunk[which.max(chunk$Score),])

Enfin, ramenez à un data.frame do.calling rbind:

do.call(rbind,lapply(split(z,z$Group),function(chunk) chunk[which.max(chunk$Score),]))

Résultat:

  Group Score Info
1     1     3    c
2     2     4    d

Une ligne, pas de sorts magiques, rapide, le résultat a de bons noms =)

36
mbq

Une solution de base R consiste à combiner la sortie de aggregate() avec une étape merge(). Je trouve l'interface de formule à aggregate() un peu plus utile que l'interface standard, en partie parce que les noms sur la sortie sont plus agréables, je vais donc utiliser ceci:

L'étape aggregate() est

maxs <- aggregate(Score ~ Group, data = dat, FUN = max)

et l'étape merge() est simplement

merge(maxs, dat)

Cela nous donne la sortie souhaitée:

R> maxs <- aggregate(Score ~ Group, data = dat, FUN = max)
R> merge(maxs, dat)
  Group Score Info
1     1     3    c
2     2     4    d

Vous pouvez, bien sûr, coller ceci dans un one-liner (l'étape intermédiaire était plutôt pour l'exposition):

merge(aggregate(Score ~ Group, data = dat, FUN = max), dat)

La principale raison pour laquelle j'ai utilisé l'interface de formule est qu'elle renvoie un bloc de données avec la variable names correcte pour l'étape de fusion. ce sont les noms des colonnes de l'ensemble de données d'origine dat. La sortie de aggregate() doit avoir les noms corrects pour que merge() sache quelles colonnes des trames de données d'origine et agrégées correspondent.

L'interface standard donne des noms impairs, peu importe comment vous l'appelez:

R> aggregate(dat$Score, list(dat$Group), max)
  Group.1 x
1       1 3
2       2 4
R> with(dat, aggregate(Score, list(Group), max))
  Group.1 x
1       1 3
2       2 4

Nous pouvons utiliser merge() sur ces sorties, mais nous devons faire plus de travail en indiquant à R quelles colonnes correspondent.

49
Gavin Simpson

Voici une solution utilisant le package plyr

La ligne de code suivante indique essentiellement à ddply de grouper d’abord vos données par groupe, puis, au sein de chaque groupe, renvoie un sous-ensemble où le score est égal au score maximum de ce groupe.

library(plyr)
ddply(data, .(Group), function(x)x[x$Score==max(x$Score), ])

  Group Score Info
1     1     3    c
2     2     4    d

Et, comme le souligne @SachaEpskamp, ​​cela peut encore être simplifié:

ddply(df, .(Group), function(x)x[which.max(x$Score), ])

(ce qui présente également l'avantage que which.max renverra plusieurs lignes max, s'il y en a).

13
Andrie

Le package plyr peut être utilisé pour cela. Avec la fonction ddply(), vous pouvez fractionner un bloc de données sur une ou plusieurs colonnes, appliquer une fonction et renvoyer un bloc de données. Avec la fonction summarize(), vous pouvez également utiliser les colonnes du bloc de données fractionné pour créer le nouveau bloc de données /;

dat <- read.table(textConnection('Group Score Info
1     1     1    a
2     1     2    b
3     1     3    c
4     2     4    d
5     2     3    e
6     2     1    f'))

library("plyr")

ddply(dat,.(Group),summarize,
    Max = max(Score),
    Info = Info[which.max(Score)])
  Group Max Info
1     1   3    c
2     2   4    d
4
Sacha Epskamp

Pour ajouter à la réponse de Gavin: avant la fusion, il est possible d’obtenir un agrégat pour utiliser les noms propres lorsque l’interface de formule n’est pas utilisée:

aggregate(data[,"score", drop=F], list(group=data$group), mean) 
4
Dan

Une réponse tardive, mais et approche en utilisant data.table

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

DT[, .SD[which.max(Score),], by = Group]

Ou, s'il est possible d'avoir plus d'un score égal.

DT[, .SD[which(Score == max(Score)),], by = Group]

Notant que (de ?data.table

.SD est une table de données contenant le sous-ensemble de données de x pour chaque groupe, à l'exclusion de la ou des colonnes du groupe

4
mnel

C'est ainsi que je base pense du problème.

my.df <- data.frame(group = rep(c(1,2), each = 3), 
        score = runif(6), info = letters[1:6])
my.agg <- with(my.df, aggregate(score, list(group), max))
my.df.split <- with(my.df, split(x = my.df, f = group))
my.agg$info <- unlist(lapply(my.df.split, FUN = function(x) {
            x[which(x$score == max(x$score)), "info"]
        }))

> my.agg
  Group.1         x info
1       1 0.9344336    a
2       2 0.7699763    e
3
Roman Luštrik

Je n'ai pas assez de réputation pour commenter la réponse de Gavin Simpson, mais je tenais à signaler qu'il semble y avoir une différence dans le traitement par défaut des valeurs manquantes entre la syntaxe standard et la syntaxe de formule pour aggregate

#Create some data with missing values 
a<-data.frame(day=rep(1,5),hour=c(1,2,3,3,4),val=c(1,NA,3,NA,5))
  day hour val
1   1    1   1
2   1    2  NA
3   1    3   3
4   1    3  NA
5   1    4   5

#Standard syntax
aggregate(a$val,by=list(day=a$day,hour=a$hour),mean,na.rm=T)
  day hour   x
1   1    1   1
2   1    2 NaN
3   1    3   3
4   1    4   5

#Formula syntax.  Note the index for hour 2 has been silently dropped.
aggregate(val ~ hour + day,data=a,mean,na.rm=T)
  hour day val
1    1   1   1
2    3   1   3
3    4   1   5
0
John