【问题标题】:Filter according to several columns and thresholds specific to each (in R)根据多个列和特定于每个列的阈值进行过滤(在 R 中)
【发布时间】:2018-10-05 10:13:54
【问题描述】:

我有一个大约 600 列的数据框。我想根据变量对我的数据框进行分组,并根据每个列和组的特定阈值对这些“感兴趣的列”(通常是总列数的一小部分)的n 进行过滤。

我已经开始使用 dplyr 执行此操作。我将使用iris 数据集(因为我不是原创)来演示:

library(tidyverse)

iris %>%
  group_by(Species) %>%
  mutate_at(vars(starts_with("Petal")), 
            funs(threshold = quantile(., 0.5) - IQR(.)))

这会计算我的阈值(每组和每列)并将它们放入名为 Petal.Length_threshold 和 Petal.Width_threshold 的新列中。

# A tibble: 150 x 7
# Groups:   Species [3]
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species Petal.Length_th…
          <dbl>       <dbl>        <dbl>       <dbl> <fct>              <dbl>
 1          5.1         3.5          1.4         0.2 setosa              1.32
 2          4.9         3            1.4         0.2 setosa              1.32
 3          4.7         3.2          1.3         0.2 setosa              1.32
 4          4.6         3.1          1.5         0.2 setosa              1.32
 5          5           3.6          1.4         0.2 setosa              1.32
 6          5.4         3.9          1.7         0.4 setosa              1.32
 7          4.6         3.4          1.4         0.3 setosa              1.32
 8          5           3.4          1.5         0.2 setosa              1.32
 9          4.4         2.9          1.4         0.2 setosa              1.32
10          4.9         3.1          1.5         0.1 setosa              1.32

我现在要做的是检查每一行的所有感兴趣的列是否大于它们各自的(组和列)阈值。我是这样做的:

columns <- colnames(
iris %>% 
  select(starts_with("Petal"))
)

threshold_cols <- paste(columns, "threshold", sep = "_")

filtered_iris <- iris %>%
  group_by(Species) %>%
  mutate_at(vars(starts_with("Petal")), 
            funs(threshold = quantile(., 0.5) - IQR(.))) %>%
  filter(UQ(as.name(columns[1])) > UQ(as.name(threshold_cols[1])) &
           UQ(as.name(columns[2])) > UQ(as.name(threshold_cols[2])))

(注意UQ(as.name()) 是由于烦人的 dplyr 非标准评估导致难以将列名作为变量输入到 dplyr 函数中)。

问题是我想概括这一点(因为我想编写一个可重用的函数),以便它能够将任意数量的“感兴趣的列”与其各自的(组和列)阈值进行比较。我可以使用starts_with() 找出在每种情况下我有多少感兴趣的列,这将是上面代码中columns 的长度。

此外,写出UQ(as.name(columns[1])) &gt; UQ(as.name(threshold_cols[1])) 既丑陋又耗时,因此对于如何改进这一点的任何建议也将不胜感激。

我通过编写自己的函数来添加到 dplyr 管道的末尾来尝试此操作。该功能变得非常hacky且难以阅读,但它是:

columns <- colnames(
iris %>% 
  select(starts_with("Petal"))
)

threshold_fun <- function(x){

  # obtain only columns of interest
  reduced_x <- x[,columns]

  # create empty threshold vector
  threshold <- vector(mode = "numeric",
                      length = length(columns))

  # fill vector with the threshold
  # result should be a vector of 2 (in this case) with the
  # Petal.Length threshold then the Petal.Width threshold

  for (i in 1:length(columns)){

    print(i)

   threshold[i] <- quantile(reduced_x[,i], 0.5) + IQR(reduced_x[,i])

  }

  # for each row check that all elements are greater than 
  # threshold. Result should be vector of TRUEs and FALSEs
  filter_rows <- apply(reduced_x, 1, function(a)
    sum(a > threshold) == length(columns))

  # subset using vector above
  filtered_x <- x[filter_rows,]

  return(filtered_x)

}

my_filter <- iris %>%
  group_by(Species) %>%
  threshold_fun()

这给了我一个错误Error: Can't use matrix or array for column indexing。我尝试添加print() 语句来试图找出问题出现在这个函数的哪个位置,并且它似乎在for循环中。仅此一项就会出现上述错误:quantile(reduced_x[,i], 0.5)。

我的问题是,如何概括第一个 dplyr 代码或修复我的函数?

编辑

Calum You 的回答很好,但如果它对将来想知道这个问题的人有用,我已经设法使我的功能正常工作:

columns <- colnames(
iris %>% 
  select(starts_with("Petal"))
)

threshold_fun <- function(x){

  # obtain only columns of interest
  reduced_x <- x[,columns]

  # create empty threshold vector
  threshold <- vector(mode = "numeric",
                      length = length(columns))

  for (i in 1:length(columns)){

    threshold[i] <- quantile(reduced_x[,i][[1]], 0.5) - IQR(reduced_x[,i][[1]])

  }

  # for each row check that all elements are greater than threshold. 
  # Result should be vector of TRUEs and FALSEs

   filter_rows <- apply(reduced_x, 1, function(a){
     sum(a > threshold) == length(columns)}
     )

   # subset using vector above
   filtered_x <- x[filter_rows,]
   # 
   return(filtered_x)

}

myiris <- iris %>%
  group_by(Species) %>%
  do(threshold_fun(.))
  • reduced_x[,i] 返回一个数据帧,而 reduced_x[,i][[1]] 返回一个向量。 mean 和 quantile 等函数需要向量
  • 添加do() (do(threshold_fun(.))) 可确保尊重组,并在数据帧组而不是整个数据帧上执行功能(如直接通过管道传输到threshold_fun() 时)。更多信息here

(是的,现在这是一个荒谬的长问题)

【问题讨论】:

  • 您到底希望能够传递给这个函数什么?列名列表,或者因为您只是在查找带有starts_with 的列,您可以将其作为参数传递吗?
  • 抱歉,我应该指定 - 我希望分组的变量和放入 starts_with() 以查找感兴趣的列的字符串每次都是相同的。所以这个函数应该只需要将df作为参数。
  • @JPL:您能解释一下quantile(., 0.5) - IQR(.)) 衡量的阈值,统计数据吗?

标签: r dplyr


【解决方案1】:

对于我发现自己使用mutate_at 的许多此类任务,通过gathering 数据并使用不同的分组来实现我想要做的事情通常更容易概括。这是一个接受字符 starts_with 参数作为“感兴趣的列”的选择器的示例,因为您说“我能够使用starts_with() 找出每种情况下我有多少感兴趣的列”。

基本上,我们可以将所有感兴趣的列放入colname和value。这使得每个新行都成为旧行-列组合。然后我们可以通过在分组中包含colname 来计算阈值而无需诉诸mutate_at。对于filter 结果,我们对rowid 而不是colname 进行分组并使用all(因此对于每个原始行,如果感兴趣的列中的任何值超过其各自的阈值,则该列中的所有值行被删除)。最后,我们可以spread 退出并清理我们创建的临时变量。

带有"Sepal" 参数和mtcars 的示例。

library(tidyverse)
filter_threshold <- function(df, group_col, starts_with){
  group_col <- enquo(group_col)
  df %>%
    rowid_to_column() %>%
    gather(colname, value, starts_with(starts_with)) %>%
    group_by(!!group_col, colname) %>%
    mutate(threshold = quantile(value, 0.5) - IQR(value)) %>%
    group_by(rowid, !!group_col) %>%
    filter(all(value > threshold)) %>%
    ungroup() %>%
    select(-threshold) %>%
    spread(colname, value) %>%
    select(-rowid)
}
iris %>% filter_threshold(Species, "Petal")
#> # A tibble: 122 x 5
#>    Sepal.Length Sepal.Width Species Petal.Length Petal.Width
#>           <dbl>       <dbl> <fct>          <dbl>       <dbl>
#>  1          5.1         3.5 setosa           1.4         0.2
#>  2          4.9         3   setosa           1.4         0.2
#>  3          4.6         3.1 setosa           1.5         0.2
#>  4          5           3.6 setosa           1.4         0.2
#>  5          5.4         3.9 setosa           1.7         0.4
#>  6          4.6         3.4 setosa           1.4         0.3
#>  7          5           3.4 setosa           1.5         0.2
#>  8          4.4         2.9 setosa           1.4         0.2
#>  9          5.4         3.7 setosa           1.5         0.2
#> 10          4.8         3.4 setosa           1.6         0.2
#> # ... with 112 more rows
iris %>% filter_threshold(Species, "Sepal")
#> # A tibble: 121 x 5
#>    Petal.Length Petal.Width Species Sepal.Length Sepal.Width
#>           <dbl>       <dbl> <fct>          <dbl>       <dbl>
#>  1          1.4         0.2 setosa           5.1         3.5
#>  2          1.4         0.2 setosa           4.9         3  
#>  3          1.3         0.2 setosa           4.7         3.2
#>  4          1.4         0.2 setosa           5           3.6
#>  5          1.7         0.4 setosa           5.4         3.9
#>  6          1.5         0.2 setosa           5           3.4
#>  7          1.5         0.1 setosa           4.9         3.1
#>  8          1.5         0.2 setosa           5.4         3.7
#>  9          1.6         0.2 setosa           4.8         3.4
#> 10          1.4         0.1 setosa           4.8         3  
#> # ... with 111 more rows
mtcars %>% filter_threshold(cyl, "d")
#> # A tibble: 26 x 11
#>      mpg   cyl    hp    wt  qsec    vs    am  gear  carb  disp  drat
#>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#>  1  21       6   110  2.62  16.5     0     1     4     4  160   3.9 
#>  2  21       6   110  2.88  17.0     0     1     4     4  160   3.9 
#>  3  22.8     4    93  2.32  18.6     1     1     4     1  108   3.85
#>  4  18.7     8   175  3.44  17.0     0     0     3     2  360   3.15
#>  5  14.3     8   245  3.57  15.8     0     0     3     4  360   3.21
#>  6  22.8     4    95  3.15  22.9     1     0     4     2  141.  3.92
#>  7  19.2     6   123  3.44  18.3     1     0     4     4  168.  3.92
#>  8  17.8     6   123  3.44  18.9     1     0     4     4  168.  3.92
#>  9  16.4     8   180  4.07  17.4     0     0     3     3  276.  3.07
#> 10  17.3     8   180  3.73  17.6     0     0     3     3  276.  3.07
#> # ... with 16 more rows

由reprex package (v0.2.0) 于 2018 年 10 月 5 日创建。

【讨论】:

  • 非常感谢!很有魅力!在group_by(rowid, !!group_col) 行中 - 我注意到您只需要group_by(rowid) 因为这无论如何都会将group_col 单独分组。还有关于为什么我的threshold_fun 出现错误的任何想法? i 应该只是一个数字而不是“矩阵或数组”?它仍然困扰着我......
  • 没有测试,我的猜测是filtered_x &lt;- x[filter_rows,],因为filter_rows来自apply,可能是一个矩阵
  • 我认为你不需要group_by(rowid, !!group_col) %&gt;% filter(all(value &gt; threshold))。改用更简单的ungroup() %&gt;% filter(value&gt;threshold)
猜你喜欢
  • 2021-10-17
  • 2022-01-17
  • 1970-01-01
  • 2020-04-06
  • 2015-03-26
  • 1970-01-01
  • 1970-01-01
  • 2021-10-20
  • 1970-01-01
相关资源
最近更新 更多