Wenhu Next Generation Bioinformatician

02-R的主要数据结构

2018-03-13
Wenhu

本讲简要介绍R中常见的数据结构,重在梳理,不求详尽,部分内容改编自Hadley Wickham的《Advanced R》一书。欢迎转载,但请注明出处!

数据结构

当你在使用电脑时,各种五光十色的界面、操作、交互等等,究其本质来说,无非是一个你给电脑数据指令,电脑进行处理,再返回给你新数据的过程,而程序语言就类似你和电脑沟通的媒介,毕竟电脑不通人言啊!

说到各式各样的数据交互,通常量还蛮大,我们怎能忍心duang的一下,直接一股脑抛给电脑兄: “处理去吧!”,脑: “WTF?”,因为原本很多数据彼此之间预先存在联系、又或在接下来的处理过程中也是难解难分,所以为了方便电脑读取和处理数据,我们得用一些人为的方法将数据合理而高效的安排及存放在电脑中,这就是数据结构的由来及含义。数据结构不仅包含数据本身,还包括它们的内在关系,以及一系列操作处理数据的规范。

对于绝大多数高级编程语言,我们无需担心要自己弄数据结构,这些都必然是内置好的了。那么,在R中,我们主要使用的是哪些数据结构呢?

  元素种类相同 元素种类不同
一维 Atomic vector (原子向量) Recursive vector = List (递归向量 = 列表)
二维 Matrix (矩阵) Data frame (数据集)
多维 Array (数组)  

从上表我们可以看出,一维的两种向量结构向二维及多维拓展,进而形成了R的其他主要数据结构,向量之于R类似于基石之于广厦!

向量 (vector):R之魂

我们最初接触这个概念,大都是在中学学习数学的时候,对于既有大小又有方向的量,我们称之向量,在坐标系中它意味的是一个点相对于另一个点的距离和方位,形象的表示为一个箭头。在n维坐标系中,一个点距离原点的向量就表示为 (X1, X2, ... , Xn)

而在程序语言中,向量最初只是用来描述装了一串数字的载体,比如 (1, 2, 3, 4, 5)就是一个五维向量,装了1-5这五个数字,这和数学坐标系中的定义是一致的,而如今,其含义已经大为拓展,不再只是装数字,可以装anything!

当向量其中包含的元素类型为同一种基本类型 (逻辑,整型,浮点,字符等) 时,称其为原子向量;而类型不一致时,则称为递归向量,俗称列表 (List)。所谓原子,指的是向量中每个元素不可再分割成sub-vector,是最小实体;而list中的元素还可以是atomic vector或者list,甚至array和data frame,它们可以继续包含多个内部元素,一直循环下去,这种行为也就是递归的本义。

dbl.atom <- c(1, 2, 3) # 浮点型,R中多称为numeric type。
int.atom <- c(1L, 2L, 3L) # 只有加上大L,才会得到整型!
typeof(dbl.atom)
## [1] "double"
typeof(int.atom)
## [1] "integer"
alist <- list(1:9, c("I", "LOVE", "U"), c(TRUE, FALSE), c(1.2, 3.4, 5.6))
str(alist)
## List of 4
##  $ : int [1:9] 1 2 3 4 5 6 7 8 9
##  $ : chr [1:3] "I" "LOVE" "U"
##  $ : logi [1:2] TRUE FALSE
##  $ : num [1:3] 1.2 3.4 5.6

向量作为一个装载容器,它是动态的,即容量可变,可以随时插入或删除其中的元素;同时,它也是有序的,即其中的元素是有序号 (index)的。

R向量的三个特性

  • 函数向量化:这是R语言中的一大亮点,R虽然是一种混合式编程语言 (函数式+面向对象),但几乎所有的操作都是以函数的形式来完成 (包括操作符,也是函数之一,大家可以猜猜用a[i]取向量的第i个值,使用的是什么函数?)。绝大多数函数可以直接通过作用于向量,而等同于遍历作用了其中每个元素,这样可以极大提高代码可读性和可维护性 (其实是因为用R做循环整体性能较差,而用向量化这一方法既可以隐藏背后在用底层语言 (比如C) 实现着高效循环,又显得有13格)!
dbl.atom == int.atom 
## [1] TRUE TRUE TRUE
"=="(dbl.atom, int.atom) # 奇妙不?
## [1] TRUE TRUE TRUE
identical(dbl.atom, int.atom) # 黑人不?
## [1] FALSE

注意:有朋友可能会联想到著名的apply家族函数,它们可以让代码更简洁和紧凑,可其实背后执行的是R循环,所以效率方面并未有太大提升。然而大多数时候,简洁易懂的代码更有意义!详情请深入了解Tidyverse R包集合思想

  • 循环性:当函数操作多个向量时,长度较短的向量会循环自己的元素,直到和最长的向量同长。这也蕴含了R中的单数值变量 (标量) 本质上就是长度为1的原子向量。
c(1, 2) + c(3, 4, 5, 6, 7) # c(1, 2) -> c(1, 2, 1, 2, 1)
## Warning in c(1, 2) + c(3, 4, 5, 6, 7): longer object length is not a
## multiple of shorter object length
## [1] 4 6 6 8 8
1 + 1:9 # 1其实是c(1)
## [1]  2  3  4  5  6  7  8  9 10
  • 滤过性:我们可以通过设定条件,直接过滤向量,得到我们想要的子向量。
emotion <- c("LOVE", "HATE")
my.emo.2u <- emotion[emotion > "I"]
my.emo.2u
## [1] "LOVE"

因为其他主要数据结构也是来自于向量的拓展,所以上面三个特性基本普适!

矩阵和数组

没多少可讲的,概念比较简单,就是在原子向量的基础上加上了维度 (dim) 这个属性,有行,有列,有维,就在这晒,晒足180天

my.matrix <- matrix(1:9, nrow = 3)
my.matrix
##      [,1] [,2] [,3]
## [1,]    1    4    7
## [2,]    2    5    8
## [3,]    3    6    9
dim(my.matrix)
## [1] 3 3
my.array <- array(1:12, c(2, 3, 2)) # 后面的向量装着维度
my.array
## , , 1
## 
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
## 
## , , 2
## 
##      [,1] [,2] [,3]
## [1,]    7    9   11
## [2,]    8   10   12
dim(my.array)
## [1] 2 3 2

数据集

初看起来,数据集很像矩阵,一般都是有行和列两个维度,然而,数据集是异质性的,即其中的数值并不需要都是同一种类型,故而其本质上更像一个列表,每一列就是列表中的一个元素,唯独的一点限制,就是每个元素长度得相同

xiaochou <- data.frame(x = c("一", "杯", "敬", "明", "天"), y = c("一", "杯", "敬", "过", "往"))
xiaochou
##    x  y
## 1 一 一
## 2 杯 杯
## 3 敬 敬
## 4 明 过
## 5 天 往
str(xiaochou)
## 'data.frame':	5 obs. of  2 variables:
##  $ x: Factor w/ 5 levels "杯","敬","明",..: 5 1 2 3 4
##  $ y: Factor w/ 5 levels "杯","过","敬",..: 5 1 3 2 4

因子 (Factor)

最后,还要提一位重要的小朋友——因子,它从本质上来说是整数型 (integer) 原子向量,额外多了两个属性:类属性factor以及levels属性,后者用来定义向量中预先设定好的的值,换言之,因子是专门用来存放离散数据 (分类数据) 的,这在数据集的处理过程中有重要意义,以后会详解。

x <- factor(c(3, 4, 4, 3))
x
## [1] 3 4 4 3
## Levels: 3 4
class(x)
## [1] "factor"
levels(x)
## [1] "3" "4"
## 在后台x实际上是c(1L, 2L, 2L, 1L),即level1 = 3,level2 = 4
as.double(x)
## [1] 1 2 2 1

结语

一切皆对象,运行靠向量!

关注我的最新博文,请订阅my RSS ~~


Similar Posts

评论 / Comments