《数据驱动的网络分析》——6.2 R语言基础知识

    xiaoxiao2024-01-28  171

    本节书摘来自异步社区《数据驱动的网络分析》一书中的第6章,第6.2节,作者: 【美】Michael Collins 更多章节内容可以访问云栖社区“异步社区”公众号查看。

    6.2 R语言基础知识

    本节是R语言的速成教程。R是一种特性丰富的语言,我也只是略懂一二。但是,在本节结束时,你就能够编写简单的R程序,在命令行上运行,并将其保存为一个库。

    6.2.1 R提示符启动R,将会显示一个窗口和命令提示符。图6-1展示了一个R控制台的例子。如图所示,控制台主要是一个大的文本窗口,顶部的一系列按钮提供了辅助功能。注意按钮栏下的两个文本框,第一个显示当前工作目录,第二个是帮助功能。R有很好的文档,所以一定要习惯使用帮助框。

    在图6-1中,我输入了几条简单的命令:

    > s<-'Hi There' > x<- 3 + 11 + (3 * log(exp(2))) > print(s) [1] "Hi There" > print(x) [1] 20

    R的命令行提示符是>,你可以在提示符后面手工输入命令,如果命令只完成了一部分(例如,有左括号而没有右括号),下一个提示符将变成加号,并持续到命令结束。

    > s<- 3 * ( + 5 + 11 + + 2 + ) > s [1] 54

    注意,当R返回一个值(例如,上例中的s输出),它将打印“[1]”。方括号中的值是数组索引,如果数组分布到多行,每行的开始将打印相应的索引。

    > s<-seq(1,20) > s [1] 1 2 3 4 5 6 7 8 9 10 11 12 [13] 13 14 15 16 17 18 19 20

    帮助可以使用help(term)或?term访问,可以用help.search()或??在帮助中搜索。

    可以使用开关图标或者操作系统对应的退出命令(Command-Q或Ctrl-Q)退出R。如果你使用纯命令行R(不使用图形界面),可以使用Ctrl-D组合键或者在命令行上键入q()结束会话。

    R终止时,会询问是否保存工作区。工作区文件可以在会话之后重新加载,继续终止时正在完成的任何工作。

    6.2.2 R变量R支持一些不同的数据类型,包括标量整数、字符数据(字符串)、布尔和浮点值、矢量、矩阵和列表。标量类型(如下面的例子中所示)可以用←(“取值”)、=和→运算符赋值。R的赋值运算符中包含了一些复杂的作用域,对于我们的目的(以及几乎所有R编程),R风格指南建议使用←运算符代替=符号。

    > #直接赋值 > a<-1 > b<-1.0 > c<-'A String' > d<-T > #将e赋值为d > e<-d > e [1] TRUE > d [1] TRUE > #现在我们重新为d赋值,可以看到,d变化但是e仍保持不变 > d<-2 > d [1] 2 > e [1] TRUE

    R矢量(vector)是有序的一个或者多个同类值:字符、逻辑或者字符串。矢量可以用c函数或者一些其他的函数创建,是R中最常用的元素:我们在前面使用的标量值从技术上说是长度为1的矢量值。1

    > # 整数矢量的一个例子 > int.vec<-c(1,2,3,4,5) > int.vec [1] 1 2 3 4 5 > #在必要的时候,浮点数会被转换为整数,整数也可能被转换为浮点数 > float.vec<-c(1,2.0,3) > float.vec [1] 1 2 3 > float.vec<-c(1,2.45,3) > float.vec [1] 1.00 2.45 3.00 > #矢量也可以包含逻辑值 > logical.vec<-c(T,F,F,T) > logical.vec [1] TRUE FALSE FALSE TRUE > #如果被加入到数值型矢量,逻辑值会被转换为整数 > mixed.vec<-c(1,2,FALSE,TRUE) > mixed.vec [1] 1 2 0 1 > #字符矢量由一个或者多个字符串组成,注意,每个字符串是一个元素。 > char.vec <- c("One","Two","Three") > char.vec [1] "One"  "Two"   "Three" > # Length显示矢量的长度 > length(int.vec) [1] 5 > #注意,字符矢量的长度是字符串的总数,而不是单独字符的总数 > length(char.vec) [1] 3

    注意字符矢量的长度:在R中,字符串不管字符数量多少,都被当作单个元素。有一些函数可以访问字符串——nchar获得字符串长度,substr和strsplit从字符串提取元素——但是单独的字符串不能像Python中那样直接访问。

    R提供了许多矢量算术函数。矢量可以加或者乘以另一个矢量,如果它们的大小相等,结果是逐个元素计算得出的。如果其中一个矢量较小,它将被重复,形成相同大小的矢量。(如果一个矢量的长度不是另一个矢量长度的因数,会出现错误)这也适用于单元素矢量:将单一元素多次加上较长矢量中的元素。

    矢量可以索引。单个元素可以用方括号访问,v[k]是v的第k个元素。矢量还支持范围切片,如v[a:b]。负数索引将把该索引代表的元素从矢量中删除,如下面的代码块:

    > #我们首先根据两个矢量创建一个新的矢量 > v1 <- c(1,2,3,4,5) > v2 <- c(6,7,8,9,10) > v3 <- c(v1,v2) > v3 [1] 1 2 3 4 5 6 7 8 9 10 > #注意,这里没有发生嵌套 > #基本运算——乘法和加法 > 2 * v1 [1] 2 4 6 8 10 > 2 * v3 [1] 2 4 6 8 10 12 14 16 18 20 > 1 + v1 [1] 2 3 4 5 6 > v1 * v2 #乘法 [1] 6 14 24 36 50 #范围切片 > v3[1:3] [1] 1 2 3 #这和v3[1]相等 > v3[1:1] [1] 1 > v3[2:4] [1] 2 3 4 # 反转范围,使矢量转向 > v3[3:1] [1] 3 2 1 #使用负数删除元素 > v3[-3] [1] 1 2 4 5 6 7 8 9 10 > v3[-1:-3] [1] 4 5 6 7 8 9 10 > #你可以用逻辑矢量作为选择器,选择返回索引值为真的元素 > v3[c(T,F)] [1] 1 3 5 7 9

    R可以用matrix函数,从矢量中构建矩阵。和矢量一样,矩阵可以进行加法和乘法运算(和自身、矢量以及其他矩阵),也可以使用不同的方法进行选择和切片,如:

    > #矩阵用matrix构造,下面是基本形式,注意,列先被填充。 > s<-matrix(v3,nrow=2,ncol=5) > s    [,1] [,2] [,3] [,4] [,5] [1,]   1   3  5   7   9 [2,]   2   4  6   8  10 > # 加上单个元素 > s + 3    [,1] [,2] [,3] [,4] [,5] [1,]  4  6  8  10  12 [2,]  5  7  9  11  13 > #乘法 > s * 2    [,1] [,2] [,3] [,4] [,5] [1,]  2  6  10  14  18 [2,]  4  8  12  16  20 > #乘以一个矩阵 > s * s    [,1] [,2] [,3] [,4] [,5] [1,]  1  9  25  49  81 [2,]  4  16  36  64 100 > #加上一个矢量,注意,加法先从列开始 > s + v3    [,1] [,2] [,3] [,4] [,5] [1,]  2  6  10  14  18 [2,]  4  8  12  16  20 > #加上一个较小的矢量,注意,它在矩阵中循环,先从列开始 > s + v1    [,1] [,2] [,3] [,4] [,5] [1,]  2  6  10  9  13 [2,]  4  8  7  11  15 > #切片:逗号的使用让大多数人觉得奇怪 > #逗号之前是行,逗号之后是列。 > #返回的结果是一个矢量,这就是“列”现在是横向的原因。 > s[,1] [1] 1 2 > s[1,] [1] 1 3 5 7 9 > #访问单个元素 > s[1,1] [1] 1 > #现在,我将访问第1行第1列和第2列的元素;同样,得到一个矢量 > s[1,1:2] [1] 1 3 > #第1列第1行和第2行的元素 > s[1:2,1]    [1] 1 2 > #现在,因为我提取两个矢量,所以得到一个矩阵 > s[1:2,1:2]    [,1] [,2] [1,]  1  3 [2,]  2  4 > #使用布尔值选择,第一个值是提取的行 > s[c(T,F)] [1] 1 3 5 7 9 > s[c(F,T)] [1] 2 4 6 8 10 > #如果我指定另一个矢量,将选取列 > s[c(F,T),c(T,F,T,T,F)] [1] 2 6 8

    R列表(List)实际上是一个矢量,其中每个元素都可以由列表组成。和矩阵一样,列表可以用特殊的命令构造,可以像矢量那样切片,但是单个元素使用两个方括号访问。更有趣的是,列表可以命名,单独的数量可以指定一个名称,然后用$运算符访问。

    #查看前面建立的矢量元素 > v3 [1]  1  2  3  4  5  6  7  8  9  10 > v4 [1] "Hi"   "There" "Kids" > #创建一个列表;注意,我们可以添加任意数量的元素。 > #添加的每个元素是一个新的索引。 > list.a <- list(v3,v4,c('What','The'),11) > #输出列表;注意双括号中的列表索引。 > list.a [[1]] [1]  1  2  3  4  5  6  7  8  9  10 [[2]] [1] "Hi"   "There" "Kids" [[3]] [1] "What"  "The" [[4]] [1] 11 > #列表不支持矢量运算。 > list.a + 1 Error in list.a + 1 : non-numeric argument to binary operator > #单独的元素可以用索引检查。单个方括号返回一个列表。 > list.a[1] [[1]] [1]  1  2  3  4  5  6  7  8  9  10 > #双方括号返回元素本身;注意,列表索引([[1]])在这里不会出现 > list_a[[1]] [1]  1  2  3  4  5  6  7  8  9  10 > #单括号返回一个列表,双括号返回单元素列表的第一个元素。 > list_a[1][[1]] [1]  1  2  3  4  5  6  7  8  9  10 > #使用双括号访问,然后在矢量中使用单括号。 > list_a[[1]][1] [1] 1 > list_a[[2]][2] [1] "There" > #我们可以修改结果。 > list_a[[2]][2] <- 'Wow' > #现在,我们将创建一个命名列表。 > list_b <- list(values=v1,rant=v2,miscellany=c(1,2,3,4,5,9,10)) > #参数名称变成列表元素名,参数是列表的实际元素。 > list_b $values [1] 1 2 3 4 5 $rant [1] 6 7 8 9 10 $miscellany [1] 1 2 3 4 5 9 10 > #命名元素用$符号访问。 > list_b$miscellany [1] 1 2 3 4 5 9 10 > #访问之后,你可以使用标准的切片。 > list_b$miscellany[2] [1] 2 > #注意,索引和名称指向相同的值 > list_b[[3]] [1] 1 2 3 4 5 9 10

    理解列表语法对于数据帧很重要,我们将在后面更深入地探讨。

    6.2.3 编写函数R函数通过把function命令绑定到一个符号来创建,例如:

    > add_elements <- function(a,b) a + b > add_elements(2,3) [1] 5 > simple_math <- function(x,y,z) { +  t <- c(x,y) +  z * t + }

    注意花括号。在R中,花括号用于容纳多个表达式,并返回最后一条语句的结果。花括号可以在没有函数或者任何其他内容的情况下使用,例如:

    > { 8 + 7 + 9 + 2 + c('hi','there') + } [1] "hi"  "there"

    所以,在simple_math函数中,括号里的结果顺序求值,返回最终结果。最终结果不一定和代码块中靠前的语句有任何关系。R可以使用return语句控制函数的终止和返回,但是如果结果很明显,一般的惯例是不使用它。

    正如例中所示,函数参数在函数语句中定义。参数可以使用=号提供默认值。定义默认值的参数都是可选的。参数可以通过顺序或者明确使用参数名赋值,如:

    #创建具有可选参数的函数 > test<-function(x,y=10) { x + y } #如果没有传递参数,R将使用默认值 > test(1) [1] 11 > #参数和值都按照位置设置 > test(1,5) [1] 6 > #也可以用参数名赋值 > test(1,y=9) [1] 10 > #对所有变量赋值 > test(x=3,y=3) [1] 6 > #名称优先于位置 > test(y=8,x=4) [1] 12 > #没有默认值的参数必须赋值 > test() Error in x + y : 'x' is missing

    R的函数型特征使你可以将函数看作可以操纵、求值的对象,在必要时应用。函数可以传递给其他函数作为参数,也可以使用apply和Reduce函数,支持更复杂的求值。

    > #创建一个供其他函数调用的函数 > inc.func<-function(x) { x + 1 } > dual.func<-function(y) { y(2) } > dual.func(inc.func) [1] 3 > #根据输入类型(矩阵、列表、矢量)和输出类型,R有不同的apply函数 > test.vec<-1:20 > test.vec [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 > #在匿名函数上运行sapply;注意,该函数没有绑定到某个对象;它在运行期间存在。 > #我可以轻松地调用sapply(c,inc.func),使用上面定义的inc.func函数。 > sapply(test.vec,function(x) x+2) [1] 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 > #sapply是经典的映射函数,Reduce是经典的合并/归约函数,将矢量简化为单个数值。 > #在本例中,传递的函数将a和b合并起来,将整数1添加到20中间,得到210 > #注意,Reduce首字母采用大写 > Reduce(function(a,b) a+b,1:20) [1] 210

    关于R中的循环,要注意一点:众所周知,R循环(特别是for循环)的速度缓慢。许多在Python或者C中完成的for循环在R中使用许多函数型结构完成,其中最常用的是sapply和Reduce。

    6.2.4 条件与循环R的基本条件语句是if…then…else,else if用于表示多条语句。if语句本身是一个函数,返回一个可以求取的数值:

    > #打印输出一个字符串的简单if/then语句 > if (a == b) print("Equivalent") else print("Not Equivalent") [1] "Not Equivalent" > #我们可以直接返回值 > if (a==b) "Equivalent" else "Not Equivalent" [1] "Not Equivalent" # if/then是一个函数,所以我们可以将其放入另一个函数,或者另一条 if/then语句中 > if((if (a!=b) "Equivalent" else "Not Equivalent") == \    "Not Equivalent") print("Really not equivalent") > a<-45 > #用else if链接多条if/then语句 > if (a == 5) "Equal to five" else if (a == 20) "Equal to twenty" \  else if (a == 45) "Equal to forty five" else "Odd beastie" [1] "Equal to forty five" > a<-5 > if (a == 5) "Equal to five" else if (a == 20) "Equal to twenty" \ else if (a == 45) "Equal to forty five" else "Odd beastie" [1] "Equal to five" > a<-97 > if (a == 5) "Equal to five" else if (a == 20) "Equal to twenty" \ else if (a == 45) "Equal to forty five" else "Odd beastie" [1] "Odd beastie"

    R提供了switch语句,作为多个if/then子句的紧凑替代品。开关语句对整数比较使用位置型参数,对文本比较则采用可选的参数赋值:

    > # switch的第一个参数为数字时,它返回索引对应于该数字的参数,所以下面的语句将返回 > #第二个参数“Is” > switch(2,"This","Is","A","Test") [1] "Is" > proto<-'tcp' > #如果参数是命名的,这些文本字符串用于匹配 > switch(proto,tcp=6,udp=17,icmp=1) [1] 6 > #最后一个参数是默认参数 > proto<-'unknown' > switch(proto, tcp=6,udp=17,icmp=1, -1) [1] -1 > #为了重复使用switch语句,将其绑定到一个函数 > proto<-function(x) { switch(x, tcp=6,udp=17,icmp=1)} > proto('tcp') [1] 6 > proto('udp') [1] 17 > proto('icmp') [1] 1

    R有3种循环结构:repeat默认提供无限循环,while每次循环都进行条件求值,for在一个矢量上循环。循环的内部操作由break(终止循环)和next(跳过一次循环)控制,如:

    > # repeat循环,注意,除非循环中有break语句,否则repeat循环无限运行。 > #如果没有指定条件,它将永远运行下去。 > i<-0 > repeat { +  i <- i + 1 +  print(i) +  if (i > 4) break; + } [1] 1 [1] 2 [1] 3 [1] 4 [1] 5 > # while循环的功能完全相同,这种循环不需要break语句 > i <- 1 > while( i < 6) { +   print(i) +   i <- i + 1 + } [1] 1 [1] 2 [1] 3 [1] 4 [1] 5 > # for循环最为紧凑 > s<-1:5 > for(i in s) print(i) [1] 1 [1] 2 [1] 3 [1] 4 [1] 5

    虽然R提供了这些循环结构,但是最好是避免循环,而用sapply等函数型操作代替。R不是通用的编程语言,它的设计目的是为统计分析人员提供丰富的操作工具包。R包含了大量经过优化的函数和用于操纵数据的其他工具。我们将在本章后面介绍一些这方面的功能,但是好的R参考来源是很宝贵的。

    相关资源:网络数据的统计分析_R语言实践
    最新回复(0)