R语言的数据结构(包含向量和向量化详细解释)

更多内容请参考《R语言编程艺术》
———————————————

向量类型是R语言的核心。深入理解向量对R中数据结构及其操作,函数的开发和应用有着重要意义。

1 几个概念:向量,向量化,标量,元素,组件,标签,原子向量,递归向量

以下叙述参考书籍加自己理解,有叙述不妥的留言

向量vector和标量

个人理解,向量是有方向的,由大于等于2个元素构成的数据类型。也就是说,向量的所有元素必须属于同种模式(mode),或数据类型(见1.2),比如数值型,字符型等。其类型可以用typeof()查看。
标量只含有一个元素,在R中没有0维度或标量类型。单独的数字或字符串本质是一元向量。

> x <- c(3,23,5)
> x
[1]  3 23  5
> length(x)
[1] 3

上面x是三元向量,并且赋值给了x。[1]表示这行得第一项是输出结果的第一项。
x由3个元素组成,分别是3,23,5
长度就是其包含的元素的个数。注意区别后面的列表的长度。

向量有哪些基本类型

两大类,原子向量和列表(又叫递归向量)

原子向量有6种类型:逻辑型,整型,双精度型,字符型,复数型和原始型。整型和双精度型统称为数值型向量。
为什么叫原子型(atomic):向量的元素已经是最小的,不可再分的。
列表型,又叫递归型,因为是列表中可以继续包括列表。列表中的“元素”就是列表的各组件,其名称叫标签(tag)。

2向量的循环补齐

两个向量使用运算符,如果两个向量长度不同,R会自动循环补齐(recycle),也就是它会自动重复较短的向量,直到与另外一个向量匹配。举例如下

> c(1,2,3)+c(4,5,6,7,8,9)
[1]  5  7  9  8 10 12
> c(1,2,3,1,2,3)+c(4,5,6,7,8,9)
[1]  5  7  9  8 10 12
> 1+1:8
[1] 2 3 4 5 6 7 8 9
> c(1,1,1,1,1,1,1,1)+c(1,2,3,4,5,6,7,8)
[1] 2 3 4 5 6 7 8 9

有没有感觉像生物学中的复制,只是模版决定了待合成的链的长度,并不决定其组成序列,影响其组成的是自身。但是当要进行两者运算的时候,必须一一匹配,就像碱基互补配对,不能错配。

继续看下面这个例子

> x <- matrix(1:6,nrow = 3)
> x
     [,1] [,2]
[1,]    1    4
[2,]    2    5
[3,]    3    6
> length(x)
[1] 6
> x[5]
[1] 5

x是矩阵。有6个元素。x[5]是第五个元素,值是5,明显看出,矩阵就是向量,按列填充(可以更改填充方向)。

> x+100
     [,1] [,2]
[1,]  101  104
[2,]  102  105
[3,]  103  106

100被重复6次(矩阵的长度)。相当于纵向拉长,但最终仍然生成矩阵。

> x+c(100,200,300,400,500,600)
     [,1] [,2]
[1,]  101  404
[2,]  202  505
[3,]  303  606

上面这个更清晰看出按列进行填充。

3向量化及向量化函数

3.1向量输入,向量或矩阵输出

向量输入,向量输出

向量化就是对向量的每一个元素应用函数,如果一个函数使用了向量化的运算符,那么它也被向量化了,代码运行速度会提升。
上面的+,还有*,/等都是向量化运算符。再举一个>

> c(5,2,4)<c(2,8,0)
[1] FALSE  TRUE FALSE
> c(5,2,8)>7
[1] FALSE FALSE  TRUE

返回的都是逻辑型向量。记得原则是短的自动循环补充,然后一一配对,返回一一配对的向量化结果(也可能直接输出矩阵结果)。

向量输入,矩阵输出sapply函数

举例:

> z12 <- function(x) return(c(x,x^2))
> z12(4)
[1]  4 16
> z12(1:8)
 [1]  1  2  3  4  5  6  7  8  1  4  9 16 25 36 49 64

输出结果都是向量化的。但看起来并不是我们想要的呈现方式。所以可以转变为矩阵

> matrix(z12(1:8),ncol = 2)
     [,1] [,2]
[1,]    1    1
[2,]    2    4
[3,]    3    9
[4,]    4   16
[5,]    5   25
[6,]    6   36
[7,]    7   49
[8,]    8   64

除了上面,如果函数本身的返回值就是向量,可用sapply函数进行简化,调用sapply(x,f)可对x的每一个元素使用函数f(),并将结果转化为矩阵。注意

> sapply(1:8, z12)
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
[1,]    1    2    3    4    5    6    7    8
[2,]    1    4    9   16   25   36   49   64

直接输出8*2矩阵。
注意sapply是simplify apply的缩写,简化结果,但不是简单。它也可以用于列表操作,使得结果输出不再是列表,而是向量。类似本处结果的逆操作。最终目的是让结果看起来更自然更简洁。

3.2向量筛选

筛选filtering就是提取向量中符合一定条件的元素。

3.2.1生成筛选索引

目的,筛选x中平方值大于8的元素(不是元素位置,是元素本身)

x <- c(5,2,-3,8)
> x <- c(5,2,-3,8)
> x[x*x>8]
[1]  5 -3  8

向量化操作。x是4元向量,x*x也是4元向量,>是向量运算符,所以8实际是进行了循环补齐,实际是进行了如下比较

x*x>c(8,8,8,8)

返回值是布尔值向量

[1]  TRUE FALSE  TRUE  TRUE

所以实际是用布尔值向量筛选x中的符合条件的元素,也就是执行的是

x[c(TRUE,FALSE,TRUE,TRUE)]

运用上述方式可以筛选另一个向量,也可以筛选自身。
再看下面这个例子

> x <- c(5,2,-3,8)
> x[x>3] <- 100
[1] 100   2  -3 100
> x[x>3] <- 'up'
> x
[1] "up" "2"  "-3" "up"

第一个是x中大于3的元素赋值为100,结果仍然是数值型向量
第二个是x中大于3的赋值为‘up’,结果全部变为字符型向量

3.2.2使用subset(好处是自动去除NA值)

> subset(x,x*x>10)
[1] 5 8

3.2.3which:返回元素所处位置

> x <- c(5,2,-3,8)
> which(x*x>10)
[1] 1 4

返回的满足条件的元素所在的位置。这个对找出满足条件的元素首次出现的未知很重要,结合break
另外match,%in%也可以进行筛选。但这两个不是向量化函数。???

3.3向量化的ifelse函数

ifelse(b,u,v)
b是布尔值向量,u和v是向量。返回向量。

> x <- c(5,2,-4,3,0)
> ifelse(x%%2==0,5,12)
[1] 12  5  5 12  5
> ifelse(x>=3,'up', ifelse(x<=-3,'down','no'))
[1] "up"   "no"   "down" "up"   "no"  

可见,返回值都是向量。其中进行的是x中的每一个元素一次进行ifelse中的逻辑判断,返回相应的值,自动进行了循环补齐。所以ifelse是向量化的。

4 常见数据结构和向量的关系及常见操作

4.1矩阵

前已述及,矩阵也是向量,特殊的向量,包含量阿哥附加的属性:行和列。所以,矩阵也有模式,例如数值型或字符型。但向量不能看做有一列或一行的矩阵。

我对矩阵的比喻是‘神龙摆尾’。从左上角开始到右下角结束,有向无环。

对矩阵可以进行各种线性代数运算,矩阵索引,矩阵筛选

矩阵因为是特殊的向量所以可以用向量的方式索引(意义不大)或根据行列进行索引。

> z <- matrix(1:24,nrow = 6)
> z
     [,1] [,2] [,3] [,4]
[1,]    1    7   13   19
[2,]    2    8   14   20
[3,]    3    9   15   21
[4,]    4   10   16   22
[5,]    5   11   17   23
[6,]    6   12   18   24
> z[15]
[1] 15
> which(z>17)
[1] 18 19 20 21 22 23 24
> z[2,]
[1]  2  8 14 20
> z[,-c(3:4)]
     [,1] [,2]
[1,]    1    7
[2,]    2    8
[3,]    3    9
[4,]    4   10
[5,]    5   11
[6,]    6   12
> z[2,2]
[1] 8
> z[,2:4]
     [,1] [,2] [,3]
[1,]    7   13   19
[2,]    8   14   20
[3,]    9   15   21
[4,]   10   16   22
[5,]   11   17   23
[6,]   12   18   24
> z[,c(TRUE,FALSE,FALSE,TRUE)]
     [,1] [,2]
[1,]    1   19
[2,]    2   20
[3,]    3   21
[4,]    4   22
[5,]    5   23
[6,]    6   24

上述最后一个很重要,很多运算基于此。比如我们想找出第二列大于10的行。
注意上述返回的结果,有的是向量有的是矩阵。

> z[z[,2]>10,]
     [,1] [,2] [,3] [,4]
[1,]    5   11   17   23
[2,]    6   12   18   24

实际进行的是:
z中第二列的每一个元素与10进行比较,所以z[,2]是向量,而10需要自动补齐,实际运行的是

> z[,2]>10
[1] FALSE FALSE FALSE FALSE  TRUE  TRUE

这就把返回值为TRUE的行提取出来了。

4.2对矩阵的行和列调用函数

apply函数(在矩阵的各行和格列上调用制定的函数)

apply(m,dimcode,f,fargs)

m为矩阵
dimcode为维度编号,1代表对每一行应用函数,2代表对列应用函数
f是应用在行或列上的函数(内部函数,自定义函数都可以)
fargs是f的可选参数集

4.3 增加或删除矩阵的行或列

矩阵一旦产生,其行列固定,但可以对其重新赋值。
类似操作可以改变矩阵大小,比如rbind``cbind

> cbind(100,z)
     [,1] [,2] [,3] [,4] [,5]
[1,]  100    1    7   13   19
[2,]  100    2    8   14   20
[3,]  100    3    9   15   21
[4,]  100    4   10   16   22
[5,]  100    5   11   17   23
[6,]  100    6   12   18   24
> rbind(100,z)
     [,1] [,2] [,3] [,4]
[1,]  100  100  100  100
[2,]    1    7   13   19
[3,]    2    8   14   20
[4,]    3    9   15   21
[5,]    4   10   16   22
[6,]    5   11   17   23
[7,]    6   12   18   24

可见,进行了自动补齐。

5 列表和数据框(都不是向量)

5.1 列表

列表创建及基本结构

向量的元素要求同种类型,而列表list与向量不同,可以组合多个不同类型的对象。所以列表不是向量。但从技术上来说,列表就是向量,属于递归型向量(recursive vector)。

看例子

> j <- list(name="Joe",salary=55000,union=T)
> j
$name
[1] "Joe"

$salary
[1] 55000

$union
[1] TRUE

> length(j)
[1] 3
> str(j)
List of 3
 $ name  : chr "Joe"
 $ salary: num 55000
 $ union : logi TRUE

上面这个list有3个组件(又叫列表的元素。其中的joe是元素的内容)其标签(tag)分别是name,salary,union。并且三个变量的类型不一样,分别是字符型,数字型,逻辑值。
注意,列表的长度是3,是组件(元素)的个数
这个地方要理解,因为,这对lapply的应用很重要。

列表索引

三种方式访问列表lst中的组件c,返回值是c的数据类型
lst$c
lst[["c"]]
lst[[i]]

> j$salary
[1] 55000
> j$sa
[1] 55000
> j[[2]]
[1] 55000
> j[["salary"]]
[1] 55000

列表上应用apply系列函数lapply``sapply

lapply=list apply,对每个组件执行给定的函数,并返回另一个列表

> lapply(list(1:3,1:9), median)
[[1]]
[1] 2

[[2]]
[1] 5

> sapply(list(1:3,1:9), median)
[1] 2 5

可见,sapply输出的是向量。还记得上面3.1部分吗
如果函数本身的返回值就不是标量,而是向量。则sapply可以自动把向量形式的结果转化为矩阵输出
如下

> sapply(1:8,function(x) return(c(x^2, sqrt(x))))
     [,1]     [,2]     [,3] [,4]      [,5]     [,6]      [,7]      [,8]
[1,]    1 4.000000 9.000000   16 25.000000 36.00000 49.000000 64.000000
[2,]    1 1.414214 1.732051    2  2.236068  2.44949  2.645751  2.828427

5.2数据框

直观上看,数据框更类似矩阵,有行和列两个维度,但是数据框与矩阵的不同是,数据框的每一列可以是不同的模式mode。比如一列数字,一列字符串,一列布尔值。
所以,数据框可以类比为二维矩阵,当然这里的类比是异质性的,因为每个组件的数据类型不同。
技术层面看,数据框是每个组件长度相等的列表。
数据框是实际应用中最为常见。

> d <- data.frame(kids=c("Jack","Jill"),
+                 ages=c(12,10),
+                 stringsAsFactors = FALSE)
> d
  kids ages
1 Jack   12
2 Jill   10

数据框的访问,提取,增加,删除和矩阵非常类似,不再详述。
还有合并

apply族函数在数据框中的用法

apply
lapply
sapply

apply

如果数据框的每一列的数据类型相同,则可以对该数据框使用apply函数。或针对数据框中的某些列应用。

lapply和sapply

因为数据框技术上就是列表,所以lapply和sapply可以应用于数据框。
数据框是列表的特例,数据框的列构成列表的组件,所以lapply函数会作用于数据框的每一列,返回返回一个列表。但未知错乱,意义不大。

> lapply(d,sort)
$kids
[1] "Jack" "Jill"

$ages
[1] 10 12

> as.data.frame(lapply(d,sort))
  kids ages
1 Jack   10
2 Jill   12
> apply(d[,-1],2,mean)
 ages score 
 11.0  92.5 
> lapply(d[,-1],mean)
$ages
[1] 11

$score
[1] 92.5

> sapply(d[,-1],mean)
 ages score 
 11.0  92.5

6 因子factor

因子是R中许多强大运算和可视化的基础,暴多很多针对表格数据的运算。其来源是统计学中的名义变量(nominal variables),或称之为分类变量(categorical variables)。这种变量的本质不是数字,而是对应分类。
因子可以看做附加了更多信息的向量。

> x <- c(5,12,13,12)
> xf <- factor(x)
> xf
[1] 5  12 13 12
Levels: 5 12 13
> str(xf)
 Factor w/ 3 levels "5","12","13": 1 2 3 2
> unclass(xf)
[1] 1 2 3 2
attr(,"levels")
[1] "5"  "12" "13"
> x <- c(5,12,13,12)
> xf <- factor(x)
> xf
[1] 5  12 13 12
Levels: 5 12 13
> length(x)
[1] 4
> str(xf)
 Factor w/ 3 levels "5","12","13": 1 2 3 2
> unclass(xf)
[1] 1 2 3 2
attr(,"levels")
[1] "5"  "12" "13"

其中值得注意的几个地方

1 xf包含四个数值,共3个水平(levels,就是xf中不同的数值)
2 length返回的是数据的长度,而不是水平的个数
3 unclass要引起注意。其中返回的1232代表的是第1,2,3,2个水平,在这里这些数字已经重新编码为水平,而不是数值2,是水平2.

因子的常用函数tapply split by

tapply

tapply(x,f,g)其中,x是向量,f是因子(比如性别,党派),g是函数
要求f中每个因子需要与x有想通的长度。
tapply()执行的操作是,暂时将x分组,每组对应一个因子水平(多个因子对应一组因子组合),得到x的子向量,然后对这些子向量应用函数g()

> ages <- c(25,26,55,37,21,42)
> affils <- c('R','D','D','R','U','D')
> tapply(ages, affils, mean)
 D  R  U 
41 31 21 

第二个例子

> d <- data.frame(list(gender=c("M","M","F","M","F","F"),
+                      age=c(47,59,21,32,33,24),
+                      income=c(55000,88000,32450,76500,12300,45650)))
> d
  gender age income
1      M  47  55000
2      M  59  88000
3      F  21  32450
4      M  32  76500
5      F  33  12300
6      F  24  45650
> tapply(d$income,d$gender,mean)
       F        M 
30133.33 73166.67 

现在假如同时对age和gender感兴趣,想知道其每组平均收入。假如我们以25岁为条件,那么需要把年龄转化为因子,比如大于25的为1,小于25的为0,或其他,用前面的ifelse函数进行赋值
排列组合,性别2个因子,年龄2个因子,所以会将收入分为4组,每组代表性别和年龄的一种组合,然后对每个组合应用函数。

> d$over25 <- ifelse(d$age>25,'over','under')
> tapply(d$income, list(d$gender,d$over25), mean)
      over under
F 12300.00 39050
M 73166.67    NA

split 只是形成分组

注意,这点和tapply不同,tapply是将向量分割为组,然后针对每个组应用制定函数。split的基本形式是split(x,f),注意返回的是列表
还有一点注意的是split中x可以是数据框,而tapply不可以。

> split(d$income,d$over25)
$over
[1] 55000 88000 76500 12300

$under
[1] 32450 45650

> as.data.frame(split(d$income,d$over25))
   over under
1 55000 32450
2 88000 45650
3 76500 32450
4 12300 45650

split可以很方便的找出各个因子的索引

> split(1:length(d$over25),d$over25)
$over
[1] 1 2 4 5

$under
[1] 3 6

split与lapply联合使用非常方便。

by函数

假如现在有这么一个需求,想对不同的性别编码组分别做年龄对收入的回归分析。
tapply好像很适合,因为分组,应用函数。但是,tapply的第一个参数必须是向量,不能是矩阵或数据框,而回归分析必须至少两列的数据或数据框,其中第一列是被预测的变量,第二列或多列是预测变量。所以tapply函数不能满足任务。

> by(d,d$gender,function(m) lm(d$income~d$age))
d$gender: F

Call:
lm(formula = d$income ~ d$age)

Coefficients:
(Intercept)        d$age  
       8493         1199  

--------------------------------------------------------------------- 
d$gender: M

Call:
lm(formula = d$income ~ d$age)

Coefficients:
(Intercept)        d$age  
       8493         1199

by()的调用和tapply()非常相似,第一个参数是数据,第二个是分组因子,第三个是函数。
tapply是根据因子水平简历索引的分组,by会查找数据框不同分组的行号,从而产生2个子数据框,分别对应2个性别水平。lm函数被调用2次,作了2次回归分析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容