У меня есть датафрейм с ~ 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()
).Больше информации здесь
(Да, сейчас это нелепо длинный вопрос)