Подтвердить что ты не робот

Как суммировать только часть таблицы?

У меня есть два связанных варианта использования, в которых мне нужно суммировать только части таблицы, указанные способом, аналогичным 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 и как ее использовать?

Конечно, довольно легко получить результат выше, но мне нужно сделать что-то подобное для многих разных случаев, со сложными и переменными критериями фильтрации.

4b9b3361

Ответ 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 вы должны позаботиться об изменении только значений внутри отмеченной группы.