MOOC-R语言数据分析-听课笔记-2

2020-05-15

疫情居家起间,学习了北京邮电大学 艾新波老师的MOOC 课程之R语言数据分析,十分精彩,特整理听课笔记,便于回顾,也飨读者。全课程共有三篇笔记,本篇为第二篇。

数据科学最令人着迷的地方:一旦进行量化,看似风马牛不相及的事物或属性,经过数学运算,居然可以画上等号,刻画各种各样的规律。

R的知识体系

R语言的知识体系:基础编程+数据对象 基础编程:R的运行机制及编程环境,代码的组成,扩展包,函数的调用,基本控制流,编写函数,二元操作符,泛型函数。 数据对象:向量,因子,矩阵,数组,列表,数据框。

R专用IDE:RStudio

先安装R,然后再安装Rstudio。

  1. 从CRAN 下载R并安装
  2. 下载Rstudio并安装 建议将R安装在C://Program Files文件夹下,安装完之后将library文件夹权限设置为完全读写 Rstudio建议下载免安装版(即zip文件),可解压缩在D盘适当位置,并且注意解压路径不能含有中文。 (但是根据我的经验,我还是觉得下载安装版的.exe比较好,因为用免安装版时,发现打开project之后不能自动加载出对应工作目录的文件)

R编程,站在巨人的肩膀上

R编程,用别人的包和函数讲述你自己的故事。

重点是如何找到需要的包,推荐一下四种方法:

  1. 搜索引擎搜索, bing, baidu
  2. 逛论坛找答案,Stackoverflow,搜索时,最好在问题后面加一个[R],就表示是R语言问题
  3. SOS扩展包,使用findFn()函数进行模糊查找。
  4. 按图索骥,Task views,或许是最正统的,可以鸟瞰R的全景,比如task, machine learning and stastics learning.
  5. 专注于某个领域,日积月累。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. 采用1 ~ n的正整数来指定,n为向量长度。下标可以重复,顺序可以变。
  2. 采用负整数,反向选出某些元素,就是丢掉某些元素
  3. 逻辑下标,要求提供的逻辑下标向量是和要查看的向量的长度是一样的。
  4. 通过元素的名称访问响应的子集

向量的基本操作

向量排序:

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中都可以实现。

r-matlab

数组

矩阵本质上是二维的数组,数组一般就是说高纬数组了,一般三维数组使用频率最高。

彩色图像,就是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中数据预处理的目标。

发现数据背后的规律,很大程度上就是发现数据框背后的规律。

最主要的关联、分类、聚类分析都蕴藏在数据框的数据中。

一切都是关系结构,关系表几乎可以上升为一个数学概念。