1.4 R语言编程基础
R是一种区分字母大小写的解释性语言,R语句的分隔符是分号(;)或换行符。当语句结束时,可以不使用分号,R语言会自动识别语句结束的位置。R语言只支持单行注释,注释由#开头,当前行出现在#之后的任何文本都会被R解释器忽略。R语句由函数和赋值构成。R使用<-,而不是传统的=作为赋值符号。R语言的数学运算与我们平时的数学运算(加+,减-,乘*,除/)基本一致。在这里,我们会重点讲解与R语言数据可视化相关的编程基础内容。
1.4.1 数据类型
R语言有很多不同的数据类型,用于存储不同的数据。最常用到的4种数据类型为数值型(numeric)、字符型(character)、日期型(date)和逻辑型(logical)。变量中存储的数据类型都可以使用class()函数查看。
①数值型(numeric):
a<-1, is.numeric(a) #输出判定a是否为数值型:TRUE
②字符型(character):
b<- "peter"; nchar(b)#输出字符串的长度为:5
③日期型(date):最常用的日期型数据类型是Date(仅存储日期)和POSIXct(同时存储日期与时间)
c<-as.Date("2012-06-12"); class(c) #输出c的数据类型为:"Date" d<-as.POSIXct("2012-06-12 17:32"); class(d) #输出d的数据类型为:"POSIXct""POSIXt"
④逻辑型(logical):
e<-TRUE, f<-FALSE
其中,在处理时序数据时,我们需要处理日期型数据,往往需要使用as.Date()函数将读入的数据从数值型转换成日期型,有时候还需要进一步提取日期型数据的年、月、周等数据信息。此时我们需要使用as.numeric()函数或者as.integer()函数将日期型数据转换成数值型。其中,strftime(x, format= "")函数可以定义日期型数据的格式,比如strftime(c, '%Y')表示只显示年份。
c_Year<-as.integer(strftime(c, '%Y')) #输出年份:2012 c_month <- as.integer(strftime(c, '%m'))#输出月份:6 c_week<-as.integer(strftime(c, '%W')) #输出周数:24
1.4.2 数据结构
常见的数据结构包括:向量(vector)、数据框(data.frame)、矩阵(matrix)、列表(list)和数组(array)。其中,矩阵是将数据用行和列排列的长方形表格,它是二维数组,其单元必须是相同的数据类型,通常用列来表示不同的变量,用行表示各个对象;数组可以看作是带有多个下标的类型相同的元素的集合;列表是一个对象的有序集合构成的对象,列表中包含的对象又称为它的分量(component),分量可以是不同的模式或(和)类型。我们在本书的数据可视化中,比较常用的是向量(因子属于特殊的向量)和数据框,所以我们重点介绍这两种类型的数据结构,还将介绍与数据可视化密切相关的函数。
1.向量
向量是用于存储数值型、字符型或逻辑型数据的一维数组。执行组合功能的函数c()可用来创建向量(c代表合并:combine)。值得注意的是,单个向量中的数据类型是固定的,比如数值型向量中的元素就必须全为向量。向量是R语言中最基本的数据结构,其他类型的数据结构都可以由向量构成。最常见的向量有三种类型:数值型、字符型、逻辑型。
(1)向量的创建
向量的创建有多种方法,我们既可以手动输入,使用函数c()创建向量;也可以使用现有的函数创建向量,比如seq()、rep()等函数,具体如表1-4-1所示。
表1-4-1 向量的创建
(2)向量的处理
· 向量的排序。向量的排序和数据框的排序有时候对数据的展示尤为重要,很多时候我们需要先对数据进行降序处理,再展示数据。sort()函数可以实现对向量的排序处理,index.return=TRUE,表示返回排序的索引;decreasing = TRUE,表示降序处理。如下输出的结果order包括两部分:$x为[5 4 3 2 1], $ix为[4 2 3 5 1]。
Vec<-c(1,4,3,5,2) order<-sort(Vec, index.return=TRUE, decreasing = TRUE)
· 向量的唯一值。unique()函数主要是返回一个删除了重复元素或行的向量、数据框或数组。在需要对数据框根据某列进行分组运算时,需要使用该函数先获取类别总数。
Vec<-c("peter", "jack", "peter", "jack", "eelin") Uni<-unique(Vec) #输出:"peter", "jack", "eelin"
· 连续向量的离散化。在做数据挖掘模型时,我们有时会需要把连续型变量转换为离散型变量,这种转换的过程就是数据离散化,分箱就是离散化常用的一种方法。数据离散化最简单的方法就是使用cut()函数自定义离散区间,从而对数据进行离散处理。
Num_Vector<- c(10, 5, 4, 7, 6, 1, 4, 8, 8, 5) Cut_Vector<-cut(Num_Vector, breaks=c(0,3,6,9,11), labels=c("0~3", "3~6", "6~9", ">9"), right = TRUE) # 输出结果为因子向量:>9,3~6,3~6,6~9,3~6,0~3,3~6,6~9,6~9,3~6;其水平Levels为:0~3,3~6,6~9, >9
(3)向量的索引
向量是多个元素的集合,当我们只需要指定或者说提取该向量中的某个元素时,就可以使用向量的索引(indexing)。向量元素有三种基本类型的向量索引:整数型,索引的是元素位置;字符型,索引的是名称属性;逻辑型,索引的是相同长度的逻辑向量对应的逻辑值为真的元素。
x<-c(1,4,3,5,2)
· 整数型索引,选择某个或多个元素:x[2]; x[-2]; x[2:4]; x[c(1,4)]
· 逻辑型索引,逻辑运算选择元素:x[x>2]; x[x==1]; x[x<=5]
2.因子
因子(factor)是R语言中许多强大运算的基础,包括许多针对表格数据的运算,可分为类别型变量和有序型变量。因子可以看成是包含了额外信息的向量,这额外的信息就是不同的类别,称之为水平(level)。因子在R中非常重要,因为它决定了数据的分析方式,以及如何进行视觉呈现。
(1)因子的创建
一个因子不仅包括分类变量本身,还包括变量不同的可能水平(即使它们在数据中不出现)。因子函数factor()用下面的选项创建一个因子。对于字符型向量,因子的水平默认依字母顺序创建:
(Fair, Good, Ideal, Premium, Very Good) Cut<-c("Fair", "Good", "Very Good", "Premium", "Ideal") Cut_Facor1<-as.factor(Cut)
(2)水平的更改
很多时候,按默认的字母顺序排序的因子很少能够让人满意。因此,可以指定levels选项来覆盖默认排序。更改因子向量的levels为("Good", "Fair", "Very Good", "Ideal", "Premium"),就需要使用factor()函数更改levels。
Cut_Facor2<-factor(x=c("Fair", "Good", "Very Good", "Premium", "Ideal"), levels= c("Good", "Fair", "Very Good", "Ideal", "Premium"), ordered=TRUE)
(3)类型的转换
数值型因子向量的类型变换。有时候我们需要将数值型的因子向量重新转换成数值型向量,这时,需要使用as.numeric(as.character())组合函数,而不能直接使用as.numeric()函数。其中as.character()函数表示将向量变成字符型,as.numeric()函数表示将向量变成数值型。
Num _Facor<-factor(x=c(1,3,5,2), levels= c(5,3,2,1), ordered=TRUE) Num_Vector1<-as.numeric(as.character(Num_Facor))# 输出:1 3 5 2 Num_Vector2<-as.numeric(Num_Facor) # 输出:4 2 1 3
3.数据框
数据框是R语言中的一种表格结构,对应于数据库中的表,类似Excel中的数据表。数据框是由多个向量构成的,每个向量的长度相同。数据框类似矩阵,也是一个二维表结构。在统计学术语中,用行来表示观测(observation),用列来表示变量(variable)。
(1)数据框的创建与查看
创建数据框,最简单的方法就是,用同名的定义函数data.frame(),输入每个变量的名称及对应的向量,每个向量的长度相同。一个数据框可能包含多个变量(向量),有时需要单独提取某个变量,使用特殊的$符号来访问,由“数据框$变量名”构成。数据框数据的选取如表1-4-2所示。
表1-4-2 数据框数据的选取
· 获取数据框的行数、列数和维数:nrow()、ncol()、dim()。
· 获取数据框的列名或行名:names()、rownames()、colnames();重新定义列名:names(df)<-c("X","Y", "Z")。
· 观察数据框的内容:view(df)、head(df, n=3)、tail(df)。
(2)空数据框的创建
创建空数据框,在需要自己构造绘图的数据框数据信息时尤为重要。有时候,在绘制复杂的数据图表的过程中,我们需要对现有数据进行插值、拟合等处理,这时需要使用空的数据框存储新的数据,最后使用新的数据框绘制图表。创建空的数据框主要有如下两种方法。
· 创建一个名为Df_Empty,包括两个变量(var_a为numeric类型;var_b为character类型)的data.frame。但是注意:要加上stringsAsFactors=FALSE,否则在后面逐行输入数据时,会因为var_b的取值未经定义的factor level而报错。
Df_Empty1<- data.frame(var_a = numeric(), var_b = character(), stringsAsFactors=FALSE)
· 先使用矩阵创建空的数据框,同时通过dimnames设定数据框的列名。这个相比前一种方法可以更快速地创建多列空数据框:
Df_Empty2 <- data.frame(matrix(ncol=2, nrow=0, dimnames=list(c(), c("var_a", "var_b"))))
1.4.3 数据属性
数据框作为R语言数据分析与可视化很常用的数据结构,常由多列不同数据属性的变量组成。在我们实现数据可视化时,很有必要先了解这些变量的属性。我们平时记录的实验数据所用的表(table)就是由一系列不同属性的变量组成的。Jiawei Han等人的Data mining: concepts and techniques[13]根据数据属性取值的集合类型,将数据属性分成了三类:类别型、序数型和数值型,如图1-4-1所示。Pang-Ning Ta等人的Introduction to Data Mining[14],将序数型和类别型数据统称为类别型(categorical)或者定性型(qualitative),将数值型(numeric)也称为定量型(quantitative)。
图1-4-1 不同数据类型
1.类别型
类别型属性(categorical attribute)是用于区分不同数据对象的符号或名称,而它们是没有顺序关系的,又包含多元类别和二元类别两种类型。对于多元类别,可以理解为购买服装时的不同服装名称,如衬衫、毛衣、T恤、夹克等;对于二元类别,可以理解为购买服装时的不同性别,只有男士和女士两种性别分类。类别型数据的可视化一般使用标尺类中的分类尺度。
2.序数型
序数型属性(ordinal attribute)的属性值是具有顺序关系,或者存在衡量属性值顺序关系的规则。比如常见的时序数据,一般按时间先后排序;还有调查问卷中经常使用的5个喜欢程度:非常喜欢、比较喜欢、无所谓、不太喜欢、非常不喜欢。序数型数据的可视化一般使用标尺类中的顺序尺度和时间尺度两种类型。
序数型数据的排列方向有三种,分别是单向型(sequential),有公共零点的双向型(diverging),以及环状周期型(cyclic),如图1-4-2所示。
图1-4-2 不同数据结构的序数型
3.数值型
数值型属性(numeric attribute)使用定量方法表达属性值,如整数或者实数,包括区间型数值属性(interval-scaled attribute)和比值型数值属性(ratio-scaled attribute),如表1-4-3所示。区间型与比值型数值最大的区别就是有无基准点,通常为零点(internal zero-point)。
比值型数值属性的数据一般拥有基准点,比如开氏温标(K)以绝对零度(0K=-273.15oC)为其零点,以及平时通常使用的数量、重量、高度和速度等。
而区间型数值属性的数据的起始值一般是在整个实数区间上取值,可进行差异运算,但不能进行比值运算。比如摄氏温标(oC)与华氏温标(oF)下的温度、日历中的年份、经度(longitude)与纬度(latitude),它们都没有真正的零点。在日历中,0年并不对应时间的开始,但0oC并不代表没有温度。所以可以说10oC比5oC温度高(差异运算),但是不能说10oC是5oC的2倍(比值运算)。
表1-4-3 包含不同数据属性的变量组合表[13]
我们也可以用值的个数区分数据类型,可以分为离散型和连续型[14]。离散型属性具有有限个值或者无限个值,这样的属性可以是分类的,也可以是数值型的。其中二元属性(binary attribute)是离散型属性的一种特殊情况,并只接受两个值,比如True/False(真/假)、Yes/No(是/否)、Male/Female(男/女),以及0/1。通常二元属性使用布尔变量表示,或者只取0和1两个值的整数变量表示。连续型属性是取实数值的属性,通常使用浮点数变量表示。理论上讲,基于数据集合类型划分的数据类型(类别型、序数型和数值型)可以与基于属性值个数的任意类型(离散型和连续型)组合,从而不同的数据可能有不同的数据属性组合。
1.4.4 数据的导入与导出
1.数据文件的导入与导出
我们常用外部保存的数据文件来绘制图表。此时,就需要借助可以导入数据的函数导入不同格式的数据,包括CSV、TXT,以及Excel、SQL、HTML等数据文件。有时候,我们也需要将处理好的数据从R语言中导出保存。其中,在数据可视化中使用最多的就是前3种格式的数据文件。
(1)CSV格式数据的导入与导出
使用read.csv()函数,可以导入CSV格式的数据,并存储为数据框形式。需要注意是:当stringsAsFactors=TRUE时,R会自动将读入的字符型变量转换成因子,但是这样很容易导致数据只按默认字母顺序展示。在导入大批量数据时,为了提高性能,尽可能分两步走:
① 显式指定“stringsAsFactors = FALSE”;
② 依次将所需要的数据列(向量)转换为因子。
mydata<-read.csv("Data.csv", sep=", ", na.strings="NA", stringsAsFactors=FALSE)
使用write.csv()函数,可以将data.frame的数据存储为CSV文件:
write.csv(mydata, file = "File.csv")
CSV文件主要有以下3个特点。
① 文件结构简单,基本上和TXT文本的差别不大;
② 可以和Excel进行转换,这是一个很大的优点,很容易进行查看模式转换,但是其文件的存储大小比Excel小。
③ 由于其简单的存储方式,一方面可以降低存储信息的容量,这样有利于网络传输及客户端的再处理;另一方面,由于是一堆没有任何说明的数据,其具备基本的安全性。所以相比TXT和Excel数据文件,我们更加推荐使用CSV格式的数据文件进行导入与导出操作。
(2)TXT格式数据的导入与导出
使用read.table()函数不仅可以导入CSV格式的文件数据,还可以导入TXT格式的文件数据,并存储为数据框数据。
mydata<-read.table("Data.txt", header = TRUE)
使用write.table()函数可以将data.frame的数据存储为TXT文件:
write.table(mydata, file = "File.txt")
(3)Excel格式数据的导入与导出
使用xlsx包的read.xlsx()函数和read.xlsx2()函数可以导入XLSX格式的数据文件。但是更推荐使用CSV格式导入数据文件。
mydata<- read.xlsx("Data.xlsx", sheetIndex=1)
也可以使用write.xlsx()函将数据文件导出为XLSX格式:
write.xlsx(mydata, "Data.xlsx", sheetName="Sheet Name")
需要注意的是:在使用R ggplot2绘图时,通常使用一维数据列表的数据框。但是如果导入的数据表格是二维数据列表,那么我们需要使用reshape2包的melt()函数或者tidyr包的gather()函数,可以将二维数据列表的数据框转换成一维数据列表。
一维数据列表和二维数据列表的区别
一维数据列表就是由字段和记录组成的表格。一般来说字段在首行,下面每一行是一条记录。一维数据列表通常可以作为数据分析的数据源,每一行代表完整的一条数据记录,所以可以很方便地进行数据的录入、更新、查询、匹配等,如图1-4-3所示。
图1-4-3 一维数据列表
二维数据列表就是行和列都有字段,它们相交的位置是数值的表格。这类表格一般是由分类汇总得来的,既有分类,又有汇总,所以是通过一维数据列表加工处理过的,通常用于呈现展示,如图1-4-4所示。
图1-4-4 二维数据列表
一维数据列表也常被称为流水线表格,它和二维数据列表做出的数据透视表最大的区别在于“行总计”。判断数据是一维数据列表还是二维数据列表的一个最简单的办法,就是看其列的内容:每一列是否是一个独立的参数。如果每一列都是独立的参数那就是一维数据列表,如果每一列都是同类参数那就是二维数据列表。
注意 为了后期更好地创建各种类型的数据透视表,建议用户在数据录入时,采用一维数据列表的形式进行数据录入,避免采用二维数据列表的形式对数据进行录入。
2.缺失值的处理
有时候,我们导入的数据存在缺失值。另外,在统计与计算中,缺失值也起着至关重要的作用。R语言中主要有两种类型的缺失数据:NA和NULL。
(1)NA
在R中,使用NA代替缺失数据作为向量中的另外一种元素出现。我们可以使用is.na()函数来检查向量或数据框中的每个元素是否缺失数据。我们先构造一个含有缺失数据的数据框,然后使用tidyr包实现常用的缺失数据的处理,具体方法如表1-4-4所示。
表1-4-4 缺失值的处理
(2)NULL
NULL就是没有任何东西,表示数据的空白,而并非数据的缺失,也不能成为向量或者数据框的一部分。在函数中,参数有可能是NULL,返回的结果也可能是NULL。我们可以使用is.null()函数判定变量是否为NULL。
1.4.5 控制语句与函数编写
我们常用的控制语句包括if…else、ifelse条件语句,以及for和while循环语句。其中我们最常见的就是if…else,主要用于检查判定。其条件最基本的检查包括等于(=)、小于(<)、小于等于(<=)、大于(>)、大于等于(>=)和不等于(! =)。if…else语句对数据的操作运算命令都需要放在{}里面。需要注意的是:在else左边的大括号“}”必须与else在同一行,否则程序无法识别,会导致代码运行错误。另外,R语言还有一个ifelse()语句,可以向量化if语句,从而加速代码的运行,如表1-4-5所示的if…else条件语句可以使用ifelse()重写为:ifelse(i > 3, print('Yes'), print('No'))。该语句可以结合transform()函数等对数据框的每个元素进行判别运算,从而生成新的列。
表1-4-5 控制语句
我们最常用的循环是for循环,for循环的向量不一定是连续型的,也可以是其他类型的向量,如表1-4-6所示的for循环示例。其中,1:4的输出起点为1、终点为4、步长为1的等差数列向量(1,2,3,4),效果类似于seq(1,4,1)。另外,while循环虽然没有for循环用得普遍,但是更加易于操作。对新手来说,while循环容易由于设定的循环条件有误而导致循环不停迭代,从而陷入“死循环”。
表1-4-6 自定义函数
我们在实现数据可视化时,更多是使用现有包的函数,比如等差数列生成函数seq()、向量排序函数sort()、插值函数spline()等,而很少需要自定义函数(表1-4-6为各种自定义函数的语法格式)。我们更加需要了解的是现有函数的输入参数与数据的结构、输出参数的数据内容等,比如plot3D包的persp3D()函数和lattice包的wireframe()函数都可以绘制相同的三维曲面图,但是persp3D()函数要求输入的数据是向量与矩阵形式,而wireframe()函数要求输入的数据是数据框。