Dplyr filter: 获取变量最小的行,但是如果有多个最小值,则只获取第一个行

我想使用 dplyr制作一个分组过滤器,在每个分组中只有那一行返回,它的最小值是变量 x

我的问题是: 正如预期的那样,在多个最小 所有行的情况下,返回最小值。但在我的情况下,如果存在多个极小值,则 我只要第一排

这里有一个例子:

df <- data.frame(
A=c("A", "A", "A", "B", "B", "B", "C", "C", "C"),
x=c(1, 1, 2, 2, 3, 4, 5, 5, 5),
y=rnorm(9)
)


library(dplyr)
df.g <- group_by(df, A)
filter(df.g, x == min(x))

正如预期的那样,返回所有最小值:

Source: local data frame [6 x 3]
Groups: A


A x           y
1 A 1 -1.04584335
2 A 1  0.97949399
3 B 2  0.79600971
4 C 5 -0.08655151
5 C 5  0.16649962
6 C 5 -0.05948012

如果是 ddply,我会这样处理这项任务:

library(plyr)
ddply(df, .(A), function(z) {
z[z$x == min(z$x), ][1, ]
})

这个方法很有效:

  A x           y
1 A 1 -1.04584335
2 B 2  0.79600971
3 C 5 -0.08655151

问: 有没有办法在 dplyr 中解决这个问题? (出于速度的原因)

74400 次浏览

不管怎样,这里有一个 data.table解决方案,对于那些可能感兴趣的人:

# approach with setting keys
dt <- as.data.table(df)
setkey(dt, A,x)
dt[J(unique(A)), mult="first"]


# without using keys
dt <- as.data.table(df)
dt[dt[, .I[which.min(x)], by=A]$V1]

为了完整起见: 这是最终的 dplyr解决方案,来自@hadley 和@Arun 的评论:

library(dplyr)
df.g <- group_by(df, A)
filter(df.g, rank(x, ties.method="first")==1)

更新

使用 dplyr > = 0.3,您可以将 slice函数与 which.min结合使用,这将是我最喜欢的完成此任务的方法:

df %>% group_by(A) %>% slice(which.min(x))
#Source: local data frame [3 x 3]
#Groups: A
#
#  A x          y
#1 A 1  0.2979772
#2 B 2 -1.1265265
#3 C 5 -1.1952004

原始答案

对于样本数据,也可以相继使用两个 filter:

group_by(df, A) %>%
filter(x == min(x)) %>%
filter(1:n() == 1)

这可以通过使用 row_number结合 group_by来实现。row_number不仅通过值而且通过向量中的相对顺序来分配排名,从而处理关系。要获取每个组的最小值为 x的第一行:

df.g <- group_by(df, A)
filter(df.g, row_number(x) == 1)

有关更多信息,请参见 dplyr 窗口函数的小插图

我喜欢 sqldf 的简单性. 。

sqldf("select A,min(X),y from 'df.g' group by A")

产出:

A min(X)          y


1 A      1 -1.4836989


2 B      2  0.3755771


3 C      5  0.9284441

另一种方法是:

set.seed(1)
x <- data.frame(a = rep(1:2, each = 10), b = rnorm(20))
x <- dplyr::arrange(x, a, b)
dplyr::filter(x, !duplicated(a))

结果:

  a          b
1 1 -0.8356286
2 2 -2.2146999

还可以很容易地调整以获取每个组中具有最大值的行。

dplyr提供 slice_min函数,它使用参数 with_ties = FALSE完成这项工作

library(dplyr)


df %>%
group_by(A) %>%
slice_min(x, with_ties = FALSE)

产出:

# A tibble: 3 x 3
# Groups:   A [3]
A         x      y
<fct> <dbl>  <dbl>
1 A         1  0.273
2 B         2 -0.462
3 C         5  1.08

如果你想先过滤 x 的最小值,然后再过滤 y 的最小值。一种直观的方法就是使用过滤函数:

> df
A x            y
1 A 1  1.856368296
2 A 1 -0.298284187
3 A 2  0.800047796
4 B 2  0.107289719
5 B 3  0.641819999
6 B 4  0.650542284
7 C 5  0.422465687
8 C 5  0.009819306
9 C 5 -0.482082635


df %>% group_by(A) %>%
filter(x == min(x), y == min(y))
 

# A tibble: 3 x 3
# Groups:   A [3]
A         x      y
<chr> <dbl>  <dbl>
1 A         1 -0.298
2 B         2  0.107
3 C         5 -0.482

这段代码将过滤 x 和 y 的最小值。

你也可以做一个双重过滤 看起来更具可读性:

df %>% group_by(A) %>%
filter(x == min(x)) %>%
filter(y == min(y))


# A tibble: 3 x 3
# Groups:   A [3]
A         x      y
<chr> <dbl>  <dbl>
1 A         1 -0.298
2 B         2  0.107
3 C         5 -0.482

为了完整起见,以下是 base R的答案:

df[with(df, ave(x, A, FUN = \(x) rank(x, ties.method = "first")) == 1), ]


#  A x          y
#1 A 1  0.1076158
#4 B 2 -1.3909084
#7 C 5  0.3511618