Фильтровать по нескольким столбцам и пороговым значениям, характерным для каждого (в R) - PullRequest
0 голосов
/ 05 октября 2018

У меня есть датафрейм с ~ 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])) > UQ(as.name(threshold_cols[1])) ужасно ипоэтому мы будем благодарны за любые предложения относительно того, как улучшить это.

Я попытался сделать это, написав свою собственную функцию для добавления в конец канала dplyr.Функция оказалась чрезвычайно хакерской и трудной для чтения, но вот она:

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()).Больше информации здесь

(Да, сейчас это нелепо длинный вопрос)

1 Ответ

0 голосов
/ 06 октября 2018

Для многих задач, подобных этой, где я использую mutate_at, часто проще обобщить, используя gather данные и используя различные группировки для достижения того, что я хочу сделать.Вот пример, который принимает символьный аргумент starts_with в качестве селектора для «столбцов интереса», поскольку вы говорите: «Я могу выяснить, сколько столбцов интереса у меня есть в каждом случае, используя start_with ()».

По сути, мы можем поместить все интересующие нас столбцы в colname и value.Это делает каждую новую строку комбинацией oldrow-column.Затем мы можем рассчитать пороговое значение без необходимости прибегать к mutate_at, включив colname в группировку.Чтобы 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

Создано в 2018 г.-10-05 для представительного пакета (v0.2.0).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...