R语言数据科学新类型tibble

R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。

R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。

要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。

关于作者:

  • 张丹, 程序员R,Nodejs,Java
  • weibo:@Conan_Z
  • blog: http://blog.fens.me
  • email: bsspirit@gmail.com

转载请注明出处:
http://blog.fens.me/r-tibble/

前言

最近正在整理用R语言进行数据处理的操作方法,发现了 RStudio 公司开发的数据科学工具包tidyverse,一下子就把我吸引了。通过2天时间,我把tidyverse项目整体的学了一遍,给我的启发是非常大的。tidyverse 重新定义了数据科学的工作路径,而且路径上每个核心节点,都定义了对应的R包。这真是一项造福数据分析行业的工程,非常值得称赞!!

tidyverse个项目,包括了一系列的子项目,其中tibble被定义为取代传统data.frame的数据类型,完全有颠覆R的数据操作的可能。跟上R语言领袖的脚步,领先进入数据科学新的时代。

目录

  1. tibble介绍
  2. tibble安装
  3. tibble包的基本使用
  4. tibble的源代码分析

1. tibble介绍

tibble是R语言中一个用来替换data.frame类型的扩展的数据框,tibble继承了data.frame,是弱类型的,同时与data.frame有相同的语法,使用起来更方便。tibble包,也是由Hadley开发的R包。

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

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

tibble的项目主页:https://github.com/tidyverse/tibble

2. tibble安装

本文所使用的系统环境

  • Win10 64bit
  • R: 3.2.3 x86_64-w64-mingw32/x64 b4bit

tibble是在CRAN发布的标准库,安装起来非常简单,2条命令就可以了。


~ R
> install.packages('tibble')
> library(tibble)

RStudio官方把tibble项目,集成到了tidyverse项目中了,官方建议直接安装tidyverse项目,这样整个用来做数据科学的库都会被下载下来。


~ R
> install.packages('tidyverse')
> library(tidyverse)
#> Loading tidyverse: ggplot2
#> Loading tidyverse: tibble
#> Loading tidyverse: tidyr
#> Loading tidyverse: readr
#> Loading tidyverse: purrr
#> Loading tidyverse: dplyr
#> Conflicts with tidy packages ----------------------------------------------
#> filter(): dplyr, stats
#> lag():    dplyr, stats

tidyverse项目,是一个包括了数据科学的一个集合工具项目,用于数据提取,数据清理,数据类型定义,数据处理,数据建模,函数化编程,数据可视化,包括了下面的包。

  • ggplot2, 数据可视化
  • dplyr, 数据处理
  • tidyr, 数据清理
  • readr, 数据提取
  • purrr, 函数化编程
  • tibble, 数据类型定义

tidyverse项目的地址:https://github.com/tidyverse/tidyverse。高效的使用R语言做数据科学,请参考开源图书 R for Data Science.

3. tibble包的基本使用

对于tibble包的使用,主要需要掌握创建、数据转型、数据查看、数据操作、与data.frame的区别点。复杂的数据处理功能,是dplyr项目来完成,下一篇讲dplyr的文章再给大家介绍。

3.1 创建tibble

创建一个tibble类型的data.frame是非常简单的,语法与传统的data.frame是类似的。


# 创建一个tibble类型的data.frame
> t1<-tibble(1:10,b=LETTERS[1:10]);t1
# A tibble: 10 x 2
   `1:10`     b
    <int> <chr>
 1      1     A
 2      2     B
 3      3     C
 4      4     D
 5      5     E
 6      6     F
 7      7     G
 8      8     H
 9      9     I
10     10     J

# 创建一个data.frame
> d1<-data.frame(1:10,b=LETTERS[1:10]);d1
   X1.10 b
1      1 A
2      2 B
3      3 C
4      4 D
5      5 E
6      6 F
7      7 G
8      8 H
9      9 I
10    10 J

从上面的输出可以看到tibble类型,会在输出时多一行,用来指定每一列的类型。

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

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

查看类型,发现tbl_df继承了tbl继承是data.frame,所以tibble是data.frame的子类型。


# t1为tbl_df类型
> class(t1)
[1] "tbl_df"     "tbl"        "data.frame"

# 是data.frame类型
> class(d1)
[1] "data.frame"

让我们多角度来观察t1变量。


# 判断是不是tibble类型
> is.tibble(t1)
[1] TRUE

# 查看t1的属性
> attributes(t1)
$names
[1] "1:10" "b"   

$class
[1] "tbl_df"     "tbl"        "data.frame"

$row.names
 [1]  1  2  3  4  5  6  7  8  9 10

# 查看t1的静态结构
> str(t1)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':	10 obs. of  2 variables:
 $ 1:10: int  1 2 3 4 5 6 7 8 9 10
 $ b   : chr  "A" "B" "C" "D" ...

通过文本排列来创建一个tibble


> tribble(
+   ~colA, ~colB,
+   "a",   1,
+   "b",   2,
+   "c",   3
+ )
# A tibble: 3 x 2
   colA  colB
  <chr> <dbl>
1     a     1
2     b     2
3     c     3

通过vector创建tibble


> tibble(letters)
# A tibble: 26 x 1
   letters
     <chr>
 1       a
 2       b
 3       c
 4       d
 5       e
 6       f
 7       g
 8       h
 9       i
10       j
# ... with 16 more rows

通过data.frame创建tibble,这时就会报错了。


> tibble(data.frame(1:5))
Error: Column `data.frame(1:5)` must be a 1d atomic vector or a list

通过list创建tibble


> tibble(x = list(diag(1), diag(2)))
# A tibble: 2 x 1
              x
         <list>
1 <dbl [1 x 1]>
2 <dbl [2 x 2]>

我们看到tibble其实是存储list类型,这是data.frame做不到的。

通过一个tibble,创建另一个tibble,这时也会报错了。

> tibble(x = tibble(1, 2, 3))
Error: Column `x` must be a 1d atomic vector or a list

3.2 数据类型转换

tibble是一个新的类型,R语言中大部分的数据都是基于原有的数据类型,所以原有数据类型与tiblle类型的转换就显的非常重要了。

把一个data.frame的类型的转换为tibble类型


# 定义一个data.frame类型变量
> d1<-data.frame(1:5,b=LETTERS[1:5]);d1
  X1.5 b
1    1 A
2    2 B
3    3 C
4    4 D
5    5 E

# 把data.frame转型为tibble
> d2<-as.tibble(d1);d2
# A tibble: 5 x 2
   X1.5      b
  <int> <fctr>
1     1      A
2     2      B
3     3      C
4     4      D
5     5      E

# 再转回data.frame
> as.data.frame(d2)
  X1.5 b
1    1 A
2    2 B
3    3 C
4    4 D
5    5 E

我们可以看到tibble与data.frame的转型是非常平滑的,一个转型函数就够,不需要中间做任何的特殊处理。

把一个vector转型为tibble类型,但是不能再转回vector了。


# vector转型到tibble
> x<-as.tibble(1:5);x
# A tibble: 5 x 1
  value
  <int>
1     1
2     2
3     3
4     4
5     5

# tibble转型到vector, 不成功
> as.vector(x)
# A tibble: 5 x 1
  value
  <int>
1     1
2     2
3     3
4     4
5     5

把list转型为tibble。


# 把list转型为tibble
> df <- as.tibble(list(x = 1:500, y = runif(500), z = 500:1));df
# A tibble: 500 x 3
       x          y     z
   <int>      <dbl> <int>
 1     1 0.59141749   500
 2     2 0.61926125   499
 3     3 0.06879729   498
 4     4 0.69579561   497
 5     5 0.05087461   496
 6     6 0.63172517   495
 7     7 0.41808985   494
 8     8 0.78110219   493
 9     9 0.95279741   492
10    10 0.98930640   491
# ... with 490 more rows

# 把tibble再转为list
> str(as.list(df))
List of 3
 $ x: int [1:500] 1 2 3 4 5 6 7 8 9 10 ...
 $ y: num [1:500] 0.5914 0.6193 0.0688 0.6958 0.0509 ...
 $ z: int [1:500] 500 499 498 497 496 495 494 493 492 491 ...

tibble与list的转型也是非常平滑的,一个转型函数就够。

把matrix转型为tibble。


# 生成一个matrix
> m <- matrix(rnorm(15), ncol = 5)

# matrix转为tibble
> df <- as.tibble(m);df
# A tibble: 3 x 5
          V1         V2         V3         V4         V5
                               
1  0.8436494  2.1420238  0.2690392 -0.4752708 -0.2334994
2  1.0363340  0.8653771 -0.3200777 -1.7400856  1.2253651
3 -0.2170344 -1.1346455  0.2204718  1.2189431  0.7020156

# tibble转为matrix
> as.matrix(df)
             V1         V2         V3         V4         V5
[1,]  0.8436494  2.1420238  0.2690392 -0.4752708 -0.2334994
[2,]  1.0363340  0.8653771 -0.3200777 -1.7400856  1.2253651
[3,] -0.2170344 -1.1346455  0.2204718  1.2189431  0.7020156

从上面的转型测试可以看到,tibble类型是非常友好的,可以与data.frame, list, matrix 进行相互转型操作。tibble与vector是不能进行直接转型的,这与data.frame的行为是一致的,如果需要转型,我们可以分别取出每一列进行拼接,或转为matrix再操作。

3.3 tibble数据查询

通常我们是str()函数来观察数据的静态组成结果,在tibble包提供了一个glimpse(),可以方便我们来观察tibble和data.frame类型的数据。

比较glimpse()和str()对于data.frame的数据查看输出


> glimpse(mtcars)
Observations: 32
Variables: 11
$ mpg   21.0, 21.0, 22.8, 21.4, 18.7, 18.1, 14.3, 24.4, 22.8, 19.2, 17.8, 16.4, 17....
$ cyl   6, 6, 4, 6, 8, 6, 8, 4, 4, 6, 6, 8, 8, 8, 8, 8, 8, 4, 4, 4, 4, 8, 8, 8, 8, ...
$ disp  160.0, 160.0, 108.0, 258.0, 360.0, 225.0, 360.0, 146.7, 140.8, 167.6, 167.6...
$ hp    110, 110, 93, 110, 175, 105, 245, 62, 95, 123, 123, 180, 180, 180, 205, 215...
$ drat  3.90, 3.90, 3.85, 3.08, 3.15, 2.76, 3.21, 3.69, 3.92, 3.92, 3.92, 3.07, 3.0...
$ wt    2.620, 2.875, 2.320, 3.215, 3.440, 3.460, 3.570, 3.190, 3.150, 3.440, 3.440...
$ qsec  16.46, 17.02, 18.61, 19.44, 17.02, 20.22, 15.84, 20.00, 22.90, 18.30, 18.90...
$ vs    0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, ...
$ am    1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, ...
$ gear  4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, ...
$ carb  4, 4, 1, 1, 2, 1, 4, 2, 2, 4, 4, 3, 3, 3, 4, 4, 4, 1, 2, 1, 1, 2, 2, 4, 2, ...

# 打印静态结构
> str(mtcars)
'data.frame':	32 obs. of  11 variables:
 $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
 $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
 $ disp: num  160 160 108 258 360 ...
 $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
 $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
 $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
 $ qsec: num  16.5 17 18.6 19.4 17 ...
 $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
 $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
 $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
 $ carb: num  4 4 1 1 2 1 4 2 2 4 ...

比较glimpse()和str()对于tibble的数据查看输出。


# 新建tibble
> df <- tibble(x = rnorm(500), y = rep(LETTERS[1:25],20))

# 查看df
> glimpse(df)
Observations: 500
Variables: 2
$ x  -0.3295530, -2.0440424, 0.1444697, 0.8752439, 1.7705952, 0.5898253, 0.1991844,...
$ y  "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P"...

# 查看df静态结构
> str(df)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':	500 obs. of  2 variables:
 $ x: num  -0.33 -2.044 0.144 0.875 1.771 ...
 $ y: chr  "A" "B" "C" "D" ...

按列出数据,一层[]返回的结果还是tibbe,二层[]与$返回的结果为列组成的向量。


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

# 按列取,返回tibble
> df[1]
# A tibble: 3 x 1
      x
  <int>
1     1
2     2
3     3

# 按列取,返回向量
> df[[1]]
[1] 1 2 3
> df$x
[1] 1 2 3

按行取数据,这时一定要用,来做分隔符


# 取第一行
> df[1,]
# A tibble: 1 x 2
      x     y
  <int> <int>
1     1     3

# 取前2行
> df[1:2,]
# A tibble: 2 x 2
      x     y
  <int> <int>
1     1     3
2     2     2

# 取第二列的2,3行
> df[2:3,2]
# A tibble: 2 x 1
      y
  <int>
1     2
2     1

3.4 tibble数据操作

增加一列。


# 创建一个tibble
> df <- tibble(x = 1:3, y = 3:1);df
# A tibble: 3 x 2
      x     y
  <int> <int>
1     1     3
2     2     2
3     3     1

# 增加一列
> add_column(df, z = -1:1, w = 0)
# A tibble: 3 x 4
      x     y     z     w
  <int> <int> <int> <dbl>
1     1     3    -1     0
2     2     2     0     0
3     3     1     1     0

增加一行,还是基于上面生成的df变量。


# 在最后,增加一行
> add_row(df, x = 99, y = 9)
# A tibble: 4 x 2
      x     y
  <dbl> <dbl>
1     1     3
2     2     2
3     3     1
4    99     9

# 插入第二行,增加一行
> add_row(df, x = 99, y = 9, .before = 2)
# A tibble: 4 x 2
      x     y
  <dbl> <dbl>
1     1     3
2    99     9
3     2     2
4     3     1

3.5 tibble与data.frame的区别

列名,可以自由定义,并且会自动补全。


> tb <- tibble(
+   `:)` = "smile",
+   ` ` = "space",
+   `2000` = "number",
+   `列名` = "hi",
+   1,1L
+ )
> tb
# A tibble: 1 x 6
   `:)`   ` ` `2000`  列名   `1`  `1L`
  <chr> <chr>  <chr> <chr> <dbl> <int>
1 smile space number    hi     1     1

数据,按顺序执行懒加载。


> a <- 1:5
> tibble(a, b = a * 2)
# A tibble: 5 x 2
      a     b
  <int> <dbl>
1     1     2
2     2     4
3     3     6
4     4     8
5     5    10

打印输出控制,tibble的打印控制被重写了,所以执行print()函数时,模型会先进行类型匹配,然后调用print.tbl()。


# 创建tiblle
> tb<-tibble(a=1:5, b = a * 2, c=NA, d='a', e=letters[1:5])

# 打印前10行,不限宽度
> print(tb,n = 10, width = Inf)
# A tibble: 5 x 5
      a     b     c     d     e
  <int> <dbl> <lgl> <chr> <chr>
1     1     2    NA     a     a
2     2     4    NA     a     b
3     3     6    NA     a     c
4     4     8    NA     a     d
5     5    10    NA     a     e

# 打印前3行,宽度30
> print(tb,n = 3, width = 30)
# A tibble: 5 x 5
      a     b     c     d
  <int> <dbl> <lgl> <chr>
1     1     2    NA     a
2     2     4    NA     a
3     3     6    NA     a
# ... with 2 more rows, and 1
#   more variables: e 

# 用print函数,打印data.frame
> df<-data.frame(tb)
> print(df)
  a  b  c d e
1 1  2 NA a a
2 2  4 NA a b
3 3  6 NA a c
4 4  8 NA a d
5 5 10 NA a e

3.7 特殊的函数

lst,创建一个list,具有tibble特性的list。 lst函数的工作原理,类似于执行[list()],这样的操作。


# 创建一个list,懒加载,顺序执行
> lst(n = 5, x = runif(n))
$n
[1] 5
$x
[1] 0.6417069 0.2674489 0.5610810 0.1771051 0.1504583

enframe,快速创建tibble。enframe提供了一个模板,只有2列name和value,快速地把2个向量匹配的tibble中,可以按行生成或按列生成。


# 按列生成
> 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))
# A tibble: 2 x 2
   name value
  <chr> <dbl>
1     a     5
2     b     7

deframe,把tibble反向转成向量,这个函数就实现了,tibble到向量的转换。它默认把name列为索引,用value为值。


# 生成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 

3.8 用于处理data.frame函数

tibble还提供了一些用于处理data.frame的函数。


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

# 判断是否有叫x的列
> has_name(df,'x')
[1] TRUE

# 判断是否有行名
> has_rownames(df)
[1] FALSE

# 给df增加行名
> row.names(df)<-LETTERS[1:3];df
  x y
A 1 3
B 2 2
C 3 1

# 判断是否有行名
> has_rownames(df)
[1] TRUE

# 去掉行名
> remove_rownames(df)
  x y
1 1 3
2 2 2
3 3 1

# 把行名转换为单独的一列
> df2<-rownames_to_column(df, var = "rowname");df2
  rowname x y
1       A 1 3
2       B 2 2
3       C 3 1

# 把一列设置为行名
> column_to_rownames(df2, var = "rowname")
  x y
A 1 3
B 2 2
C 3 1

# 把行索引转换为单独的一列
> rowid_to_column(df, var = "rowid")
  rowid x y
1     1 1 3
2     2 2 2
3     3 3 1

这些data.frame的工具函数,我猜是用于data.frame到tibble的数据类型转换用的,因为tiblle是没有行名的。

4. tibble的源代码分析

对于tibble包的深入理解,我们需要分析tibble包底层的源代码,以及设计原理。我们打开github上是tibble项目,找到tibble.R的源代码,先来了解一下tibble类型的定义。

找到tibble函数的定义:


tibble <- function(...) {
  xs <- quos(..., .named = TRUE)
  as_tibble(lst_quos(xs, expand = TRUE))
}

tibble函数的构成是非常简单地,用quos()和lst_quos()函数来分割参数,再用as_tibble()函数,生成tibble类型。

我们再找到as_tibble函数的定义:


as_tibble <- function(x, ...) {
  UseMethod("as_tibble")
}

as_tibble.tbl_df <- function(x, ..., validate = FALSE) {
  if (validate) return(NextMethod())
  x
}

这个函数是一个S3类型的函数,可以S3面向对象类型的方法,来查找tibble相关的重写的函数。关于S3类型的详细介绍,请参与文章R语言基于S3的面向对象编程


> methods(generic.function=as_tibble)
[1] as_tibble.data.frame* as_tibble.default*    as_tibble.list*       as_tibble.matrix*    
[5] as_tibble.NULL*       as_tibble.poly*       as_tibble.table*      as_tibble.tbl_df*    
[9] as_tibble.ts*    

利用S3的查询函数,把整个tibble类型定义的泛型化函数都找到了。

接下来,我们继续到tbl_df的类型的定义


#' @importFrom methods setOldClass
setOldClass(c("tbl_df", "tbl", "data.frame"))

最后,这样就明确了tbl_df是类的定义,包括了属性和方法,而tibble是实例化的对象。通过对tibble函数的源代码分析,了解tibble本身的结构是怎么样的。那么再接下来,就是如何利用tibble来进行用于数据科学的数据处理过程。请继续阅读下一篇文章:R语言数据科学数据处理包dplyr。

转载请注明出处:
http://blog.fens.me/r-tibble/

打赏作者

This entry was posted in R语言实践

0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
zhehao

tibble转回vector,purrr包(包含在tidyverse里)里的as_vector可以实现,tibble会被转换成named vector

Dan Zhang

强,我还没有细看purr包,找时间研究一下。

2
0
Would love your thoughts, please comment.x
()
x