программно создавать новые переменные, которые представляют собой суммы вложенных серий других переменных

У меня есть данные, дающие мне процент людей в некоторых группах с различным уровнем образования:

df <- data_frame(group = c("A", "B"),
             no.highschool = c(20, 10),
             high.school = c(70,40),
             college = c(10, 40),
             graduate = c(0,10))

df
    # A tibble: 2 x 5
  group no.highschool high.school college graduate
  <chr>         <dbl>       <dbl>   <dbl>    <dbl>
1 A               20.         70.     10.       0.
2 B               10.         40.     40.      10.

Например, в группе А 70% людей имеют среднее образование.

Я хочу сгенерировать 4 переменные, которые дают мне долю людей в каждой группе с уровнем образования меньше, чем каждый из 4 (например, lessthan_no.highschool, lessthan_high.school и т. Д.).

желаемый df будет:

desired.df <- data.frame(group = c("A", "B"),
                     no.highschool = c(20, 10),
                     high.school = c(70,40),
                     college = c(10, 40),
                     graduate = c(0,10),
                     lessthan_no.highschool = c(0,0),
                     lessthan_high.school = c(20, 10),
                     lessthan_college = c(90, 50),
                     lessthan_graduate = c(100, 90))

По моим фактическим данным, у меня много групп и гораздо больше уровней образования. Конечно, я мог бы делать это по одной переменной за раз, но как я могу сделать это программно (и элегантно) с помощью tidyverse инструментов?

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


person lost    schedule 26.08.2018    source источник
comment
нет уровня ниже no.highschool, поэтому lessthan_no.highschool всегда будет 0.   -  person lost    schedule 26.08.2018
comment
В desired.df у вас есть переменная less.than.hs. Разве это не должно быть no.highschool?   -  person Rui Barradas    schedule 26.08.2018
comment
не уверен о чем ты?   -  person lost    schedule 26.08.2018
comment
@lost Gregor превзошел меня, в желаемом результате вы повторяете переменные вашего ввода, поэтому их имена должны быть одинаковыми. Одного из них нет. Я решил, что это опечатка. О, и я пропустил часть о tidyverse, поэтому я был занят кодированием базового R-способа. Было бы интересно?   -  person Rui Barradas    schedule 26.08.2018
comment
это была опечатка, извините. Фиксированный.   -  person lost    schedule 26.08.2018
comment
@RuiBarradas, базовый метод R меня сейчас не интересует, но если вы уже начали его использовать, возможно, это может быть кто-то другой, кто найдет это позже :)   -  person lost    schedule 26.08.2018
comment
Хорошо, я отправлю ответ.   -  person Rui Barradas    schedule 26.08.2018


Ответы (2)


как я могу сделать это программно (и элегантно) с помощью инструментов tidyverse?

Определенно, первым делом нужно привести данные в порядок. Информация о кодировании (например, об уровне образования) в именах столбцов некорректна. При преобразовании education в коэффициент убедитесь, что уровни расположены в правильном порядке - я использовал порядок, в котором они появляются в исходных именах столбцов данных.

library(tidyr)
tidy_result = df %>% gather(key = "education", value = "n", -group) %>%
  mutate(education = factor(education, levels = names(df)[-1])) %>%
  group_by(group) %>%
  mutate(lessthan_x = lag(cumsum(n), default = 0) / sum(n) * 100) %>%
  arrange(group, education)
tidy_result
# # A tibble: 8 x 4
# # Groups:   group [2]
#   group education         n lessthan_x
#   <chr> <fct>         <dbl>      <dbl>
# 1 A     no.highschool    20          0
# 2 A     high.school      70         20
# 3 A     college          10         90
# 4 A     graduate          0        100
# 5 B     no.highschool    10          0
# 6 B     high.school      40         10
# 7 B     college          40         50
# 8 B     graduate         10         90

Это дает нам приятный аккуратный результат. Если вы хотите _3 _ / _ 4_ эти данные в ваш неаккуратный desired.df формат, я бы рекомендовал использовать data.table::dcast, поскольку (насколько мне известно) tidyverse не предлагает хороший способ разложить несколько столбцов. См. Распространение нескольких столбцов с помощью tidyr или Как я могу распределить повторяющиеся измерения нескольких переменных в широкий формат? для решения data.table или неэлегантной версии _8 _ / _ 9_. Перед выкладыванием можно было создать ключ less_than_x_key = paste("lessthan", education, sep = "_").

person Gregor Thomas    schedule 26.08.2018
comment
это намеренно в неаккуратном формате. Он в этом формате, потому что он будет присоединен к данным индивидуального уровня, которые находятся в аккуратном формате и которые будут использоваться для моделирования и т. Д. - person lost; 26.08.2018
comment
Отлично. Но если вы хотите элегантно использовать tidyverse инструменты, вам нужно сначала привести их в порядок. И вопросы, на которые я ссылался, должны помочь вам вернуть его в нужный вам нестандартный формат. Я не думаю, что мне нужно здесь повторять эти ответы. Если есть изменения или обновления, следует обновить те вопросы, которые касаются этой части проблемы. - person Gregor Thomas; 26.08.2018
comment
Это работает, хотя порядок переменных отличается от OP: желаемый.df ‹- tidy_result%›% select (-n)% ›% mutate (education = paste0 (lessthan_, education))%›% spread (education, lessthan_x) )% ›% Right_join (df) - person lost; 26.08.2018

Вот базовое решение R. Хотя вопрос требует tidyverse, учитывая диалог в комментариях к вопросу, я решил его опубликовать.
Он использует apply и cumsum для выполнения тяжелой работы. Перед тем, как перейти к окончательному результату, нужно внести некоторые косметические изменения.

tmp <- apply(df[-1], 1, function(x){
    s <- cumsum(x)
    100*c(0, s[-length(s)])/sum(x)
})
rownames(tmp) <- paste("lessthan", names(df)[-1], sep = "_")
desired.df <- cbind(df, t(tmp))

desired.df
#  group no.highschool high.school college graduate lessthan_no.highschool
#1     A            20          70      10        0                      0
#2     B            10          40      40       10                      0
#  lessthan_high.school lessthan_college lessthan_graduate
#1                   20               90               100
#2                   10               50                90
person Rui Barradas    schedule 26.08.2018