Превращение значений переменных в имена столбцов; повторяющиеся идентификаторы строк в tyr::spread

Я работаю с грязным файлом избирателя. Рассмотрим следующее tibble:

library(dplyr)
library(tidyr)
dat <- tibble(
  id = factor(c("A","B","C","D","E")),
  demographic_info1 = round(rnorm(5),2),
  demographic_info2 = round(rnorm(5),2),
  election_1 = c(NA,"GN2016","GN2016","SE2016","GN2008"),
  election_2 = c(NA,"MT2014","GN2012","GN2016","GN2004"),
  election_3 = c(NA,NA,NA,"MT2014","GN2000"),
  election_4 = c(NA,NA,NA,"GN2012",NA),
  election_5 = c(NA,NA,NA,"MT2010",NA),
)

Что выглядит так:

# A tibble: 5 x 8
      id demographic_info1 demographic_info2 election_1 election_2 election_3 election_4 election_5
  <fctr>             <dbl>             <dbl>      <chr>      <chr>      <chr>      <chr>      <chr>
1      A             -1.50              0.81       <NA>       <NA>       <NA>       <NA>       <NA>
2      B             -1.84             -0.64     GN2016     MT2014       <NA>       <NA>       <NA>
3      C              1.66             -0.10     GN2016     GN2012       <NA>       <NA>       <NA>
4      D              0.91             -0.08     SE2016     GN2016     MT2014     GN2012     MT2010
5      E              0.04             -1.15     GN2008     GN2004     GN2000       <NA>       <NA>
  • Каждый id является уникальным идентификатором избирателя.
  • Два столбца demographic_info являются заполнителями, просто чтобы продемонстрировать, что я хочу сохранить эти значения при изменении формы данных.

Меня интересуют столбцы с election_1 по election_5. Данные структурированы таким образом, что файл включает последние 5 выборов, в которых кто-то участвовал. election_1 — самые последние, election_5 — самые недавние.

Обратите внимание, что человек A никогда не приходил голосовать, в то время как человек D всегда делает это. Что я хотел бы сделать, так это превратить эти столбцы в ряд переменных: SE2016, GN2016, MT2014, GN2012 и т. д.; то есть все значения в диапазоне от election_1 до election_5. Я хотел бы, чтобы каждая из этих переменных была либо TRUE, либо FALSE для того, явился ли этот человек на опросы. Я пробовал этот код:

dat %>% # take data
  gather(election, race, election_1:election_5) %>% # gather by election
  mutate(temp=TRUE) %>% # make new variable that is all TRUE
  select(-election) %>% # drop election variable
  spread(race, temp, fill=FALSE) # spread by this all TRUE variable, fill all NAs as FALSE

Однако spread выдает ошибку:

Error: Duplicate identifiers for rows (1, 6, 11, 16, 21), (12, 17, 22), (13, 18, 23), (20, 25)

Это связано с тем, что для каждого значения переменной race имеется несколько записей. Я пытался выполнить group_by(id) перед выполнением spread, но выдается та же ошибка.

Я бы хотел, чтобы полученный tibble выглядел так:

# A tibble: 5 x 11
      id demographic_info1 demographic_info2 SE2016 GN2016 MT2014 GN2012 MT2010 GN2008 GN2004 GN2000
  <fctr>             <dbl>             <dbl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>
1      A             -0.91             -0.56  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE
2      B              1.24             -1.78  FALSE   TRUE   TRUE  FALSE  FALSE  FALSE  FALSE  FALSE
3      C              0.61              0.11  FALSE   TRUE  FALSE   TRUE  FALSE  FALSE  FALSE  FALSE
4      D              2.43             -0.53   TRUE   TRUE   TRUE   TRUE   TRUE  FALSE  FALSE  FALSE
5      E             -1.40             -1.23  FALSE  FALSE  FALSE  FALSE  FALSE   TRUE   TRUE   TRUE

person Mark White    schedule 19.08.2017    source источник
comment
Вам нужно создать столбец последовательности по группам, прежде чем выполнять spread   -  person akrun    schedule 19.08.2017
comment
@akrun переменная id не считается столбцом последовательности? Не уверен, что следую.   -  person Mark White    schedule 19.08.2017


Ответы (3)


tidyr предоставляет некоторый синтаксис для решения этой проблемы.

# set up
library(dplyr)
library(tidyr)
dat <- tibble(
  id = factor(c("A","B","C","D","E")),
  demographic_info1 = round(rnorm(5),2),
  demographic_info2 = round(rnorm(5),2),
  election_1 = c(NA,"GN2016","GN2016","SE2016","GN2008"),
  election_2 = c(NA,"MT2014","GN2012","GN2016","GN2004"),
  election_3 = c(NA,NA,NA,"MT2014","GN2000"),
  election_4 = c(NA,NA,NA,"GN2012",NA),
  election_5 = c(NA,NA,NA,"MT2010",NA)
)

В конечном итоге мы хотим получить TRUE или FALSE для каждой пары избирателей (5) x выборы (8). Когда мы собираем данные в длинном формате, мы видим только те комбинации избирателей и выборов, которые существуют в наборе данных.

d_votes <- dat %>%
  gather("variable", "election", election_1:election_5) %>%
  select(-variable) %>%
  mutate(voted = TRUE)
d_votes
#> # A tibble: 25 x 5
#>        id demographic_info1 demographic_info2 election voted
#>    <fctr>             <dbl>             <dbl>    <chr> <lgl>
#>  1      A              0.76             -0.23     <NA>  TRUE
#>  2      B             -0.80              0.08   GN2016  TRUE
#>  3      C             -0.33              1.60   GN2016  TRUE
#>  4      D             -0.50             -1.27   SE2016  TRUE
#>  5      E             -1.03              0.59   GN2008  TRUE
#>  6      A              0.76             -0.23     <NA>  TRUE
#>  7      B             -0.80              0.08   MT2014  TRUE
#>  8      C             -0.33              1.60   GN2012  TRUE
#>  9      D             -0.50             -1.27   GN2016  TRUE
#> 10      E             -1.03              0.59   GN2004  TRUE
#> # ... with 15 more rows

count(d_votes, election)
#> # A tibble: 9 x 2
#>   election     n
#>      <chr> <int>
#> 1   GN2000     1
#> 2   GN2004     1
#> 3   GN2008     1
#> 4   GN2012     2
#> 5   GN2016     3
#> 6   MT2010     1
#> 7   MT2014     2
#> 8   SE2016     1
#> 9     <NA>    13

Нам нужно сгенерировать каждую комбинацию избирателя и выборов. Функция expand() tidyr создает все комбинации переменных из разных столбцов/векторов данных. (Она работает как базовая функция expand.grid(), поэтому имя expand() вызывает ассоциации).

d_possible_votes <- d_votes %>%
  expand(nesting(id, demographic_info1, demographic_info2),
         election)
d_possible_votes
#> # A tibble: 40 x 4
#>        id demographic_info1 demographic_info2 election
#>    <fctr>             <dbl>             <dbl>    <chr>
#>  1      A              0.76             -0.23   GN2000
#>  2      A              0.76             -0.23   GN2004
#>  3      A              0.76             -0.23   GN2008
#>  4      A              0.76             -0.23   GN2012
#>  5      A              0.76             -0.23   GN2016
#>  6      A              0.76             -0.23   MT2010
#>  7      A              0.76             -0.23   MT2014
#>  8      A              0.76             -0.23   SE2016
#>  9      B             -0.80              0.08   GN2000
#> 10      B             -0.80              0.08   GN2004
#> # ... with 30 more rows

Обратите внимание, что теперь у нас есть 8 выборов x 5 идентификаторов = 40 строк.

Мы использовали функцию nesting() для обработки каждого набора/строки (id, demographic_info1, demographic_info2) как единой единицы; демографические данные вложены в идентификаторы. Расширение предоставило все 40 комбинаций (id, demographic_info1, demographic_info2) x election.

Если мы присоединим наблюдаемые голоса к возможным голосам, столбец voted будет заполнен значениями TRUE или NA. Функция replace_na() tidyr может исправить эти значения NA.

d_possible_votes <- d_possible_votes %>%
  left_join(d_votes) %>%
  replace_na(list(voted = FALSE))
#> Joining, by = c("id", "demographic_info1", "demographic_info2", "election")
d_possible_votes
#> # A tibble: 40 x 5
#>        id demographic_info1 demographic_info2 election voted
#>    <fctr>             <dbl>             <dbl>    <chr> <lgl>
#>  1      A              0.76             -0.23   GN2000 FALSE
#>  2      A              0.76             -0.23   GN2004 FALSE
#>  3      A              0.76             -0.23   GN2008 FALSE
#>  4      A              0.76             -0.23   GN2012 FALSE
#>  5      A              0.76             -0.23   GN2016 FALSE
#>  6      A              0.76             -0.23   MT2010 FALSE
#>  7      A              0.76             -0.23   MT2014 FALSE
#>  8      A              0.76             -0.23   SE2016 FALSE
#>  9      B             -0.80              0.08   GN2000 FALSE
#> 10      B             -0.80              0.08   GN2004 FALSE
#> # ... with 30 more rows

Теперь мы можем распределить выборы и получить желаемый фрейм данных.

spread(d_possible_votes, election, voted)
#> # A tibble: 5 x 11
#>       id demographic_info1 demographic_info2 GN2000 GN2004 GN2008 GN2012 GN2016 MT2010 MT2014 SE2016
#> * <fctr>             <dbl>             <dbl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>
#> 1      A              0.76             -0.23  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE
#> 2      B             -0.80              0.08  FALSE  FALSE  FALSE  FALSE   TRUE  FALSE   TRUE  FALSE
#> 3      C             -0.33              1.60  FALSE  FALSE  FALSE   TRUE   TRUE  FALSE  FALSE  FALSE
#> 4      D             -0.50             -1.27  FALSE  FALSE  FALSE   TRUE   TRUE   TRUE   TRUE   TRUE
#> 5      E             -1.03              0.59   TRUE   TRUE   TRUE  FALSE  FALSE  FALSE  FALSE  FALSE

Этот шаблон создания комбинаций идентификаторов, объединения фактических данных и исправления отсутствующих значений очень распространен — настолько, что tidyr включает функцию complete(), которая выполняет все три действия одновременно.

d_votes %>%
  complete(nesting(id, demographic_info1, demographic_info2),
           election, fill = list(voted = FALSE)) %>%
  spread(election, voted)
#> # A tibble: 5 x 11
#>       id demographic_info1 demographic_info2 GN2000 GN2004 GN2008 GN2012 GN2016 MT2010 MT2014 SE2016
#> * <fctr>             <dbl>             <dbl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>
#> 1      A              0.76             -0.23  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE
#> 2      B             -0.80              0.08  FALSE  FALSE  FALSE  FALSE   TRUE  FALSE   TRUE  FALSE
#> 3      C             -0.33              1.60  FALSE  FALSE  FALSE   TRUE   TRUE  FALSE  FALSE  FALSE
#> 4      D             -0.50             -1.27  FALSE  FALSE  FALSE   TRUE   TRUE   TRUE   TRUE   TRUE
#> 5      E             -1.03              0.59   TRUE   TRUE   TRUE  FALSE  FALSE  FALSE  FALSE  FALSE
person TJ Mahr    schedule 21.08.2017
comment
Спасибо за прекрасное объяснение того, как работает complete. Я не знал об этой функции! Что вы думаете о моем решении, в сравнении? Мой код кажется менее элегантным, но microbenchmark говорит, что он работает быстрее (примерно на 6 миллисекунд быстрее, что может иметь большое значение, когда я запускаю его для файла размером 1 ГБ) - person Mark White; 21.08.2017

Мы можем использовать group_by для 'id', чтобы создать переменную последовательности, поскольку 'id' дублируется, а затем удалить ее после spread.

dat %>%
   gather(election, race, election_1:election_5) %>%
   mutate(temp=TRUE)%>% group_by(id) %>%
   mutate(i1 = row_number()) %>% 
   select(-election) %>%
   spread(race, temp, fill=FALSE) %>%
   select(-i1)
person akrun    schedule 19.08.2017
comment
Блестяще, спасибо. Просто чтобы убедиться, что я понимаю, почему это работает: i1 предоставляет уникальный идентификатор в каждой группе...? - person Mark White; 19.08.2017
comment
@MarkWhite Да, потому что для каждого «идентификатора» есть по 5 строк, то есть dat %>% gather(election, race, election_1:election_5) %>% mutate(temp=TRUE)%>% count(id) %>% .$n# [1] 5 5 5 5 5 Таким образом, «i1» создает последовательность из 1, 2, ,3, ..5 для каждого «идентификатора», и это может помочь в распространении - person akrun; 19.08.2017
comment
На второй взгляд, используя это в моем реальном наборе данных: приведенный выше код создает пять строк для каждого id; одна строка для каждого поля election_*. - person Mark White; 19.08.2017
comment
Я видел и другие ваши ответы, например этот (stackoverflow.com/a/43259735/7903456). Кажется, это должно работать для моих целей, но по какой-то причине я получаю новую строку для каждого поля election_*. - person Mark White; 20.08.2017

Проблема заключалась в том, что были повторяющиеся записи для значений NA. Я решил проблему дублирующихся идентификаторов и проблемы с несколькими строками из ответа akrun, взяв только unique строки, а затем сгруппировав их по id:

dat %>%
  gather(election, race, election_1:election_5) %>%
  mutate(temp=TRUE) %>%
  select(-election) %>%
  unique() %>% # GET RID OF DUPLICATE NA ENTRIES
  group_by(id) %>% 
  spread(race, temp, fill=FALSE) %>%
  select(-`<NA>`)

# A tibble: 5 x 11
# Groups:   id [5]
      id demographic_info1 demographic_info2 GN2000 GN2004 GN2008 GN2012 GN2016 MT2010 MT2014 SE2016
* <fctr>             <dbl>             <dbl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>  <lgl>
1      A             -1.19             -0.94  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE  FALSE
2      B              1.41             -0.62  FALSE  FALSE  FALSE  FALSE   TRUE  FALSE   TRUE  FALSE
3      C             -0.21              1.62  FALSE  FALSE  FALSE   TRUE   TRUE  FALSE  FALSE  FALSE
4      D              1.51              0.09  FALSE  FALSE  FALSE   TRUE   TRUE   TRUE   TRUE   TRUE
5      E              0.65             -2.09   TRUE   TRUE   TRUE  FALSE  FALSE  FALSE  FALSE  FALSE
person Mark White    schedule 19.08.2017