疫情居家起间,学习了北京邮电大学 艾新波老师的MOOC 课程之R语言数据分析,十分精彩,特整理听课笔记,便于回顾,也飨读者。全课程共有三篇笔记,本篇为第二篇。
数据科学最令人着迷的地方:一旦进行量化,看似风马牛不相及的事物或属性,经过数学运算,居然可以画上等号,刻画各种各样的规律。
R的知识体系
R语言的知识体系:基础编程+数据对象 基础编程:R的运行机制及编程环境,代码的组成,扩展包,函数的调用,基本控制流,编写函数,二元操作符,泛型函数。 数据对象:向量,因子,矩阵,数组,列表,数据框。
R专用IDE:RStudio
先安装R,然后再安装Rstudio。
- 从CRAN 下载R并安装
- 下载Rstudio并安装 建议将R安装在C://Program Files文件夹下,安装完之后将library文件夹权限设置为完全读写 Rstudio建议下载免安装版(即zip文件),可解压缩在D盘适当位置,并且注意解压路径不能含有中文。 (但是根据我的经验,我还是觉得下载安装版的.exe比较好,因为用免安装版时,发现打开project之后不能自动加载出对应工作目录的文件)
R编程,站在巨人的肩膀上
R编程,用别人的包和函数讲述你自己的故事。
重点是如何找到需要的包,推荐一下四种方法:
- 搜索引擎搜索, bing, baidu
- 逛论坛找答案,Stackoverflow,搜索时,最好在问题后面加一个[R],就表示是R语言问题
- SOS扩展包,使用findFn()函数进行模糊查找。
- 按图索骥,Task views,或许是最正统的,可以鸟瞰R的全景,比如task, machine learning and stastics learning.
- 专注于某个领域,日积月累。R的真正精通,需要不断积累总结。
caret 包,是机器学习与数据挖掘中强烈推荐的,其中支持的分类回归模型达200多种。分类回归的模型,其体量就是数以百计的。
对于某个扩展包中的具体某个函数的使用,利用好帮助文档。比如,对于c()函数,
## R基础编程:控制流
任何复杂的逻辑都是三种结构
* 顺序结构:代码一行一行逐行执行(四则运算,逻辑运算,均可以向量化运算)
* 分支结构:
* 循环结构
分支结构:
```R
if(){
}else if(){
} else{
}
ifelse(condition,
yes,
no)
all()函数、any()函数,就是向量化的逻辑运算函数
需要注意的是,else 必须要与上一个花括号在同一行,else语句不能单起一行。
注意,if中的条件判定结果只能是一个标量,如果传入一个向量的逻辑向量,那么R会只取向量的第一个值。
循环结构,R中有三种循环
for,枚举型循环
while,有条件的循环
repeat,无条件循环,
两种语句实现控制循环,break, next
斐波那契序列的实现: 1,1,2,3,5,8,13,21,34,55…
# 列举前16个序列数,可以使用for 循环
n_fib <- 16
fib <- numeric(n_fib)
fib[1:2] <- c(1,1)
for(i in 3:n_fib){
fib[i] <- fib[i-1] + fib[i-2]
show(fib[i])
}
# 求1000以内的斐波那契序列,此时不确定要枚举多少,所以使用while循环
fib <- c(1,1)
while(sum(tail(fib, 2)) < 1000){
fib <- c(fib, sum(tail(fib, 2)))
}
# repeat循环,需要定义终止条件,表示直到满足某个条件时break
fib <- c(1,1)
repeat{
if((sum(tail(fib, 2))>= 1000){
break
}
fib <- c(fib, sum(tail(fib, 2)))
}
再说向量化
尽量不要使用显示循环
能用向量化运算的,尽量用向量化运算
# 举例比较1亿数字加和的时间,向量化运算和for循环
x <- 1:1e8
y <- 2:(1e8+1)
z <- integer(1e8)
system.time(z <- x + y, gcFirst = TRUE)
## 显示耗时0.45s
# use for loop
system.time({
for (i in 1:1e8){
z[i] <- x[i] + y[i]
}
}, gcFirst = TRUE)
## 显示耗时11.7s
记住,只要串行模式不是必须的,就是说下一轮的循环不受上一轮的影响,那么就应该使用并行运算。
R里面的并行作业
- 向量化函数
- split - apply - combine 数据处理模式: 如apply函数族,以及tidyverse::group_by + summerize函数
- 专门的并行主题, task views High-performance and parallel computing with R
R基础编程:函数I
事不过三的原则
只要代码粘贴达到3次时,就应该写一个函数进行实现。因为反复的拷贝粘贴,造成的忘记修改某一个参数时,这种非语法错误的代码很难调试。
编写函数
fun_name <- function(arg1, arg2 = default, ...){
#注释
表达式
return(value)
}
-
R里面一切皆是对象,对象通过赋值来产生,函数也不例外。
-
函数声明的关键字是function,function 的返回值就是函数。
-
参数列表是以逗号分隔,函数主体可以是任何合法的R表达式
-
若没有return语句,最后一个表达式的值就作为返回值。
- 以fun_name(arg1, arg2, …)的形式调用函数。
- 如果不跟(),就是显示函数这个变量的内容
位置参数 和 名义参数
frm <- function(name, frm = 'Beijing'){
cat(name, ' is from ', frm)
}
R中既有位置参数, 也有名义参数。
特殊的参数,比如对于c()函数, sum()函数,打开这两个函数的信息,可以参数看到是 …
my_func <- function(...){
cat("The second argument is ", ..2) # ..2就是表示提取第二个参数,可以依次类推
dot_args <- list(...)# 通过list来捕捉任意参数
message("\nThe sum is", sum(dot_args[[1]], dot_args[[5]]))
}
my_func(1, 'arg2', 3,4,5,6,7,8)
# 输出结果
# The second argument is arg2
# The sum is 6
还有一些熟悉的函数,作为函数的二元操作符, 加减乘除,%in% 等都是函数
自己定一个二元操作符函数,求直角三角形的斜边长度(a, b为直角边,c为斜边)
%ab2c% <- function(a,b){
sqrt(sum(a^2, b^2))
}# 没有return语句,就是返回最后一行语句的值
3 %ab2c% 4
# 输出结果
# 5
到这里,也可以理解purr包中的管道操作符 %>%,其实它也是一个二元操作符函数。
注意的是,二元操作符函数,在查询其函数使用文档时,需要用引号将其引起来,比如?”+”, ?”-“。
R基础编程:函数II
泛型函数
因为在使用R时,比如plot()函数时,你发现传入不同的数据类型,会生成不同的结果,也就是同一个函数,在接收不同的对象时,有不同的函数行为,这就是泛型函数。
泛型函数,其实是一组函数。在定义时可以明显发现是同一个interface,但是其实是一组函数,使用了分发机制。
编写泛型函数,理解泛型函数的定义和调用过程
interface <- function(x, y){
message("Single interface")
UseMethod("particular", y)
}
particular.classA <- function(x, y){
message("Different behavior: classA")
}
particular.classB <- function(x, y){
message("Different behavior: classB")
}
particular.default <- function(x, y){
message("Different behavior: default")
}
x <- 1:10
y <- 1:20
class(y) <- "classA"# 给y贴上标签- classA
interface(x, y)
#> Single interface
#> Different behavior: classA
class(y) <- "classB" # 给y贴上标签- classB
interface(x, y)
#> Single interface
#> Different behavior: classB
class(y) <- "classC" # 给y贴上标签- classC
interface(x, y)
#> Single interface
#> Different behavior: default
class(y) <- NULL # 或者撕掉y 的标签
interface(x, y)
#> Single interface
#> Different behavior: default
从上面就可以看出,同一个interface接口,可以有不同的行为。并且可以看出,R中的类,并不是说数据的存储类型,而只是一种标签。
重新审视 + 这个函数
> methods("+")
[1] +.Date +.POSIXt
see '?methods' for accessing help and source code
> library(ggplot2)
> methods("+") # 同一条语句,不同的结果
[1] +.Date +.gg* +.POSIXt
see '?methods' for accessing help and source code
所以可以,进一步看 “+.gg” 这个函数的内容。
类似于“+.gg”, 可以定义自己的+
"+onlyFirst" <- function(a, b){
return(a[1] + b[1])
}
a <- 1:5
a + 6:10
#> [1] 7 9 11 13 15
class(a) <- "onlyFirst" # 给a贴上一个类标签, onlyFirst
a + 6:10
#> [1] 7 # 输出结果就只有7了,因为这个函数只计算第一个元素的加和
递归函数
再看斐波那契序列,可以通过递归的思想来实现。层层递进,逐层回归。
递归函数,都一个触底的点。
fib <- function(n){
if(n ==1){
return(1)
} else{
return(c(fib(n-1),sum(tail(fib(n-1), 2)))) # 这里没有对n=2的情况进行触底,是因为tail函数在调用时,如果只有一个值,tail(fib, 2) 就直接返回这个值了
}
}
fib(10)
R数据对象:向量与因子
R数据对象,分为三组,六类。
单一属性特征:向量与因子进行存储
数据的材质:也是6种。参考文档,?atomic
logical, 逻辑型
integer, 如1L, 300L,整型
numeric, 1, 3.14, 双精度型
complex, 1+2i
character, ‘hello, world’
raw(原始字节数据,如b0, ac, d0, c2, b2, a8).
向量
创建向量:c()函数,不能有混合类型,否则会强制进行转换为同一类型。
不存在包含向量的向量,R会一律拆包展开。
创建向量: vector(“numeric”, 8) ,实现知道数据类型和向量长度进行创建
> (x1 <- vector("numeric", 8))
[1] 0 0 0 0 0 0 0 0
> (x2 <- numeric(8))
[1] 0 0 0 0 0 0 0 0
> (x3<- character(8))
[1] "" "" "" "" "" "" "" "" #默认是空字符
> (x4 <- vector(len = 8))
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
> (x5 <- logical(8))
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE # FALSE 就是默认的0
# 等差数列, seq()
> seq(from = 1, to = 20, by = 2)
[1] 1 3 5 7 9 11 13 15 17 19
> seq(from = 20, to = 1, by = -2)
[1] 20 18 16 14 12 10 8 6 4 2
> seq(from = 1, to = 20, len = 10) # 设置长度个数len 参数
[1] 1.000000 3.111111 5.222222 7.333333 9.444444 11.555556 13.666667
[8] 15.777778 17.888889 20.000000
# 显然,此时的by = (to - from)/(len -1)
# 产生随机数向量,sample()
sample(10)
set.seed(2012) #设定随机数种子
sample(10) # 无放回抽样
re_sample <- sample(1:100, 100, replace = TRUE) # 有放回抽样
访问向量
[] 来指定
- 采用1 ~ n的正整数来指定,n为向量长度。下标可以重复,顺序可以变。
- 采用负整数,反向选出某些元素,就是丢掉某些元素
- 逻辑下标,要求提供的逻辑下标向量是和要查看的向量的长度是一样的。
- 通过元素的名称访问响应的子集
向量的基本操作
向量排序:
sort()函数, 返回的是排序后的向量,可以设置decreasing = TRUE
order()函数,order函数,返回的是下标。
rev()函数,向量逆序
并且c(),向量,其实就是数学中的向量,它满足数学向量的运行和运算含义。
因子
测量的尺度:
类型 | 量化尺度 | 举例 | 数学性质 | R数据对象 |
---|---|---|---|---|
定类 | 是否相同 | 性别、人种 | 等于 不等于 | 无序因子 (因子) |
定序 | 比较大小 | 等级、规模 | 大于 小于 | 有序因子(因子) |
定距 | 确定差别 | 温度、时刻 | 加 减 | 数值向量(向量) |
定比 | 确定比例 | 长度、薪资 | 乘 除 | 数值向量(向量) |
向量用于存储数值变量(定距 定比)
因子用于存储类别变量(定类 定序)
第一种创建因子方法: factor()函数
xb <- c('F','M','F','M')
xb <- factor(xb)
xb <- factor(xb, levels = c('M','F','Lost'))
typeof(xb)
# integer # 说明内存中存的还是整型,并且其实就是1, 2 等整数。
nlevels(xb)
levels(xb)
因子在内存中存储的是整数,见下面的示例
> number_factors <- factor(c(10,20,20,20,10))
> mean(number_factors)
[1] NA
Warning message:
In mean.default(number_factors) : 参数不是数值也不是逻辑值:回覆NA
> mean(as.numeric(number_factors))
[1] 1.6
> as.numeric(number_factors)
[1] 1 2 2 2 1
> mean(as.numeric(as.character(number_factors)))
[1] 16
> mean(as.numeric(levels(number_factors)[number_factors]))
[1] 16
> levels(number_factors)
[1] "10" "20"
> levels(number_factors)[number_factors]
[1] "10" "20" "20" "20" "10"
因子,可以创建为有序的。有序的因子就可以比较大小
> score <- factor(c('Best', "Good", "Bad"), ordered = TRUE)
> score[1] > score[2]
[1] FALSE
> score[1] < score[2]
[1] TRUE
> score[1]
[1] Best> score <- factor(c('Best', "Good", "Bad"), ordered = TRUE)
> score[1] > score[2]
[1] FALSE
> score[1] < score[2]
[1] TRUE
> score[1]
[1] Best
Levels: Bad < Best < Good
> score <- factor(c('Best', "Good", "Bad"), ordered = TRUE, levels = c("Bad","Good","Best")) # 这个levels如果不设置的话,R 其实是默认按照编码顺序排的,比如字母表或者拼音
> score[1] > score[2]
[1] TRUE
> score[1]
[1] Best
Levels: Bad < Good < Best
Levels: Bad < Best < Good
第二种创建因子方法:R中通过cut()函数分箱,对数据向量进行分组,来实现创建因子。以成绩划分为五分制进行经典示例。数据分箱 + 最小区间为闭区间 + 左开右闭 + 有序因子 + 标签
> yw <- c(94, 87, 92, 91, 85, 92)
> # 数据分箱 + 最小区间为闭区间 + 左开右闭 + 有序因子 + 标签
> yw5 <- cut(yw, breaks = c(0, (6:10)*10), include.lowest = TRUE, right = FALSE, ordered_result = TRUE, labels = c("不及格","及格","中","良","优"))
> yw5
[1] 优 良 优 优 良 优
Levels: 不及格 < 及格 < 中 < 良 < 优
R数据对象:矩阵与数组
对观测对象的多个属性同时进行记录(多变量)
若这些数据是同质的,宜采用矩阵作为一个整体进行存储。
矩阵,是二维的
数组是多维的。
matrix(c()) 按照列优先的方式进行填充。如果数据要按行进行填充,则设置byrow = TRUE.
子集的访问依然是通过[],由于矩阵是二维的,所以需要’,’分别指定行和列。
diag(3)生成单位矩阵
solve()函数,可以解方程组
强烈推荐R与matlab对照的一个pdf,基本上matlab中可以实现的功能,R中都可以实现。
数组
矩阵本质上是二维的数组,数组一般就是说高纬数组了,一般三维数组使用频率最高。
彩色图像,就是RGB,就是存储为三维数组的。其实图片的操作,本质上都是数组的操作。
操作数组就可以实现,对图像的操作,比如更改图像颜色,“图像泛黄处理”,以及在特定区域添加马赛克。
R数据对象:数据库与列表
数据的属性不是同质的。
列表
列表,本质就是对象的有序集合。列表最为灵活,最具有包容性,对所包含的对象没有限制,可以是不同的类型,不同的长度。
创建列表list()
访问列表:$, [], [[]]. 单个方框号提出来的数据,依然是一个列表。两个方框号才进入包装箱的内部。
更改列表元素,也是直接赋值就可以。
删除一个列表元素,就是对该列表元素赋值NULL 就可以。
对列表的每一个组成部分,执行某种操作,使用的是lapply()函数。lapply()函数返回的还是一个列表,所以可以再用unlist()将列表结果展平。这两步操作,可以直接用sapply()函数,来实现。
数据框
是最核心的R语言中的数据对象类型,绝大部分的时间,就是把既有的数据转换成数据框,进而以数据框为基础,开展数据探索和数据建模。
数据框,是R中最美好的数据对象。
数据框形式上是矩阵,本质上是列表。
与数据库关系表,excel中的sheet相近。
列是变量,属性,特征,维度
行是记录,观测值,是数据空间中的一个点
创建数据框
data.frame()
因为本质上是列表,所以列表的访问方式,对data.frame都适用。不过一般用$,或者单方括号访问。还有矩阵的访问方式,对数据框也都适用。
矩阵中的cbind(), apply(),也可以对数据框使用。
查看数据, head(), tail(), str(), summary()
机器学习是一个举三反一的过程,所以一般嘛,就是训练集70%,测试集30%,二者三七开。
人人都爱tidyverse
tidyverse 是一个扩展包的套装
library(tidy)即加载下下述八个包。
ggplot2, dplyr, tidyr, readr, purr, tibble, stringr, forcats
管道操作符,
lhs %>% rhs
lhs 是value, rhs是函数
表达式 | 用法 |
---|---|
x %>% y | f(x) |
x %>% f(y) | f(x, y) |
y %>% f(x, .) | f(x, y) |
z %>% f(x, y, arg = .) | f(x, y, arg = z) |
x %>% f %>% g %>% h | h(g(f(x))) |
更多用法参见 ?magrittr:: %>%
dplyr: data manipulation
select()
mutate()
filter()
arrange()
summarize(),一般和group_by函数一起使用
cjb %>%
mutate_at(vars(bj, xb, wlfk), factor) %>%
mutate(zcj = rowSums(.[4:12])) %>%
arrange(desc(zcj)) %>%
tail(n = 2)
如果想让上述修改生效,一种方法是上述代码中,第一个%>%替换为%<>%,第二种方法是,在开始的第一行加一句赋值语句。如下代码所示(cjb 就是成绩表的例子,bj 班级,xb 性别,wlfk 文理分科):
cjb %<>%
mutate_at(vars(bj, xb, wlfk), factor) %>%
mutate(zcj = rowSums(.[4:12])) %>%
arrange(desc(zcj)) %>%
tail(n = 2)
# 上述代码和下面的代码等价
cjb <- cjb %>%
mutate_at(vars(bj, xb, wlfk), factor) %>%
mutate(zcj = rowSums(.[4:12])) %>%
arrange(desc(zcj)) %>%
tail(n = 2)
对行的过滤操作,filter()函数
# 显示语文成绩不及格的同学
cjb %>%
filter(yw < 60)
# 显示有一科成绩不及格的同学
cjb %>%
filter_at(vars(4:12), any_vars(.<60))
分组统计功能
基于已有的分组变量,或者基于新生成的分组变量
# 举例,分组看一下男生 女生的总成绩有无差别
cjb %>%
filter(zcj != 0) %>%
group_by(xb) %>%
summarize(count = n(),
max = max(zcj),
mean = mean(zcj),
min = min(zcj))
# 举例,另外一种分组的情况,需要做数据的长宽变化的情况,就是看所有学生的不同科目的成绩有没有差别,就是按科目进行汇总统计
# 长宽变化,使用tidyr中的gather() 和spread() 函数
cjb %>%
filter(zcj != 0) %>%
gather(key = ke_mu, value = cheng_ji, yw:sw) %>%
group_by(ke_mu) %>%
summarize(max = max(cheng_ji),
mean = mean(cheng_ji),
median = median(cheng_ji),
min = min(zcj)) %>%
arrange(mean)
最美不过数据框
数据框,是R中数据预处理的目标。
发现数据背后的规律,很大程度上就是发现数据框背后的规律。
最主要的关联、分类、聚类分析都蕴藏在数据框的数据中。
一切都是关系结构,关系表几乎可以上升为一个数学概念。