Dplyr: Сохранять группы нулевой длины

Созданный на 20 мар. 2014  ·  44Комментарии  ·  Источник: tidyverse/dplyr

http://stackoverflow.com/questions/22523131

Не уверен, какой интерфейс для этого должен быть - вероятно, по умолчанию должно быть drop = FALSE.

feature wip

Самый полезный комментарий

+1 - это препятствие для многих анализов

Все 44 Комментарий

Спасибо, что открыли этот выпуск, Хэдли.

: +1: сегодня столкнулся с той же проблемой, drop = FALSE мне очень поможет!

Есть идеи относительно сроков помещения эквивалента .drop = FALSE в dplyr? Мне это нужно для правильного рендеринга некоторых rCharts.

Тем временем я ДЕЙСТВИТЕЛЬНО получил ответ в вашей ссылке на работу.
http://stackoverflow.com/questions/22523131

Я сгруппировал по двум переменным.

+1 за возможность не отбрасывать пустые группы

Возможно некоторое совпадение с # 486 и # 413.

Было бы очень полезно не отбрасывать пустые группы. Часто требуется при создании сводных таблиц.

+1 - это препятствие для многих анализов

Согласен со всем вышесказанным - было бы очень полезно.

@romainfrancois В настоящее время build_index_cpp() не уважает атрибут drop:

t1 <- data_frame(
  x = runif(10),
  g1 = rep(1:2, each = 5),
  g2 = factor(g1, 1:3)
)
g1 <- grouped_df(t1, list(quote(g2)), drop = FALSE)
attr(g1, "group_size")
# should be c(5L, 5L, 0L)
attr(g1, "indices")
# shoud be list(0:4, 5:9, integer(0))

Атрибут drop применяется только при группировке по фактору, и в этом случае нам нужно иметь одну группу для каждого уровня фактора, независимо от того, действительно ли этот уровень применяется к данным.

Это также повлияет на глаголы одной таблицы следующим образом:

  • select() : без эффекта
  • arrange() : без эффекта
  • summarise() : функции, применяемые к нулевым группам строк, должны получать целые числа нулевого уровня. n() должен вернуть 0, mean(x) должен вернуть NaN
  • filter() : набор групп должен оставаться постоянным, даже если некоторые группы теперь не имеют строк
  • mutate() : не нужно оценивать выражения для пустых групп

В конце концов, drop = FALSE будет значением по умолчанию, и если сложно написать ветки drop = FALSE и drop = TRUE , я бы с радостью отказался от поддержки drop = FALSE ( так как вы всегда можете изменить уровень фактора самостоятельно или вместо этого использовать вектор символов).

Имеет ли это смысл? Если будет много работы, мы можем оттолкнуться до 0,4.

@statwonk , @wsurles , @jennybc , @slackline , @mcfrank , @ eipi10 Если вы хотите помочь, лучше всего будет поработать над набором тестовых примеров, которые проверяют все способы взаимодействия различных глаголов. с группами нулевой длины.

Ах. Думаю, я просто не знал, что должен был делать drop . Это проясняет. Не думаю, что это много работы.

Я открыл запрос на вытягивание # 833, который проверяет, правильно ли описанные выше глаголы одной таблицы обрабатывают группы нулевой длины. Большинство тестов закомментированы, потому что, конечно, dplyr в настоящее время их не выполняет.

+1, здесь есть обновления статуса? люблю резюмировать, нужно держать пустые уровни!

@ebergelson , вот мой текущий хак для получения групп нулевой длины. Мне это часто нужно, чтобы мои гистограммы складывались.

Здесь df имеет 3 столбца: имя, группа и метрика.

df2 <- expand.grid(name = unique(df$name), group = unique(df$group)) %>%
    left_join(df, by=c("name","group")) %>%
    mutate(metric = ifelse(is.na(metric),0,metric))

Я делаю нечто подобное - проверяю отсутствующие группы, затем, если они есть, генерирую все комбинации и left_join .

К сожалению, похоже, что эта проблема не пользуется большой популярностью ... возможно, потому, что существует простой обходной путь.

@wsurles , @bpbond спасибо, да, я использовал обходной путь, аналогичный тому, что вы предлагаете! хотелось бы увидеть встроенное исправление, например .drop.

Просто чтобы добавить и согласиться со всеми вышеизложенными - это очень важный аспект многих анализов. Хотелось бы увидеть реализацию.

Здесь необходимы дополнительные сведения:

Если у меня есть это:

> df <- data_frame( x = c(1,1,1,2,2), f = factor( c(1,2,3,1,1) ) )
> df
Source: local data frame [5 x 2]

  x f
1 1 1
2 1 2
3 1 3
4 2 1
5 2 1

И я группирую их по x затем f , в итоге я получаю 6 (2x3) групп, в которых группы (2, 2) и (2,3) пусты. Это нормально. Думаю, мне удастся реализовать это.

теперь, что, если у меня есть это:

> df <- data_frame( f = factor( c(1,1,2,2), levels = 1:3), x = c(1,2,1,4) )
> df
Source: local data frame [4 x 2]

  f x
1 1 1
2 1 2
3 2 1
4 2 4

и я хочу сгруппировать по f затем x . Какие бы группы были? @hadley

И stats::aggregate и plyr::ddply в этом случае возвращают 4 группы (1,1; 1,2; 2,1; и 2,4), поэтому я бы предложил, чтобы это поведение соответствовало .

Разве вместо этого он не должен согласиться с table() , т.е. вернуть 9 групп?

> table(df$f, df$x)
  1 2 4
1 1 1 0
2 1 0 1
3 0 0 0

Я ожидал, что df %>% group_by(f, x) %>% tally в основном даст тот же результат, что и with(df, as.data.frame(table(f, x))) и ddply(df, .(f, x), nrow, .drop=FALSE) .

Я думал, что нашим желаемым поведением было сохранение групп нулевой длины, если они являются факторами (например, .drop в plyr), поэтому я предполагаю, что нам понадобится предложение @huftis . Я бы посоветовал по умолчанию drop = TRUE, чтобы поведение по умолчанию не изменилось, повторяя предложение

Хммм, трудно понять, каким именно должно быть поведение. Правильно ли выглядят эти очень простые мысленные эксперименты?

df <- data_frame(x = 1, y = factor(1, levels = 2))
df %>% group_by(x) %>% summarise(n())
#> x n
#> 1 1  

df %>% group_by(y) %>% summarise(n())
#> y n
#> 1 1
#> 2 0

df %>% group_by(x, y) %>% summarise(n()
#> x y n
#> 1 1 1
#> 1 2 0

Но что, если x имеет несколько значений? Должно ли так работать?

df <- data_frame(x = 1:2, y = factor(1, levels = 2))
df %>% group_by(x, y) %>% summarise(n()
#> x y n
#> 1 1 1
#> 2 1 1
#> 1 1 0
#> 2 2 0

Может быть, сохранение пустых групп имеет смысл только при группировке по одной переменной? Если мы представим его более реалистично, например, data_frame(age_group = c(40, 60), sex = factor(M, levels = c("F", "M")) , действительно ли вам нужны подсчеты для женщин? Я думаю, что иногда да, а иногда нет. Мне кажется, что раскрытие всех комбинаций несколько отличается (и не зависит от использования факторов).

Может быть, group_by нужны аргументы drop и expand ? drop = FALSE сохранит все группы нулевого размера, сгенерированные уровнями факторов, которые не отображаются в данных. expand = TRUE сохранит все группы нулевого размера, созданные комбинациями значений, которые не появляются в данных.

@hadley Ваши примеры кажутся мне правильными (при условии, что вы имели в виду levels = 1:2 , а не levels = 2 ). И я думаю, что сохранение пустых групп имеет смысл даже при группировке по нескольким переменным. Например, если переменные были sex ( male и female ) и answer (в анкете с уровнями disagree , neutral , agree ), и вы хотите подсчитать частоту каждого ответа для каждого пола (например, для таблицы или для последующего построения графика), вы бы не хотели просто отбрасывать категорию ответов потому что ни одна женщина не ответила на это.

Я также ожидал бы, что факторные переменные останутся факторными переменными в результирующем data_frame (не преобразованном в строки) и с _оригинальными уровнями_. (Таким образом, при построении данных категории ответов будут располагаться в правильном порядке, а не в алфавитном порядке agree , disagree , neutral ).

В вашем последнем примере _в некоторых случаях_ было бы естественным опустить переменную sex (например, если _намеренно_ ни одна женщина не была обследована), и _ в других случаях_ нет (например, при подсчете количества врожденных дефектов, стратифицированных по пол (и, возможно, год)). Но с этим можно (и нужно) легко справиться _после_ агрегирования данных. (Другим решением было бы принять аргумент _vector-valued_ .drop . Это было бы неплохо, но я думаю, это может усложнить ситуацию?)

(Другим решением было бы принять аргумент .drop с векторным значением. Это было бы неплохо, но я думаю, это может усложнить ситуацию?)

Да, наверное, слишком сложно. В остальном я согласен с комментариями @huftis .

@hadley
думаю
ДА разверните все комбинации значений в group_by, если они существуют в данных.
НЕТ, не расширяйте уровни факторов, которых не существует.

Чаще всего я использую подготовку набора обобщенных данных для диаграммы (во время исследования). И диаграммы должны иметь все комбинации значений. Но им не обязательно иметь уровни факторов, которые имеют 0 для всех групп .. например, вы не можете сложить гистограмму без всех комбинаций. Но вам не нужны значения коэффициентов, которых нет в данных, они будут просто равны 0 при сложении и пустое значение в легенде.

Я считаю, что расширение всех значений до group_by должно быть по умолчанию, потому что гораздо проще (и гораздо более интуитивно понятно) отфильтровать 0 случаев после группы, если это необходимо. Я не думаю, что аргумент .drop необходим, потому что после него достаточно легко отфильтровать 0 случаев. Мы не используем дополнительных аргументов ни для одной из других функций, так что это нарушит стереотип. По умолчанию должно быть показано только отображение результатов для всех комбинаций существующих значений на основе group_by.

Думаю, это правильное поведение по умолчанию. Здесь уникальное будет расширяться только на основе существующих значений фактора, а не на всех уровнях фактора. (Это то, что я запускаю после запуска group_by, который сбрасывает 0 значений)

## Expand data so plot groups works correctly
  df2 <- expand.grid(name = unique(df$name), group = unique(df$group)) %>%
    left_join(df, by=c("name","group")) %>%
    mutate(
      measure = ifelse(is.na(measure),0,measure)
    )

Единственный случай, когда я вижу, где вы хотели бы получить значение, даже если все группы имели ноль, - это временные данные. Может быть, где-то посередине не хватает данных за день. Здесь все равно потребуется расширение и присоединение к диапазону дат. Случай факторного уровня не применяется. Я думаю, что это справедливо, если обработчик данных самостоятельно обработает недостающие даты.

Спасибо за вашу отличную работу над этой библиотекой. 90% моей работы использует dplyr. :)

Я полностью согласен с @huftis.

Я не думаю, что падение уровней или их комбинации должны иметь какое-либо отношение к данным. Возможно, вы создаете прототип функции или рисунка, используя небольшой образец. Или выполните операцию разделения-применения-объединения, и в этом случае вам нужна гарантия, что выходные данные каждой группы будут согласованы со всеми остальными.

Смягчение моей позиции: я думаю, что стоит подумать о том, должно ли поведение по умолчанию отличаться, когда группирующая переменная уже является подходящим фактором, или когда ее принуждают к фактору. Я вижу, что в случае принуждения обязанность хранить неиспользуемые уровни может быть меньше. Но если я приложил все усилия, чтобы настроить что-то в качестве фактора и взять под контроль уровни ... обычно есть веская причина, и я не хочу постоянно бороться за ее сохранение.

К вашему сведению, я бы тоже хотел увидеть эту функцию. У меня похожий сценарий, описанный @huftis, и препятствия , чтобы получить нужные мне результаты.

Пришел сюда из SO. Разве это не то, с чем должен помочь complete от "tidyr"?

Да. На самом деле я недавно только что узнал о «завершенном», и, похоже, это сделано вдумчиво.

Реализовать это для бэкэндов SQL сложно, потому что они по умолчанию отбрасывают все группы. Оставим ли мы все как есть и, возможно, реализуем tidyr :: complete () для SQL?

Я создал проблему №3033, не осознавая, что эта проблема уже существует - извиняюсь за дубликат. Чтобы добавить свое скромное предложение, в настоящее время я использую pull() и forcats::fct_count() в качестве решения этой проблемы.

Я не являюсь поклонником этого метода, потому что fct_count() выдает принцип тидиверса по созданию вывода, который всегда имеет тот же тип, что и ввод (т.е. эта функция создает тиббл из вектора), и у меня есть для переименования столбцов в выводе. Это создает 3 шага ( pull() %>% fct_count() %>% rename() ), когда dplyr::count() должен был покрыть один. Было бы замечательно, если бы forcats::fct_count() и dplyr::count() можно было каким-то образом объединить и исключить forcats::fct_count() .

Работает ли tidyr::complete() для факторов?

Все уровни факторов и уровни комбинаций факторов должны быть сохранены по умолчанию. Таким поведением можно управлять с помощью таких параметров, как drop , expand и т. Д. Таким образом, поведение по умолчанию dplyr::count() должно быть таким:

df <- data.frame(x = 1:2, y = factor(c(1, 1), levels = 1:2))
df %>% dplyr::count(x, y)
#>  # A tibble: 4 x 3
#>       x        y       n
#>     <int>   <fct>    <int>
#> 1     1        1       1
#> 2     2        1       1
#> 3     1        2       0
#> 4     2        2       0

Группы нулевой длины (комбинации групп) могут быть отфильтрованы позже. Но для исследовательского анализа мы должны увидеть полную картину.

  1. Есть ли обновления статуса решения этой проблемы?
  2. Есть ли планы полностью решить эту проблему?

2: да, безусловно
1: Эта проблема связана с некоторыми техническими трудностями при реализации, но я рассмотрю ее в ближайшие несколько недель.

Мы могли бы избежать этого, расширив данные постфактум, примерно так:

library(tidyverse)

truly_group_by <- function(data, ...){
  dots <- quos(...)
  data <- group_by( data, !!!dots )

  labels <- attr( data, "labels" )
  labnames <- names(labels)
  labels <- mutate( labels, ..index.. =  attr(data, "indices") )

  expanded <- labels %>%
    tidyr::expand( !!!dots ) %>%
    left_join( labels, by = labnames ) %>%
    mutate( ..index.. = map(..index.., ~if(is.null(.x)) integer() else .x ) )

  indices <- pull( expanded, ..index..)
  group_sizes <- map_int( indices, length)
  labels <- select( expanded, -..index..)

  attr(data, "labels")  <- labels
  attr(data, "indices") <- indices
  attr(data, "group_sizes") <- group_sizes

  data
}

df  <- data_frame(
  x = 1:2,
  y = factor(c(1, 1), levels = 1:2)
)
tally( truly_group_by(df, x, y) )
#> # A tibble: 4 x 3
#> # Groups:   x [?]
#>       x y         n
#>   <int> <fct> <int>
#> 1     1 1         1
#> 2     1 2         0
#> 3     2 1         1
#> 4     2 2         0
tally( truly_group_by(df, y, x) )
#> # A tibble: 4 x 3
#> # Groups:   y [?]
#>   y         x     n
#>   <fct> <int> <int>
#> 1 1         1     1
#> 2 1         2     1
#> 3 2         1     0
#> 4 2         2     0

очевидно, что в будущем это будет обрабатываться внутри, без использования tidyr или purrr.

Кажется, это решает исходный вопрос:

> df = data.frame(a=rep(1:3,4), b=rep(1:2,6))
> df$b = factor(df$b, levels=1:3)
> df %>%
+   group_by(b) %>%
+   summarise(count_a=length(a), .drop=FALSE)
# A tibble: 2 x 3
  b     count_a .drop
  <fct>   <int> <lgl>
1 1           6 FALSE
2 2           6 FALSE
> df %>%
+   truly_group_by(b) %>%
+   summarise(count_a=length(a), .drop=FALSE)
# A tibble: 3 x 3
  b     count_a .drop
  <fct>   <int> <lgl>
1 1           6 FALSE
2 2           6 FALSE
3 3           0 FALSE

Ключевым моментом здесь является это

 tidyr::expand( !!!dots ) %>%

что означает расширение всех возможностей независимо от того, являются ли переменные факторами или нет.

Я бы сказал, что мы либо:

  • развернуть все, когда drop=FALSE , потенциально имея много групп нулевой длины
  • делаем то, что делаем сейчас, если drop=TRUE

возможно, есть функция для переключения падения.

Я бы сказал, что это относительно дешевая операция, потому что она включает только манипулирование метаданными, поэтому, может быть, сначала сделать это в R менее рискованно?

Вы имели в виду crossing() вместо expand() ?

Глядя на внутреннее устройство, согласны ли вы, что нам «всего лишь» нужно изменить build_index_cpp() , в частности, генерацию фрейма данных labels , чтобы это произошло?

Можно ли начать с расширения только факторов с помощью drop = FALSE ? Я считал "естественным" синтаксис, но в конечном итоге он может быть слишком запутанным (и, возможно, даже недостаточно мощным):

group_by(data, crossing(col1, col2), col3)

Семантика: использование всех комбинаций col1 и col2 , а также существующих комбинаций с col3 .

Да, я бы сказал, что это влияет только на build_index_cpp и создание атрибутов labels , indices и group_sizes которые я хотел бы раздавить опрятное строение в составе №3489

«Единственные факторы расширения» в этой дискуссии - это то, что заняло так много времени.

Каковы были бы результаты этого:

library(dplyr)

d <- data_frame(
  f1 = factor( rep( c("a", "b"), each = 4 ), levels = c("a", "b", "c") ),
  f2 = factor( rep( c("d", "e", "f", "g"), each = 2 ), levels = c("d", "e", "f", "g", "h") ),
  x  = 1:8,
  y  = rep( 1:4, each = 2)
)

f <- function(data, ...){
  group_by(data, !!!quos(...))  %>%
    tally()
}
f(d, f1, f2, x)
f(d, x, f1, f2)

f(d, f1, f2, x, y)
f(d, x, f1, f2, y)

Я думаю, что f(d, f1, f2, x) должен дать те же результаты, что и f(d, x, f1, f2) , если порядок строк игнорируется. То же самое и с двумя другими.

Также интересно:

f(d, f2, x, f1, y)
d %>% sample_frac(0.3) %>% f(...)

Мне нравится идея реализации полного расширения только для факторов. Для несимвольных данных (включая логические) мы могли бы определить / использовать факторный класс, который наследует соответствующий тип данных. Возможно, предоставлено forcats ? Это затрудняет прострелить себе ногу.

реализация в # 3492

library(dplyr)
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
df <- data_frame( f = factor( c(1,1,2,2), levels = 1:3), x = c(1,2,1,4) )

( res1 <- tally(group_by(df,f,x, drop = FALSE)) )
#> # A tibble: 9 x 3
#> # Groups:   f [?]
#>   f         x     n
#>   <fct> <dbl> <int>
#> 1 1        1.     1
#> 2 1        2.     1
#> 3 1        4.     0
#> 4 2        1.     1
#> 5 2        2.     0
#> 6 2        4.     1
#> 7 3        1.     0
#> 8 3        2.     0
#> 9 3        4.     0
( res2 <- tally(group_by(df,x,f, drop = FALSE)) )
#> # A tibble: 9 x 3
#> # Groups:   x [?]
#>       x f         n
#>   <dbl> <fct> <int>
#> 1    1. 1         1
#> 2    1. 2         1
#> 3    1. 3         0
#> 4    2. 1         1
#> 5    2. 2         0
#> 6    2. 3         0
#> 7    4. 1         0
#> 8    4. 2         1
#> 9    4. 3         0

all.equal( res1, arrange(res2, f, x) )
#> [1] TRUE

all.equal( filter(res1, n>0), tally(group_by(df, f, x)) )
#> [1] TRUE
all.equal( filter(res2, n>0), tally(group_by(df, x, f)) )
#> [1] TRUE

Создано 10.04.2018 пакетом REPEX (v0.2.0).

Что касается того, решает ли complete() проблему - нет, не совсем. Какие бы сводки ни вычислялись, их поведение на пустых векторах необходимо сохранять, а не исправлять постфактум. Например:

data.frame(x=factor(1, levels=1:2), y=4:5) %>%
     group_by(x) %>%
     summarize(min=min(y), sum=sum(y), prod=prod(y))
# Should be:
#> x       min   sum  prod
#> 1         4     9    20
#> 2       Inf     0     1

sum и prod (и, в меньшей степени, min ) (и различные другие функции) имеют очень четко определенную семантику для пустых векторов, и нехорошо иметь затем придите с complete() и заново определите это поведение.

@kenahoo Не уверен, что понимаю. Это то, что вы получаете с текущей версией разработчика. Так что единственное, чего вы не получаете, это предупреждения от min()

library(dplyr)

data.frame(x=factor(1, levels=1:2), y=4:5) %>%
  group_by(x) %>%
  summarize(min=min(y), sum=sum(y), prod=prod(y))
#> # A tibble: 2 x 4
#>   x       min   sum  prod
#>   <fct> <dbl> <int> <dbl>
#> 1 1         4     9    20
#> 2 2       Inf     0     1

min(integer())
#> Warning in min(integer()): no non-missing arguments to min; returning Inf
#> [1] Inf
sum(integer())
#> [1] 0
prod(integer())
#> [1] 1

Создано 15.05.2018 пакетом REPEX (v0.2.0).

@romainfrancois О, круто, я не знал, что вы уже так далеко продвинулись в этой реализации. Выглядит отлично!

Этот старый выпуск был автоматически заблокирован. Если вы считаете, что обнаружили связанную проблему, сообщите о новой проблеме (с помощью REPEX) и укажите ссылку на нее. https://prex.tidyverse.org/

Была ли эта страница полезной?
0 / 5 - 0 рейтинги