Skip to content

Latest commit

 

History

History
1101 lines (885 loc) · 34.7 KB

tutorial.md

File metadata and controls

1101 lines (885 loc) · 34.7 KB

R 语言教程

R 语言解释器以及 IDE 的安装与配置

⚠️ 注意:本部分主要针对 Windows 平台用户,mac 与 linux 用户请参照 windows 的内容自行解决。

安装过程中可能用到的网站:

作为一个 R 语言初学者,你需要安装以下软件:

  • R 语言本体:base / r-base(conda 库中的名字)

    base 可通过两种途径安装,对于新手我建议直接使用 CRAN 官方提供的R 语言安装器像一般 windows 软件一样安装。

    后文中大部分的R语言指的就是 base。

    熟悉命令行或者对版本控制有要求的朋友可以使用 conda 安装。但注意 conda 所有源中的 R 相关软件均有r-前缀,因此使用 conda 安装时,包名为 r-base。

    ⚠️关于 conda:请注意 conda 源中的 r-base 包版本比较落后,尤其是 Windows 平台的包!因此如果你想使用最新版本的 R,请谨慎考虑 conda。我的自制的 conda r-base 包可以部分解决该问题,但更新未必足够勤且目前只有 windows 版本。

  • R 语言的 windows 平台编译工具包:Rtools

    Rtools 只需要安装,其他什么都不用管,用到的时候会自动调用。

    mac 平台没有打包好的整套工具包,需要在 R 语言官网CRAN分别安装。

    Rtools 本质是一些 C/C++ 和 Fortran 的编译器,用于将这些语言的代码编译成二进制文件。base 和大部分 R 包安装时是直接安装编译好的二进制文件,因此其实并不需要编译步骤,也用不到 Rtools。所以理论上来说 Rtools 并不是必须安装的。但根据我的经验,大部分需要从 github 直接安装的包都需要 Rtools 编译。这种包虽然不多,但你总有用到的一天,因此建议把 Rtools 装上。

    ⚠️Rtools 版本需与 R 版本匹配,要求不是很严格但最起码大版本需要匹配。不知道装哪个就都装最新的。

  • R 语言集成开发环境 (IDE):Rstudio

    IDE 对于 R 语言的使用也不是必须的。但是它提供了美观,便于使用的界面和方便配置的 gui 菜单界面。总之没必要折磨自己裸奔用 R。

    R 语言有多种其他可选 IDE,比如 Vscode,jupyter notebook 等。尽管 Rstudio 也有很多缺点,但对于新手来说 Rstudio 永远是功能最完善最好用的 IDE,因此新手请一定要无脑选择 Rstudio。

    Rstudio 的版本与 base 版本相互独立,更新时互不干涉。Rstudio 只是一个壳子。

Rstudio 的开箱设置

  • 设置镜像源:

    默认的 R 包安装会从国外服务器下载 R 包,容易出现网络连接不上或者速度慢的情况。国内很多高校都架设了公用的国内镜像站,自动同步国外服务器的资源。从国内镜像站下载会快很多。

    注:除了 CRAN,即官方镜像之外,我们还经常需要使用 Bioconductor 网站上的 R 包。因此 Bioconductor 的软件源也是有镜像的。不过一般来说使用 Bioconductor 安装的时候会自动套用 CRAN 镜像或者在命令行提示询问你要使用哪个镜像站,所以一般可以不管。

    • Rstduio 设置步骤如下:

    • 使用命令行设置:
      此配置重启 R 后失效,因此需要将它们写到 R 的启动脚本里,每次打开 R 的时候自动加载。

      options("repos" = c(CRAN="https://mirrors.tuna.tsinghua.edu.cn/CRAN/"))
      options(BioC_mirror="https://mirrors.tuna.tsinghua.edu.cn/bioconductor")
  • 打开 Rstudio 的语法高亮

设置 Rstudio 使用的 R 版本

正常安装后,Rstudio 会自动找到系统中 R 的路径并调用。但如果你同时安装了多个 R 版本,可以在 Rstudio 的设置菜单中选择你要使用的 R 版本。

如果设置版本自定义 R 路径时设置了错误路径
由于 Rstudio 的大部分功能基于 R 语言,一旦指定了错误的 R 语言路径会导致 Rstudio 无法启动,也无法再次回到这个设置界面更改。需要手动修正在配置文件C:/User/xxx/AppData/Roaming/Rstudio/config.json中的 R 语言路径值rExecutablePath⚠️ 记得将路径中的xxx换成自己的用户名。

环境变量的查看与修改

R 语言中主要有 2 种环境变量。里面都包含了大量的设置项,一些 Rstudio 设置中没有出现的设置选项可以在这里修改。

  • 系统级别:使用Sys.getenv()Sys.setenv()查看与修改。

    这些环境变量主要包括一些默认目录的位置,如 Rtools 所在路径RTOOLS<版本号>_HOME

    安装了多个版本的 Rtools 或者在 conda 中使用 R 导致 R 无法自动获取 Rtools 路径时可以通过该设置项指定正确的 Rtools 路径。

    另外,网络代理的设置也在此处修改。常见的全局代理对 Rstudio 中的 R 终端不起作用。 如Sys.setenv(all_proxy='socks5://127.0.0.1:10808')会让 R 中的流量全部通过本机的 10808 端口 socks5 代理 (重启 Rstudio 后失效)。偶尔安装 github 上的包时网络连接不上或者特别慢时可以启用代理加速。

  • R 语言解释器级别:使用options()查看与修改。

    options() 实际上是查看并修改 globalEnv 中的.Options对象。options 中的变量很多很杂,一些 R 包的设置项也会写进这里。options()主要指定了 R 语言的一些默认行为,比如控制台终端一次最多打印多少行输出,read.table()时是否自动讲字符串变量转换成factor类型,R 包安装时使用的镜像站地址等等。

R 语言的常见数据类型

R 语言的一些基础知识

命令提示符

首先需要知道,所有运算和操作,都是需要时间的。一些简单的运算时间很短,瞬间就能完成。但是有的运算耗时相对比较长,而在它运算完成之前,R 命令行会处于忙碌状态,无法接受新的命令并执行。因此为了提示使用者现在是否可以输入命令,有一个叫命令提示符的东西。Rstudio 中的命令提示符为>。只有在控制台最下端显示>的时候你才能输入命令。

没用小知识

命令提示符其实是可以改的。控制台中输入options(prompt='新的命令提示符')可以修改你的命令提示符。 比如:

然后,你必须能了解与分辨编程语言中的一些常见的基础数据类型:

  • 字符串:
    字符串可以简单理解为文本。

    example='hello world'
    print(example)
    输出:
    [1] "hello world"
    
    example2="1.14"
    print(example2)
    输出:
    [1] "1.14"
    
    example+1
    输出:
     Error in example + 1 : 二进列运算符中有非数值参数
    

    解释器区分字符串与非字符串的主要依据是有没有使用引号将文本括起来。有的编程语言中双引号 "与单引号'有细微区别,但在 R 语言中两种引号的功能完全相同,挑选一个喜欢的用就 可以。遇到需要在引号中使用引号的场合,可以结合两种引号一起使用以区分,比如 text='He said "Hello!"'

    注意,字符串是可以有数字的。字符的 1 和数值的 1 并不相同!如上面代码中的"1.14"没有任 何大小上的意义,无法进行加减乘除等运算。

  • 数值:

    如 1234 等,数值型的对象代表数值大小,可以进行加减乘除等运算。

    example=1.14
    print(example)
    输出:
     [1] 1.14
    
    example+1
    输出:
     [1] 2.14
    

    其他一些编程语言中会需要区分整数,浮点数等不同种类的数值,但在 R 语言中一般不需要关注 这个。

  • 空值与特殊值

    R 语言中的空值或者说与空值类似的值一共有三种。

    • NA
      NA的意义为空,是"Not Available"是缩写,但是它会占位 (长度为 1)。比如一个表格:

      位置 1 2 3
      内容 'a' NA 'c'

      NA 代表此处为空,但是有个空格子。
      ⚠️NA无法用常规的等于判断语句识别。比如NA==NA的结果为F。 必须使用is.na()函数判断。

    • NULL
      NULL是真正意义上的空,代表连格子也不存在 (长度为 0)。

      位置 1 不存在 2
      内容 'a' NULL 'c'

      上表为了便于理解画出了 NULL 的位置,但实际上它长这样:

      位置 1 2
      内容 'a' 'c'

      在 R 中将一个值赋值为NULL等于删除这个值。
      NA类似,

    • logical(0),numeric(0),character(0)
      这几个值都是分别属于逻辑值,数值,字符串。但它们的长度都是零,类似于有类型的NULL。它们的出现大多是NULL经过运算的产物,一般不会主动创建。具体见下文逻辑值(布尔值) 部分。

    • NaN
      NaN其实并非空值。NaN是"Not a Number"的缩写,代表该值为数学上不存在的值,比如log(-1),是一种数学运算的产物。
      ⚠️ 正数除以 0 的结果并不是NaN,而是无限Inf。0 除以 0 的结果是NaN

      log(-1)
      输出:
      [1] NaN
      Warning message:
      In log(-1) : NaNs produced
      
      10/0

      输出:
      [1] Inf

      0/0

      输出:
      [1] NaN

  • 逻辑值 (布尔值)

    逻辑值其实就是TRUEFALSE,也可以简写成TF。一般用于判断语句。
    ⚠️True是错误的写法,必须全大写。
    除了TF,其实还有一种logical(0)的值也是逻辑值。logical(0)是一种长度为 0 的逻辑值,它没有值,但是使用类型判断函数is.logical()判断的时候结果为T。这种值的出现一般与NULL有关,判断语句或函数中出现NULL常常会导致结果为logical(0)。而NULL出现在这种地方的最常见原因是使用函数的时候没写参数,参数使用了默认值NULL

    age <- NULL
    18 > age
    输出:
    logical(0)
    
    is.logical(logical(0))
    输出:
    [1] TRUE
    

没什么用的小知识

严格来说,只有TRUEFALSE算是 R 语言中的逻辑关键字。TF其实只是两个初始设置好的变量,其值分别为TRUEFALSE。因此其实TF是可以被重新赋值的,而TRUEFALSE则因为是关键字,不能被赋值。

T <- FALSE
print(T)
输出:
[1] FALSE
TRUE <- FALSE
输出:
Error in TRUE <- FALSE : invalid (do_set) left-hand side to assignment

因此,如果你的同学和你有仇,你可以偷偷打开他的电脑,在他的用户目录下新建或修改 R 每次启动自动运行的脚本.Rprofile,在其中加入

T <- FALSE
F <- TRUE

😈😈😈 让他在使用简写逻辑值的时候享受一个逆反的 R 语言解释器。

还有一些其他的数据类型,后面提到再说。暂时你只需要了解这些。

一维对象

R 语言中常见的一维数据类型有:

  • vector (向量)
  • factor (矢量)
  • list (列表)

vector

使用c()创建 vector

最常见的创建 vector 的方法为使用c()函数。
可以在 vector 后面接[],对 vector 取子集。

# 创建vector
>example1 <- c(1,'2','a',NA)
>print(example1)
输出:
[1] "1" "2" "a" NA
# 对 vector 取子集
example1[2]
输出:
[1] "2"
# 使用 vector 对 vector 取子集
example1[c(2,4)]
输出:
[1] "2" NA

example1 结构示意图:

index 1 2 3 4
name NULL NULL NULL NULL
value "1" "2" "a" NA

⚠️ 大部分编程语言的索引从 0 开始递增,而 R 语言的索引从 1 开始。 ⚠️vector 中的值必须为同一数据类型。如 example1,由于第二、三元素为"2","a",是字符串类型,因此第一位的数值 1 也被转换成了字符串的"1"NA代表此处为空,不会被转换类型。

# vector 中的元素可以有名字
example2 <- c(a=1,b=2,c=3)
print(example2)
输出:
a b c
1 2 3
# 使用名字(字符串索引)对 vector 取子集
example2["b"]
输出:
b
2
# ⚠️ 错误示范
> example2[b]
输出:
Error: object 'b' not found
# ⚠️ 挽救错误示范
b<-'c'
example2[b] # 等价于 example2["c"]
输出:
c
3

example2 结构示意图:

index 1 2 3
name "a" "b" "c"
value 1 2 3

一个 vector 主要由它的值,索引,名字 (字符串索引) 组成。 其中值和索引是必定会有的,名字可以不指定,默认为NULL,即不存在。

使用:可以快速生成连续数字向量。该方法得到的向量没有名字。

1:10 # 等价于c(1,2,3,4,5,6,7,8,9,10)
输出:
[1] 1 2 3 4 5 6 7 8 9 10
5:2 # 逆序也可以
输出:
[1] 5 4 3 2
修改 vector

使用数值索引与字符串索引对 vector 取子集在前面的示例代码中已经示范过了。 除此之外,还有一种使用逻辑值 vector 对 vector 进行子集操作的方法。

example1[c(T,F,T,F)] #使用一个长度与被取子集的vector一致的逻辑值向量作为索引
输出:
[1] "1" "a"
# 只返回了位置为 T 的值

⚠️ 对向量取子集时,如果使用了不存在的索引或字符串索引,并不会报错,而是会返回NA

example1[99]
输出:
<NA>
 NA

⚠️R 语言的索引中,负号-的意义与 python 中不一样,不能使用如example1[-1]这样的方式取example1的最后一位元素。在 R 语言中,在数字索引 vector 前面带负号代表删除索引对应位置的元素的意思。比如:

example1[-1]
输出:
[1] "2" "a" NA
example1[-c(1:2)] #可以一次删多个
输出:
[1] "a" NA
example2[-c('a','b')]
输出:
Error in -c("a", "b") : invalid argument to unary operator
# 只有数字索引能这么干,因为实际上这个操作是先计算-c(1:2)的值,
# 其结果为c(-1,-2),然后R解释器认为负号代表删除,删掉了1,2号位置的元素。
# 而-c('a','b')本质是对字符串进行四则运算操作,于是在这一步报错。

合并向量给向量增加元素可以使用c()

example3<-c(114514,example1,example2)
print(example3)
输出:
                                                    a        b        c
"114514"      "1"      "2"      "a"       NA      "1"      "2"      "3"
# 页面宽度不够的话此处会换行

⚠️ 重要小知识:关于 R 语言的打印函数print()cat()以及 R 命令行的默认打印行为

想必你已经注意到了,前文的示例代码中多次出现了print()这个函数。

print()函数的功能是输出传递给它的对象(或者说变量).其中这个具体的输出方式,根据具体的变量类型会有所变化,这个变化的具体实现是依赖于 R 语言的 S3 面向对象编程方式,这个后面再细讲。

但是所有的输出方式都是根据这个对象本身的类型来设计的,主旨都是为了能给更好地让使用者能够直观地了解到这个对象是什么样子。比如对于一个字符串,数值,向量或者表格这种存贮数据的对象类型,print()的输出方式是将它们的内容直接在控制台用文本的方式打印出来。而对于函数(是的,函数也是一种对象),print()的输出方式则是将函数的代码在控制台中打印出来。而对于一个 ggplot2绘图包生成的ggplot2图象对象,print()的输出方式是在绘图窗口中把这个图片画出来。

而如果你在R 的命令行中只输入一个对象的名字,没有指定任何的函数来对这个对象进行操作,R 语言解释器会自动为该对象使用print()函数。比如前文中的对向量取子集的示例代码:

# 使用名字(字符串索引)对vector取子集
example2["b"]
输出:
b
2

此处的example2["b"]完全等价于print(example2["b"]),而且后者才是它的真实情况。

⚠️ 这个 R 解释器默认print()的行为,只会发生在用户直接在命令行/控制台中输入对象名的情况。与之不同的是,在循环或者函数的代码块中,是不会发生默认print()的行为的。比如:

library(ggplot2)
# 此处,变量p为一张ggplot2图像,print(p)会画出它的内容。
p<-ggplot(data=data.frame(x=1:3,y=1:3))+geom_point(aes(x=x,y=y))
#直接p
p
输出:
右下角画出了图
#在循环中p
for (i in 1:3){
 p
}
输出:
什么也没有发生

使用 R 绘图的时候经常会遇到需要通过print()输出图片以保存的情况,如果是使用循环批量存图记得循环体中一定要print()哦。

list

什么是list

list的结构与特点与vector十分相似,其区别在于list中每个元素的内容可以是 R 语言中的任何对象,也不要求它们的类型同一。而vector中的元素只能是字符串,数值,逻辑值空值等原子型数据

创建list

创建list的方法与向量类似,只是把c()换成了list()

example_list<-list(ex1=example1,ex2=example2,example3)
print(example_list)
输出:
$ex1
[1] "1" "2" "a" NA

$ex2
a b c
1 2 3

[[3]]
                                                    a        b        c
"114514"      "1"      "2"      "a"       NA      "1"      "2"      "3"
# 使用数字索引取list的子集
example_list[1]
输出: #注意,此处返回的对象类型为list,是一个只有一个元素的list。
$ex1
[1] "1" "2" "a" NA
# 使用字符串索引取list的子集
example_list['ex2']
输出:#注意,此处返回的对象类型为list,是一个只有一个元素的list。
$ex2
a b c
1 2 3
# 使用逻辑值取list的子集
example_list[c(T,T,F)]
输出:
$ex1
[1] "1" "2" "a" NA

$ex2
a b c
1 2 3

通过上面的例子,我们可以发现,list 同样可以使用数字索引,字符串以及逻辑值来进行取子集的操作,与向量十分类似。对 list 中的元素进行删除操作也是一样的,可以通过负号-删除 list 中的部分元素。

⚠️ 重要小知识:查看对象类型
对象可以被分成不同的种类,如vector,list,data.frame,function等。这些是 R 语言本身就有的class。还可以在这些的基础上自己定义新的class,如ggplot2包的ggplot2对象。
由于不同的数据类型有着不同的行为,输入给同一个函数以后也有可能会触发完全不同的函数代码,因此弄清楚一个对象到底是什么类型是十分重要的。

#使用class()查看对象的类型
class(example_list[1])
输出:
[1] "list"
取出 list 内部的对象

通过前面的例子我们可以发现,单纯的使用[]list取子集,无论怎么取最终得到的始终是一个list,哪怕它的长度只有 1. 要打开list这个盒子,得到里面的对象,有两种办法:

  • 双中括号[[]]

    # 写list的索引时使用两层中括号即可得到内部的对象而非一个截短的`list`。
    example_list[[1]]
    输出:
    [1] "1" "2" "a" NA
    
    # 也可以用字符串索引。但是这时候不能使用逻辑值的索引了。
    example_list[['ex1']]
    输出:
    [1] "1" "2" "a" NA
    

    事实上,在上面的例子中,如果你试图使用单个逻辑值的索引,而且你恰巧使用的是T,比如example_list[[T]],是可以运行而不报错的。然而实际上,T被转换成了数字 1,因此等价于example_list[[1]]。而其他的逻辑值,如F或者试图加入多个逻辑值组成的vector,都会报错。

  • 美元符号$

    使用美元符号取出list内部的对象只适用于内部对象有字符串索引的情况。

    example_list$ex1
    输出:
    [1] "1" "2" "a" NA
    

    ⚠️ 注意,这种使用美元符号取list内部对象的方法要求你写出完整的内部变量名。因此如果你要取出的内部变量名存在一个变量里,你就无法使用这种方式。

    比如现在需要你写一个函数,根据用户的具体需求返回example_list中的ex1或者ex2的其中之一。使用readline()函数接收用户的输入,并将输入的得到的值储存在变量user_input内。假设最终user_input的值只会是"ex1"或者"ex2"的其中之一,现在你需要根据这个user_input的值去取出example_list中对应名字的对象。

    # 错误示范
    user_input<-readline(prompt = 'ex1 or ex2? please choose one of them:')
    #使用readline函数捕获用户输入。假设用户只会输入ex1或者ex2.
    example_list$user_input # 会返回NULL
    输出:
    NULL
    

    返回NULL是因为example_list$user_input这个操作本质是在example_list的内部变量中搜索名为user_input变量,而非我们想要的ex1.由于example_list中不存在user_input,因此返回了一个空值NULL.在这种情况下,必须使用[[来取,因为[[中的内容可以是一个变量。

    # 正确示范
    user_input<-readline(prompt = 'ex1 or ex2? please choose one of them:')
    #假设最终user_input的值为"ex1"
    example_list[[user_input]]
    输出:
    [1] "1" "2" "a" NA
    

    注意到了吗,在之前直接用"ex1"作为索引的例子中,代码是example_list[["ex1"]] ,而将"ex1"装在变量中以后,代码是example_list[[user_input]],没有引号了。

重要小知识:R 语言中的变量名 以及 符号函数

一般来说,R 语言的变量名需要遵循以下几条规则,否则会报错:

1,只能含有:大小写英文字母,数字,符号_.

2,不能以数字或下划线_开头。

3,不能含有横杠/减号-,空格" "等特殊符号。

4,可以含有.,甚至可以在开头,这点与 python 区别很大。

这些规则的存在目的主要就是为了消除歧义,让变量名能够与 R 中的一些符号操作区分开,比如不能使用-是因为如果变量名中带有-,比如a-b,既可以理解成一个叫"a-b"的变量也可以理解为变量a减去变量b.

⚠️ 但是!实际上这些规则都是可以被打破的。只需要加入一点魔法。 在赋值和调用的时候,使用反引号`将变量名的两边括起来就可以正常地使用不符合上面规定的变量名。比如以下这个五毒俱全的变量名:

`1-@\'._~!?` <- 1 # 給一个奇怪名字的变量赋值为1
print(`1-@\'._~!?`)
输出:
1

⚠️ 当你学会使用反引号以后,你会惊讶的发现,其实四则运算的符号+-*/,以及在对 list 取索引的时候用的[,$,甚至包括赋值用的<-=其实全都是函数!

class(`+`)
class(`[`)
输出:
[1] "function" #两个都是
print(`+`) # 尝试查看+的代码
输出:
function (e1, e2)  .Primitive("+")

由于这种符号函数大多是 R 最底层的一些操作,因此它们真正的代码很多都是用 C 写的,R 中留的只是一个调用的接口,显示为.Primitive("+")

也可以像正常函数一样调用,比如:

`+`(114000,514) # 114000+514
输出:
[1] 114514

至于它们为什么可以变成现在这样把参数放在函数两边,我还没编好,暂时不展开讲。

顺带一提,[是函数,但是]不是。

增加,删除,修改 list 中的对象

list是 R 语言中最灵活的数据结构,它的内部可以装任何东西,包括另一个list。因此经常会使用list存储一些不那么规整或者无法预期具体形式的数据。增加,删除,修改 list 中的对象都可以通过赋值操作来完成。

# 删除list中的一个对象:赋值为NULL
example_list[3]<-NULL # 任何上述的取子集的方法都可以,取了以后赋值为NULL
example_list$ex2<-NULL # $ 也可以。取出来,然后赋值
print(exmaple_list)
输出:
$ex1
[1] "1" "2" "a" NA

想要修改 list 中的某个元素,可以直接取出来,然后重新赋值。

example_list$ex1<-1:3
print(example_list)
输出:
$ex1
[1] 1 2 3

增加元素,直接取一个不存在的元素然后赋值.

example_list[["ex4"]]<-list(a=1,b=2)
print(example_list)
输出:
$ex1
[1] 1 2 3

$ex4
$ex4$a
[1] 1

$ex4$b
[1] 2

⚠️ 在对列表的内容进行增加,修改,删除等操作的时候,[[[的作用完全一直,不需要考虑取出来的是一个子列表还是列表中的内容的问题,修改的对象都是列表中的内容。

虽然正经但是好像不怎么重要的可有可无小知识:关于 R 中一些值的取出与修改

看了上面列表的操作方式,是不是觉得很方便?我可以直接将列表中的对象取出来使用example_list[[ex1]],想修改的时候只要用赋值修改这个取出来的东西就可以了,非常的直觉化。事实上,类似的操作逻辑在 R 语言中大量存在,比如names(), rownames(), colnames()等与名字相关函数,以及所有的索引操作。

example=c(a=1,b=2,c=3)
names(example) # 《你的名字》
输出:
[1] "a" "b" "c"
names(example)<-c('a1','b1','c1') # 改个名字
print(example)
输出:
a1 b1 c1
 1  2  3

但是你只要仔细想一想,就会发现有点不对劲。names(example)的结果已经不是example的 names 了,它是一个新生成的,和example本身脱离了关系的另一个对象。类似于我给你的照片上了美了颜,结果不但你的照片变漂亮了,连你本人也跟着变漂亮了。

其实,我们在使用names(example)查看名字,和使用names(example)<-c('a1','b1','c1')的时候,使用的是两个不同的函数。一个叫`names`,一个叫`names<-`。没想到吧.jpg

`names`
输出:
function (x)  .Primitive("names")
 `names<-`
function (x, value)  .Primitive("names<-")

一个函数专门用于取出值,一个函数专门用于修改值。但是由于长得一模一样,所以用户用起来是没有感觉的。包括上面的[其实也有个对应的赋值版本[<-。这也是为什么从列表中取出东西的时候,一个中括号和两个中括号有区别,但是赋值的时候一个中括号和两个中括号没区别的原因。

factor

factor是一种与vector十分相似的数据结构。在 R 语言中,尤其是绘图的时候会经常用到它,但是它也经常是各种 bug 的罪魁祸首。

创建一个factor
example<-factor(x=c('a','a','b','c'))
print(example)
输出:
[1] a a b c
Levels: a b c

相比于vectorfactor多了一个level的概念。一个factor中的元素种类是固定的,只能是levels中有的种类,或者NA。如果试图直接往其中添加新的元素会失败,并且变成NA

example[1]<-'d'
print(example)
输出:
Warning message:
In `[<-.factor`(`*tmp*`, 1, value = "d") :
  invalid factor level, NA generated

[1] <NA> <NA> b    c
Levels: a b c

从使用上来看,factor类似于由字符串组成的vector,哪怕是由数字组成的factor也一样。但factor的本质其实是数值型vector,但是它内部会储存一个类似于下表的对应关系。

1 1 2 3
显示为 "a" "a" "b" "c"

这样用数字来代替原本可能很长的字符串,比如用 1 和 2 代替"古典武侠","校园春色"。一个长度为 100 的这样子的factor,就可以将原本需要 100*4 个中文字符大小的内存空间占用缩减为 100 个数字+对应关系表的空间占用。

但事实上,在 R 中使用factor的时候大多不是为了减少内存占用,而是为了使用factorlevels的顺序。比如在使用ggplot2包绘图时,图中图例和 x,y 轴中元素的排布顺序,会跟对应变量的levels顺序有关。

重新定义 factor 的 levels 顺序
example<-factor(x=c('a','a','b','c')) # 重新变回aabc

example<-factor(x=example,levels=c('d','c','b','a'))
print(example)
输出:
[1] a a b c
Levels: d c b a

可以看到,example 的 levels 顺序被重新定义了。由于新的 levels 中还有'd',因此此时可以直接将其中的值修改为'd'

example[1]<-'d'
print(example)
输出:
[1] d a b c
Levels: d c b a

⚠️ 特别注意:

遇到纯数值组成的factor时,不要直接将它用as.numeric()函数转成数值。

# 错误示范
example<-factor(c(11.4,5.14,810))
as.numeric(example)
输出:
[1] 2 1 3
# 正确示范
example<-factor(c(11.4,5.14,810))
as.numeric(as.character(example))
输出:
[1]  11.40   5.14 810.00

由于factor在类型转换时比较僵硬,也容易产生各种预料之外的结果,因此如无必要不用factor

实用小知识:类型转换

除了上面例子中的as.numeric(),as.character()外,R 语言中还有很多其他的as.xxx函数,比如as.factor(),as.list(),as.data.frame()等等。当你需要将将一个对象转换成类似的数据结构的时候,都可以尝试一下在控制台中输入"as."然后按下tab键打开自动补全,找找有没有你需要的as.xxx()函数.但它们的具体效果必须查看对应函数的帮助文档以及自己试一下看看出来的东西对不对,千万不要无脑使用。

集合操作

解决实际问题的时候,对一维的vectorfactor最常用的操作主要有:

  • 统计每项个数table()
  • 保留唯一值unique()
  • 去重duplicated()
  • 合并c()
  • 向量中是否含有某个值%in%
  • 交集intersect()
  • 差集setdiff()
  • 长度length()
table() 统计每种元素的个数

table()函数虽然名叫table(),但它处理和返回的都是一维的数据。

example=c('a','b','b','c','c','c',NA,NaN)
table(example)
输出:
a b s
1 2 3

返回的是一个vector,abc是每一项的值。值得注意的是,table()有一个useNA参数,其值默认为no,在该模式下会自动忽略统计数据中的NANaN⚠️在出现问题排查错误使用table()的时候请务必把table()useNA参数设置为any或者always,因为NA值很多时候是错误产生的原因。

unique() 保留唯一值

unique()最常见的用途是快速查看某个vector或者factor中有哪些种类,并以此为基础写循环。

example=c('a','b','b','c','c','c',NA,NaN)
unique(example)
输出:
[1] "a"   "b"   "c"   NA    "NaN"
duplicated() 判断每一个值是否重复
example=c(1,1,2)
duplicated(example)
输出:
[1] FALSE  TRUE FALSE

返回一个逻辑值向量,第二次出现的值都返回TRUE

c()合并

前文讲过,不再赘述.注意c()是简单的合并,不会管其中的值是否重复.

%in%向量中是否含有某个值
example=c(1,1,2,3)
c(1,2) %in% example
输出:
[1] TRUE TRUE

%in%是一个逻辑判断符号.用法为x %in% y.其中的x可以是多个值,会按顺序返回每个值是否在y中存在.

intersect()交集

当你想要得到x,y两个集合中共有的元素时,可以使用intersect().⚠️注意,intersect()返回的值是不重复的.

x=c(1,1,2,3)
y=c(1,1,2,4)
intersect(x=x,y=y)
输出:
[1] 1 2
setdiff()差集

setdiff()intersect()类似,也有x,y两个参数.但与intersect()不同的是,setdiff()中的x和y并不能随便互换.setdiff()返回的是x中有而y中没有的值.⚠️注意,setdiff()返回的值也是不重复的.

x=c(1,1,2,3,3)
y=c(1,1,2,4)
setdiff(x=x,y=y)
输出:
[1] 3
length()长度

length()可以对所有的一维对象使用,包括vector,factor,list等.返回的都是这个一维对象有多长.

x=c('a','b','c')
length(x)
输出:
[1] 3

一维对象是可以有0长度的.

x=list()
length(x)
输出:
[1] 0

对于部分二维对象,length()函数也会生效,返回的内容根据具体对象种类而定.对于二维对象,一般使用与length()类似的dim()函数来获取它的长与宽.

二维对象

data.frame (数据框)

df (data.frame)是 R 语言中最常用的数据类型,可以简单地理解为是一个 excel 表格。df 的各种操作必须熟练掌握到能替代 excel 的程度。

创建一个data.frame
example<-data.frame(
  row.names=c('a1','b2','c3'),
  number=1:3,
  alphabet=c('a','b','c'),
  language='english'
)
print(example)
输出:
   number alphabet language
a1      1        a  english
b2      2        b  english
c3      3        c  english

data.frame以列为单位进行组织,每一列可以看作的一个vector,要求一列中的每一个值类型相同,不同列之间的类型可以不同。data.frame()本质是多个长度一致的vector组成的list,因此大部分list的操作都可以用在data.frame上。 上述代码中的row.namesdata.frame()函数中的一个参数,指定了df的行名是什么。后面的每一项都是列名=值的格式。