上节简单的介绍了 R 语言的三种特性,这节讨论编程技术的核心技术 数据结构 ,不要被吓到了。(这个逼装灰常好, 哈哈)。
一提到数据结构,第一印象多半是大学时代折磨人的课程,事实上 R 语言内置的数据结构是被底层语言封装过的,所以没有复杂的指针指向指的你发晕,也不会一不小心造成了内存泄漏,这就是使用高级语言的好处,但是也注定了调包侠的命运,知其然而不知所以然(悲哀!呜呜~)。
R 内置了五种最基本的数据结构:向量,列表,矩阵,数据框,多维数组,见下表:
数据结构名 | 创建函数 | 类型 | 特性 | 类比底层数据结构 |
---|---|---|---|---|
向量 | c | 一维 | 同类型数据, 最基本结构 | 动态数组 |
列表 | list | 一维 | S3对象基本结构, 灵活多变 | 多叉树 |
矩阵 | matrix | 二维 | 同数据类型, 运算快 | 二维动态数组 |
数据框 | data.frame | 二维 | 存储不同类型数据, 标准数据表 | 多叉树 |
多维数组 | array | 可多维 | 高维度 | 多维动态数组 |
这里我觉得列表和数据框应该是多叉树,也许是哈希,欢迎拍砖指正,这里特别说明一下列表和数据框,数据框应该属于列表的一个特例,列表的当中第一层元素就相当于数据框的列,用代码说话:
# 创建一个列表
l <- list(1,2,3)
# [[1]]
# [1] 1
#
# [[2]]
# [1] 2
#
# [[3]]
# [1] 3
# 创建数据框
df <- data.frame(1, 2, 3)
# X1 X2 X3
# 1 1 2 3
# 检查类型
typeof(l)
# [1] "list"
typeof(df)
# [1] "list"
检查类型是发现 list 与 data.frame 都是 list ,没错这就是真相,S3对象都是由list构造的,内置的data.frame是S3对象,因此它的本质就是一个list,只是给了它取了一个类名叫data.frame。不信?代码为证。
# 查看函数data.frame代码
data.frame
# .......
# return(structure(list(), names = character(), row.names # = row.names, class = "data.frame"))
这句函数返回语句充分说明了问题,构建了一个名为data.frame的对象,若不太清楚 R 对象 S3对象系统,可以阅读一下 R神 的 AdvanceR ,许多博客和参考资料都有很详细的讲解,我这里就不多废话了。
最后还是要强调一下明白这些有什么卵用,毕竟这才是使用者比较关心的,但是我说的是,你要做一辈子的调包侠,还是博古通今? (这个逼装的,我给自己满分)。
事实上在做数据处理工作时,基本上就是在对数据进行压缩,扩展,映射,转化,而完成这些工作的最基本就是在进行数据结构的变换,又到代码说话时刻:
# 以iris数据集为例, 这里借用一下R神的dplyr包来说明问题
library(dplyr)
iris %>% as.tbl()
# A tibble: 150 x 5
# Groups: Species [3]
# Sepal.Length Sepal.Width Petal.Length Petal.Width Species
# <dbl> <dbl> <dbl> <dbl> # <fctr>
# 1 5.1 3.5 1.4 0.2 setosa
# 2 4.9 3.0 1.4 0.2 setosa
# 3 4.7 3.2 1.3 0.2 setosa
# 4 4.6 3.1 1.5 0.2 setosa
# 5 5.0 3.6 1.4 0.2 setosa
# 6 5.4 3.9 1.7 0.4 setosa
# 7 4.6 3.4 1.4 0.3 setosa
# 8 5.0 3.4 1.5 0.2 setosa
# 9 4.4 2.9 1.4 0.2 setosa
# 10 4.9 3.1 1.5 0.1 setosa
# ... with 140 more rows
这里为了方便观察数据, 用dplyr包来说明问题,可以直观的观察到数据结构的变化(因为屏幕大小有限)。
# 分类计数
iris %>% as.tbl() %>% group_by(Species) %>% count()
# A tibble: 3 x 2
# Groups: Species [3]
# Species n
# <fctr> <int>
# 1 setosa 50
# 2 versicolor 50
# 3 virginica 50
不就是个分类汇总吗,有啥了不起的,我要说明的不是分类汇总这个操作,而是数据结构的变化,数据由原始的 150 × 5 被映射成了 3 × 2,数据类型也发生了变化,这里每个分类组的数据通过 count 函数被映射成单值,最终组成了一个新的数据框,但是这里数据结构没有发生变化。
iris %>% as.tbl() %>% class()
# [1] "tbl_df" "tbl" "data.frame"
iris %>% as.tbl() %>% typeof()
# [1] "list"
iris %>% as.tbl() %>% group_by(Species) %>% count() %>% typeof()
# [1] "list"
注意: as.tbl 函数操作只是给原生态的 data.frame 添加了新的类,扩充了一下它的功能,能更好的兼容使用dplyr包当中的函数,数据结构的本质上它还是一个 list
最后再举一个数据结构发生变化的例子:
iris %>% as.tbl() %>% length()
# [1] 5
list 通过 length 函数被映射成了一个向量, 这里我们理解函数就等价于某种算法, 算法作用在数据结构上,得到了新的数据结构,代码的工作就是在做这样的事情,因此掌握数据结构对编程技术来说显得非常重要,因为你的任何需求都基本包含在这个套路当中。