使用tidyverse包处理数据

library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.1 --
## v ggplot2 3.3.5     v purrr   0.3.4
## v tibble  3.1.3     v dplyr   1.0.7
## v tidyr   1.1.3     v stringr 1.4.0
## v readr   2.0.1     v forcats 0.5.1
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()

Tidyverse简介与管道

  • 可以直接使用函数
a <- matrix(1:4, nrow = 2)
b <- t(a)
b
##      [,1] [,2]
## [1,]    1    2
## [2,]    3    4
apply(b, 1, mean)
## [1] 1.5 3.5
a %>%
  t() %>%
  apply(1, mean)
## [1] 1.5 3.5
  • 实现管道操作的例子
f <- function(x) {
  y <- x^2 + 1
  return(y)
}
f(1)
## [1] 2
g <- function(x) {
  y <- 4 * x - 1
  return(y)
}
g(2)
## [1] 7
x <- 1
x %>%
  f() %>%
  g()
## [1] 7
  • 常用的管道操作
c(1, 3, 4, 5, NA) %>% mean(., na.rm = TRUE) # na.rm表示除去缺失值
## [1] 3.25
# 或者也可以省略“.”,直接写成
c(1, 3, 4, 5, NA) %>% mean(na.rm = TRUE) # 两者的结果一致的
## [1] 3.25

数据读写

实例

library(readr)
df <- read_csv(
  file = "data/sub_data/product.csv", col_names = TRUE, skip = 0,
  n_max = 10
)
## Rows: 10 Columns: 4
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (1): date
## dbl (3): CLOSE, close, cs
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
df
## # A tibble: 10 x 4
##    date      CLOSE close    cs
##    <chr>     <dbl> <dbl> <dbl>
##  1 2019/7/1  6274.  11.7  2.52
##  2 2019/7/2  6269.  11.6  2.52
##  3 2019/7/3  6230.  11.6  2.52
##  4 2019/7/4  6222.  11.6  2.52
##  5 2019/7/5  6197.  11.6  2.52
##  6 2019/7/8  6084.  11.4  2.52
##  7 2019/7/9  6040.  11.4  2.52
##  8 2019/7/10 6002.  11.4  2.52
##  9 2019/7/11 6016.  11.4  2.52
## 10 2019/7/12 6060.  11.5  2.52

改变参数,第一行不作为列名,并且跳过前3行

df1 <- df <- read_csv(
  file = "data/sub_data/product.csv", col_names = FALSE,
  skip = 3, n_max = 10
)
## Rows: 10 Columns: 4
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (1): X1
## dbl (3): X2, X3, X4
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
df1
## # A tibble: 10 x 4
##    X1           X2    X3    X4
##    <chr>     <dbl> <dbl> <dbl>
##  1 2019/7/3  6230.  11.6  2.52
##  2 2019/7/4  6222.  11.6  2.52
##  3 2019/7/5  6197.  11.6  2.52
##  4 2019/7/8  6084.  11.4  2.52
##  5 2019/7/9  6040.  11.4  2.52
##  6 2019/7/10 6002.  11.4  2.52
##  7 2019/7/11 6016.  11.4  2.52
##  8 2019/7/12 6060.  11.5  2.52
##  9 2019/7/15 6051.  11.5  2.52
## 10 2019/7/16 6041.  11.6  2.52

观察对比可知,n_max中的参数指的是输出为值的行数

批量读取Excel表格的数据

  • 首先需要导入全部Excel文件的完整路劲,可以任意嵌套,只要设置参数recursive为TRUE即可
files <- list.files("data/sub_data", pattern = "xlsx", full.names = TRUE, recursive = TRUE)

files
## [1] "data/sub_data/animal.xlsx" "data/sub_data/class1.xlsx"
## [3] "data/sub_data/class2.xlsx" "data/sub_data/class3.xlsx"
  • 接着使用map_dfr()在该路径上迭代,应用read_xlsx()到每个路径,再按行合并。
library(readxl)
df <- map_dfr(files, read_xlsx)
df
## # A tibble: 22 x 8
##    type    year heads 班级  姓名  性别   语文  数学
##    <chr>  <dbl> <dbl> <chr> <chr> <chr> <dbl> <dbl>
##  1 sheep   2016  2188 <NA>  <NA>  <NA>     NA    NA
##  2 cattle  1975    32 <NA>  <NA>  <NA>     NA    NA
##  3 camel   1985   515 <NA>  <NA>  <NA>     NA    NA
##  4 camel   2018   505 <NA>  <NA>  <NA>     NA    NA
##  5 camel   2008   206 <NA>  <NA>  <NA>     NA    NA
##  6 goat    1996  1999 <NA>  <NA>  <NA>     NA    NA
##  7 sheep   1999  2268 <NA>  <NA>  <NA>     NA    NA
##  8 cattle  2008  4952 <NA>  <NA>  <NA>     NA    NA
##  9 sheep   2006  3248 <NA>  <NA>  <NA>     NA    NA
## 10 sheep   2015  1314 <NA>  <NA>  <NA>     NA    NA
## # ... with 12 more rows
  • 将数据写出到Excel文件当中去需要用到readr包中的write_csv或者writexl中的write_xlsx()。以写到csv文件为例子
write_csv(df, "data/sub_data/out.csv")

数据连接

行合并与列合并

library(dplyr) # 导入需要用到的函数包
# 首先创建两组数据
data1 <- data.frame(
  namea = c("bob", "mike", "alen"),
  value = c(1, 2, 3)
)
data1
##   namea value
## 1   bob     1
## 2  mike     2
## 3  alen     3
# 再创建一组数据
data2 <- data.frame(
  namea = c("trump"),
  value = c(4)
)
data2
##   namea value
## 1 trump     4
# 首先展示行合并,合并以上两个data,直接使用dplyr中的bind_row函数
bind_rows(
  data1,
  data2
)
##   namea value
## 1   bob     1
## 2  mike     2
## 3  alen     3
## 4 trump     4
# 需要再创建一组和以上两个data行相同的数据进行列合并
data3 <- data.frame(character = c("gentle", "small", "big"))
data3
##   character
## 1    gentle
## 2     small
## 3       big
# 对data1和data3进行合并
bind_cols(data1, data3)
##   namea value character
## 1   bob     1    gentle
## 2  mike     2     small
## 3  alen     3       big
# 如果对行不同的数据进行合并,则会出现数据重复
bind_cols(data2, data3) # 所以列合并使规定行必须相同
##   namea value character
## 1 trump     4    gentle
## 2 trump     4     small
## 3 trump     4       big

根据值匹配并合并数据框

band <- tibble(
  name = c("MIck", "Jhon", "Paul"),
  band = c("stone", "beatles", "beatles")
)
instrument <- tibble(
  name = c("Jhon", "Paul", "Keith"),
  plays = c("guitar", "bass", "guitar")
)
band
## # A tibble: 3 x 2
##   name  band   
##   <chr> <chr>  
## 1 MIck  stone  
## 2 Jhon  beatles
## 3 Paul  beatles
instrument
## # A tibble: 3 x 2
##   name  plays 
##   <chr> <chr> 
## 1 Jhon  guitar
## 2 Paul  bass  
## 3 Keith guitar
  • 左连接
band %>%
  left_join(instrument, by = "name")
## # A tibble: 3 x 3
##   name  band    plays 
##   <chr> <chr>   <chr> 
## 1 MIck  stone   <NA>  
## 2 Jhon  beatles guitar
## 3 Paul  beatles bass
# 若两个表中的键列列名不同,用by=c(name1=name2),若根据多个键值匹配,用by=c(name1,name2)
  • 右连接
band %>%
  right_join(instrument, by = "name")
## # A tibble: 3 x 3
##   name  band    plays 
##   <chr> <chr>   <chr> 
## 1 Jhon  beatles guitar
## 2 Paul  beatles bass  
## 3 Keith <NA>    guitar
  • 全连接
band %>%
  full_join(instrument, by = "name")
## # A tibble: 4 x 3
##   name  band    plays 
##   <chr> <chr>   <chr> 
## 1 MIck  stone   <NA>  
## 2 Jhon  beatles guitar
## 3 Paul  beatles bass  
## 4 Keith <NA>    guitar
  • 内连接
band %>%
  inner_join(instrument, by = "name")
## # A tibble: 2 x 3
##   name  band    plays 
##   <chr> <chr>   <chr> 
## 1 Jhon  beatles guitar
## 2 Paul  beatles bass
  • 半连接
band %>%
  semi_join(instrument, by = "name")
## # A tibble: 2 x 2
##   name  band   
##   <chr> <chr>  
## 1 Jhon  beatles
## 2 Paul  beatles
  • 反链接
band %>%
  anti_join(instrument, by = "name")
## # A tibble: 1 x 2
##   name  band 
##   <chr> <chr>
## 1 MIck  stone

数据重塑

宽表变长表

#宽表变长表
w_chart=read_csv('data/sub_data/宽表变长表1.csv')
## Rows: 4 Columns: 4
## -- Column specification --------------------------------------------------------
## Delimiter: ","
## chr (1): 地区
## dbl (3): 2019, 2018, 2017
## 
## i Use `spec()` to retrieve the full column specification for this data.
## i Specify the column types or set `show_col_types = FALSE` to quiet this message.
w_chart
## # A tibble: 4 x 4
##   地区     `2019` `2018` `2017`
##   <chr>     <dbl>  <dbl>  <dbl>
## 1 北京市    35371  33106  28015
## 2 天津市    14104  13363  18549
## 3 河北省    35105  32495  34016
## 4 黑龙江省  13613  12846  15903
library(tidyr)
w_chart%>%
  pivot_longer(-地区,names_to = '年份',values_to = 'GDP')
## # A tibble: 12 x 3
##    地区     年份    GDP
##    <chr>    <chr> <dbl>
##  1 北京市   2019  35371
##  2 北京市   2018  33106
##  3 北京市   2017  28015
##  4 天津市   2019  14104
##  5 天津市   2018  13363
##  6 天津市   2017  18549
##  7 河北省   2019  35105
##  8 河北省   2018  32495
##  9 河北省   2017  34016
## 10 黑龙江省 2019  13613
## 11 黑龙江省 2018  12846
## 12 黑龙江省 2017  15903

长表变宽表

l_chart <- read_excel("data/sub_data/animal.xlsx")
l_chart
## # A tibble: 19 x 3
##    type    year heads
##    <chr>  <dbl> <dbl>
##  1 sheep   2016  2188
##  2 cattle  1975    32
##  3 camel   1985   515
##  4 camel   2018   505
##  5 camel   2008   206
##  6 goat    1996  1999
##  7 sheep   1999  2268
##  8 cattle  2008  4952
##  9 sheep   2006  3248
## 10 sheep   2015  1314
## 11 goat    2019   565
## 12 dog     2017   486
## 13 cattle  2007   432
## 14 cattle  2003   956
## 15 dog     1993  1336
## 16 dog     2018  1896
## 17 goat    1991  1556
## 18 sheep   1992  1887
## 19 sheep   1990  1665

使用函数使其变成宽表

l_chart %>%
  pivot_wider(names_from = type, values_from = heads, values_fill = 0)
## # A tibble: 17 x 6
##     year sheep cattle camel  goat   dog
##    <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl>
##  1  2016  2188      0     0     0     0
##  2  1975     0     32     0     0     0
##  3  1985     0      0   515     0     0
##  4  2018     0      0   505     0  1896
##  5  2008     0   4952   206     0     0
##  6  1996     0      0     0  1999     0
##  7  1999  2268      0     0     0     0
##  8  2006  3248      0     0     0     0
##  9  2015  1314      0     0     0     0
## 10  2019     0      0     0   565     0
## 11  2017     0      0     0     0   486
## 12  2007     0    432     0     0     0
## 13  2003     0    956     0     0     0
## 14  1993     0      0     0     0  1336
## 15  1991     0      0     0  1556     0
## 16  1992  1887      0     0     0     0
## 17  1990  1665      0     0     0     0

数据操作

读取数据

library(readxl)
df=read_xlsx("data/ExamDatas_NAs.xlsx")
df
## # A tibble: 50 x 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
## # ... with 40 more rows

选择列

用列名或索引选择列

df %>% 
  select(name,sex,math)  #或者select(2,3,5)
## # A tibble: 50 x 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
## # ... with 40 more rows

借助运算符选择列

  • 1.用:选择连续的若干列
  • 2.用!选择变量集合的余集(反选)。
  • 3.&和|选择变量集合的交或并
  • 4.c()合并多个选择
df %>% select(2:4)#选第2至4列
## # A tibble: 50 x 3
##    name   sex   chinese
##    <chr>  <chr>   <dbl>
##  1 何娜   女         87
##  2 黄才菊 女         95
##  3 陈芳妹 女         79
##  4 陈学勤 男         NA
##  5 陈祝贞 女         76
##  6 何小薇 女         83
##  7 雷旺   男         NA
##  8 陈欣越 男         57
##  9 黄亦婷 女         77
## 10 陈媚   女         68
## # ... with 40 more rows
df %>% select(!chinese)#不选语文成绩
## # A tibble: 50 x 7
##    class name   sex    math english moral science
##    <chr> <chr>  <chr> <dbl>   <dbl> <dbl>   <dbl>
##  1 六1班 何娜   女       92      79     9      10
##  2 六1班 黄才菊 女       77      75    NA       9
##  3 六1班 陈芳妹 女       87      66     9      10
##  4 六1班 陈学勤 男       79      66     9      10
##  5 六1班 陈祝贞 女       79      67     8      10
##  6 六1班 何小薇 女       73      65     8       9
##  7 六1班 雷旺   男       80      68     8       9
##  8 六1班 陈欣越 男       80      60     9       9
##  9 六1班 黄亦婷 女       NA      54     8      10
## 10 六1班 陈媚   女       55      66     8       9
## # ... with 40 more rows

借助选择助手函数

df %>% 
  select(everything())#选择所有列
## # A tibble: 50 x 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
## # ... with 40 more rows
df %>% 
  select(last_col())#选择最后一列
## # A tibble: 50 x 1
##    science
##      <dbl>
##  1      10
##  2       9
##  3      10
##  4      10
##  5      10
##  6       9
##  7       9
##  8       9
##  9      10
## 10       9
## # ... with 40 more rows
df %>% select(contains("a"))#选择包含“a”的列名
## # A tibble: 50 x 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
## # ... with 40 more rows

删除列(“-”)

df %>% select(-c(name,chinese,science))#或者select(-ends_with("e"))
## # A tibble: 50 x 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
## # ... with 40 more rows
df %>% select(math,everything(),-ends_with("e"))#依次进行对应操作
## # A tibble: 50 x 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
## # ... with 40 more rows

调整列的顺序

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

df %>% select(ends_with("e"),math,name,class,sex)
## # A tibble: 50 x 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班 女   
## # ... with 40 more rows

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

df %>% select(math,everything())
## # A tibble: 50 x 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
## # ... with 40 more rows

用relocate()函数,将选择的列移到某列之前或之后,基本语法为:relocate(.data,…,.before,.after)

df %>% 
  relocate(where(is.numeric),.after=name)#将数值列移到name列的后面
## # A tibble: 50 x 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 女   
## # ... with 40 more rows

重命名列

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

df %>% 
  set_names("班级","姓名","性别","语文",
            "数学","英语","品德","科学")
## # A tibble: 50 x 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
## # ... with 40 more rows

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

df %>% rename(数学=math,科学=science)
## # A tibble: 50 x 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
## # ... with 40 more rows

修改列

创建新列

用dplyr包中的mutate()创建或修改列,返回原数据框并增加新列;若改用transmute()则只返回增加的新列。

若只给新列1个值,则循环使用得到值相同的一列:

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

正常是以长度等于行数的向量赋值:

df %>% 
  mutate(new_col=1:n())#n()返回当前分组的样本数,未分组则为总行数
## # A tibble: 50 x 9
##    class name   sex   chinese  math english moral science new_col
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>   <int>
##  1 六1班 何娜   女         87    92      79     9      10       1
##  2 六1班 黄才菊 女         95    77      75    NA       9       2
##  3 六1班 陈芳妹 女         79    87      66     9      10       3
##  4 六1班 陈学勤 男         NA    79      66     9      10       4
##  5 六1班 陈祝贞 女         76    79      67     8      10       5
##  6 六1班 何小薇 女         83    73      65     8       9       6
##  7 六1班 雷旺   男         NA    80      68     8       9       7
##  8 六1班 陈欣越 男         57    80      60     9       9       8
##  9 六1班 黄亦婷 女         77    NA      54     8      10       9
## 10 六1班 陈媚   女         68    55      66     8       9      10
## # ... with 40 more rows

计算新列

用数据框的列计算新列,若修改当前列,只需要赋值给原列名。

df %>% 
  mutate(total=chinese+math+english+moral+science)#计算总分,注意不能使用sum、mean
## # A tibble: 50 x 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
## # ... with 40 more rows

在同一个mutate()中可以同时创建或计算多个列,它们是从前往后依次计算, 所以可以使用前面新创建的列,例如:

  • 计算df中math列的中位数
  • 创建标记math是否大于中位数的逻辑值列。
  • 用as.numeric()将TRUE/FALSE转化为1/0
df %>% 
  mutate(med=median(math,na.rm=TRUE),
         label=math>med,
         label=as.numeric(label))#使用了前面创建的列label
## # A tibble: 50 x 10
##    class name   sex   chinese  math english moral science   med label
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl> <dbl> <dbl>
##  1 六1班 何娜   女         87    92      79     9      10    73     1
##  2 六1班 黄才菊 女         95    77      75    NA       9    73     1
##  3 六1班 陈芳妹 女         79    87      66     9      10    73     1
##  4 六1班 陈学勤 男         NA    79      66     9      10    73     1
##  5 六1班 陈祝贞 女         76    79      67     8      10    73     1
##  6 六1班 何小薇 女         83    73      65     8       9    73     0
##  7 六1班 雷旺   男         NA    80      68     8       9    73     1
##  8 六1班 陈欣越 男         57    80      60     9       9    73     1
##  9 六1班 黄亦婷 女         77    NA      54     8      10    73    NA
## 10 六1班 陈媚   女         68    55      66     8       9    73     0
## # ... with 40 more rows

修改多列

结合across()和选择列语法可以应用函数到多列,从而实现同时修改多列。

df %>% 
  mutate(across(everything(),as.character))#将所有列转化为字符型
## # A tibble: 50 x 8
##    class name   sex   chinese math  english moral science
##    <chr> <chr>  <chr> <chr>   <chr> <chr>   <chr> <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      
## # ... with 40 more rows
rescale = function(x) {#对所有数值列做归一化
  rng=range(x,na.rm=TRUE)
  (x-rng[1])/(rng[2]-rng[1])
}
df %>% 
  mutate(across(where(is.numeric),rescale))
## # A tibble: 50 x 8
##    class name   sex   chinese   math english  moral science
##    <chr> <chr>  <chr>   <dbl>  <dbl>   <dbl>  <dbl>   <dbl>
##  1 六1班 何娜   女      0.843  0.974   1      0.875   1    
##  2 六1班 黄才菊 女      1      0.776   0.926 NA       0.833
##  3 六1班 陈芳妹 女      0.686  0.908   0.759  0.875   1    
##  4 六1班 陈学勤 男     NA      0.803   0.759  0.875   1    
##  5 六1班 陈祝贞 女      0.627  0.803   0.778  0.75    1    
##  6 六1班 何小薇 女      0.765  0.724   0.741  0.75    0.833
##  7 六1班 雷旺   男     NA      0.816   0.796  0.75    0.833
##  8 六1班 陈欣越 男      0.255  0.816   0.648  0.875   0.833
##  9 六1班 黄亦婷 女      0.647 NA       0.537  0.75    1    
## 10 六1班 陈媚   女      0.471  0.487   0.759  0.75    0.833
## # ... with 40 more rows

替换NA

replace_na():实现用某个值替换一列中的所有NA值,该函数接受一个命名列表,其成分为列名=替换值:

starwars %>% 
  replace_na(list(hair_color="UNKNOWN",
                  height=mean(.$height,na.rm=TRUE)))
## # A tibble: 87 x 14
##    name    height  mass hair_color  skin_color eye_color birth_year sex   gender
##    <chr>    <dbl> <dbl> <chr>       <chr>      <chr>          <dbl> <chr> <chr> 
##  1 Luke S~    172    77 blond       fair       blue            19   male  mascu~
##  2 C-3PO      167    75 UNKNOWN     gold       yellow         112   none  mascu~
##  3 R2-D2       96    32 UNKNOWN     white, bl~ red             33   none  mascu~
##  4 Darth ~    202   136 none        white      yellow          41.9 male  mascu~
##  5 Leia O~    150    49 brown       light      brown           19   fema~ femin~
##  6 Owen L~    178   120 brown, grey light      blue            52   male  mascu~
##  7 Beru W~    165    75 brown       light      blue            47   fema~ femin~
##  8 R5-D4       97    32 UNKNOWN     white, red red             NA   none  mascu~
##  9 Biggs ~    183    84 black       light      brown           24   male  mascu~
## 10 Obi-Wa~    182    77 auburn, wh~ fair       blue-gray       57   male  mascu~
## # ... with 77 more rows, and 5 more variables: homeworld <chr>, species <chr>,
## #   films <list>, vehicles <list>, starships <list>

fill():用前一个(或后一个)非缺失值填充NA。有些表在记录时,会省略与上一条记录相同的内容,如下表:

load("data/gap_data.rda")
knitr::kable(gap_data,align="c")
site species sample_num bees_present
Bilpin A. longiforlia 1 TRUE
NA NA 2 TRUE
NA NA 3 TRUE
NA A. elongata 1 TRUE
NA NA 2 FALSE
NA NA 3 TRUE
Grose Vale A. terminalis 1 FALSE
NA NA 2 FALSE
NA NA 2 TRUE

tidyr包中的fill()适合处理这种结构的缺失值,默认是向下填充,即用上一个非缺失值填充

gap_data %>% 
  fill(site,species)
## # A tibble: 9 x 4
##   site       species        sample_num bees_present
##   <chr>      <chr>               <dbl> <lgl>       
## 1 Bilpin     A. longiforlia          1 TRUE        
## 2 Bilpin     A. longiforlia          2 TRUE        
## 3 Bilpin     A. longiforlia          3 TRUE        
## 4 Bilpin     A. elongata             1 TRUE        
## 5 Bilpin     A. elongata             2 FALSE       
## 6 Bilpin     A. elongata             3 TRUE        
## 7 Grose Vale A. terminalis           1 FALSE       
## 8 Grose Vale A. terminalis           2 FALSE       
## 9 Grose Vale A. terminalis           2 TRUE

重新编码

df %>% 
  mutate(sex=if_else(sex=="男","M","F"))#用if_else()作是/否决策以确定用哪个值做重新编码
## # A tibble: 50 x 8
##    class name   sex   chinese  math english moral science
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六1班 何娜   F          87    92      79     9      10
##  2 六1班 黄才菊 F          95    77      75    NA       9
##  3 六1班 陈芳妹 F          79    87      66     9      10
##  4 六1班 陈学勤 M          NA    79      66     9      10
##  5 六1班 陈祝贞 F          76    79      67     8      10
##  6 六1班 何小薇 F          83    73      65     8       9
##  7 六1班 雷旺   M          NA    80      68     8       9
##  8 六1班 陈欣越 M          57    80      60     9       9
##  9 六1班 黄亦婷 F          77    NA      54     8      10
## 10 六1班 陈媚   F          68    55      66     8       9
## # ... with 40 more rows
df %>% 
  mutate(math=case_when(math>=75~"High",#用case_when()做更多条件下的重新编码,避免使用很多if_else()嵌套
                        math>=60~"Middle",
                        TRUE~"Low"))
## # A tibble: 50 x 8
##    class name   sex   chinese math   english moral science
##    <chr> <chr>  <chr>   <dbl> <chr>    <dbl> <dbl>   <dbl>
##  1 六1班 何娜   女         87 High        79     9      10
##  2 六1班 黄才菊 女         95 High        75    NA       9
##  3 六1班 陈芳妹 女         79 High        66     9      10
##  4 六1班 陈学勤 男         NA High        66     9      10
##  5 六1班 陈祝贞 女         76 High        67     8      10
##  6 六1班 何小薇 女         83 Middle      65     8       9
##  7 六1班 雷旺   男         NA High        68     8       9
##  8 六1班 陈欣越 男         57 High        60     9       9
##  9 六1班 黄亦婷 女         77 Low         54     8      10
## 10 六1班 陈媚   女         68 Low         66     8       9
## # ... with 40 more rows

case_when()中用的是公式形式

  • 1.左边是返回TRUE或FALSE的表达式或函数
  • 2.右边是若左边表达式为TRUE,则重新编码的值,也可以是表达式或函数
  • 3.每个分支条件将从上到下的计算,并接受第一个TURE条件
  • 4.最后一个分支直接用TRUE表示若其它条件都不为TRUE时怎么做

筛选行

即按行选择数据子集,包括删除行、对行切片、过滤行

set.seed(123)
df_dup=df %>% 
  slice_sample(n=60,replace = TRUE)#创建一个包含重复行的数据框(有放回抽取69行)

删除行

df_dup %>% 
  distinct()#删除重复行(只保留第1个,删除其余)
## # A tibble: 35 x 8
##    class name   sex   chinese  math english moral science
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六4班 周婵   女         92    94      77    10       9
##  2 六2班 杨远芸 女         93    80      68     9      10
##  3 六2班 陈华健 男         92    84      70     9      10
##  4 六1班 陈芳妹 女         79    87      66     9      10
##  5 六5班 陆曼   女         88    84      69     8      10
##  6 六5班 胡玉洁 女         74    61      52     9       6
##  7 六5班 容唐   女         83    71      56     9       7
##  8 六4班 关小孟 男         84    78      49     8       5
##  9 六3班 洪琦希 男         NA    31      69     6       4
## 10 六3班 刘虹均 男         72    23      74     3       6
## # ... with 25 more rows
df_dup %>% 
  distinct(sex,math,.keep_all=TRUE)#只根据sex和math判定重复
## # A tibble: 32 x 8
##    class name   sex   chinese  math english moral science
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六4班 周婵   女         92    94      77    10       9
##  2 六2班 杨远芸 女         93    80      68     9      10
##  3 六2班 陈华健 男         92    84      70     9      10
##  4 六1班 陈芳妹 女         79    87      66     9      10
##  5 六5班 陆曼   女         88    84      69     8      10
##  6 六5班 胡玉洁 女         74    61      52     9       6
##  7 六5班 容唐   女         83    71      56     9       7
##  8 六4班 关小孟 男         84    78      49     8       5
##  9 六3班 洪琦希 男         NA    31      69     6       4
## 10 六3班 刘虹均 男         72    23      74     3       6
## # ... with 22 more rows
#默认只返回选择的列,要返回所有列,需要设置参数.keep_all=TRUE
df_dup %>% 
  drop_na()#删除所有包含NA的行
## # A tibble: 38 x 8
##    class name   sex   chinese  math english moral science
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六4班 周婵   女         92    94      77    10       9
##  2 六2班 杨远芸 女         93    80      68     9      10
##  3 六2班 陈华健 男         92    84      70     9      10
##  4 六1班 陈芳妹 女         79    87      66     9      10
##  5 六5班 陆曼   女         88    84      69     8      10
##  6 六5班 胡玉洁 女         74    61      52     9       6
##  7 六5班 容唐   女         83    71      56     9       7
##  8 六4班 关小孟 男         84    78      49     8       5
##  9 六2班 陈华健 男         92    84      70     9      10
## 10 六3班 刘虹均 男         72    23      74     3       6
## # ... with 28 more rows
df_dup %>%
  drop_na(sex:math)#也可以只删除某些列包含NA的行
## # A tibble: 50 x 8
##    class name   sex   chinese  math english moral science
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六4班 周婵   女         92    94      77    10       9
##  2 六2班 杨远芸 女         93    80      68     9      10
##  3 六2班 陈华健 男         92    84      70     9      10
##  4 六1班 陈芳妹 女         79    87      66     9      10
##  5 六5班 陆曼   女         88    84      69     8      10
##  6 六5班 胡玉洁 女         74    61      52     9       6
##  7 六5班 容唐   女         83    71      56     9       7
##  8 六4班 关小孟 男         84    78      49     8       5
##  9 六2班 陈华健 男         92    84      70     9      10
## 10 六3班 刘虹均 男         72    23      74     3       6
## # ... with 40 more rows

对行切片:slice_*() ####

df %>% 
  slice_max(math,n=5)#选择math列值中前5大的行
## # A tibble: 5 x 8
##   class name   sex   chinese  math english moral science
##   <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
## 1 六4班 周婵   女         92    94      77    10       9
## 2 六4班 陈丽丽 女         87    93      NA     8       6
## 3 六1班 何娜   女         87    92      79     9      10
## 4 六5班 符苡榕 女         85    89      76     9      NA
## 5 六2班 黄祖娜 女         94    88      75    10      10
df %>% 
  slice_sample(n=3)#随机选择3行
## # A tibble: 3 x 8
##   class name   sex   chinese  math english moral science
##   <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
## 1 六3班 刘虹均 男         72    23      74     3       6
## 2 六4班 周婵   女         92    94      77    10       9
## 3 六2班 雷开茂 男         83    NA      45     9       7

用filter()根据值或条件筛选行

df_dup %>% 
  filter(sex=="男",math>80)#多个条件之间用","隔开,相当于and
## # A tibble: 8 x 8
##   class name   sex   chinese  math english moral science
##   <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
## 1 六2班 陈华健 男         92    84      70     9      10
## 2 六2班 陈华健 男         92    84      70     9      10
## 3 六4班 <NA>   男         84    85      52     9       8
## 4 六2班 陈华健 男         92    84      70     9      10
## 5 六4班 李小龄 男         90    87      69    10      10
## 6 六4班 李小龄 男         90    87      69    10      10
## 7 六4班 杨昌晟 男         84    85      64     8      10
## 8 <NA>  徐达政 男         90    86      72     9      10
df_dup %>% 
  filter(sex=="女",(is.na(english)|math>80))
## # A tibble: 11 x 8
##    class name   sex   chinese  math english moral science
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六4班 周婵   女         92    94      77    10       9
##  2 六1班 陈芳妹 女         79    87      66     9      10
##  3 六5班 陆曼   女         88    84      69     8      10
##  4 六5班 陆曼   女         88    84      69     8      10
##  5 六2班 徐雅琦 女         92    86      72    NA       9
##  6 六5班 陆曼   女         88    84      69     8      10
##  7 六5班 符苡榕 女         85    89      76     9      NA
##  8 六2班 徐雅琦 女         92    86      72    NA       9
##  9 六4班 陈丽丽 女         87    93      NA     8       6
## 10 六3班 江佳欣 女         80    69      NA     6       5
## 11 六5班 符苡榕 女         85    89      76     9      NA
df_dup %>% 
  filter(between(math,70,80))#闭区间
## # A tibble: 15 x 8
##    class name   sex   chinese  math english moral science
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六2班 杨远芸 女         93    80      68     9      10
##  2 六5班 容唐   女         83    71      56     9       7
##  3 六4班 关小孟 男         84    78      49     8       5
##  4 六1班 陈祝贞 女         76    79      67     8      10
##  5 六1班 陈欣越 男         57    80      60     9       9
##  6 六1班 雷旺   男         NA    80      68     8       9
##  7 六4班 林传顺 男         85    75      52    NA       9
##  8 六2班 林师满 男         70    74      25     8      10
##  9 六5班 容唐   女         83    71      56     9       7
## 10 六2班 杨远芸 女         93    80      68     9      10
## 11 六1班 雷旺   男         NA    80      68     8       9
## 12 六1班 雷旺   男         NA    80      68     8       9
## 13 六1班 陈祝贞 女         76    79      67     8      10
## 14 六1班 陈欣越 男         57    80      60     9       9
## 15 六2班 杨远芸 女         93    80      68     9      10

在限定范围内根据条件筛选

结合across()和选择列语法,可以在限定列范围内,根据应用函数得到的结果作为条件筛选行。

df[,4:6]%>% 
  filter(across(everything(),~.x>75))#选出所有列范围内,所有值都>75的行
## # A tibble: 3 x 3
##   chinese  math english
##     <dbl> <dbl>   <dbl>
## 1      87    92      79
## 2      92    94      77
## 3      85    89      76
df %>%
  filter(if_any(where(is.numeric),~.x>90))#选出数列范围内,存在值>90的行
## # A tibble: 8 x 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 六2班 黄祖娜 女         94    88      75    10      10
## 4 六2班 徐雅琦 女         92    86      72    NA       9
## 5 六2班 陈华健 男         92    84      70     9      10
## 6 六2班 杨远芸 女         93    80      68     9      10
## 7 六4班 周婵   女         92    94      77    10       9
## 8 六4班 陈丽丽 女         87    93      NA     8       6

对行进行排序

使用arrange()进行排序,默认是递增

df_dup %>% 
  arrange(math,sex)
## # A tibble: 60 x 8
##    class name   sex   chinese  math english moral science
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六3班 邹嘉伟 男         67    18      62     8      NA
##  2 六3班 刘虹均 男         72    23      74     3       6
##  3 六3班 刘虹均 男         72    23      74     3       6
##  4 六3班 黄凯丽 女         70    23      61     4       4
##  5 六3班 黄凯丽 女         70    23      61     4       4
##  6 六3班 黄凯丽 女         70    23      61     4       4
##  7 六3班 黄凯丽 女         70    23      61     4       4
##  8 六3班 黄凯丽 女         70    23      61     4       4
##  9 六3班 陈逾革 男         47    24      67     2       5
## 10 六3班 陈逾革 男         47    24      67     2       5
## # ... with 50 more rows
df_dup %>% 
  arrange(desc(math))#递减排序
## # A tibble: 60 x 8
##    class name   sex   chinese  math english moral science
##    <chr> <chr>  <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六4班 周婵   女         92    94      77    10       9
##  2 六4班 陈丽丽 女         87    93      NA     8       6
##  3 六5班 符苡榕 女         85    89      76     9      NA
##  4 六5班 符苡榕 女         85    89      76     9      NA
##  5 六1班 陈芳妹 女         79    87      66     9      10
##  6 六4班 李小龄 男         90    87      69    10      10
##  7 六4班 李小龄 男         90    87      69    10      10
##  8 六2班 徐雅琦 女         92    86      72    NA       9
##  9 六2班 徐雅琦 女         92    86      72    NA       9
## 10 <NA>  徐达政 男         90    86      72     9      10
## # ... with 50 more rows

分组汇总

分组汇总,相当于Excel的透视表功能

当数据框被分组时,上述一些操作就是分别在每个分组上独立执行;

可以认为数据框被分组,相当于被拆分成更小的数据框(并没有实际拆分); #### 创建分组 #### 用group_by()创建分组,只是对数据框增加了分组信息(用group_keys()查看),并不是真的将数据分割为多个数据框。

df_grp=df %>% 
  group_by(sex)
group_keys(df_grp)#分组键值(唯一识别分组)
## # A tibble: 3 x 1
##   sex  
##   <chr>
## 1 男   
## 2 女   
## 3 <NA>
group_indices(df_grp)#查看每一行属于哪一分组
##  [1] 2 2 2 1 2 2 1 1 2 2 2 2 1 1 2 1 1 1 2 1 2 2 2 1 1 1 2 1 1 2 2 1 2 1 1 1 1 2
## [39] 1 2 2 2 2 2 1 1 3 1 1 2
group_rows(df_grp)#查看每一组包含哪些行
## <list_of<integer>[3]>
## [[1]]
##  [1]  4  7  8 13 14 16 17 18 20 24 25 26 28 29 32 34 35 36 37 39 45 46 48 49
## 
## [[2]]
##  [1]  1  2  3  5  6  9 10 11 12 15 19 21 22 23 27 30 31 33 38 40 41 42 43 44 50
## 
## [[3]]
## [1] 47
ungroup(df_grp)#解除分组
## # A tibble: 50 x 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
## # ... with 40 more rows

其他分组

  • 真正将数据框分割为多个分组:group_split(),返回列表,其每个成分是一个分组数据框
  • 将数据框分组(group_by),再做嵌套(nest),生成嵌套数据框:group_nest()
iris %>% 
  group_nest(Species)
## # A tibble: 3 x 2
##   Species                  data
##   <fct>      <list<tibble[,4]>>
## 1 setosa               [50 x 4]
## 2 versicolor           [50 x 4]
## 3 virginica            [50 x 4]

purrr风格的分组迭代:将函数.f依次应用到分组数据框.data的每个分组上

  • group_map(.data,.f,…):返回列表

  • group_walk(.data,.f,…):不返回,只关心副作用

  • group_modify(.data,.f,…):返回修改后的分组数据框 #### 分组汇总 #### 对数据框做分组最主要的目的就是做分组汇总,汇总就是以某种方式组合行,用dplyr包中的summarise()函数实现,结果只保留唯一值和新创建的汇总列。 summarise()

  • n():观测数

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

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

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

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

函数summarise(),配合across()可以对所选择的列做汇总。好处是可以借助辅助选择器或判断条件选择多列,还能在这些列上执行多个函数,只需要将它们放入一个列表。

对某些列做汇总

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

对所有列做汇总

df %>% 
  select(-name) %>% #删除姓名列
  group_by(class,sex) %>% 
  summarise(across(everything(),mean,na.rm=TRUE))#对所有列做汇总
## `summarise()` has grouped output by 'class'. You can override using the `.groups` argument.
## # A tibble: 12 x 7
## # Groups:   class [6]
##    class sex   chinese  math english moral science
##    <chr> <chr>   <dbl> <dbl>   <dbl> <dbl>   <dbl>
##  1 六1班 男       57    79.7    64.7  8.67    9.33
##  2 六1班 女       80.7  77.2    67.4  8.33    9.57
##  3 六2班 男       75.4  68.8    42.6  8.8     9.25
##  4 六2班 女       92.2  73.8    63.8  8.33    9   
##  5 六3班 男       66    30.4    67.6  4.6     4.75
##  6 六3班 女       68.4  49.2    67.8  6.25    7.2 
##  7 六4班 男       84.8  79.2    55.7  8.6     8.5 
##  8 六4班 女       85.2  74      63.7  8.75    7.75
##  9 六5班 男       66.5  64.5    65.3  8.25    8.25
## 10 六5班 女       80.4  75.4    63.4  8.75    8.25
## 11 六5班 <NA>     58    85      48    9      10   
## 12 <NA>  男       90    86      72    9      10

对满足条件的列做多种汇总

df_grp=df %>% 
  group_by(class) %>% 
  summarise(across(where(is.numeric),
                   list(sum=sum,mean=mean,min=min),na.rm=TRUE))#对所有数值列求和、均值、最小值
df_grp
## # A tibble: 6 x 16
##   class chinese_sum chinese_mean chinese_min math_sum math_mean math_min
##   <chr>       <dbl>        <dbl>       <dbl>    <dbl>     <dbl>    <dbl>
## 1 六1班         622         77.8          57      702      78         55
## 2 六2班         746         82.9          66      570      71.2       41
## 3 六3班         606         67.3          44      349      38.8       18
## 4 六4班         850         85            72      771      77.1       54
## 5 六5班         726         72.6          58      720      72         59
## 6 <NA>           90         90            90       86      86         86
## # ... with 9 more variables: english_sum <dbl>, english_mean <dbl>,
## #   english_min <dbl>, moral_sum <dbl>, moral_mean <dbl>, moral_min <dbl>,
## #   science_sum <dbl>, science_mean <dbl>, science_min <dbl>

可读性不好,再来个宽变长:

df_grp %>% 
  pivot_longer(-class,names_to=c("Vars",".value"),names_sep="_")
## # A tibble: 30 x 5
##    class Vars      sum  mean   min
##    <chr> <chr>   <dbl> <dbl> <dbl>
##  1 六1班 chinese   622 77.8     57
##  2 六1班 math      702 78       55
##  3 六1班 english   666 66.6     54
##  4 六1班 moral      76  8.44     8
##  5 六1班 science    95  9.5      9
##  6 六2班 chinese   746 82.9     66
##  7 六2班 math      570 71.2     41
##  8 六2班 english   468 52       25
##  9 六2班 moral      69  8.62     6
## 10 六2班 science    73  9.12     7
## # ... with 20 more rows

summarise()以前只支持一个返回值的汇总函数,如sum,mean等。现在也支持多返回值(返回向量值、甚至是数据框)的汇总函数,如range(),quantile()等。

qs=c(0.25,0.5,0.75)

df_q=df %>% 
  group_by (sex) %>% 
  summarise(math_qs=quantile(math,qs,na.rm=TRUE),q=qs)
## `summarise()` has grouped output by 'sex'. You can override using the `.groups` argument.
df_q
## # A tibble: 9 x 3
## # Groups:   sex [3]
##   sex   math_qs     q
##   <chr>   <dbl> <dbl>
## 1 男       57.5  0.25
## 2 男       69    0.5 
## 3 男       80    0.75
## 4 女       55    0.25
## 5 女       73    0.5 
## 6 女       86.5  0.75
## 7 <NA>     85    0.25
## 8 <NA>     85    0.5 
## 9 <NA>     85    0.75

可读性不好,再来个长变宽:

df_q %>% 
  pivot_wider(names_from=q,values_from=math_qs,names_prefix="q_")
## # A tibble: 3 x 4
## # Groups:   sex [3]
##   sex   q_0.25 q_0.5 q_0.75
##   <chr>  <dbl> <dbl>  <dbl>
## 1 男      57.5    69   80  
## 2 女      55      73   86.5
## 3 <NA>    85      85   85

分组计数

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

df %>% 
  count(class,sex,sort=TRUE)
## # A tibble: 12 x 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 x 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 x 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
## # ... with 40 more rows

其他数据操作

按行汇总

通常的数据操作逻辑都是按列方式(colwise),这使得按行汇总很困难。 dplyr包提供了rowwise()函数为数据框创建按行方式(rowwise),使用rowwise()后并不是真的改变数据框,只是创建了按行元信息,改变了数据框的操作逻辑:

rf = df %>%
  rowwise()
rf %>%
  mutate(total = sum( c (chinese,math,english),na.rm =TRUE))
## # A tibble: 50 x 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   145
##  5 六1班 陈祝贞 女         76    79      67     8      10   222
##  6 六1班 何小薇 女         83    73      65     8       9   221
##  7 六1班 雷旺   男         NA    80      68     8       9   148
##  8 六1班 陈欣越 男         57    80      60     9       9   197
##  9 六1班 黄亦婷 女         77    NA      54     8      10   131
## 10 六1班 陈媚   女         68    55      66     8       9   189
## # ... with 40 more rows

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

rf %>%
  mutate(total = sum(c_across(where(is.numeric))))
## # A tibble: 50 x 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
## # ... with 40 more rows

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

df %>%
  mutate(total=rowSums(across(where(is.numeric))))
## # A tibble: 50 x 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
## # ... with 40 more rows

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

df %>%
  rowwise(name) %>%
  summarise(total = sum(c_across(where(is.numeric))))
## `summarise()` has grouped output by 'name'. You can override using the `.groups` argument.
## # A tibble: 50 x 2
## # Groups:   name [50]
##    name   total
##    <chr>  <dbl>
##  1 何娜     277
##  2 黄才菊    NA
##  3 陈芳妹   251
##  4 陈学勤    NA
##  5 陈祝贞   240
##  6 何小薇   238
##  7 雷旺      NA
##  8 陈欣越   215
##  9 黄亦婷    NA
## 10 陈媚     206
## # ... with 40 more rows

窗口函数

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

例如,cumsum( )、 cummean ( )、rank()、 lead()、 lag()等。 #### 排名和排序函数 #### 共有6个排名函数,只介绍最常用的min_rank():从小到大排名(ties.method=“min”),若要从大到小排名需要套一个desc()

df %>%
  mutate(ranks = min_rank(desc(math))) %>% 
  arrange(ranks)
## # A tibble: 50 x 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
## # ... with 40 more rows

移位函数

  • lag()取前一个值;
  • lead()取后一个值;
library(lubridate)
## 
## 载入程辑包:'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union
dt = tibble(
  day = as_date( "2019-08-30") + c(0,4:6),wday = weekdays(day) ,
  #生成日期字符串并生成对应星期字符串
  sales = c(2,6,2,3),
  balance = c(30,25,-40,30))
dt %>%
  mutate(sales_lag = lag(sales), sales_delta = sales - lag(sales))
## # A tibble: 4 x 6
##   day        wday   sales balance sales_lag sales_delta
##   <date>     <chr>  <dbl>   <dbl>     <dbl>       <dbl>
## 1 2019-08-30 星期五     2      30        NA          NA
## 2 2019-09-03 星期二     6      25         2           4
## 3 2019-09-04 星期三     2     -40         6          -4
## 4 2019-09-05 星期四     3      30         2           1

累计汇总

dt %>%
  filter ( cumany (balance < 0))#选择第一次透支之后的所有行
## # A tibble: 2 x 4
##   day        wday   sales balance
##   <date>     <chr>  <dbl>   <dbl>
## 1 2019-09-04 星期三     2     -40
## 2 2019-09-05 星期四     3      30
dt %>%
  filter(cumall( ! (balance < 0)))#选择所有行直到第一次透支
## # A tibble: 2 x 4
##   day        wday   sales balance
##   <date>     <chr>  <dbl>   <dbl>
## 1 2019-08-30 星期五     2      30
## 2 2019-09-03 星期二     6      25

滑窗迭代

“窗口函数”术语来自sQL,意味着逐窗口浏览数据,将某函数重复应用于数据的每个“窗口”。窗口函数的典型应用包括滑动平均、累计和以及更复杂如滑动回归。

安装第三方包slider

install.packages("slider")
library(slider)
## Warning: 程辑包'slider'是用R版本4.1.2 来建造的
dt %>% 
  mutate(avg_3 = slide_dbl(sales,mean,.before = 1,.after = 1))
## # A tibble: 4 x 5
##   day        wday   sales balance avg_3
##   <date>     <chr>  <dbl>   <dbl> <dbl>
## 1 2019-08-30 星期五     2      30  4   
## 2 2019-09-03 星期二     6      25  3.33
## 3 2019-09-04 星期三     2     -40  3.67
## 4 2019-09-05 星期四     3      30  2.5
#查看连续三值滑动与连续3日滑动窗口
dt %>%
  mutate(avg_3 = slide_index_dbl(sales,day,mean,.before = 1,.after = 1))
## # A tibble: 4 x 5
##   day        wday   sales balance avg_3
##   <date>     <chr>  <dbl>   <dbl> <dbl>
## 1 2019-08-30 星期五     2      30  2   
## 2 2019-09-03 星期二     6      25  4   
## 3 2019-09-04 星期三     2     -40  3.67
## 4 2019-09-05 星期四     3      30  2.5

整洁计算

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

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

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

  • 数据屏蔽:使得可以不用带数据框(环境变量)名字,就能使用数据框内的变量(数据变量),便于在数据集内计算值
  • 整洁选择:即各种选择列语法,便于使用数据集中的列

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

var_summary = function(data,var) {
    data %>%
      summarise(n = n( ), mean = mean({{var}}))
}
mtcars %>%
  group_by(cyl) %>%
  var_summary(mpg)
## # A tibble: 3 x 3
##     cyl     n  mean
##   <dbl> <int> <dbl>
## 1     4    11  26.7
## 2     6     7  19.7
## 3     8    14  15.1

同样地,整洁选择作为函数参数时的间接使用,也需要用两个大括号括起来{{vars}}:

summarise_mean = function(data,vars) {
    data %>%
      summarise(n = n(), across({{vars}}, mean))
}
mtcars %>%
  group_by(cyl)%>%
  summarise_mean(where(is.numeric))
## # A tibble: 3 x 12
##     cyl     n   mpg  disp    hp  drat    wt  qsec    vs    am  gear  carb
##   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1     4    11  26.7  105.  82.6  4.07  2.29  19.1 0.909 0.727  4.09  1.55
## 2     6     7  19.7  183. 122.   3.59  3.12  18.0 0.571 0.429  3.86  3.43
## 3     8    14  15.1  353. 209.   3.23  4.00  16.8 0     0.143  3.29  3.5

引用与反引用

创建tidyverse风格的整洁函数,另一种做法是使用引用与反引用机制。这需要额外的两个步骤:

  • 用enquo()让函数自动引用其参数。
  • 用!!反引用该参数

以自定义计算分组均值函数为例:

grouped_mean = function(data,summary_var, group_var) {
  summary_var = enquo(summary_var)
  group_var = enquo(group_var)
  data %>%
    group_by( ! ! group_var) %>%
    summarise(mean = mean( ! ! summary_var))
}
grouped_mean(mtcars,mpg,cyl)
## # A tibble: 3 x 2
##     cyl  mean
##   <dbl> <dbl>
## 1     4  26.7
## 2     6  19.7
## 3     8  15.1

数据处理神器:data.table包

data.table包是data.frame的高性能版本,不依赖其它包就能胜任各种数据操作,速度超快,让个人电脑都能轻松处理几G甚至几十G的数据。data.table的高性能来源于内存管理(引用语法)、并行化和大量精细优化。

但是,与tidyverse一次用一个函数做一件事,通过管道依次连接整洁地完成复杂事情的理念截然不同data.table语法高度抽象、简洁、一致:

dt[i,j,by]:用i选择行,用j操作列,根据by分组 其中j表达式非常强大和灵活,可以选择、修改、汇总、计算新列,甚至可以接受表达式最关键一点是:只要返回等长元素或长度为1元素的list,每个list元素将转化为结果data.table 的一列。

通用语法

创建data.table

安装第三方包data.table

install.packages("data.table")
library(data.table)
## 
## 载入程辑包:'data.table'
## The following objects are masked from 'package:lubridate':
## 
##     hour, isoweek, mday, minute, month, quarter, second, wday, week,
##     yday, year
## The following objects are masked from 'package:dplyr':
## 
##     between, first, last
## The following object is masked from 'package:purrr':
## 
##     transpose
dt=data.table(
  x=1:2,
  y=c("A","B")
)
dt
##    x y
## 1: 1 A
## 2: 2 B

使用as.data.table()可将数据框、列表、矩阵等转化为data.table #### 引用语法 #### 高效计算的编程都支持引用语法,也叫浅拷贝。

浅拷贝,只是拷贝列指针向量(对应数据框的列),而实际数据在内存中不做物理拷贝;而相对的概念:深拷贝,则拷贝整个数据到内存中的另一位置,深拷贝这种冗余的拷贝极大地影响性能,特别是大数据的情形。

data.table使用:=算符,做整列或部分列替换时都不做任何拷贝,因为:=算符是通过引用就地(in-place)更新data.table的列。

若想要复制不想按引用(修改数据本身),使用DT2 = copy(DT1).

键与索引

data.table支持设置键和索引,使得选择行、做数据连接更加方便快速(快170倍)。

  • 键:一级有序索引;
  • 索引:自动二级索引;
setkey(dt,v1,v3) #设置键
setindex(dt,v1,v3) #设置索引

二者的主要不同:

  • 1.使用键时,数据在内存中做物理重排序;而使用索引时,顺序只是保存为属性;
  • 2.键是显式定义的;索引可以手动创建,也可以运行时创建(比如用==或%in%时)
  • 3.索引与参数on连用;键的使用是可选的,但为了可读性建议使用键。

特殊符号

data.table提供了一些辅助操作的特殊符号:

  • .():代替list()
  • :=:按引用方式增加、修改列。
  • .N:行数
  • .sD:每个分组的数据子集,除了by或 keyby的列。
  • .SDcols: 与.sD连用,用来选择包含在.SD中的列。
  • .BY:包含所有by分组变量的list
  • .I:整数向量seq_len(nrow(x)),例如DT[,.I[ which.max (somecol)],by=grp]
  • .GRP:分组索引,1代表第1分组,2代表第2分组,…
  • .NGRP:分组数
  • .EACHI:用于by/keyby = .EACHI表示根据i表达式的每一行分组

链式操作

data.table也有自己专用的管道操作,成为链式操作:

DT[...][...] #或者写开为
DT[
  ...
  ][
  ...
  ]

数据读写

函数fread()和 fwrite()是data.table最强大的函数之二。它们最大的优势,仍是读取大数据时速度超快(100倍),且非常稳健,分隔符、列类型、行数都是自动检测;它们非常通用,可以处理不同的文件格式(但不能直接读取Excel文件),还可以接受URLs甚至是操作系统指令。

读入数据

fread( "DT.csv")
fread("DT.txt",sep = "\t")#选择部分行列读取
fread ("DT.csv", select = c("V1","V4"))fread("DT.csv",drop = "V4",nrows = 100)# 读取压缩文件
fread(cmd = "unzip -cq myfile.zip")fread ("myfile.gz")
# 批量读取
c ( "DT.csv", "DT.csv") %>%
  lapply(fread) %>%
  rbindlist()   #多个数据框/列表按行合并

写出数据

fwrite(DT,"DT.csv")
fwrite(DT,"DT.csv", append = TRUE) #追加内容
fwrite(DT,"DT.txt",sep = "\t")
fwrite(setDT(list(o,list(1:5))),"DT2.csv")#支持写出列表列
fwrite(DT,"myfile.csv.gz",compress = "gzip")#写出到压缩文件

数据连接

data.table提供了简单的按行合并函数:

  • rbind(DT1,DT2,…):按行堆叠多个data.table
  • rbindlist(DT_list,idcol):堆叠多个data.table构成的list

最常用的六种数据连接:左连接、右连接、内连接、全连接、半连接、反连接,前四种连接又称为修改连接,后两种连接又称为过滤连接。 #### 左连接 #### 外连接至少保留一个数据表中的所有观测,分为左连接、右连接、全连接,其中最常用的是左连接:保留x所有行,合并匹配的y中的列。

y[x,on = "v1"] #注意是以×为左表
y[x] #若v1是键
merge(x, y,all.x = TRUE,by = "v1")

右连接

保留y所有行,合并匹配的x中的列:

merge(x, y,all.y = TRUE,by = "v1")

内连接

内连接是保留两个数据表中所共有的观测:只保留x中与y匹配的行,合并匹配的y 中的列:

merge(x, y, by = "v1")

全连接

保留x和y中的所有行,合并匹配的列: merge(x, y, all = TRUE,by = “v1”) #### 半连接 #### 根据在y 中,来筛选x中的行:

x[y$v1,on = "v1",nomatch = ]

反连接

根据不在y中,来筛选x中的行:

x[!y, on = "v1"]

集合运算

fintersect(x, y)
fsetdiff(x,y)
funion(x,y)
fsetequal(x,y)

滚动连接

滚动连接也是很有用的一种数据连接,往往涉及日期时间,特别是处理在时间上有先后关联的两个事件。基本语法为:

x[y, on = .(id = id,date = date), roll = TRUE]#同roll = inf

根据id等值匹配行,date是滚动匹配,匹配与左表y日期最接近的前一个日期,匹配成功的列合并进来;roll = -inf 则匹配最接近的后一个日期。

数据重塑

宽变长

宽表的特点是:表比较宽,本来该是“值”的,却出现在“变量(名”中。这就需要给它变到“值”中,新起个列名存为一列,这就是所谓的宽表变长表。

  • 每一行只有1个观测的情形
DT = fread( "data/分省年度GDP. csv", encoding = "UTF-8")
## Warning in (if (.Platform$OS.type == "unix") system
## else shell)(paste0("(", : '(data/分省年度GDP. csv) > C:
## \Users\WENKUA~1\AppData\Local\Temp\RtmpS2s7JL\file224026bf1312'运行失败,错误码
## 为1
## Warning in fread("data/分省年度GDP. csv", encoding = "UTF-8"): File 'C:
## \Users\WENKUA~1\AppData\Local\Temp\RtmpS2s7JL\file224026bf1312' has size 0.
## Returning a NULL data.table.
DT %>%
  melt(measure = 2:4,variable = "年份",value = "GDP")
## Null data.table (0 rows and 0 cols)

参数measure是用整数向量指定要变形的列,也可以使用正则表达式patterns(“年$”),也可以改用参数id指定不变形的列;若需要忽略缺失值,设置参数na.rm =TRUE.

对比tidyr : : pivot_longer ()实现:

DT %>%
  pivot_longer(-地区,names_to = "年份",values_to = "GDP")

两种语法基本相同,都是指定要变形的列、为存放变形列的列名中的“值”指定新列名、为存放变形列中的“值”指定新列名。 - 每一行有多个观测的情形

load ( "data/family.rda")
DT = as.data.table(family)
#family数据
DT %>%
  melt(measure = patterns ( "^dob","Agender"),
       value = c( "dob", "gender") , na.rm = TRUE)

长变宽

长表的特点是:表比较长。

有时候需要将分类变量的若干水平值,变成变量(列名)。这就是长表变宽表,它与宽表变长表正好相反(二者互逆)。

  • 只有1个列名列和1个值列的情形
load ( "data/ animals.rda")
DT = as.data.table(animals)#农场动物数据
DT %>%
  dcast(Year ~ Type,value = "Heads",fill = 0)

数据分割与合并

函数split(DT,by)可将data.table分割为list,然后就可以接map_*()函数逐分组迭代。

  • 拆分列
DT = as.data.table(table3)
#将case列拆分为两列,并删除原列
DT[,c("cases", "population") := tstrsplit(DT$rate,split = "/")][,
                                                                rate := NULL]
  • 合并列
DT = as.data.table(table5)
# 将century和year列合并为新列new,并删除原列
DT[,new := paste0(century,year)][,c("century", "year") := NULL]

数据操作

选择行

用i表达式,选择行。

  • 根据索引
dt[3:4,]#或dt[3:4]
##     x    y
## 1: NA <NA>
## 2: NA <NA>
dt[ !3:7,]#反选,或dt[-(3:7)]
##     x    y
## 1: NA <NA>
## 2: NA <NA>
## 3: NA <NA>
## 4: NA <NA>
## 5: NA <NA>
  • 根据逻辑表达式
dt[v2 >5]
dt[v4 %chin% c("A", "C")] #比%in6更快
dt[v1==1 & v4=="A"]
  • 删除重复行
unique(dt)
unique(dt,by = c("v1" , "v4"))#返回所有列
  • 删除包含NA的行
na.omit(dt,cols = 1:4)
  • 行切片
dt[sample(.N,3)]#随机抽取3 行
dt[sample(.N,.N * 0.5)]#随机抽取506的行
dt[ frankv(-v1,ties.method = "dense") < 2]# v1 值最大的行
  • 其它
dt[v4 %like% "^B"]# v4值以B开头
dt[v2 %between% c(3,5)]#闭区间
dt[between(v2,3,5,incbounds = FALSE)]#开区间
dt[v2 %inrange% list(-1:1,1:3)]# v2值属于多个区间的某个
dt[inrange(v2,-1:1,1:3,incbounds = TRUE)] #同上

排序行

dt[order(v1)]#默认按v1从小到大
dt[order(-v1)]#按v1从大到小
dt[order(v1,-v2)]#按v1从小到大,v2从大到小

若按引用对行重排序:

setorder(DT,V1,-V2)

操作列

用j表达式操作列

  • 选择一列或多列
#根据索引
dt[[3]]  #或dt[ [ "v3"]],dt$v3,返回向量
dt[,3]   #或dt[ , "v3"],返回data.table
#根据列名
dt[,.(v3)]#或dt[ , list(v3)]
dt[,.(v2,v3,v4)]
dt[, v2:v4]
dt[,!c("v2" , "v3")]#反选列
  • 调整列序
cols = rev ( names(DT))#或其它列序
setcolorder(DT, cols)
  • 修改列名
setnames(DT,old,new)
  • 修改因子水平
DT[, setattr(sex, "levels",c("M","F"))]

tidyverse是用mutate()修改列,不修改原数据框,必须赋值结果;data.table修改列,是用列赋值符号:=(不执行复制),直接对原数据框修改。

  • 修改或增加一列
dt[, v1 := v1 ^ 2][]#修改列,加[]输出结果
dt[, v2 := log(v1)]#增加新列
dt[, .(v2 = log(v1), v3 = v2 + 1)]#只保留新列
# 使用不带NA 的考试成绩数据
DT = readxl::read_xlsx("data/ExamDatas.xlsx")%>%
  as.data.table()
#应用函数到所有列
DT[,lapply(.SD,as.character)]
#应用函数到满足条件的列
DT[,lapply (.sD,rescale),   #rescale()为自定义的归一化函数
       .SDcols = is.numeric]
#应用函数到指定列
DT = as.data.table(iris)
DT[,.SD * 10,.SDcols = patterns( " (Length) | (width)")]
  • 删除列
dt[,v1 := NULL]
dt[,c("v2" , "v3") := NULL]
cols = c("v2" , "v3")
dt[,(cols) := NULL]        #注意不是dt[, cols := NULL]
  • 重新编码
#一分支
dt[v1 < 4,v1 := 0]
#二分支
dt[, vl := fifelse(v1 < 0,-v1,v1)]
#多分支
dt[, v2 := fcase(v2 <4,"low",
                 v2< 7, "middle",
                 default = "high")]
  • 前移/后移运算
shift(x,n = 1,fill = NA,type = "lag")     #1,2,3 ->NA,1,2
shift(x,n = 1,fill = NA,type = "lead")      #1,2,3 ->2,3,NA

分组汇总

用by表达式指定分组。

data.table是根据by或keyby分组,区别是,keyby会排序结果并创建键,使得更快地访问子集。

未分组数据框相当于整个数据框作为1组,数据操作是在整个数据框上进行,汇总是得到1个结果。

分组数据框,相当于整个数据框分成了m个数据框,数据操作是分别在每个数据框上进行,汇总是得到m个结果。

#使用带NA值的考试成绩数据
DT = readxl::read_xlsx("data/ExamDatas_NAs.xlsx") %>% 
  as.data.table()
  • 未分组汇总
DT[,.(math_avg = mean(math,na.rm = TRUE))]
##    math_avg
## 1: 68.04255
  • 简单的分组汇总
DT[,.(n = .N,
      math_avg = mean(math,na.rm = TRUE),
      math_med = median(math)),
    by = sex]
##     sex  n math_avg math_med
## 1:   女 25 70.78261       NA
## 2:   男 24 64.56522       NA
## 3: <NA>  1 85.00000       85

可以直接在by中使用判断条件或表达式,特别是根据整合单位的日期时间汇总:

date = as.IDate( "2021-01-01") +1:50
DT = data.table(date, a = 1:50)
DT[,mean(a),by = list(mon = month(date))]# 按月平均
##    mon   V1
## 1:   1 15.5
## 2:   2 40.5

data.table提供快速处理日期时间的IDateTime类,更多信息可查阅帮助。

  • 对某些列做汇总
DT[,lapply (.sD,mean),.sDcols = patterns("h"),
    by = .(class,sex)]#或用by = c ( "class", "sex")
  • 对所有列做汇总
DT[,name := NULL][,lapply (.SD,mean,na.rm = TRUE),
                   by = .( class,sex)]
  • 对满足条件的列做汇总
DT[,lapply(.sD,mean,na.rm = TRUE), by = class,
    .SDcols = is.numeric]
  • 分组计数
DT = na.omit(DT)
DT[,.N,by = .(class,cut(math,c(0,60,100)))]%>%
  print(topn = 2)