第 12 章 简单数据框

library(tidyverse)
library(tibble) # 事实上,library(tidyverse)已经加装了library(tibble)

12.1 tidyverse 家族

前面陆续介绍了tidyverse家族,家庭主要成员包括

功能 宏包
有颜值担当 ggplot2
数据处理王者 dplyr
数据转换专家 tidyr
数据载入利器 readr
循环加速器 purrr
强化数据框 tibble
字符串处理 stringr
因子处理 forcats

12.2 人性化的tibble

  • tibble是用来替换data.frame类型的扩展的数据框
  • tibble继承了data.frame,是弱类型的。换句话说,tibble是data.frame的子类型
  • tibble与data.frame有相同的语法,使用起来更方便
  • tibble更早的检查数据,方便写出更干净、更多富有表现力的代码

tibble对data.frame做了重新的设定:

  • tibble,不关心输入类型,可存储任意类型,包括list类型
  • tibble,没有行名设置 row.names
  • tibble,支持任意的列名
  • tibble,会自动添加列名
  • tibble,类型只能回收长度为1的输入
  • tibble,会懒加载参数,并按顺序运行
  • tibble,是tbl_df类型

12.3 tibble 与 data.frame

传统创建数据框

data.frame(
  a = 1:5,
  b = letters[1:5]
)
##   a b
## 1 1 a
## 2 2 b
## 3 3 c
## 4 4 d
## 5 5 e

发现,data.frame()会自动将字符串型的变量转换成因子型,如果想保持原来的字符串型,就得

data.frame(
  a = 1:5,
  b = letters[1:5],
  stringsAsFactors = FALSE
)
##   a b
## 1 1 a
## 2 2 b
## 3 3 c
## 4 4 d
## 5 5 e

Note: - 在R 4.0 后,data.frame() 不会将字符串型变量自动转换成因子型

用tibble创建数据框,不会这么麻烦,输出的就是原来的字符串类型

tibble(
  a = 1:5,
  b = letters[1:5]
)
## # A tibble: 5 x 2
##       a b    
##   <int> <chr>
## 1     1 a    
## 2     2 b    
## 3     3 c    
## 4     4 d    
## 5     5 e

我们有时候喜欢这样,构建两个有关联的变量, 比如

tb <- tibble(
  x = 1:3,
  y = x + 2
)
tb
## # A tibble: 3 x 2
##       x     y
##   <int> <dbl>
## 1     1     3
## 2     2     4
## 3     3     5

但是,如果用传统的data.frame()来构建,会报错

df <- data.frame(
  x = 1:3,
  y = x + 2
)
## Error in data.frame(x = 1:3, y = x + 2): 找不到对象'x'
df
## function (x, df1, df2, ncp, log = FALSE) 
## {
##     if (missing(ncp)) 
##         .Call(C_df, x, df1, df2, log)
##     else .Call(C_dnf, x, df1, df2, ncp, log)
## }
## <bytecode: 0x0000000025fb7ba0>
## <environment: namespace:stats>

因此,在这一点上tibble()做的比较人性化。

大家还可以发现tibble另一个优势:tibble输出时,会显示多一行,用来指定每一列的类型。

tibble用缩写定义了7种类型:

类型 含义
int 代表integer
dbl 代表double
chr 代表character向量或字符串
dttm 代表日期+时间(date+time)
lgl 代表逻辑判断TRUE或者FALSE
fctr 代表因子类型factor
date 代表日期dates

12.4 tibble数据操作

12.4.1 创建tibble

tibble()创建一个tibble类型的data.frame:

tibble(a = 1:5, b = letters[1:5])
## # A tibble: 5 x 2
##       a b    
##   <int> <chr>
## 1     1 a    
## 2     2 b    
## 3     3 c    
## 4     4 d    
## 5     5 e

刚才提到了,可以这样,

tibble(
  a = 1:5,
  b = 10:14,
  c = a + b
)
## # A tibble: 5 x 3
##       a     b     c
##   <int> <int> <int>
## 1     1    10    11
## 2     2    11    13
## 3     3    12    15
## 4     4    13    17
## 5     5    14    19
  • 为了让每列更加直观,也可以tribble()创建,数据量不大的时候,挺方便的
tribble(
  ~x, ~y, ~z,
  "a", 2, 3.6,
  "b", 1, 8.5
)
## # A tibble: 2 x 3
##   x         y     z
##   <chr> <dbl> <dbl>
## 1 a         2   3.6
## 2 b         1   8.5

12.4.2 转换成tibble类型

转换成tibble类型意思就是说,刚开始不是tibble, 现在转换成tibble, 包括

  • data.frame转换成tibble
  • vector转换成tibble
  • list转换成tibble
  • matrix转换成tibble

12.4.2.1 data.frame转换成tibble

t1 <- iris[1:6, 1:4] # data.frame类型:
class(t1)
## [1] "data.frame"
as_tibble(t1)
## # A tibble: 6 x 4
##   Sepal.Length Sepal.Width Petal.Length Petal.Width
##          <dbl>       <dbl>        <dbl>       <dbl>
## 1          5.1         3.5          1.4         0.2
## 2          4.9         3            1.4         0.2
## 3          4.7         3.2          1.3         0.2
## 4          4.6         3.1          1.5         0.2
## 5          5           3.6          1.4         0.2
## 6          5.4         3.9          1.7         0.4

12.4.2.2 vector转型到tibble

x <- as_tibble(1:5) # Use `tibble::enframe()
x
## # A tibble: 5 x 1
##   value
##   <int>
## 1     1
## 2     2
## 3     3
## 4     4
## 5     5

12.4.2.3 把list转型为tibble

df <- as_tibble(list(x = 1:6, y = runif(6), z = 6:1))
df
## # A tibble: 6 x 3
##       x      y     z
##   <int>  <dbl> <int>
## 1     1 0.577      6
## 2     2 0.0136     5
## 3     3 0.183      4
## 4     4 0.249      3
## 5     5 0.349      2
## 6     6 0.680      1

把tibble再转为list? as.list(df)

12.4.2.4 把matrix转型为tibble。

m <- matrix(rnorm(15), ncol = 5)
as_tibble(m)
## # A tibble: 3 x 5
##       V1     V2      V3     V4     V5
##    <dbl>  <dbl>   <dbl>  <dbl>  <dbl>
## 1 -0.295 -0.715 -0.0532 -0.603 0.110 
## 2 -0.294 -1.01   0.102  -0.784 0.0897
## 3  0.407  0.769  0.0147 -2.80  0.587

tibble转回matrix? as.matrix(df)

12.4.3 tibble简单操作

构建一个简单的数据框

df <- tibble(
  x = 1:2,
  y = 2:1
)

df
## # A tibble: 2 x 2
##       x     y
##   <int> <int>
## 1     1     2
## 2     2     1

增加一列

add_column(df, z = 0:1, w = 0)
## # A tibble: 2 x 4
##       x     y     z     w
##   <int> <int> <int> <dbl>
## 1     1     2     0     0
## 2     2     1     1     0

增加一行

add_row(df, x = 99, y = 9)
## # A tibble: 3 x 2
##       x     y
##   <dbl> <dbl>
## 1     1     2
## 2     2     1
## 3    99     9

在第二行,增加一行

add_row(df, x = 99, y = 9, .before = 2)
## # A tibble: 3 x 2
##       x     y
##   <dbl> <dbl>
## 1     1     2
## 2    99     9
## 3     2     1

12.4.4 有用的函数lst

lst,创建一个list,具有tibble特性的list。

lst(n = 5, x = runif(n), y = TRUE)
## $n
## [1] 5
## 
## $x
## [1] 0.30755 0.87293 0.63662 0.54475 0.09818
## 
## $y
## [1] TRUE

12.4.5 有用的函数enframe

enframe()将矢量快速创建tibble,,创建的tibble只有2列: name和value

enframe(1:3)
## # A tibble: 3 x 2
##    name value
##   <int> <int>
## 1     1     1
## 2     2     2
## 3     3     3
enframe(c(a = 5, b = 7, c = 9))
## # A tibble: 3 x 2
##   name  value
##   <chr> <dbl>
## 1 a         5
## 2 b         7
## 3 c         9

12.4.6 有用的函数deframe

deframe()可以看做是enframe() 的反操作,把tibble反向转成向量

df <- enframe(c(a = 5, b = 7))
df
## # A tibble: 2 x 2
##   name  value
##   <chr> <dbl>
## 1 a         5
## 2 b         7
# 转为vector
deframe(df)
## a b 
## 5 7

12.4.7 读取文件

read_csv()读取文件时,生成的直接就是tibble

read_csv("./demo_data/wages.csv")
## # A tibble: 1,379 x 6
##      earn height sex    race        ed   age
##     <dbl>  <dbl> <chr>  <chr>    <dbl> <dbl>
##  1 79571.   73.9 male   white       16    49
##  2 96397.   66.2 female white       16    62
##  3 48711.   63.8 female white       16    33
##  4 80478.   63.2 female other       16    95
##  5 82089.   63.1 female white       17    43
##  6 15313.   64.5 female white       15    30
##  7 47104.   61.5 female white       12    53
##  8 50960.   73.3 male   white       17    50
##  9  3213.   72.2 male   hispanic    15    25
## 10 42997.   72.4 male   white       12    30
## # ... with 1,369 more rows

12.5 关于行名

data.frame是支持行名的,但tibble不支持行名,这也是两者不同的地方

# 创建data.frame
df <- data.frame(x = 1:3, y = 3:1)

# 给df增加行名
row.names(df) <- LETTERS[1:3]
df
##   x y
## A 1 3
## B 2 2
## C 3 1
# 判断是否有行名
has_rownames(df)
## [1] TRUE

但是对于tibble

tb <- tibble(x = 1:3, y = 3:1)

row.names(tb) <- LETTERS[1:3]

需要注意的:

  • 有时候遇到含有行名的data.frame,转换成tibble后,行名会被丢弃
  • 如果想保留行名,就需要把行名转换成单独的一列

举个例子

df <- mtcars[1:3, 1:3]
df
##                mpg cyl disp
## Mazda RX4     21.0   6  160
## Mazda RX4 Wag 21.0   6  160
## Datsun 710    22.8   4  108
# 把行名转换为单独的一列
rownames_to_column(df, var = "rowname")
##         rowname  mpg cyl disp
## 1     Mazda RX4 21.0   6  160
## 2 Mazda RX4 Wag 21.0   6  160
## 3    Datsun 710 22.8   4  108
# 把行索引转换为单独的一列
rowid_to_column(df, var = "rowid")
##   rowid  mpg cyl disp
## 1     1 21.0   6  160
## 2     2 21.0   6  160
## 3     3 22.8   4  108

12.6 修复列名

规范的来说,数据框的列名应该是唯一。但现实中代码是人写的,因此可能会稀奇古怪的,所幸的是tibble也提供了人性化的解决方案

tibble(x = 1, x = 2)
## Error: Column name `x` must not be duplicated.
  • .name_repair = "check_unique" 检查列名唯一性,但不做修复(默认)

  • .name_repair = "minimal", 不检查也不修复,维持现状

  • .name_repair = "unique" 修复列名,使得列名唯一且不为空

  • .name_repair = "universal" 修复列名,使得列名唯一且语法可读

具体使用方法:

tibble(x = 1, x = 2, .name_repair = "minimal")
## # A tibble: 1 x 2
##       x     x
##   <dbl> <dbl>
## 1     1     2
tibble(x = 1, x = 2, .name_repair = "unique")
## # A tibble: 1 x 2
##   x...1 x...2
##   <dbl> <dbl>
## 1     1     2
tibble(x = 1, x = 2, .name_repair = "universal")
## # A tibble: 1 x 2
##   x...1 x...2
##   <dbl> <dbl>
## 1     1     2
tibble(`a 1` = 1, `a 2` = 2, .name_repair = "universal")
## # A tibble: 1 x 2
##     a.1   a.2
##   <dbl> <dbl>
## 1     1     2

如果认为x...1, x...2 不符合自己的审美,可以指定修复函数

tibble(x = 1, x = 2, .name_repair = make.unique)
## # A tibble: 1 x 2
##       x   x.1
##   <dbl> <dbl>
## 1     1     2
tibble(x = 1, x = 2, .name_repair = ~ make.unique(.x, sep = "_"))
## # A tibble: 1 x 2
##       x   x_1
##   <dbl> <dbl>
## 1     1     2
tibble(x = 1, x = 2, .name_repair = ~ make.names(., unique = TRUE))
## # A tibble: 1 x 2
##       x   x.1
##   <dbl> <dbl>
## 1     1     2

注意make.unique(names, sep = ".")make.names(names, unique = FALSE, allow_ = TRUE) 是基础包的函数,可通过?make.unique()或者make.names()获取说明文档。

当然也可以自定义函数

fix_names <- function(x) gsub("\\s+", "_", x)

tibble(`year 1` = 1, `year 2` = 2, .name_repair = fix_names)
## # A tibble: 1 x 2
##   year_1 year_2
##    <dbl>  <dbl>
## 1      1      2
  • 感觉越说越复杂了,事实上,我们写数据框的时候,完全可以避免上述问题,只要做到规范列名。
  • 如果真正遇到比较乱的列名,推荐使用janitor::clean_names()一步到位。
library(janitor)
tibble(`year 1` = 1, `year 2` = 2) %>%
  clean_names()
## # A tibble: 1 x 2
##   year_1 year_2
##    <dbl>  <dbl>
## 1      1      2

12.7 nested tibble

nested tibbleList-columns (列表列) 会在后面的章节详细介绍。

iris %>%
  group_by(Species) %>%
  nest()
## # A tibble: 3 x 2
## # Groups:   Species [3]
##   Species    data             
##   <fct>      <list>           
## 1 setosa     <tibble [50 x 4]>
## 2 versicolor <tibble [50 x 4]>
## 3 virginica  <tibble [50 x 4]>

12.8 延伸阅读

1、阅读Hadley Wickham的r4ds这本书第10章

2、 tibble的官方主页:https://tibble.tidyverse.org/

3、创建列表列的方法,可以参考nested tibblelist-columns