使用dplyr根据每列的分位数过滤每列数据

4

给定一个数据框,我想使用每列的分位数来过滤每一列。我更喜欢使用dplyr/tidyverse来完成这个任务。

set.seed(23)
df <- data.frame(
  x1 = runif(10, 0, 100),
  x2 = runif(10, 0, 100),
  x3 = runif(10, 0, 100)
)
df
> df
         x1       x2       x3
1  57.66037 86.59590 58.63978
2  22.30729 70.14217 27.47410
3  33.18966 39.04731 14.76570
4  71.07246 31.47697 80.14103
5  81.94490 84.59473 38.64098
6  42.37206 13.92785 82.04507
7  96.35445 51.81206 68.49373
8  97.81304 59.35508 88.33893
9  84.05219 94.24617 11.19208
10 99.66112 62.80196 77.88340

> quantile(df$x1, .95)
     95% 
98.82949 
> quantile(df$x2, .95)
     95% 
90.80355 

我的期望结果是,要么得到一个长格式的数据框,其中超出设置的百分位数的任何值都将被设为NA或完全删除;要么得到一个宽格式的数据框,其中超出设置的百分位数的任何值都将被设为NA。


这可能是一种情况,其中转换为长形数据有助于进行操作。如果对于给定的行,x1在百分位之上但x2不在,您想要做什么? - camille
我同意 - 我认为长格式会更好。我绝对不想删除整行。如果需要保持宽格式才能使其正常工作,用NA替换是可以的。然而,我认为转换为长格式会更容易,但我不确定如何继续下去。 - drj3122
2个回答

7

我认为最简单的方法是将数据转换为长格式,并使用 x1x2x3 作为分组计算分位数。如果需要,您可以将其拉伸回宽格式。您可以明确地用 NA 替换高值,但如果使用 tidyr::spread,那么缺失值会自动填充为 NA

出于清晰起见,我保留了一些中间步骤,但要点是先将数据转换为长格式,然后找到95百分位数,保留等于或小于95百分位数的值,最后再将其转换回宽格式。在分组后,我还添加了一个行号作为ID列,以避免 "重复名称..." 错误。使用分位数,代码如下:

library(tidyverse)

...

df %>%
  gather(key, value) %>%
  group_by(key) %>%
  mutate(q95 = quantile(value, 0.95), row = row_number())
#> # A tibble: 30 x 4
#> # Groups:   key [3]
#>    key   value   q95   row
#>    <chr> <dbl> <dbl> <int>
#>  1 x1     57.7  98.8     1
#>  2 x1     22.3  98.8     2
#>  3 x1     33.2  98.8     3
#>  4 x1     71.1  98.8     4
#>  5 x1     81.9  98.8     5
#>  6 x1     42.4  98.8     6
#>  7 x1     96.4  98.8     7
#>  8 x1     97.8  98.8     8
#>  9 x1     84.1  98.8     9
#> 10 x1     99.7  98.8    10
#> # ... with 20 more rows

你可以从这几行中看到,第10行的值高于相应的95百分位数,因此我们预计它将被过滤并转换为NA
然后使用分位数进行过滤和展开。
df %>%
  gather(key, value) %>%
  group_by(key) %>%
  mutate(q95 = quantile(value, 0.95), row = row_number()) %>%
  filter(value <= q95) %>%
  select(-q95) %>%
  spread(key, value) %>%
  select(-row)
#> # A tibble: 10 x 3
#>       x1    x2    x3
#>    <dbl> <dbl> <dbl>
#>  1  57.7  86.6  58.6
#>  2  22.3  70.1  27.5
#>  3  33.2  39.0  14.8
#>  4  71.1  31.5  80.1
#>  5  81.9  84.6  38.6
#>  6  42.4  13.9  82.0
#>  7  96.4  51.8  68.5
#>  8  97.8  59.4  NA  
#>  9  84.1  NA    11.2
#> 10  NA    62.8  77.9

在实践中,您不需要为q95添加整列,可以使用更简洁的方法,比如filter(value <= quantile(value, 0.95))


完美!正是我想要做的。谢谢。 - drj3122

2

截至2021年,使用filterif_all一起使用:

df %>%
    dplyr::filter(if_all(everything(), ~.x >= quantile(.x,.01) & .x <= quantile(.x,.99)))

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