给定一列的最小值,找到其他列中的最小值(dplyr)

3
假设我们在R中拥有以下数据集:
> td
  Type Rep Value1 Value2
1    A   1      7      1
2    A   2      5      4
3    A   3      5      3
4    A   4      8      2
5    B   1      5     10
6    B   2      6      1
7    B   3      7      1
8    C   1      8     13
9    C   2      8     13

> td <- structure(list(Type = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 
3L, 3L), .Label = c("A", "B", "C"), class = "factor"), Rep = c(1L, 
2L, 3L, 4L, 1L, 2L, 3L, 1L, 2L), Value1 = c(7L, 5L, 5L, 8L, 5L, 
6L, 7L, 8L, 8L), Value2 = c(1L, 4L, 3L, 2L, 10L, 1L, 1L, 13L, 
13L)), .Names = c("Type", "Rep", "Value1", "Value2"), class = "data.frame",
row.names = c(NA, -9L))

我可以为您制作以下表格:

表格如下:

  Type MinValue1 MinValue2 MeanValue1 MeanValue2
1    A         5         3       6.25        2.5
2    B         5        10       6.00        4.0
3    C         3        13       8.00       13.0

在这张表格中,数据按“类型”进行了总结。列“MinValue1”是特定类型的最小值,而列“MinValue2”是根据列“Value1”的最小值计算得出的“Value2”的最小值。列Mean*是所有观测值的平均值。
一种方法是实现循环,遍历每个类型并进行计算。然而,我正在寻找更好/更容易/更美观的方法来执行此操作。
我已经尝试使用“tidyverse”工具:
> library(tidyverse)
> td %>% 
     group_by(Type) %>% 
     summarise(MinValue1 = min(Value1), 
               MeanValue1 = mean(Value1),
               MeanValue2 = mean(Value2))
# A tibble: 3 × 4
    Type MinValue1 MeanValue1 MeanValue2
  <fctr>    <int>       <dbl>      <dbl>
1      A        5        6.25        2.5
2      B        5        6.00        4.0
3      C        8        8.00       13.0

请注意,这里没有"MinValue2"列。另请注意,“summarise(..., MinValue2 = min(Value2),...)”无法工作,因为此解决方案取一个类型的所有观测值的最小值。
我们可以通过“slice”来操作,然后合并结果。
> td %>% group_by(Type) %>% slice(which.min(Value1))
Source: local data frame [3 x 4]
Groups: Type [3]

    Type   Rep Value1 Value2
  <fctr> <int>  <int>  <int>
1      A     3      5      4
2      B     1      5     10
3      C     1      8     13

但需要注意的是,“切片”工具在这里没有帮助我们:“类型A,值1 5”应该有“值2”== 3,而不是像切片返回的那样等于4。那么,您们有什么优雅的方法来实现我想要的结果吗?谢谢!
3个回答

2

一种方法是使用order函数的属性来与另一个向量打破并列:

get_min_at_min <- function(vec1, vec2) {
  return(vec2[order(vec1, vec2)[1]])
}

这个函数返回vec1最小值对应的索引在vec2中的值。使用这个函数进行管道操作非常简单:

td %>% 
  group_by(Type) %>% 
  summarise(MinValue1 = min(Value1),
            MinValue2 = get_min_at_min(Value1, Value2),
            MeanValue1 = mean(Value1),
            MeanValue2 = mean(Value2))

或者,您可以直接利用一个事实,在dplyr函数内部只能寻址计算变量:

td %>% 
  group_by(Type) %>% 
  summarise(MinValue1 = min(Value1),
            MinValue2 = min(Value2[Value1 == MinValue1]),
            MeanValue1 = mean(Value1),
            MeanValue2 = mean(Value2))

非常感谢。我正在寻找的是最后一个选项。 - an_drade

1

按照'Type'分组后,基于选择与'Value1'最小对应的元素,创建另一组并使用min获取'Value2'的最小值,使用summarise_each获取所选列('Value1'和'Value2')的minmean,并使用select删除'Value2_min'

td %>% 
    group_by(Type) %>%
    group_by(MinValue2 = min(Value2[Value1==min(Value1)]), add=TRUE) %>%  
    summarise_each(funs(min, mean), Value1:Value2) %>% 
    select(-Value2_min)

1

非常感谢 @evgeniC 和 @akrun。你们的帮助非常宝贵。对于我的目的/数据集,这两种解决方案都非常有效。因此,为了丰富一下讨论,我运行了一些实验来测试这些提议的速度,使用以下脚本(当然,在每个实验中进行注释/取消注释):

library(tidyverse)

args <- commandArgs(TRUE)
set.seed(args[1])
n = args[2]

td = data.frame(Type = sample(LETTERS, n, replace=T),
                Value1 = sample(1:100, n, replace=T),
                Value2 = sample(1:100, n, replace=T))

ptm <- proc.time()

# Solution 1 ###
#get_min_at_min <- function(vec1, vec2) {
  #return(vec2[order(vec1, vec2)[1]])
#}

#tmp <- td %>%
       #group_by(Type) %>%
       #summarise(MinValue1 = min(Value1),
                 #MinValue2 = get_min_at_min(Value1, Value2),
                 #MeanValue1 = mean(Value1),
                 #MeanValue2 = mean(Value2))

### Solution 2 ###
tmp <- td %>%
       group_by(Type) %>%
       summarise(MinValue1 = min(Value1),
                 MinValue2 = min(Value2[Value1 == MinValue1]),
                 MeanValue1 = mean(Value1),
                 MeanValue2 = mean(Value2))

### Solution 3 ###
#tmp <- td %>%
       #group_by(Type) %>%
       #group_by(MinValue2 = min(Value2[Value1==min(Value1)]), add=TRUE) %>%
       #summarise_each(funs(min, mean), Value1:Value2) %>%
       #select(-Value2_min)

print(proc.time() - ptm)

每个算法,我都运行了。
$ Rscript test.R 270001 10000000

使用
> td %>% group_by(Alg) %>% summarise_each(funs(mean, sd), User:Elapsed)

we got the follwing results:

       Alg User_mean System_mean Elapsed_mean    User_sd   System_sd Elapsed_sd
1    akrun 1.3643333  0.13766667     1.510333 0.01069268 0.005033223 0.02050203
2 evgeniC1 0.8706667  0.07466667     0.951000 0.03323151 0.003055050 0.04073082
3 evgeniC2 0.8600000  0.09300000     0.958000 0.05546170 0.005196152 0.06331666

因此,我倾向于使用@evgeniC的解决方案2,因为它最优雅/简单,并且与解决方案1一样快。@akrun提供了一个不错的解决方案,但它有点更复杂和慢。无论如何,该设置在其他情况下可能会有用。

@akrun的回答在输入数据有许多列的情况下更好:它可以节省打字时间。 此外,我建议使用microbenchmark来衡量性能(例如,请参阅http://adv-r.had.co.nz/Performance.html的“Microbenchmarking”部分)。 - echasnovski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接