У меня есть данные, дающие мне процент людей в некоторых группах с различным уровнем образования:
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(), но не совсем очевидно, как сгенерировать этот второй список в сжатой форме. Интересно, есть ли какое-нибудь решение для гнездования ...
В desired.df у вас есть переменная less.than.hs. Разве это не должно быть no.highschool?
не уверен о чем ты?
@lost Gregor превзошел меня, в желаемом результате вы повторяете переменные вашего ввода, поэтому их имена должны быть одинаковыми. Одного из них нет. Я решил, что это опечатка. О, и я пропустил часть о tidyverse, поэтому я был занят кодированием базового R-способа. Будет ли это интересно?
это была опечатка, извините. Зафиксированный.
@RuiBarradas, базовый метод R меня сейчас не интересует, но, если вы уже начали его использовать, возможно, это может быть кто-то другой, кто найдет это позже :)
Хорошо, я отправлю ответ.





how could I do this programatically (and elegantly) using tidyverse tools?
Определенно, первым делом нужно привести данные в порядок. Информация о кодировании (например, уровень edu) в именах столбцов - не аккуратно. Когда вы конвертируете 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
Это дает нам приятный аккуратный результат. Если вы хотите преобразовать эти данные из spread / cast в ваш неупорядоченный формат desired.df, я бы порекомендовал использовать data.table::dcast, поскольку (насколько мне известно) тидиверс не предлагает удобного способа распределения нескольких столбцов. См. Распространение нескольких столбцов с помощью tidyr или Как я могу распределить повторяющиеся измерения нескольких переменных в широком формате? для решения data.table или неэлегантной версии tidyr / dplyr. Перед распространением можно было создать ключ less_than_x_key = paste("lessthan", education, sep = "_").
это намеренно в неаккуратном формате. Он в этом формате, потому что он будет присоединен к данным индивидуального уровня, которые находятся в аккуратном формате и которые будут использоваться для моделирования и т. д.
Отлично. Но если вы хотите «элегантно» использовать инструменты tidyverse, вам нужно сначала привести их в порядок. И вопросы, на которые я ссылался, должны помочь вам вернуть его в нужный вам нестандартный формат. Я не думаю, что мне нужно здесь повторять эти ответы. Если есть изменения или обновления, следует обновить те вопросы, которые касаются этой части проблемы.
Это работает, хотя порядок переменных отличается от OP: желаемый.df <- tidy_result%>% select (-n)%>% mutate (education = paste0 ("lessthan_", education))%>% spread (education , меньшее_x)%>% right_join (df)
Вот базовое решение R. Хотя вопрос касается tidyverse, учитывая диалог в комментариях к вопросу, я решил опубликовать его.
Он использует apply и cumsum для выполнения тяжелой работы. Затем есть некоторые косметические проблемы, прежде чем cbind войдет в окончательный результат.
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
нет уровня ниже no.highschool, поэтому lessthan_no.highschool всегда будет 0.