【问题标题】:How to use Dplyr's Summarize and which() to lookup min/max values如何使用 Dplyr 的 Summarize 和 which() 来查找最小值/最大值
【发布时间】:2015-07-23 15:48:52
【问题描述】:

我有以下数据:

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)

我想使用 dplyr 来

(1) 按“Group”对数据进行分组 (2) 显示每个组内的最小和最大年龄 (3) 显示最小和最大年龄的人的姓名

下面的代码就是这样做的:

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

效果很好:

  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

但是,如果有多个最小值或最大值,我会遇到问题:

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

我正在寻找两种解决方案:

(1) 显示最小或最大名称无关紧要,只显示一个(即找到的第一个值) (2) 如果有“关系”,则显示所有最小值和最大值

如果不清楚,请告诉我,并提前致谢!

【问题讨论】:

  • which.minwhich.max 取第一个值。
  • 这对第一个解决方案非常有用,谢谢!
  • 使用data.tablesetDT(data)[, c(.SD[which.min(Age)], .SD[which.max(Age)]), Group] 并相应地更改名称
  • @akrun 一.SDsetDT(data)[, .SD[c(which.min(Age),which.max(Age))], Group]。同样,选择行的东西在这里再次起作用:setDT(data)[data[, .I[c(which.min(Age),which.max(Age))], Group]$V1]。不过,这只是问题的前半部分(以防有人回答)。
  • @Frank 使用c 是一个很好的解决方案,但您的解决方案不是很长。

标签: r dplyr


【解决方案1】:

您可以使用which.minwhich.max 来获取第一个值。

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

要获取所有值,请使用例如粘贴适当的collapse 参数。

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 = ", "))

【讨论】:

  • 再次感谢 - 这正是我一直在寻找的。我不清楚我想要数据的宽格式。这适用于我发布的“测试”数据,但在一个有 100 条关联的示例中(作为一个极端示例),将所有内容折叠成一行可以变得笨拙。但这是我没有解释的错,不是你的——这个解决方案效果很好。
【解决方案2】:

我实际上建议将您的数据保持为“长”格式。以下是我的处理方法:

library(dplyr)

在存在平局时保留所有值:

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

有关系时只保留一个值:

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

如果你真的想要一个“宽”数据集,基本概念是gatherspread 数据,使用“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

虽然你还不清楚你想要什么样的领带形式。

【讨论】:

  • @Frank,您将如何处理多个案例?对我来说,粘贴在一起似乎更难。
  • @Frank,遥遥领先 :-)
  • @Frank,最小最大的想法已经嵌入到我的最后一个答案中。只需停在mutate 线。有多个时,可以使用dense_rankfactor
  • 非常感谢!我特别喜欢过滤器 %in% 范围解决方案的简单性。
【解决方案3】:

这里有一些data.table的方法,第一个是从@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    

我认为长格式是最好的,因为它允许您使用minmax 进行过滤,但是代码被折磨且效率低下。

这里有一些可以说不太好的方法:

# 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)

【讨论】:

  • 对于第二组代码,你是否使用了第二个示例数据(我得到了 8 行作为输出)
  • @akrun 很好。我已经切换到我原来的、明显低效的代码,它确实产生了九行。我能想到的唯一选择是将ifelse 放在代码前面的同样低效的选择。我认为dplyr 赢得了这一轮,因为在我的第二张桌子上产生结果并不难(正如 Ananda 在 cmets 中解释的那样)。
  • 非常好,谢谢!在这种情况下,我没有使用 data.table,因为性能并不重要,但我很欣赏这篇文章。
  • 自编/准编者须知:第二块中丑陋的代码可以用frank()函数清理。
猜你喜欢
  • 1970-01-01
  • 2012-09-16
  • 2017-09-20
  • 2017-04-20
  • 2018-09-21
  • 2017-04-22
  • 2019-02-25
  • 2021-09-15
  • 2014-06-17
相关资源
最近更新 更多