数据框中的集合运算

吴诗涛 2023-03-17 [R]

集合运算

在学习《概率论与数理统计》的时候运用集合运算较多,在实际数据处理的场景中,集合运算也非常实用。目前 dplyr 支持集合的交、并、补运算,可以从速查表中查看:

dplyr 目前支持的集合运算

本文以案例的形式讲解如何将集合运算嵌入到 tidyverse 数据处理中,提高数据处理效率。

问题描述

现有一班和二班两个班级,每个班级三名学生,EnglishMath 列分别是英语考试和数学考试的成绩,现需统计每个班级中两次考试都在排名前二的学生。

library(tidyverse)

df <- tibble(
  class = rep(c("一班", "二班"), each = 3),
  name = LETTERS[1:6],
  English = c(100, 90, 80, 90, 70, 60),
  Math = c(98, 80, 90, 85, 90, 60)
)  # 示例数据

df
#> # A tibble: 6 × 4
#>   class name  English  Math
#>   <chr> <chr>   <dbl> <dbl>
#> 1 一班  A         100    98
#> 2 一班  B          90    80
#> 3 一班  C          80    90
#> 4 二班  D          90    85
#> 5 二班  E          70    90
#> 6 二班  F          60    60

根据示例数据,结果应为一班的 A,二班的 D 和 E。

问题解决思路

  1. 示例数据是一个宽数据,先拉长为整洁数据;
  2. 分组,将每个班级每次考试前两名的学生姓名各形成一个集合;
  3. 将每个班级两次考试得到的集合取交集,得到结果。

问题解决流程

拉长为整洁数据

把原始数据拉长为整洁数据后,数据框中每行代表一个班级中的一名学生一门科目的考试成绩:

df %>% 
  pivot_longer(English:Math,
               names_to = "test",
               values_to = "score")
#> # A tibble: 12 × 4
#>    class name  test    score
#>    <chr> <chr> <chr>   <dbl>
#>  1 一班  A     English   100
#>  2 一班  A     Math       98
#>  3 一班  B     English    90
#>  4 一班  B     Math       80
#>  5 一班  C     English    80
#>  6 一班  C     Math       90
#>  7 二班  D     English    90
#>  8 二班  D     Math       85
#>  9 二班  E     English    70
#> 10 二班  E     Math       90
#> 11 二班  F     English    60
#> 12 二班  F     Math       60

每次考试前两名学生集合

classtest 分组提取前两名:

df %>% 
  pivot_longer(English:Math,
               names_to = "test",
               values_to = "score") %>% 
  slice_max(score, n = 2,
            by = c(class, test))
#> # A tibble: 8 × 4
#>   class name  test    score
#>   <chr> <chr> <chr>   <dbl>
#> 1 一班  A     English   100
#> 2 一班  B     English    90
#> 3 一班  A     Math       98
#> 4 一班  C     Math       90
#> 5 二班  D     English    90
#> 6 二班  E     English    70
#> 7 二班  E     Math       90
#> 8 二班  D     Math       85

同样是以 classtest 分组,将每个班级每次考试前两名的学生姓名组成集合:

df1 <- df %>% 
  pivot_longer(English:Math,
               names_to = "test",
               values_to = "score") %>% 
  slice_max(score, n = 2,
            by = c(class, test)) %>% 
  group_by(class, test) %>% 
  reframe(name_set = list(name))
df1
#> # A tibble: 4 × 3
#>   class test    name_set 
#>   <chr> <chr>   <list>   
#> 1 一班  English <chr [2]>
#> 2 一班  Math    <chr [2]>
#> 3 二班  English <chr [2]>
#> 4 二班  Math    <chr [2]>

name_set 是一个列表列,包含了每次考试前两名的姓名,它长这样:

df1$name_set
#> [[1]]
#> [1] "A" "B"
#> 
#> [[2]]
#> [1] "A" "C"
#> 
#> [[3]]
#> [1] "D" "E"
#> 
#> [[4]]
#> [1] "E" "D"

从上到下分别是:

  1. 一班英语成绩前两名的学生姓名;
  2. 一班数学成绩前两名的学生姓名;
  3. 二班英语成绩前两名的学生姓名;
  4. 二班数学成绩前两名的学生姓名。

集合取交集

再次拉宽:

df2 <- df1 %>% 
  pivot_wider(id_cols = class,
              names_from = test,
              values_from = name_set)
df2
#> # A tibble: 2 × 3
#>   class English   Math     
#>   <chr> <list>    <list>   
#> 1 一班  <chr [2]> <chr [2]>
#> 2 二班  <chr [2]> <chr [2]>

拉宽后形成的数据框含 3 列,分别是班级列、英语考试中排名前二的学生姓名集合列、数学考试中排名前二的学生姓名集合列1。对每个班级的两次考试前两名集合取交集,即可得到每个班级两次考试都在前两名的学生:

df3 <- df2 %>% 
  mutate(student = map2(English, Math, dplyr::intersect))
df3
#> # A tibble: 2 × 4
#>   class English   Math      student  
#>   <chr> <list>    <list>    <list>   
#> 1 一班  <chr [2]> <chr [2]> <chr [1]>
#> 2 二班  <chr [2]> <chr [2]> <chr [2]>

student 列也是一个列表列,里面包含了每次考试都排名班级前二的学生姓名:

df3$student
#> [[1]]
#> [1] "A"
#> 
#> [[2]]
#> [1] "D" "E"

可以根据需要通过 unnest() 系列函数将其展开:

横向展开

df3 %>% 
  select(class, student) %>% 
  unnest_wider(col = student,
               names_sep = "")
#> # A tibble: 2 × 3
#>   class student1 student2
#>   <chr> <chr>    <chr>   
#> 1 一班  A        <NA>    
#> 2 二班  D        E

纵向展开

df3 %>% 
  select(class, student) %>% 
  unnest_longer(col = student)
#> # A tibble: 3 × 2
#>   class student
#>   <chr> <chr>  
#> 1 一班  A      
#> 2 二班  D      
#> 3 二班  E

总结

在数据分析中,集合运算可以用来计算重复数据,计算交集并集等。多多学习,多多运用🎇。


  1. 操作列表列时需注意,使用 map() 系列函数。 ↩︎