2.2 Comparing tibble and data.frame

tibble 和传统 data.frame 的机理主要有三处不同:创建、打印和取子集。

2.2.1 Creating

tibble() 创建一个 tibble

可以发现 tibble() 不会默认更改 y 类型,它将原原本本地被当做一个字符向量处理。

data.frame() 用于创建一个传统数据框:

2.2.1.1 Coersion

data.frame 中,为了防止 y 被强制转换为因子,必须设置 stringAsFactors = FALSE

By the way, 创建 tibble 的另一种方法是使用tribble(),transposed tibble 的缩写。tribble() 是高度定制化的,就像在 Markdown 中创建表格一样,一个一个填入元素:列标题以波浪线 ~ 开头,不同列的元素之间以逗号分隔,这样就可以用易读的方式对少量数据创建一个 tibble

2.2.1.2 Row labels

data.frame 支持提供一个不包含重复元素的字符向量作为行标签:

row.names参数为data.frame创建了一个同名的属性,rownames()函数可以提取这个属性:

如果我们认为数据框是二维矩阵的自然拓展(不止包含数值),那么行标签的存在似乎是很自然的,毕竟矩阵有ij两个索引。但是矩阵和数据框有本质区别,矩阵是添加了维度属性的原子向量,数据框则是列表,我们可以对矩阵取转置,因为它们的行和列可以互换,一个矩阵的转置是另一个矩阵。数据框则是不可转置的,它的行列互换之后就不再是数据框(一行未必能构成一个原子向量)。

出于以下三个原因,我们不应该使用row.names这一属性,也不应该考虑在任何场合添加行标签:

  1. 元数据也是数据,所以把行标签从其他变量中抽出来单独对待不是个好主意。否则我们可能要对行标签单独发展出一套操作工具,而不能直接应用我们已经习惯的对变量的操作方法
  2. 行标签很多时候不能完成唯一标识观测的任务,因为 row.names 要求只能传入数值或者字符串向量。如果我们想要用时间日期型数据标识观测呢?或者需要传入不止一个向量(例如标识位置同时需要经度和纬度)
  3. 行标签中的元素必须是唯一的,但很多场合(比如bootstrapping)中同一个对象也可能有多条记录

所以,tibble 的设计思想就是不支持添加行标签,且提供了一套很方便的、处理已有行标签的工具,要么移除它,要么把它直接变成tibble中的一列:

Tools for working with row names
Description
While a tibble can have row names (e.g., when converting from a regular data frame), they are removed when subsetting with the [ operator. A warning will be raised when attempting to assign non-NULL row names to a tibble. Generally, it is best to avoid row names, because they are basically a character column with different semantics to every other column. These functions allow to you detect if a data frame has row names (has_rownames()), remove them (remove_rownames()), or convert them back-and-forth between an explicit column (rownames_to_column() and column_to_rownames()). Also included is rowid_to_column() which adds a column at the start of the dataframe of ascending sequential row ids starting at 1. Note that this will remove any existing row names.

Usage
has_rownames(.data)

remove_rownames(.data)

rownames_to_column(.data, var = "rowname")

rowid_to_column(.data, var = "rowid")

column_to_rownames(.data, var = "rowname")

Arguments
.data
A data frame.

var Name of column to use for rownames.

Value
column_to_rownames() always returns a data frame. has_rownames() returns a scalar logical. All other functions return an object of the same class as the input.

一些示例:

mtcars %>% 
  has_rownames()
#> [1] TRUE

mtcars %>%
  remove_rownames()
#>     mpg cyl  disp  hp drat   wt qsec vs am gear carb
#> 1  21.0   6 160.0 110 3.90 2.62 16.5  0  1    4    4
#> 2  21.0   6 160.0 110 3.90 2.88 17.0  0  1    4    4
#> 3  22.8   4 108.0  93 3.85 2.32 18.6  1  1    4    1
#> 4  21.4   6 258.0 110 3.08 3.21 19.4  1  0    3    1
#> 5  18.7   8 360.0 175 3.15 3.44 17.0  0  0    3    2
#> 6  18.1   6 225.0 105 2.76 3.46 20.2  1  0    3    1
#> 7  14.3   8 360.0 245 3.21 3.57 15.8  0  0    3    4
#> 8  24.4   4 146.7  62 3.69 3.19 20.0  1  0    4    2
#> 9  22.8   4 140.8  95 3.92 3.15 22.9  1  0    4    2
#> 10 19.2   6 167.6 123 3.92 3.44 18.3  1  0    4    4
#> 11 17.8   6 167.6 123 3.92 3.44 18.9  1  0    4    4
#> 12 16.4   8 275.8 180 3.07 4.07 17.4  0  0    3    3
#> 13 17.3   8 275.8 180 3.07 3.73 17.6  0  0    3    3
#> 14 15.2   8 275.8 180 3.07 3.78 18.0  0  0    3    3
#> 15 10.4   8 472.0 205 2.93 5.25 18.0  0  0    3    4
#> 16 10.4   8 460.0 215 3.00 5.42 17.8  0  0    3    4
#> 17 14.7   8 440.0 230 3.23 5.34 17.4  0  0    3    4
#> 18 32.4   4  78.7  66 4.08 2.20 19.5  1  1    4    1
#> 19 30.4   4  75.7  52 4.93 1.61 18.5  1  1    4    2
#> 20 33.9   4  71.1  65 4.22 1.83 19.9  1  1    4    1
#> 21 21.5   4 120.1  97 3.70 2.46 20.0  1  0    3    1
#> 22 15.5   8 318.0 150 2.76 3.52 16.9  0  0    3    2
#> 23 15.2   8 304.0 150 3.15 3.44 17.3  0  0    3    2
#> 24 13.3   8 350.0 245 3.73 3.84 15.4  0  0    3    4
#> 25 19.2   8 400.0 175 3.08 3.85 17.1  0  0    3    2
#> 26 27.3   4  79.0  66 4.08 1.94 18.9  1  1    4    1
#> 27 26.0   4 120.3  91 4.43 2.14 16.7  0  1    5    2
#> 28 30.4   4  95.1 113 3.77 1.51 16.9  1  1    5    2
#> 29 15.8   8 351.0 264 4.22 3.17 14.5  0  1    5    4
#> 30 19.7   6 145.0 175 3.62 2.77 15.5  0  1    5    6
#> 31 15.0   8 301.0 335 3.54 3.57 14.6  0  1    5    8
#> 32 21.4   4 121.0 109 4.11 2.78 18.6  1  1    4    2


mtcars %>% 
  rownames_to_column(var = "car_type")
#>               car_type  mpg cyl  disp  hp drat   wt qsec vs am gear carb
#> 1            Mazda RX4 21.0   6 160.0 110 3.90 2.62 16.5  0  1    4    4
#> 2        Mazda RX4 Wag 21.0   6 160.0 110 3.90 2.88 17.0  0  1    4    4
#> 3           Datsun 710 22.8   4 108.0  93 3.85 2.32 18.6  1  1    4    1
#> 4       Hornet 4 Drive 21.4   6 258.0 110 3.08 3.21 19.4  1  0    3    1
#> 5    Hornet Sportabout 18.7   8 360.0 175 3.15 3.44 17.0  0  0    3    2
#> 6              Valiant 18.1   6 225.0 105 2.76 3.46 20.2  1  0    3    1
#> 7           Duster 360 14.3   8 360.0 245 3.21 3.57 15.8  0  0    3    4
#> 8            Merc 240D 24.4   4 146.7  62 3.69 3.19 20.0  1  0    4    2
#> 9             Merc 230 22.8   4 140.8  95 3.92 3.15 22.9  1  0    4    2
#> 10            Merc 280 19.2   6 167.6 123 3.92 3.44 18.3  1  0    4    4
#> 11           Merc 280C 17.8   6 167.6 123 3.92 3.44 18.9  1  0    4    4
#> 12          Merc 450SE 16.4   8 275.8 180 3.07 4.07 17.4  0  0    3    3
#> 13          Merc 450SL 17.3   8 275.8 180 3.07 3.73 17.6  0  0    3    3
#> 14         Merc 450SLC 15.2   8 275.8 180 3.07 3.78 18.0  0  0    3    3
#> 15  Cadillac Fleetwood 10.4   8 472.0 205 2.93 5.25 18.0  0  0    3    4
#> 16 Lincoln Continental 10.4   8 460.0 215 3.00 5.42 17.8  0  0    3    4
#> 17   Chrysler Imperial 14.7   8 440.0 230 3.23 5.34 17.4  0  0    3    4
#> 18            Fiat 128 32.4   4  78.7  66 4.08 2.20 19.5  1  1    4    1
#> 19         Honda Civic 30.4   4  75.7  52 4.93 1.61 18.5  1  1    4    2
#> 20      Toyota Corolla 33.9   4  71.1  65 4.22 1.83 19.9  1  1    4    1
#> 21       Toyota Corona 21.5   4 120.1  97 3.70 2.46 20.0  1  0    3    1
#> 22    Dodge Challenger 15.5   8 318.0 150 2.76 3.52 16.9  0  0    3    2
#> 23         AMC Javelin 15.2   8 304.0 150 3.15 3.44 17.3  0  0    3    2
#> 24          Camaro Z28 13.3   8 350.0 245 3.73 3.84 15.4  0  0    3    4
#> 25    Pontiac Firebird 19.2   8 400.0 175 3.08 3.85 17.1  0  0    3    2
#> 26           Fiat X1-9 27.3   4  79.0  66 4.08 1.94 18.9  1  1    4    1
#> 27       Porsche 914-2 26.0   4 120.3  91 4.43 2.14 16.7  0  1    5    2
#> 28        Lotus Europa 30.4   4  95.1 113 3.77 1.51 16.9  1  1    5    2
#> 29      Ford Pantera L 15.8   8 351.0 264 4.22 3.17 14.5  0  1    5    4
#> 30        Ferrari Dino 19.7   6 145.0 175 3.62 2.77 15.5  0  1    5    6
#> 31       Maserati Bora 15.0   8 301.0 335 3.54 3.57 14.6  0  1    5    8
#> 32          Volvo 142E 21.4   4 121.0 109 4.11 2.78 18.6  1  1    4    2

mtcars %>% 
  rowid_to_column()
#>    rowid  mpg cyl  disp  hp drat   wt qsec vs am gear carb
#> 1      1 21.0   6 160.0 110 3.90 2.62 16.5  0  1    4    4
#> 2      2 21.0   6 160.0 110 3.90 2.88 17.0  0  1    4    4
#> 3      3 22.8   4 108.0  93 3.85 2.32 18.6  1  1    4    1
#> 4      4 21.4   6 258.0 110 3.08 3.21 19.4  1  0    3    1
#> 5      5 18.7   8 360.0 175 3.15 3.44 17.0  0  0    3    2
#> 6      6 18.1   6 225.0 105 2.76 3.46 20.2  1  0    3    1
#> 7      7 14.3   8 360.0 245 3.21 3.57 15.8  0  0    3    4
#> 8      8 24.4   4 146.7  62 3.69 3.19 20.0  1  0    4    2
#> 9      9 22.8   4 140.8  95 3.92 3.15 22.9  1  0    4    2
#> 10    10 19.2   6 167.6 123 3.92 3.44 18.3  1  0    4    4
#> 11    11 17.8   6 167.6 123 3.92 3.44 18.9  1  0    4    4
#> 12    12 16.4   8 275.8 180 3.07 4.07 17.4  0  0    3    3
#> 13    13 17.3   8 275.8 180 3.07 3.73 17.6  0  0    3    3
#> 14    14 15.2   8 275.8 180 3.07 3.78 18.0  0  0    3    3
#> 15    15 10.4   8 472.0 205 2.93 5.25 18.0  0  0    3    4
#> 16    16 10.4   8 460.0 215 3.00 5.42 17.8  0  0    3    4
#> 17    17 14.7   8 440.0 230 3.23 5.34 17.4  0  0    3    4
#> 18    18 32.4   4  78.7  66 4.08 2.20 19.5  1  1    4    1
#> 19    19 30.4   4  75.7  52 4.93 1.61 18.5  1  1    4    2
#> 20    20 33.9   4  71.1  65 4.22 1.83 19.9  1  1    4    1
#> 21    21 21.5   4 120.1  97 3.70 2.46 20.0  1  0    3    1
#> 22    22 15.5   8 318.0 150 2.76 3.52 16.9  0  0    3    2
#> 23    23 15.2   8 304.0 150 3.15 3.44 17.3  0  0    3    2
#> 24    24 13.3   8 350.0 245 3.73 3.84 15.4  0  0    3    4
#> 25    25 19.2   8 400.0 175 3.08 3.85 17.1  0  0    3    2
#> 26    26 27.3   4  79.0  66 4.08 1.94 18.9  1  1    4    1
#> 27    27 26.0   4 120.3  91 4.43 2.14 16.7  0  1    5    2
#> 28    28 30.4   4  95.1 113 3.77 1.51 16.9  1  1    5    2
#> 29    29 15.8   8 351.0 264 4.22 3.17 14.5  0  1    5    4
#> 30    30 19.7   6 145.0 175 3.62 2.77 15.5  0  1    5    6
#> 31    31 15.0   8 301.0 335 3.54 3.57 14.6  0  1    5    8
#> 32    32 21.4   4 121.0 109 4.11 2.78 18.6  1  1    4    2

2.2.1.4 Invalid column names

tibble的一个很大特色是可以使用在 R 中无效的变量名称,即不符合变量命名规定的名称可以在 tibble 中成为列名,实际上这个规则约束了 R 中所有“名称”的设定。R 规定名称只能包含字母、数字、点.和下划线_,必须以字母开头,数字不能跟在.之后,也不能和R中保留的关键字重名(see ?Reserved)。

如果想创建不合法的列名,可以用反引号```将它们括起来:

类似地,如果想要在 ggolot2 或者其他tidyverse包中使用这些名称特殊的变量,也需要用反引号括起来。

相比之下,data.frame()会依据make.names()的规则自行更改无效的列名称(除非设置check.names = FALSE)。如果你的名称不以字母开头(字母的界定依据当前电脑的地域设置,但不能超越ASCII字符集),这个函数会地添加X作为前缀;如果包含特殊字符,用.代替;未给出的名称用NA代替;与R的保留关键字重名的,在后面添加.:

2.2.1.5 Referencing a column

最后,我们可以在创建 tibble 创建过程中就引用其中的变量,因为变量在 tibble 中是被从左到右依次添加的(而 data.frame() 不支持这一点):

2.2.2 Printing

tibble 的打印方法进行了优化,只显示前 10 行结果,显示列的数目将自动适应屏幕的宽度,这种打印方式非常适合大数据集。除了打印列名,tibble 还会第一行的下面打印出列的类型,这项功能有些类似于 str() 函数

在打印大数据框时,tibble的这种设计避免了输出一下子占据控制台的很多行。

有时需要比默认显示更多的输出,这是要设置几个参数。
首先,可以明确使用print()函数来打印数据框(实际上是print.tbl()),并控制打印的行数(n)和显示的宽度(width)。width = Inf可以显示出所有列:

2.2.3 Subsetting

取子集(Subsetting)时的行为又是区分 data.frametibble 很重要的一个特性。简单来讲,R中有两种取子集的系统。一种是用[在原子向量、列表、矩阵、数组和数据框中提取任意数量的元素,一种是用 [[ 或者 $ 在以上对象中提取单个元素

传统的数据框 data.frame 在这两种方式上均有缺陷:

  • 当想用df[, vars]data.frame 中提取列时,如果vars包含多个变量,则返回一个数据框;如果vars只包含一个变量,则返回一个向量(因为[不要求必须提取多于一个元素)。这种不一致性有时这会导致很多bug,因为很多函数不能作用于向量。
  • 当想用df$x提取出变量x时,如果x不在data.frame中,data.frame会返回一个名字以x开始的变量(这种行为被称为部分匹配(partial matching)),如果不存在这样的变量,则返回NULL。这使得我们很容易选取到错误的变量

tibble 在这两点缺陷上做了改进。首先,当 df[, vars]作用于tibble 时,无论 vars 包含多少个变量,返回值总是一个tibble:

其次,使用$或者[[]]时,tibble不会进行部分匹配,如果该变量不存在,直接报错:

另外,[[可以按名称和位置提取变量,$只能按名称提取变量,但可以减少一些输入:

如果想在管道中使用这些取子集操作,需要使用特殊的占位符 .