3 数据操作

3.1 数据读写

这里先介绍一下rio包,主要用于excel和R数据框之间相互导出。如excel的表格复制粘贴到R中,以及R中的数据框复制粘贴到excel中。

剪切板中的数据复制粘贴到R中:

df = data.frame(
  a=0:3,
  b=10:13,
  c=c("A","B","C","D")
)
rio::export(x = df,file = "clipboard")

及R中的数据框复制到剪切板中:

rio::import(file = "clipboard")

3.2 数据连接

3.2.1 合并行与和并列

合并行和和并列分别用dplyr 包中的bind_rows() 和bind_cols() 实现。

bind_rows(
  sample_n(iris,size = 2),
  sample_n(iris,size = 2),
  sample_n(iris,size = 2)
)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
## 1          6.9         3.1          5.4         2.1  virginica
## 2          6.5         3.2          5.1         2.0  virginica
## 3          6.6         3.0          4.4         1.4 versicolor
## 4          6.4         2.8          5.6         2.1  virginica
## 5          7.7         2.8          6.7         2.0  virginica
## 6          5.0         3.5          1.3         0.3     setosa
one=mtcars[1:4,1:3]
two=mtcars[1:4,4:5]
bind_cols(one,two)
##                 mpg cyl disp  hp drat
## Mazda RX4      21.0   6  160 110 3.90
## Mazda RX4 Wag  21.0   6  160 110 3.90
## Datsun 710     22.8   4  108  93 3.85
## Hornet 4 Drive 21.4   6  258 110 3.08

3.2.2 根据值匹配合并数据框

3.2.3 实现R中实现stata的merge命令

A
小米
华为
苹果
A B
小米 1
小米 2
苹果 1
OPPO 1
A B merge_
小米 1 匹配上
小米 2 匹配上
华为 NA 只在data1
苹果 1 匹配上
OPPO 1 只在data2
data1 <- data.frame(
  A=c("小米","华为","苹果")
)

data2 <- data.frame(
  A=c("小米","小米","苹果","OPPO"),
  B=c("1","2","1","1")
)
data1 %>%
  mutate(C=1) %>%
  full_join(data2,by="A") %>%
  mutate(merge_=case_when(is.na(C)~"只在data2",
                          is.na(B) ~"只在data1",
                          TRUE~"匹配上")) %>%
  select(-C)
##      A    B    merge_
## 1 小米    1    匹配上
## 2 小米    2    匹配上
## 3 华为 <NA> 只在data1
## 4 苹果    1    匹配上
## 5 OPPO    1 只在data2

3.3 数据操作

  • select()—— 选择列

  • filter()/slice()——筛选行

  • arrange()——对行排序

  • mutate()—— 修改列/创建新列

  • summarize()—— 汇总

相同之处是

  • 这些函数都可以与group_by()——分组

  • 第1 个参数是数据框,方便管道操作

  • 根据列名访问数据框的列,且列名不用加引号

  • 返回结果是一个新数据框,不改变原数据框

3.3.1 选择列(select)

数据

df = readxl::read_xlsx("datas/ExamDatas_NAs.xlsx")
df
## # A tibble: 50 × 8
##    class name   sex   chinese  math english moral science
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六1班 何娜   女         87    92      79     9      10
##  2 六1班 黄才菊 女         95    77      75    NA       9
##  3 六1班 陈芳妹 女         79    87      66     9      10
##  4 六1班 陈学勤 男         NA    79      66     9      10
##  5 六1班 陈祝贞 女         76    79      67     8      10
##  6 六1班 何小薇 女         83    73      65     8       9
##  7 六1班 雷旺   男         NA    80      68     8       9
##  8 六1班 陈欣越 男         57    80      60     9       9
##  9 六1班 黄亦婷 女         77    NA      54     8      10
## 10 六1班 陈媚   女         68    55      66     8       9
## # ℹ 40 more rows

3.3.2 选择列语法

(1)用列名或索引选择列

df %>%
  select(name,sex,math) # 或者select(2, 3, 5)
## # A tibble: 50 × 3
##    name   sex    math
##    <chr>  <chr> <dbl>
##  1 何娜   女       92
##  2 黄才菊 女       77
##  3 陈芳妹 女       87
##  4 陈学勤 男       79
##  5 陈祝贞 女       79
##  6 何小薇 女       73
##  7 雷旺   男       80
##  8 陈欣越 男       80
##  9 黄亦婷 女       NA
## 10 陈媚   女       55
## # ℹ 40 more rows

(2) 借助运算符选择列

  • 用: 选择连续的若干列

  • 用! 选择变量集合的余集(反选)

  • & 和| 选择变量集合的交或并

  • c() 合并多个选

(3) 借助选择助手函数

  • 选择指定列:
    • everything(): 选择所有列
    • last_col(): 选择最后一列,可以带参数,如last_col(5) 选择倒数第6 列
  • 选择列名匹配的列:
    • starts_with(): 以某前缀开头的列名
    • ends_with(): 以某后缀结尾的列名
    • contains(): 包含某字符串的列名
    • matches(): 匹配正则表达式的列名
    • num_range(): 匹配数值范围的列名,如num_range(“x”, 1:3) 匹配x1, x2, x3
  • 结合函数选择列:
    • where(): 应用一个函数到所有列,选择返回结果为TRUE 的列,比如与is.numeric 等函数连用。

3.3.3 选择列的例子

df %>% 
  select(starts_with("m"))
## # A tibble: 50 × 2
##     math moral
##    <dbl> <dbl>
##  1    92     9
##  2    77    NA
##  3    87     9
##  4    79     9
##  5    79     8
##  6    73     8
##  7    80     8
##  8    80     9
##  9    NA     8
## 10    55     8
## # ℹ 40 more rows
df %>%
  select(ends_with("e"))
## # A tibble: 50 × 3
##    name   chinese science
##    <chr>    <dbl>   <dbl>
##  1 何娜        87      10
##  2 黄才菊      95       9
##  3 陈芳妹      79      10
##  4 陈学勤      NA      10
##  5 陈祝贞      76      10
##  6 何小薇      83       9
##  7 雷旺        NA       9
##  8 陈欣越      57       9
##  9 黄亦婷      77      10
## 10 陈媚        68       9
## # ℹ 40 more rows
df %>% 
  select(contains("a"))
## # A tibble: 50 × 4
##    class name    math moral
##    <chr> <chr>  <dbl> <dbl>
##  1 六1班 何娜      92     9
##  2 六1班 黄才菊    77    NA
##  3 六1班 陈芳妹    87     9
##  4 六1班 陈学勤    79     9
##  5 六1班 陈祝贞    79     8
##  6 六1班 何小薇    73     8
##  7 六1班 雷旺      80     8
##  8 六1班 陈欣越    80     9
##  9 六1班 黄亦婷    NA     8
## 10 六1班 陈媚      55     8
## # ℹ 40 more rows

根据条件(逻辑判断)选择列,例如选择所有数值型的列:

library(tidyselect)
df %>%
  select(where(is.numeric))
## # A tibble: 50 × 5
##    chinese  math english moral science
##      <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1      87    92      79     9      10
##  2      95    77      75    NA       9
##  3      79    87      66     9      10
##  4      NA    79      66     9      10
##  5      76    79      67     8      10
##  6      83    73      65     8       9
##  7      NA    80      68     8       9
##  8      57    80      60     9       9
##  9      77    NA      54     8      10
## 10      68    55      66     8       9
## # ℹ 40 more rows

也可以自定义返回TRUE 或FALSE 的判断函数,支持purrr 风格公式写法。例如,选择列和>3000 的列:

df[,4:8]%>%
  select(where(~sum(.x,na.rm = TRUE)>3000))
## # A tibble: 50 × 2
##    chinese  math
##      <dbl> <dbl>
##  1      87    92
##  2      95    77
##  3      79    87
##  4      NA    79
##  5      76    79
##  6      83    73
##  7      NA    80
##  8      57    80
##  9      77    NA
## 10      68    55
## # ℹ 40 more rows

结合n_distinct() 选择唯一值数目< 10 的列:

df %>%
  select(where(~n_distinct(.x)<10))
## # A tibble: 50 × 4
##    class sex   moral science
##    <chr> <chr> <dbl>   <dbl>
##  1 六1班 女        9      10
##  2 六1班 女       NA       9
##  3 六1班 女        9      10
##  4 六1班 男        9      10
##  5 六1班 女        8      10
##  6 六1班 女        8       9
##  7 六1班 男        8       9
##  8 六1班 男        9       9
##  9 六1班 女        8      10
## 10 六1班 女        8       9
## # ℹ 40 more rows

3.3.4 用“-” 删除列

df %>% 
  select(-c(name,chinese,science)) #select(-ends_with("e"))
## # A tibble: 50 × 5
##    class sex    math english moral
##    <chr> <chr> <dbl>   <dbl> <dbl>
##  1 六1班 女       92      79     9
##  2 六1班 女       77      75    NA
##  3 六1班 女       87      66     9
##  4 六1班 男       79      66     9
##  5 六1班 女       79      67     8
##  6 六1班 女       73      65     8
##  7 六1班 男       80      68     8
##  8 六1班 男       80      60     9
##  9 六1班 女       NA      54     8
## 10 六1班 女       55      66     8
## # ℹ 40 more rows

选择math和除了“e”结尾的其他列

df %>% 
  select(math,everything(),-ends_with("e"))
## # A tibble: 50 × 5
##     math class sex   english moral
##    <dbl> <chr> <chr>   <dbl> <dbl>
##  1    92 六1班 女         79     9
##  2    77 六1班 女         75    NA
##  3    87 六1班 女         66     9
##  4    79 六1班 男         66     9
##  5    79 六1班 女         67     8
##  6    73 六1班 女         65     8
##  7    80 六1班 男         68     8
##  8    80 六1班 男         60     9
##  9    NA 六1班 女         54     8
## 10    55 六1班 女         66     8
## # ℹ 40 more rows

注意: -ends_with() 要放在everything() 后面,否则删除的列就全回来了。

3.3.5 调整列的顺序

(1)列是根据被选择的顺序排列:

df %>%
select(ends_with("e"), math, name, class, sex)
## # A tibble: 50 × 6
##    name   chinese science  math class sex  
##    <chr>    <dbl>   <dbl> <dbl> <chr> <chr>
##  1 何娜        87      10    92 六1班 女   
##  2 黄才菊      95       9    77 六1班 女   
##  3 陈芳妹      79      10    87 六1班 女   
##  4 陈学勤      NA      10    79 六1班 男   
##  5 陈祝贞      76      10    79 六1班 女   
##  6 何小薇      83       9    73 六1班 女   
##  7 雷旺        NA       9    80 六1班 男   
##  8 陈欣越      57       9    80 六1班 男   
##  9 黄亦婷      77      10    NA 六1班 女   
## 10 陈媚        68       9    55 六1班 女   
## # ℹ 40 more rows

(2)将数值列移到name 列的后面:

df %>% 
  select(math,everything())
## # A tibble: 50 × 8
##     math class name   sex   chinese english moral science
##    <dbl> <chr> <chr>  <chr>   <dbl>   <dbl> <dbl>   <dbl>
##  1    92 六1班 何娜   女         87      79     9      10
##  2    77 六1班 黄才菊 女         95      75    NA       9
##  3    87 六1班 陈芳妹 女         79      66     9      10
##  4    79 六1班 陈学勤 男         NA      66     9      10
##  5    79 六1班 陈祝贞 女         76      67     8      10
##  6    73 六1班 何小薇 女         83      65     8       9
##  7    80 六1班 雷旺   男         NA      68     8       9
##  8    80 六1班 陈欣越 男         57      60     9       9
##  9    NA 六1班 黄亦婷 女         77      54     8      10
## 10    55 六1班 陈媚   女         68      66     8       9
## # ℹ 40 more rows

everything() 返回未被选择的所有列,将某一列移到第一列时很方便:

(3)将选择的列移到某列之前或之后

df %>% 
  relocate(where(is.numeric),.after = name)
## # A tibble: 50 × 8
##    class name   chinese  math english moral science sex  
##    <chr> <chr>    <dbl> <dbl>   <dbl> <dbl>   <dbl> <chr>
##  1 六1班 何娜        87    92      79     9      10 女   
##  2 六1班 黄才菊      95    77      75    NA       9 女   
##  3 六1班 陈芳妹      79    87      66     9      10 女   
##  4 六1班 陈学勤      NA    79      66     9      10 男   
##  5 六1班 陈祝贞      76    79      67     8      10 女   
##  6 六1班 何小薇      83    73      65     8       9 女   
##  7 六1班 雷旺        NA    80      68     8       9 男   
##  8 六1班 陈欣越      57    80      60     9       9 男   
##  9 六1班 黄亦婷      77    NA      54     8      10 女   
## 10 六1班 陈媚        68    55      66     8       9 女   
## # ℹ 40 more rows

3.3.6 重命名列

(1)为所有列设置新列名set_names()

 df %>%
   set_names(" 班级", " 姓名", " 性别", " 语文",
             " 数学", " 英语", " 品德", " 科学")
## # A tibble: 50 × 8
##    ` 班级` ` 姓名` ` 性别` ` 语文` ` 数学` ` 英语` ` 品德` ` 科学`
##    <chr>   <chr>   <chr>     <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
##  1 六1班   何娜    女           87      92      79       9      10
##  2 六1班   黄才菊  女           95      77      75      NA       9
##  3 六1班   陈芳妹  女           79      87      66       9      10
##  4 六1班   陈学勤  男           NA      79      66       9      10
##  5 六1班   陈祝贞  女           76      79      67       8      10
##  6 六1班   何小薇  女           83      73      65       8       9
##  7 六1班   雷旺    男           NA      80      68       8       9
##  8 六1班   陈欣越  男           57      80      60       9       9
##  9 六1班   黄亦婷  女           77      NA      54       8      10
## 10 六1班   陈媚    女           68      55      66       8       9
## # ℹ 40 more rows

(2)只修改部分列名rename(),格式为:新名= 旧名

df %>% 
  rename(数学=math,科学=science)
## # A tibble: 50 × 8
##    class name   sex   chinese  数学 english moral  科学
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl> <dbl>
##  1 六1班 何娜   女         87    92      79     9    10
##  2 六1班 黄才菊 女         95    77      75    NA     9
##  3 六1班 陈芳妹 女         79    87      66     9    10
##  4 六1班 陈学勤 男         NA    79      66     9    10
##  5 六1班 陈祝贞 女         76    79      67     8    10
##  6 六1班 何小薇 女         83    73      65     8     9
##  7 六1班 雷旺   男         NA    80      68     8     9
##  8 六1班 陈欣越 男         57    80      60     9     9
##  9 六1班 黄亦婷 女         77    NA      54     8    10
## 10 六1班 陈媚   女         68    55      66     8     9
## # ℹ 40 more rows

(3) rename_with(.data, .fn, .cols)函数

参数.col 支持用选择列语法选择要重命名的 列,.fn 是对所选列重命名的函数,将原列名的字符向量变成新列名的字符向量。比如,将包含”m’’ 的列名,都拼接上前缀”new_“:

3.4 分组汇总

3.4.1 创建分组

用group_by() 创建分组,只是对数据框增加了分组信息(用group_keys() 查看),并不是真的将数据分割为多个数据框。

df_grp = iris %>%
  group_by(Species)
group_keys(df_grp) # 分组键值(唯一识别分组)
## # A tibble: 3 × 1
##   Species   
##   <fct>     
## 1 setosa    
## 2 versicolor
## 3 virginica
group_indices(df_grp) # 查看每一行属于哪一分组
##   [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [38] 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
##  [75] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3
## [112] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
## [149] 3 3
group_rows(df_grp) # 查看每一组包含哪些行
## <list_of<integer>[3]>
## [[1]]
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
## [26] 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
## 
## [[2]]
##  [1]  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69
## [20]  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88
## [39]  89  90  91  92  93  94  95  96  97  98  99 100
## 
## [[3]]
##  [1] 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
## [20] 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
## [39] 139 140 141 142 143 144 145 146 147 148 149 150
ungroup(df_grp) # 解除分组

3.4.2 分组汇总

对数据框做分组最主要的目的就是做分组汇总,汇总就是以某种方式组合行,用dplyr 包中的summarise() 函数实现,结果只保留分组列唯一值和新创建的汇总列。

(1) summarise()

可以与很多自带或自定义的汇总函数连用,常用的汇总函数有:

  • n(): 观测数

  • n_distinct(var): 变量var 的唯一值数目

  • sum(var), max(var), min(var), . . .

  • mean(var), median(var), sd(var), IQR(var),

df = readxl::read_xlsx("datas/ExamDatas_NAs.xlsx")
df %>%
  group_by(sex) %>%
  summarise(n=n(),
            math_avg=mean(math,na.rm=TRUE),
            math_med=median(math))
## # A tibble: 3 × 4
##   sex       n math_avg math_med
##   <chr> <int>    <dbl>    <dbl>
## 1 女       25     70.8       NA
## 2 男       24     64.6       NA
## 3 <NA>      1     85         85

(2)对所有列汇总

df %>%
  select(-name) %>%
  group_by(class, sex) %>%
  summarise(across(everything(),mean,na.rm=TRUE))
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `across(everything(), mean, na.rm = TRUE)`.
## ℹ In group 1: `class = "六1班"`, `sex = "女"`.
## Caused by warning:
## ! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
## Supply arguments directly to `.fns` through an anonymous function instead.
## 
##   # Previously
##   across(a:b, mean, na.rm = TRUE)
## 
##   # Now
##   across(a:b, \(x) mean(x, na.rm = TRUE))
## # A tibble: 12 × 7
## # Groups:   class [6]
##    class sex   chinese  math english moral science
##    <chr> <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六1班 女       80.7  77.2    67.4  8.33    9.57
##  2 六1班 男       57    79.7    64.7  8.67    9.33
##  3 六2班 女       92.2  73.8    63.8  8.33    9   
##  4 六2班 男       75.4  68.8    42.6  8.8     9.25
##  5 六3班 女       68.4  49.2    67.8  6.25    7.2 
##  6 六3班 男       66    30.4    67.6  4.6     4.75
##  7 六4班 女       85.2  74      63.7  8.75    7.75
##  8 六4班 男       84.8  79.2    55.7  8.6     8.5 
##  9 六5班 女       80.4  75.4    63.4  8.75    8.25
## 10 六5班 男       66.5  64.5    65.3  8.25    8.25
## 11 六5班 <NA>     58    85      48    9      10   
## 12 <NA>  男       90    86      72    9      10

(3)对某些列汇总

计算列名中包含”h”的列的平均值

df %>%
  group_by(class, sex) %>%
  summarise(across(contains("h"), mean, na.rm = TRUE))
## `summarise()` has grouped output by 'class'. You can override using the
## `.groups` argument.
## # A tibble: 12 × 5
## # Groups:   class [6]
##    class sex   chinese  math english
##    <chr> <chr>   <dbl> <dbl>   <dbl>
##  1 六1班 女       80.7  77.2    67.4
##  2 六1班 男       57    79.7    64.7
##  3 六2班 女       92.2  73.8    63.8
##  4 六2班 男       75.4  68.8    42.6
##  5 六3班 女       68.4  49.2    67.8
##  6 六3班 男       66    30.4    67.6
##  7 六4班 女       85.2  74      63.7
##  8 六4班 男       84.8  79.2    55.7
##  9 六5班 女       80.4  75.4    63.4
## 10 六5班 男       66.5  64.5    65.3
## 11 六5班 <NA>     58    85      48  
## 12 <NA>  男       90    86      72

(4) 对满足条件的列做多种汇总 分别计算数值型各列的平均值和sum

library(tidyselect)
df %>%
  group_by(class) %>%
  summarise(across(.cols = where(is.numeric),
                   .fns = list(sum=sum,mean=mean),na.rm=TRUE))
## # A tibble: 6 × 11
##   class chinese_sum chinese_mean math_sum math_mean english_sum english_mean
##   <chr>       <dbl>        <dbl>    <dbl>     <dbl>       <dbl>        <dbl>
## 1 六1班         622         77.8      702      78           666         66.6
## 2 六2班         746         82.9      570      71.2         468         52  
## 3 六3班         606         67.3      349      38.8         609         67.7
## 4 六4班         850         85        771      77.1         525         58.3
## 5 六5班         726         72.6      720      72           561         62.3
## 6 <NA>           90         90         86      86            72         72  
## # ℹ 4 more variables: moral_sum <dbl>, moral_mean <dbl>, science_sum <dbl>,
## #   science_mean <dbl>

分组计数

用count() 按分类变量class 和sex 分组,并按分组大小排序:

df %>% 
  count(class,sex,sort = TRUE)
## # A tibble: 12 × 3
##    class sex       n
##    <chr> <chr> <int>
##  1 六1班 女        7
##  2 六4班 男        6
##  3 六2班 男        5
##  4 六3班 女        5
##  5 六3班 男        5
##  6 六5班 女        5
##  7 六2班 女        4
##  8 六4班 女        4
##  9 六5班 男        4
## 10 六1班 男        3
## 11 六5班 <NA>      1
## 12 <NA>  男        1

对已分组的数据框,用tally() 计数

df %>%
  group_by(math_level = cut(math, breaks = c(0, 60, 75, 80, 100), right = FALSE)) %>%
  tally()
## # A tibble: 5 × 2
##   math_level     n
##   <fct>      <int>
## 1 [0,60)        14
## 2 [60,75)       11
## 3 [75,80)        5
## 4 [80,100)      17
## 5 <NA>           3

注:count() 和tally() 都有参数wt 设置加权计数。

用add_count() 和add_tally() 可为数据集增加一列按分组变量分组的计数:

df %>% 
  add_count(class,sex)
## # A tibble: 50 × 9
##    class name   sex   chinese  math english moral science     n
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl> <int>
##  1 六1班 何娜   女         87    92      79     9      10     7
##  2 六1班 黄才菊 女         95    77      75    NA       9     7
##  3 六1班 陈芳妹 女         79    87      66     9      10     7
##  4 六1班 陈学勤 男         NA    79      66     9      10     3
##  5 六1班 陈祝贞 女         76    79      67     8      10     7
##  6 六1班 何小薇 女         83    73      65     8       9     7
##  7 六1班 雷旺   男         NA    80      68     8       9     3
##  8 六1班 陈欣越 男         57    80      60     9       9     3
##  9 六1班 黄亦婷 女         77    NA      54     8      10     7
## 10 六1班 陈媚   女         68    55      66     8       9     7
## # ℹ 40 more rows

3.4.3 自己的例子

表3.1: 数据形式
x y
A 1
A 2
A 1
B 1
B 1
C 3
df %>%
  group_by(x) %>%
  summarise(n=sum(y))
## # A tibble: 3 × 2
##   x         n
##   <chr> <dbl>
## 1 A         4
## 2 B         2
## 3 C         3

3.5 其他数据操作

3.5.1 按行汇总

通常的数据操作逻辑都是按列方式(colwise),这使得按行汇总很困难。

dplyr 包提供了rowwise() 函数为数据框创建按行方式(rowwise),使用rowwise() 后并不是真的 改变数据框,只是创建了按行元信息,改变了数据框的操作逻辑:

按行汇总语数外的总分

df = readxl::read_xlsx("datas/ExamDatas_NAs.xlsx")

rf = df %>%
  rowwise()
rf %>%
  mutate(total=sum(c(chinese,math,english)))
## # A tibble: 50 × 9
## # Rowwise: 
##    class name   sex   chinese  math english moral science total
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl> <dbl>
##  1 六1班 何娜   女         87    92      79     9      10   258
##  2 六1班 黄才菊 女         95    77      75    NA       9   247
##  3 六1班 陈芳妹 女         79    87      66     9      10   232
##  4 六1班 陈学勤 男         NA    79      66     9      10    NA
##  5 六1班 陈祝贞 女         76    79      67     8      10   222
##  6 六1班 何小薇 女         83    73      65     8       9   221
##  7 六1班 雷旺   男         NA    80      68     8       9    NA
##  8 六1班 陈欣越 男         57    80      60     9       9   197
##  9 六1班 黄亦婷 女         77    NA      54     8      10    NA
## 10 六1班 陈媚   女         68    55      66     8       9   189
## # ℹ 40 more rows

函数c_across() 是为按行方式(rowwise) 在选定的列范围汇总数据而设计的,它没有提供.fns 参数,只能选择列

rf %>% 
  mutate(total = sum(c_across(where(is.numeric))))
## # A tibble: 50 × 9
## # Rowwise: 
##    class name   sex   chinese  math english moral science total
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl> <dbl>
##  1 六1班 何娜   女         87    92      79     9      10   277
##  2 六1班 黄才菊 女         95    77      75    NA       9    NA
##  3 六1班 陈芳妹 女         79    87      66     9      10   251
##  4 六1班 陈学勤 男         NA    79      66     9      10    NA
##  5 六1班 陈祝贞 女         76    79      67     8      10   240
##  6 六1班 何小薇 女         83    73      65     8       9   238
##  7 六1班 雷旺   男         NA    80      68     8       9    NA
##  8 六1班 陈欣越 男         57    80      60     9       9   215
##  9 六1班 黄亦婷 女         77    NA      54     8      10    NA
## 10 六1班 陈媚   女         68    55      66     8       9   206
## # ℹ 40 more rows

若只是做按行求和或均值,直接用rowSums() / rowMeans() 速度更快(不需要分割-汇总-合并), 这里的rowwise 行化后提供可以做更多的按行汇总的可能。

df %>%
mutate(total = rowSums(across(where(is.numeric))))
## # A tibble: 50 × 9
##    class name   sex   chinese  math english moral science total
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl> <dbl>
##  1 六1班 何娜   女         87    92      79     9      10   277
##  2 六1班 黄才菊 女         95    77      75    NA       9    NA
##  3 六1班 陈芳妹 女         79    87      66     9      10   251
##  4 六1班 陈学勤 男         NA    79      66     9      10    NA
##  5 六1班 陈祝贞 女         76    79      67     8      10   240
##  6 六1班 何小薇 女         83    73      65     8       9   238
##  7 六1班 雷旺   男         NA    80      68     8       9    NA
##  8 六1班 陈欣越 男         57    80      60     9       9   215
##  9 六1班 黄亦婷 女         77    NA      54     8      10    NA
## 10 六1班 陈媚   女         68    55      66     8       9   206
## # ℹ 40 more rows

按行方式(rowwise) 可以理解为一种特殊的分组:每一行作为一组。为rowwise() 提供行ID,用 summarise() 做汇总更能体会这一点。要解除行化模式,用ungroup().

df %>% 
  rowwise(name) %>% 
  summarise(total=c_across(where(is.numeric)))
## Warning: Returning more (or less) than 1 row per `summarise()` group was deprecated in
## dplyr 1.1.0.
## ℹ Please use `reframe()` instead.
## ℹ When switching from `summarise()` to `reframe()`, remember that `reframe()`
##   always returns an ungrouped data frame and adjust accordingly.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `summarise()` has grouped output by 'name'. You can override using the
## `.groups` argument.
## # A tibble: 250 × 2
## # Groups:   name [50]
##    name   total
##    <chr>  <dbl>
##  1 何娜      87
##  2 何娜      92
##  3 何娜      79
##  4 何娜       9
##  5 何娜      10
##  6 黄才菊    95
##  7 黄才菊    77
##  8 黄才菊    75
##  9 黄才菊    NA
## 10 黄才菊     9
## # ℹ 240 more rows

rowwise 行化操作的缺点是速度相对更慢,更建议用XXX 节讲到的pmap() 逐行迭代。

3.5.2 窗口函数

汇总函数如sum() 和mean() 接受n 个输入,返回1 个值。而窗口函数是汇总函数的变体:接受n 个输入,返回n 个值。

例如,cumsum()、cummean()、rank()、lead()、lag() 等。

(1)排名和排序函数

从小到大排名(ties.method=“min”),若要从大到小排名需要套一个desc()

安装数学成绩给每个学生生存排名

df %>%
  mutate(ranks = min_rank(desc(math))) %>%
  arrange(ranks)
## # A tibble: 50 × 9
##    class name   sex   chinese  math english moral science ranks
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl> <int>
##  1 六4班 周婵   女         92    94      77    10       9     1
##  2 六4班 陈丽丽 女         87    93      NA     8       6     2
##  3 六1班 何娜   女         87    92      79     9      10     3
##  4 六5班 符苡榕 女         85    89      76     9      NA     4
##  5 六2班 黄祖娜 女         94    88      75    10      10     5
##  6 六1班 陈芳妹 女         79    87      66     9      10     6
##  7 六4班 李小龄 男         90    87      69    10      10     6
##  8 六2班 徐雅琦 女         92    86      72    NA       9     8
##  9 <NA>  徐达政 男         90    86      72     9      10     8
## 10 六4班 杨昌晟 男         84    85      64     8      10    10
## # ℹ 40 more rows

3.6 整洁计算

tidyverse 代码之所以这么“整洁、优雅”,访问列只需要提供列名,不需要加引号,不需要加数据框环境df$, 这是因为它内部采用了一套整洁计算(tidy evaluation)框架。

如果我们也想自定义这样的“整洁、优雅”函数,即在自定义函数中页这样“整洁、优雅”地传递参数,就需要掌握一点整洁计算的技术。

3.6.1 数据屏蔽与整洁选择

整洁计算的两种基本形式是

  • 数据屏蔽:使得可以不用带数据框(环境变量)名字,就能使用数据框内的变量(数据变量),便于在数据集内计算值

  • 整洁选择:即各种选择列语法,便于使用数据集中的列

数据屏蔽为直接使用带来了代码简洁,但作为函数参数时的间接使用,正常是环境变量,要想作为数据变量使用,则需要用两个大括号括起来{{var}}:

var_summary = function(data, var) {
  data %>%
    summarise(n = n(), mean = mean({{var}}))
}

mtcars %>%
  group_by(cyl) %>%
  var_summary(mpg)
## # A tibble: 3 × 3
##     cyl     n  mean
##   <dbl> <int> <dbl>
## 1     4    11  26.7
## 2     6     7  19.7
## 3     8    14  15.1

若是字符向量形式,想作为数据变量,则需要在函数体中使用.data[[var]],这里.data 是代替数据集的代词:

var_summary = function(data,var){
  data %>%
    summarise(n=n(),mean=mean(.data[[var]]))
}

mtcars %>%
  group_by(cyl) %>%
  var_summary("mpg")
## # A tibble: 3 × 3
##     cyl     n  mean
##   <dbl> <int> <dbl>
## 1     4    11  26.7
## 2     6     7  19.7
## 3     8    14  15.1