以编程方式创建新变量,这些变量是嵌套的其他变量系列的总和

programatically create new variables which are sums of nested series of other variables

我有数据显示某些群体中具有不同教育程度的人的百分比:

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.

例如A组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 工具以编程方式(优雅地)做到这一点?

我会先在 map() 中做一些类似 mutate_at() 的事情,但是我被绊倒的地方是每个新变量的求和变量列表是不同的。您可以将新变量列表及其对应的变量作为两个列表求和到 pmap(),但如何简洁地生成第二个列表并不明显。想知道是否有某种嵌套解决方案...

how could I do this programatically (and elegantly) using tidyverse tools?

当然第一步是整理你的数据。列名中的编码信息(如教育级别)不整洁。当您将 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,因为(据我所知)tidyverse 不提供传播多列的好方法。有关 data.table 解决方案或不雅的 tidyr/dplyr 版本,请参阅 or 。在传播之前,您可以创建一个密钥 less_than_x_key = paste("lessthan", education, sep = "_").

这是一个基本的 R 解决方案。虽然问题要求 tidyverse,但考虑到问题评论中的对话,我决定 post 它。
它使用 applycumsum 来完成艰苦的工作。然后在 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