在R中向量化复杂的dplyr语句

问题描述 投票:3回答:7

我正在努力计算参加课程的学生数量,从那些能够参加课程的学生,并非所有学校都提供计算机,不同的学校提供​​英语,那些能够学习计算和英语的学生会有所不同。例如。使用下面的测试数据,我们有:

df <- read.csv(text="school, student, course, result
      URN1,stu1,comp,A
      URN1,stu2,comp,B
      URN1,stu3,comp,C
      URN1,stu1,Eng,D
      URN1,stu1,ICT,E
      URN2,stu4,comp,A
      URN1,stu1,ICT,B
      URN2,stu5,comp,C
      URN3,stu6,comp,D
      URN3,stu6,ICT,E
      URN4,stu7,Eng,E
      URN4,stu8,ICT,E
      URN4,stu8,Eng,E
      URN5,stu9,comp,E
      URN5,stu10,ICT,E")

[1]“由58.3333333333333%可能的学生参加”

[1]“33.3333333333333%可能的学生参加了”

[1]“信息通信技术由38.4615384615385%的可能学生参加”

我有以下循环(嘘!)来做到这一点:

library(magrittr)
library(dplyr)

for(c in unique(df$course)){
  # c <- "comp"
  #get URNs of schools offering each course
  URNs <- df %>% filter(course == c) %>% distinct(school) %$% school
  #get number of students in each school offering course c
  num_possible <- df %>% filter(school %in% URNs) %>% summarise(n = n()) %$% n
  #get number of students taking course c 
  num_actual <- df %>% filter(course == c) %>% summarise(n = n()) %$% n

  # get % of students taking course from those who could theoretically take c
  print(paste(c, "taken by", (100 * num_actual/num_possible), "% of possible students"))
}

但是想要将它全部矢量化,但是,我无法将num_possible与num_actual放在同一个函数中:

df %>% group_by(course) %>% summarise(num_possible = somesubfunction(),
                                      num_actual = n())

somesubfunction()应该返回可能参加课程的学生人数c

r vector dplyr
7个回答
4
投票

您可以先创建一个辅助数据框,然后映射它以获得可能的学生数。考虑一下

school_students <- df %>% 
  group_by(school) %>% 
  summarise(students = n(), courses = paste0(unique(course), collapse = ", "))

df %>% 
  count(course) %>%
  mutate(possible = map_int(as.character(course), 
    ~sum(school_students[str_detect(school_students$courses, .), "students"]))) %>%
  mutate(pct = n / possible * 100)

# A tibble: 3 x 4
  course     n possible   pct
  <fct>  <int>    <int> <dbl>
1 comp       7       12  58.3
2 Eng        3        9  33.3
3 ICT        5       13  38.5

5
投票

如果你热衷于尝试与不同的东西,你可以尝试使用

library(data.table)

setDT(df)[, nb_stu:=.N, by=course] # how many students by course
df[, nb_stu_ec:=length(unique(student)), by=school] # how many students per school (!: Edited to avoid counting some students twice if they take multiple courses)

# finally compute the number of student for a course 
# divided by the number of students in the schools that have this course (sprintf is only for formating the result):
df[, sprintf("%.2f", 100*first(nb_stu)/sum(nb_stu_ec[!duplicated(school)])), by=course]
#   course    V1
#1:   comp 87.50
#2:    Eng 60.00
#3:    ICT 62.50

Nota Bene:如果仅在最后一步计算每门课程的学生人数,则可以少一步实现:

setDT(df)[, nb_stu_ec:=length(unique(student)), by=school]
df[, sprintf("%.2f", 100*(.N)/sum(nb_stu_ec[!duplicated(school)])), by=course]

#   course    V1
#1:   comp 87.50
#2:    Eng 60.00
#3:    ICT 62.50

4
投票

另一个短的dplyr答案。加入课程和学校概述,然后简单的summarise

library(dplyr)

left_join(
  count(df, course),
  df %>% group_by(school) %>% transmute(s = n(), course) %>% distinct()
) %>% 
  group_by(course) %>% 
  summarise(actual = first(n),
            total = sum(s),
            perc = actual / total * 100)

这再现了你的答案:

# A tibble: 3 x 4
  course actual total  perc
  <fct>   <int> <int> <dbl>
1 comp        7    12  58.3
2 Eng         3     9  33.3
3 ICT         5    13  38.5

但可能,你不想重复计算学生,所以寻找不同的学生:

left_join(
  count(df, course),
  df %>% group_by(school) %>% transmute(s = n_distinct(student), course) %>% distinct()
) %>% 
  group_by(course) %>% 
  summarise(actual = first(n),
            total = sum(s),
            perc = actual / total * 100)
# A tibble: 3 x 4
  course actual total  perc
  <fct>   <int> <int> <dbl>
1 comp        7     8  87.5
2 Eng         3     5  60.0
3 ICT         5     8  62.5

3
投票

我首先计算一个频率表 - 你不需要在这里使用原始数据:

ft <- with(df, as.matrix(table(school, course)))
# converting table to matrix to make it easier to handle

然后你可以使用forsapplyapply进行分割和乘法:

sapply(1:ncol(ft), function(x) {
  k <- ft[, x]
  sum(k) / sum(ft[k!=0,])*100
})

或者(这是一个简单的问题,因此使用多行是浪费行):

apply(ft, 2, function(k) sum(k) / sum(ft[k!=0,]))*100
#      comp      Eng      ICT 
#  58.33333 33.33333 38.46154 

2
投票

您将总结不同的变量级别。可能的学生数量总结在学校层面,而实际学生的数量总结在课程级别,其中级别没有嵌套在其中。

因此,我发现创建两个不同的数据帧然后将它们连接在一起更容易,但我还将提供一个解决方案,在一个长调用中执行它。

library(dplyr)

首先总结一下学校层面的数据:

df_school <- df %>% 
  group_by(school) %>% 
  summarise(n_students_school = n_distinct(student))

df_school
# A tibble: 5 x 2
#   school n_students_school
#   <fct>              <int>
# 1 URN1                   3
# 2 URN2                   2
# 3 URN3                   1
# 4 URN4                   2
# 5 URN5                   2

要为每所学校选择可能的课程,请使用原始df的left_join,但仅使用distinctschoolcourse组合:

df_possible <- df %>% 
  select(school, course) %>% 
  distinct() %>% 
  left_join(df_school, by = "school") %>% 
  group_by(course) %>% 
  summarise(n_possible = sum(n_students_school))

df_possible
# A tibble: 3 x 2
#   course n_possible
#   <fct>       <int>
# 1 comp            8
# 2 Eng             5
# 3 ICT             8

计算每门课程的实际学生人数:

df_actual <- df %>%
  group_by(course) %>% 
  summarise(n_actual = n_distinct(student))

df_actual
# A tibble: 3 x 2
#   course n_actual
#   <fct>     <int>
# 1 comp          7
# 2 Eng           3
# 3 ICT           4

将两个数据框加入到最终数据框中,计算出课程中学生的百分比:

df_final <- left_join(df_possible, df_actual, by = "course") %>%
  mutate(percentage = n_actual/n_possible)

df_final
# A tibble: 3 x 4
#   course n_possible n_actual percentage
#   <fct>       <int>    <int>      <dbl>
# 1 comp            8        7      0.875
# 2 Eng             5        3      0.600
# 3 ICT             8        4      0.500

或者在不同分组的长途电话中(积分转到@alistaire):

df %>% 
  group_by(school) %>% 
  group_by(school, course, n_students_school = n_distinct(student)) %>% 
  summarise(n_students_course = n_distinct(student)) %>% 
  group_by(course) %>% 
  summarise(n_possible = sum(n_students_school), 
            n_actual = sum(n_students_course), 
            percentage = n_actual / n_possible) 

在这里,通过将n_students_school = n_distinct(student)添加到组呼,它将被创建,然后在为每个课程的学生进行总结时不会被删除。


1
投票

我知道这必须简化,但这是使用执行此操作的一种方法:

# load necessary packages
library( dplyr )

# calculate stats
df %>%
  group_by( school ) %>%
  summarise( Total_Students        = n()
             , Offer_Comp          = "comp" %in% unique( course ) 
             , Offer_English       = "Eng" %in% unique( course )
             , Offer_ICT           = "ICT" %in% unique( course )
             , Comp_Taken          = ifelse( test = Offer_Comp == TRUE
                                             , yes = length( course[ which( course == "comp" ) ] )
                                             , no = NA 
                                             )
             , English_Taken       = ifelse( test = Offer_English == TRUE
                                             , yes = length( course[ which( course == "Eng" ) ] )
                                             , no = NA 
             )
             , ICT_Taken           = ifelse( test = Offer_ICT == TRUE
                                             , yes = length( course[ which( course == "ICT" ) ] )
                                             , no = NA 
             ) ) %>%
  summarise( Comp_Possible         = sum( Total_Students[ which( Offer_Comp == TRUE ) ] )
             , Comp_Taken_Count    = sum( Comp_Taken, na.rm = TRUE )
             , Comp_Taken_Per      = Comp_Taken_Count / Comp_Possible * 100
             , English_Possible    = sum( Total_Students[ which( Offer_English == TRUE ) ] )
             , English_Taken_Count = sum( English_Taken, na.rm = TRUE )
             , English_Taken_Per   = English_Taken_Count / English_Possible * 100
             , ICT_Possible        = sum( Total_Students[ which( Offer_ICT == TRUE ) ] )
             , ICT_Taken_Count     = sum( ICT_Taken, na.rm = TRUE )
             , ICT_Taken_Per       = ICT_Taken_Count / ICT_Possible * 100 )

# final output
# A tibble: 1 x 9
# Comp_Possible Comp_Taken_Count Comp_Taken_Per English_Possible English_Taken_Count English_Taken_Per ICT_Possible ICT_Taken_Count ICT_Taken_Per
#         <int>            <int>          <dbl>            <int>               <int>             <dbl>        <int>           <int>         <int>
# 1         12                7           58.3                9                   3              33.3           13               5           38.5

# end of script #

0
投票

这会重现您的预期输出,但可能会错误,因为您会多次计算学生:

df %>%
  count(school,course) %>%
  left_join(df %>% count(school),by="school") %>%
  group_by(course) %>%
  summarize(num_actual = sum(n.x), pc = num_actual/sum(n.y))

# # A tibble: 3 x 3
#   course num_actual        pc
#   <fctr>      <int>     <dbl>
# 1   comp          7 0.5833333
# 2    Eng          3 0.3333333
# 3    ICT          5 0.3846154

这将是纠正的答案:

df %>%
  count(school,course) %>%
  left_join(distinct(df[c("school","student")]) %>% count(school),by="school") %>%
  group_by(course) %>%
  summarize(num_actual = sum(n.x), pc = num_actual/sum(n.y))

# # A tibble: 3 x 3
#   course num_actual    pc
#   <fctr>      <int> <dbl>
# 1   comp          7 0.875
# 2    Eng          3 0.600
# 3    ICT          5 0.625
© www.soinside.com 2019 - 2024. All rights reserved.