Как суммировать только часть таблицы?
У меня есть два связанных варианта использования, в которых мне нужно суммировать только части таблицы, указанные способом, аналогичным filter
.
Вкратце, я хочу что-то вроде этого:
iris %>%
use_only(Species == 'setosa') %>%
summarise_each(funs(sum), -Species) %>%
mutate(Species = 'setosa_sum') %>%
use_all()
Чтобы это сделать:
Source: local data frame [101 x 5]
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 250.3 171.4 73.1 12.3 setosa_sum
2 7.0 3.2 4.7 1.4 versicolor
3 6.4 3.2 4.5 1.5 versicolor
4 6.9 3.1 4.9 1.5 versicolor
5 5.5 2.3 4.0 1.3 versicolor
…
Поэтому вместо группировки по значению столбца я использую критерий фильтрации для работы с представлением таблицы, не теряя при этом остальную часть таблицы (в отличие от фильтра).
Как эффективно реализовать use_only
/use_all
? Еще лучше, эта функциональность уже содержится в dplyr
и как ее использовать?
Конечно, довольно легко получить результат выше, но мне нужно сделать что-то подобное для многих разных случаев, со сложными и переменными критериями фильтрации.
Ответы
Ответ 1
Я реализовал это с помощью подхода use_only
сохранить остальную часть таблицы в глобальную опцию dplyr_use_only_rest
и связав ее use_all
.
use_only <- function(.data, ...) {
if (!is.null(.data$.index)) {
stop("data cannot already have .index column, would be overwritten")
}
filt <- .data %>%
mutate(.index = row_number()) %>%
filter(...)
rest <- .data %>% slice(-filt$.index)
options(dplyr_use_only_rest = rest)
select(filt, -.index)
}
use_all <- function(.data, ...) {
rest <- getOption("dplyr_use_only_rest")
if (is.null(rest)) {
stop("called use_all() without earlier use_only()")
}
options(dplyr_use_only_rest = NULL)
bind_rows(.data, rest)
}
Я признаю, что установка глобальных параметров - это не что иное, как идеальный дизайн для функционального программирования, но я не думаю, что есть другой способ гарантировать, что остальная часть кадра данных пройдет через любые промежуточные функции, не затронутые. Добавление дополнительного атрибута к объекту не сохранило бы такие функции, как do
или summarize
.
В этот момент
iris %>%
use_only(Species == 'setosa') %>%
summarise_each(funs(sum), -Species) %>%
mutate(Species = 'setosa_sum') %>%
use_all()
возвращает, если требуется:
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 250.3 171.4 73.1 12.3 setosa_sum
2 7.0 3.2 4.7 1.4 versicolor
3 6.4 3.2 4.5 1.5 versicolor
4 6.9 3.1 4.9 1.5 versicolor
5 5.5 2.3 4.0 1.3 versicolor
...
Вместо summarize_each
и mutate
(do
, filter
и т.д.) можно использовать любые промежуточные шаги, и они будут выполняться только с указанными строками. Вы даже можете добавить или удалить столбцы (остаток будет заполнен с помощью NA
s).
Ответ 2
Я думаю, что ваш подход к поиску функции для удовлетворения этого синтаксиса слишком ограничительный. Это то, что я сделал бы с помощью data.table
(я не уверен, что dplyr
разрешает переменные строки, подобные этому еще, я знаю, что это FR какое-то время):
library(data.table)
dt = as.data.table(iris)
dt[, if (Species == 'setosa') lapply(.SD, sum) else .SD, by = Species]
# Species Sepal.Length Sepal.Width Petal.Length Petal.Width
# 1: setosa 250.3 171.4 73.1 12.3
# 2: versicolor 7.0 3.2 4.7 1.4
# 3: versicolor 6.4 3.2 4.5 1.5
# 4: versicolor 6.9 3.1 4.9 1.5
# 5: versicolor 5.5 2.3 4.0 1.3
# ---
Вы также можете добавить [Species == 'setosa', Species := 'setosa_sum']
в конец, чтобы изменить имя на месте. Это должно быть простым расширением до нескольких критериев/любой функции.
Ответ 3
Вы можете создать новый столбец для группировки:
iris %>%
mutate( group1 = ifelse(Species == "setosa", "", row_number())) %>%
group_by( group1, Species ) %>%
summarise_each(funs(sum), -Species, -group1) %>%
ungroup() %>%
select(-group1)
Обновление - как более общее решение
library(lazyeval)
use_only_ <- function(x, condition, ...) {
condition <- as.lazy(condition, parent.frame())
mutate_(x, .group = condition) %>%
group_by_(".group", ...)
}
use_only <- function(x, condition, ...) {
use_only_(x, lazy(condition), ...)
}
use_all <- function(x) {
ungroup(x) %>%
select(- .group)
}
Используйте use_only
с любым условием в контексте фрейма данных и вызывающей среды. В этом случае:
iris %>%
use_only( ifelse(Species == "setosa", "", row_number()), "Species") %>%
summarise_each(funs(sum), -Species, -.group) %>%
use_all()
use_only_
может использоваться с формулой или строкой. Например:
condition <- ~ifelse(Species == "setosa", "", row_number())
или
condition <- "ifelse(Species == 'setosa' , "", row_number())"
И вызов:
iris %>%
use_only_(condition, "Species") %>%
summarise_each(funs(sum), -Species, -.group) %>%
use_all()
При мутации между вызовами use_only
и use_all
вы должны позаботиться об изменении только значений внутри отмеченной группы.