教材介绍:https://book.douban.com/subject/26757974/
相关资源:https://github.com/hadley/r4ds
CHPT18 - Model Basics with modelr
(1)随机参数拟合
本章介绍线性模型的拟合,首先使用最简单粗暴的方法:随机参数拟合,来理解模型的本质。以下为探索过程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| library(tidyverse)
library(modelr)
# 系统自带的模拟数据
=======================
> sim1
# A tibble: 30 x 2
x y
<int> <dbl>
1 1 4.20
2 1 7.51
3 1 2.13
4 2 8.99
5 2 10.2
# ... with 26 more rows
=======================
# 定义随机参数(250 组截距和斜率)
rand_coef <- tibble(
itc = runif(250, -20, 40),
slp = runif(250, -5, 5)
)
ggplot(sim1, aes(x, y)) + geom_abline(aes(intercept=itc, slope=slp),
data=rand_coef,
alpha=1/4) + geom_point()
# 衡量模型误差(最小二乘法)
measure_distance <- function(itc, slp, data){
diff <- data$y - itc - data$x * slp
sqrt(mean(diff^2))
}
# 计算每组系数的误差
distances <- 1:250
for (i in seq_along(distances)){
distances[i] <- measure_distance(rand_coef[['itc']][i],
rand_coef[['slp']][i],
sim1)
}
rand_coef_dist <- rand_coef %>% mutate(dist=distances)
# 取误差最小的前十组
ggplot(sim1, aes(x,y)) +
geom_point(size=2, color="grey30") +
geom_abline(aes(intercept=itc, slope=slp, color=-dist),
data=filter(rand_coef_dist, rank(dist) <= 10)
)
|
(2)系统拟合方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 使用 lm 函数一步完成最小二乘线性拟合
sim1_mod <- lm(y~x, data=sim1)
==========================
> coef(sim1_mod)
(Intercept) x
4.220822 2.051533
==========================
# 计算模型残差和预测值
sim1 <- sim1 %>% add_residuals(sim1_mod) %>% add_predictions(sim1_mod)
# 可视化拟合结果
ggplot(sim1, aes(x)) +
geom_point(aes(y=y)) +
geom_line(aes(y=pred), color="green", size=1) +
geom_line(aes(y=resid), color="red")
|
(3)其他模型拟合
lm(y~x1+x2, data)
lm(y~x1*x2, data)
lm(y~ns(x, n), data)
model_matrix(data, y~x^2+x)
model_matrix(data, y~I(x^2)+x)
model_matrxi(data, y~poly(x, 2))
(4)其他模拟拟合函数
- 广义线性模型:
stats::glm()
- 广义可加模型:
stats::gam()
- 惩罚线性模型:
glmnet::glmnet()
- 鲁棒线性模型:
MASS::rlm()
- 决策树模型:
rpart::rpart()
CHPT19 - Model Building
上一章通过模拟数据了解模型拟合函数,本章使用实际数据进行应用。迭代以下分析流程:
1
2
3
4
5
| library(modelr)
library(tidyverse)
library(lubridate)
library(nycflights13)
options(na.action = na.warn)
|
(1)钻石价格与品质的关系
1
2
3
4
5
6
7
8
9
| # 首先进行可视化分析,查看每个品质变量与价格的相关关系
ggplot(diamonds, aes(cut, price)) + geom_boxplot()
ggplot(diamonds, aes(color, price)) + geom_boxplot()
ggplot(diamonds, aes(clarity, price)) + geom_boxplot()
# 可以看到在前三个变量中,品质最高的分组都不是均价最高的
# 原因可能是由于低品质的钻石往往重量更大
# 因此需要对重量与价格的关系进行建模分析
ggplot(diamonds, aes(carat, price)) + geom_hex(bins=50, color="grey30")
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| # 观察 carat 与 price 的相关趋势,接近指数关系,因此转为对数坐标系
diamonds <- diamonds %>% filter(carat < 2.5) %>%
mutate(lprice=log2(price), lcarat=log2(carat))
# 在进行可视化,发现对数化之后呈现线性关系
# 进行线性回归
mod <- lm(lprice~lcarat, data=diamonds)
# 添加预测值和残差
diamonds <- diamonds %>%
add_predictions(mod, 'lprice_pred') %>% mutate(price_pred=2^lprice_pred) %>%
add_residuals(mod, 'lprice_resid')
===============================================================================
> diamonds[c('carat', 'price', 'lprice', 'lcarat', 'lprice_pred', 'price_pred', 'lprice_resid')] %>% head(5)
# A tibble: 5 x 7
carat price lprice lcarat lprice_pred price_pred lprice_resid
<dbl> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
1 0.23 326 8.35 -2.12 8.63 396. -0.279
2 0.21 326 8.35 -2.25 8.41 340. -0.0587
3 0.23 327 8.35 -2.12 8.63 396. -0.275
4 0.290 334 8.38 -1.79 9.19 584. -0.807
5 0.31 335 8.39 -1.69 9.35 654. -0.964
===============================================================================
# 可视化模型结果
ggplot(diamonds, aes(carat, price)) + geom_hex(bins=50) +
geom_line(aes(carat, price_pred), color="green", size=1)
# 残差值的分布
ggplot(diamonds, aes(lcarat, lprice_resid)) + geom_hex(bins=50)
|
1
2
3
4
5
6
| # 残差中去除了重量与价格的相关性,接下来查看品质变量与价格的关系
ggplot(diamonds, aes(cut, lprice_resid)) + geom_boxplot()
ggplot(diamonds, aes(color, lprice_resid)) + geom_boxplot()
ggplot(diamonds, aes(clarity, lprice_resid)) + geom_boxplot()
# 可以看到在残差中,品质因子与价格变量表现出合理的相关性
|
1
2
3
4
| # 直接使用多变量模型进行拟合
mul_mod <- lm(lprice~lcarat+color+cut+clarity, data=diamonds)
diamonds %>% add_predictions(mul_mod)
# 分析略
|
(2)影响航班每日数量的因素
1
2
3
4
| # 计算每日航班数量的分布
daily <- flights %>% mutate(date=make_date(year, month, day)) %>%
group_by(date) %>% summarise(count=n())
ggplot(daily, aes(date, count)) + geom_line()
|
1
2
3
4
5
6
7
8
9
10
11
12
| # 可视化呈现了与星期有关的周期性
daily <- daily %>% mutate(wday=wday(date, label = T))
ggplot(daily, aes(wday, count)) + geom_boxplot()
# 可以看到周末的航班最少,建模移除以上相关性
mod <- lm(count~wday, data=daily)
grid <- daily %>% data_grid(wday) %>% add_predictions(mod, "count")
ggplot(daily, aes(wday, count)) + geom_boxplot() +
geom_point(data = grid, size=4, color="red")
# 添加残差
daily <-daily %>% add_residuals(mod)
daily %>% ggplot(aes(date, resid)) + geom_ref_line(h=0) + geom_line()
|
1
2
3
4
5
6
7
8
9
| # 可以看到残差里面还遗留了模型没有捕捉到的周期性:
ggplot(daily, aes(date, resid, color=wday)) + geom_ref_line(h=0) + geom_line()
# 按wday分组可以更加清晰地观测到残差里面的模式:
# 例如周六的航班在夏天明显比较多,在秋天比较少
# 某些特定的时间航班会特别多或者特别少,例如某些重要节日
daily %>% filter(resid < -100)
# 图中还存在一个长期的趋势,可以用 geom_smooth 平滑化显示出来
daily %>% ggplot(aes(date, resid)) + geom_ref_line(h=0) +
geom_line(color="grey50") + geom_smooth(se=FALSE, span=0.2)
|
CHPT20 - More Models with purrr and broom
处理大量数据的三种思路:
- 使用多个简单的模型理解复杂的数据
- 使用列向量在 data.frame 里储存任意的数据结果(nested data.frame)
- 使用 broom 包整理数据
示例:分析 gapminder 数据集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| library(modelr)
library(tidyverse)
library(gapminder)
# 提出问题:每个国家的 LifeExpectancy 随时间的变化趋势
# 解决思路:参考上一章,通过模型拟合去除线性相关性,最后进行残差分析
# 取新西兰的数据进行分析
new_zealand <- gapminder %>% filter(country == 'New Zealand')
new_zealand %>% ggplot(aes(year, lifeExp)) + geom_line() + ggtitle("Full data =")
# 建模,得到预测值和残差
mod <- lm(lifeExp~year, data=new_zealand)
new_zealand %<>% add_predictions(mod) %>% add_residuals(mod)
new_zealand %>% ggplot(aes(year, pred)) + geom_line() + ggtitle("Linear trend + ")
new_zealand %>% ggplot(aes(year, resid)) + geom_line() + ggtitle("Remaining pattern")
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| # 将上述过程打包成函数,利用 purrr::map() 对每个国家重复类似的操作
# 首先对不同国家的数据进行分组
group <- gapminder %>% group_by(country, continent) %>% nest()
===========================================
> group %>% head(5) # nest():将组内数据打包
# A tibble: 5 x 3
country continent data
<fct> <fct> <list>
1 Afghanistan Asia <tibble [12 x 4]>
2 Albania Europe <tibble [12 x 4]>
3 Algeria Africa <tibble [12 x 4]>
4 Angola Africa <tibble [12 x 4]>
5 Argentina Americas <tibble [12 x 4]>
>
> group$data[[1]] %>% head(5)
# A tibble: 5 x 4
year lifeExp pop gdpPercap
<int> <dbl> <int> <dbl>
1 1952 28.8 8425333 779.
2 1957 30.3 9240934 821.
3 1962 32.0 10267083 853.
4 1967 34.0 11537966 836.
5 1972 36.1 13079460 740.
===========================================
# 打包建模函数
country_model <- function(df){
lm(lifeExp~year, data=df)
}
# 对每个国家进行建模
models <- purrr::map(group$data, country_model)
group <- group %>% mutate(models=models)
# 计算每个国家的预测值和残差
group <- group %>% mutate(resid=map2(data, models, add_residuals))
group <- group %>% mutate(pred=map2(data, models, add_predictions))
# 为了进行残差分析,需要把 nested 数据框转换成标准数据框
resid<-unnest(by_country,resid)
# 可视化
resid %>% ggplot(aes(year, resid)) +
geom_line(aes(group=country), alpha=1/3) + geom_smooth(se=FALSE)
# 可以看到部分国家的残差波动较大,进一步可视化每个 continent 的情况
resid %>% ggplot(aes(year, resid)) +
geom_line(aes(group=country), alpha=1/3) + facet_wrap(~continent)
# 结果显示非洲和亚洲的拟合效果较差
|
除了残差,其他判断模型质量的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # 使用 glance 函数度量模型效果
==========================================================================
> broom::glance(mod)
# A tibble: 1 x 11
r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC
<dbl> <dbl> <dbl> <dbl> <dbl> <int> <dbl> <dbl> <dbl>
1 0.954 0.949 0.804 205. 5.41e-8 2 -13.3 32.6 34.1
# ... with 2 more variables: deviance <dbl>, df.residual <int>
==========================================================================
# 计算所有模型的统计参数
glances <- group %>% mutate(glances=map(models, broom::glance)) %>%
unnest(glances, .drop=T) # 舍弃除 glances 的其他列表
# 通过对R—squared 进行排序找到拟合不够好的国家模型
glances %>% arrange(r.squared)
# 将所有国家的模型拟合度可视化
# 由于观测值相对小,小于1,而且是一个离散变量,因此使用jitter
glances %>% ggplot(aes(continent, r.squared)) + geom_jitter(width = 0.5)
# 取拟合度差的子集
bad_fit <- filter(glances, r.squared<0.25)
gapminder %>% semi_join(bad_fit, by="country") %>%
ggplot(aes(year, lifeExp, color=country)) + geom_line()
# 1992-卢旺达-艾滋病爆发
|
关于 nested data.frame 的细节补充
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 默认情况下,传入的列表,会被拆成向量分别放到每一列
data.frame(x=list(1:3,2:4))
# 创建 nested data.frame
# 使用 tidyr::nest()
gapminder %>% group_by(country, continent) %>% nest()
gapminder %>% nest(year:gdpPercap) # nesting year ~ gdpPercp
# 通过向量化函数--mutate()
df <- tribble(~x1, "a,b,c", "d,e,f,g")
df <- df %>% mutate(x2=stringr::str_split(x1, ","))
df %>% unnest()
# 通过 tribble 函数和 map
sim <- tribble(
~fun, ~params,
"runif", list(min=-1,max=1),
"rnorm", list(sd=5),
"rpois", list(lambda=10)
)
sim %>% mutate(sims=invoke_map(fun, params, n=10))
|
CHPT21 - R Markdown
简介
R Markdown provides a unified authoring framework for data science, combining your code, its results, and your prose commentary. R Markdown documents are fully reproducible and support dozens of output formats, like PDFs, Word files, slideshows, and more. R Markdown files are designed to be used in three ways:
- For communicating to decision makers, who want to focus on the conclusions, not the code behind the analysis.
- For collaborating with other data scientists (including future you!), who are interested in both your conclusions, and how you reached them (i.e., the code).
- As an environment in which to do data science, as a modern day lab notebook where you can capture not only what you did, but also what you were thinking.
_.Rmd 文件由以下三部分组成
- An (optional) YAML header surrounded by
---
- Chunks of R code surrounded by
` ` `
- Text mixed with simple text formatting like
#
heading and _italics_
R Markdown 的渲染步骤
When knit the document,Rmarkdown 把文件 send 到 knitr,knitr 生成一个新的 markdown 文件(.md),然后通过 pandoc 进行 processing,最后生成输出的文件。
示例一:Text formatting with Mardown
——– Text_format.Rmd ———
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| ---
title: "Text Format"
author: "Paradise"
date: "2020年11月22日"
output: html_document
---
Text formatting
------------------------------------------------
斜体格式
*italic* or _italic_
加粗格式
**bold** or __bold__
背景灰色填充格式
`code`
上标与下标
superscript^2^ and superscript~2~
Headings
------------------------------------------------
# 1st Level Header
## 2nd Level Header
### 3rd Level Header
Lists
------------------------------------------------
* Bulleted list item 1
* Item 2
* Item 2a
* Item 2b
1. Numbered list item 1
1. Item 2. The numbers are incremented automatically in the output.
Links and images
-----------------------------------------------
<http://example.com>
添加相关的linked phrase
[linked phrase](http://example.com)
中括号内为可选标题文字
![optional caption text](../Rplot.jpeg)
Tables
----------------------------------------------
First Header | Second Header
------------ | -------------
Content Cell | Content Cell
Content Cell | Content Cell
|
将上面的 R Markdown 文件渲染成 HTML 文件:
1
| rmarkdown::render('./Rmd/Text_format.Rmd', output_flie='Text_format.html')
|
渲染结果
示例二:Code Chuck
——– Code_chunk.Rmd ———
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
| ---
title: "Code_chunk"
author: "Paradise"
date: "2020年11月22日"
output: html_document
---
Options of Code Chunks
------------------------------
**knitr**提供了近60个参数选项用于自定义chunk的输出
通过以下网址可以查看全部的options:<http://yihui.name/knitr/options/>.
这里展示比较常用的重要的options
```{r by-name,echo=TRUE,error=TRUE}
x<-runif(10)
y<-runif(10)
x
plot(x,y,type="l")
```
`说明`
---------------------------------
* 使用eval=FALSE,(当代码块有error仍能)输出代码,但是输出文件不包含执行结果
* 使用include=FALSE,运行代码,但是不在输出文件里显示代码或输出结果
* 使用echo=FALSE,输出执行结果,但是不包含源代码
* 使用message=FALSE或者warning=FALSE,输出文件中不包含warning信息
* 使用result='hide'隐藏打印输出的内容,使用fig.show='hide'隐藏图形输出
* 使用error=TRUE,即使有代码块报错也继续输出文件,在调试阶段使用
Table
-----------------------------
默认情况下数据框输出跟在console的输出格式一样,使用kable函数可以输出表格格式
```{r name2}
mtcars[1:5,1:10]
knitr::kable(mtcars[1:5,1:10],
caption = "A knitr table")
```
`说明`
---------------------------------
* 执行**?knitr::kable**了解很多更细致的自定义表格输出的方法
* 更深入的格式显示方法,可以使用**xtable**,**stargazer**,**pander**,**tables**以及**ascii**等包
Caching
---------------------------------
为了实现可再现性,所有的输出内容都是从空白页面开始构建,以确保代码里包含所有重要信息。但是当代码块里面有计算量较大的指令,那就需要用到缓存:定义cache=TRUE。设置缓存时,计算结果会保存到特定文件,下一次执行时,如果代码块没有改变,则引用该缓存文件。
```{r raw-data,eval=FALSE}
rawdata<-readr::read_csv("a_very_large_file.csv")
```
注意例子只是样本代码,设置了eval=FAlSE,不执行
```{r proccessed-data,cache=TRUE,dependson="raw_data",eval=FALSE}
processing the terribly large data
```
此处注意,如果没有定义dependson="raw_data",那么即使读取的csv文件改变了,只要‘processing’没有改变,仍然不会重新执行该代码块,而是直接使用缓存。
如果担心"a_very_large_file.csv"文件内容本身发生改变,导致后续的代码使用缓存而出错,可以在raw_data代码块增加cache.extra=file.info("file_name")这样可以检查文件的相关信息,包括最后一次修改的时间。
```{r clean_up,eval=FALSE}
knitr::clean_cache()
```
使用上述指令可以清除所有的缓存(当缓存的文件越来越复杂混乱的时候)
Global Options
----------------------------
`关于全局设置的内容在第五部分的script中出现`
Inline Code
-----------------------------
直接在文本中间引用并嵌入R代码,knit之后以执行结果的形式显示
For example,we have data about **`r nrow(diamonds)`** diamonds.
在文本中嵌入数字的时候,最好设置精确度,以及在大数中插入逗号(使用**format**函数)
```{r,collapse=FALSE}
a<-1234567
format(a,big.mark = ",")
b<-0.1234567
format(b,digits = 2)
```
|
渲染结果