web-dev-qa-db-fra.com

Comment utiliser Dplyr's Summarize et qui () pour rechercher les valeurs min / max

J'ai les données suivantes:

Name <- c("Sam", "Sarah", "Jim", "Fred", "James", "Sally", "Andrew", "John", "Mairin", "Kate", "Sasha", "Ray", "Ed")
Age <- c(22,12,31,35,58,82,17,34,12,24,44,67,43)
Group <- c("A", "B", "B", "B", "B", "C", "C", "D", "D", "D", "D", "D", "D") 
data <- data.frame(Name, Age, Group)

Et j'aimerais utiliser dplyr pour

(1) regrouper les données par "Groupe" (2) afficher l'âge min et max au sein de chaque groupe (3) afficher le nom de la personne avec les âges min et max

Le code suivant le fait:

data %>% group_by(Group) %>%
     summarize(minAge = min(Age), minAgeName = Name[which(Age == min(Age))], 
               maxAge = max(Age), maxAgeName = Name[which(Age == max(Age))])

Ce qui fonctionne bien:

  Group minAge minAgeName maxAge maxAgeName
1     A     22        Sam     22        Sam
2     B     12      Sarah     58      James
3     C     17     Andrew     82      Sally
4     D     12     Mairin     67        Ray

Cependant, j'ai un problème s'il y a plusieurs valeurs min ou max:

Name <- c("Sam", "Sarah", "Jim", "Fred", "James", "Sally", "Andrew", "John", "Mairin", "Kate", "Sasha", "Ray", "Ed")
Age <- c(22,31,31,35,58,82,17,34,12,24,44,67,43)
Group <- c("A", "B", "B", "B", "B", "C", "C", "D", "D", "D", "D", "D", "D") 
data <- data.frame(Name, Age, Group)

> data %>% group_by(Group) %>%
+   summarize(minAge = min(Age), minAgeName = Name[which(Age == min(Age))], 
+             maxAge = max(Age), maxAgeName = Name[which(Age == max(Age))])
Error: expecting a single value

Je recherche deux solutions:

(1) où peu importe le nom min ou max affiché, juste celui-là (c'est-à-dire la première valeur trouvée) (2) où s'il y a des "liens" toutes les valeurs minimales et maximales sont affichées

Veuillez me faire savoir si ce n'est pas clair et merci d'avance!

17
dreww2

Je recommanderais en fait de conserver vos données dans un format "long". Voici comment j'aborderais ceci:

library(dplyr)

Garder toutes les valeurs quand il y a des liens:

data %>%
  group_by(Group) %>%
  arrange(Age) %>%  ## optional
  filter(Age %in% range(Age))
# Source: local data frame [8 x 3]
# Groups: Group
# 
#     Name Age Group
# 1    Sam  22     A
# 2  Sarah  31     B
# 3    Jim  31     B
# 4  James  58     B
# 5 Andrew  17     C
# 6  Sally  82     C
# 7 Mairin  12     D
# 8    Ray  67     D

Garder une seule valeur quand il y a des liens:

data %>%
  group_by(Group) %>%
  arrange(Age) %>%
  slice(if (length(Age) == 1) 1 else c(1, n())) ## maybe overkill?
# Source: local data frame [7 x 3]
# Groups: Group
# 
#     Name Age Group
# 1    Sam  22     A
# 2  Sarah  31     B
# 3  James  58     B
# 4 Andrew  17     C
# 5  Sally  82     C
# 6 Mairin  12     D
# 7    Ray  67     D

Si vous voulez vraiment un ensemble de données "large", le concept de base serait de gather et spread les données, en utilisant "tidyr":

library(dplyr)
library(tidyr)

data %>%
  group_by(Group) %>%
  arrange(Age) %>%
  slice(c(1, n())) %>%
  mutate(minmax = c("min", "max")) %>%
  gather(var, val, Name:Age) %>%
  unite(key, minmax, var) %>%
  spread(key, val)
# Source: local data frame [4 x 5]
# 
#   Group max_Age max_Name min_Age min_Name
# 1     A      22      Sam      22      Sam
# 2     B      58    James      31    Sarah
# 3     C      82    Sally      17   Andrew
# 4     D      67      Ray      12   Mairin

Mais quelle forme large vous voudriez avec des liens n'est pas claire.

12

Vous pouvez utiliser which.min et which.max pour obtenir la première valeur.

data %>% group_by(Group) %>%
  summarize(minAge = min(Age), minAgeName = Name[which.min(Age)], 
            maxAge = max(Age), maxAgeName = Name[which.max(Age)])

Pour obtenir toutes les valeurs, utilisez par exemple coller avec un argument collapse approprié.

data %>% group_by(Group) %>%
  summarize(minAge = min(Age), minAgeName = paste(Name[which(Age == min(Age))], collapse = ", "), 
            maxAge = max(Age), maxAgeName = paste(Name[which(Age == max(Age))], collapse = ", "))
20
shadow

Voilà quelque data.table approche, la première empruntée à @akrun:

setDT(data)

# show one, wide format
data[,c(min=.SD[which.min(Age)],max=.SD[which.max(Age)]),by=Group]
   # Group min.Name min.Age max.Name max.Age
# 1:     A      Sam      22      Sam      22
# 2:     B    Sarah      31    James      58
# 3:     C   Andrew      17    Sally      82
# 4:     D   Mairin      12      Ray      67

# show all, long format
data[,{
  mina=min(Age)
  maxa=max(Age)
  rbind(
    data.table(minmax="min",Age=mina,Name=Name[which(Age==mina)]),
    data.table(minmax="max",Age=maxa,Name=Name[which(Age==maxa)])
)},by=Group]
   # Group minmax Age   Name
# 1:     A    min  22    Sam
# 2:     A    max  22    Sam
# 3:     B    min  31  Sarah
# 4:     B    min  31    Jim
# 5:     B    max  58  James
# 6:     C    min  17 Andrew
# 7:     C    max  82  Sally
# 8:     D    min  12 Mairin
# 9:     D    max  67    Ray    

Je pense que le format long est le meilleur, car il vous permet de filtrer avec minmax, mais le code est torturé et inefficace.

Voici quelques moyens sans doute moins bons:

# show all, wide format (with a list column)
data[,{
  mina=min(Age)
  maxa=max(Age)
  list(
    minAge=mina,
    maxAge=maxa,
    minNames=list(Name[Age==mina]),
    maxNames=list(Name[Age==maxa]))
},by=Group]
   # Group minAge maxAge  minNames maxNames
# 1:     A     22     22       Sam      Sam
# 2:     B     31     58 Sarah,Jim    James
# 3:     C     17     82    Andrew    Sally
# 4:     D     12     67    Mairin      Ray


# show all, wide format (with a string column)
# (just look at @shadow's answer)
3
Frank