【r<-模型】使用purrr和broom处理多个模型

整理自《R for data science》

本文介绍3种方法用于处理大量模型。

  • 使用多个简单模型更好地理解复杂数据集。
  • 使用列表列在数据框中保存任意数据结构。
  • 使用broom包将模型转换为整洁数据。

准备工作

library(modelr)
library(tidyverse)
#> ─ Attaching packages ──────────────────────────────────── tidyverse 1.2.1 ─
#> ✔ ggplot2 3.0.0     ✔ purrr   0.2.5
#> ✔ tibble  1.4.2     ✔ dplyr   0.7.6
#> ✔ tidyr   0.8.1     ✔ stringr 1.3.1
#> ✔ readr   1.1.1     ✔ forcats 0.3.0
#> ─ Conflicts ────────────────────────────────────── tidyverse_conflicts() ─
#> ✖ dplyr::filter() masks stats::filter()
#> ✖ dplyr::lag()    masks stats::lag()

列表列

列表列是隐式定义在数据框中的:数据框是由相同长度的向量组成的命名列表。一个列表就是一个向量(嵌套向量),因此完全可以将列表作为数据框的一列。但在R基础包中创建列表列是非常困难的,且data.frame()函数时将列表作为列的列表来处理的:

data.frame(x = list(1:3, 3:5))
#>   x.1.3 x.3.5
#> 1     1     3
#> 2     2     4
#> 3     3     5

要想不这样处理,可以使用I()函数,但输出结果难以解释:

data.frame(
    x = I(list(1:3, 3:5)),
    y = c("1, 2", "3, 4, 5")
)
#>         x       y
#> 1 1, 2, 3    1, 2
#> 2 3, 4, 5 3, 4, 5

tibble更懒惰一些,它不会修改输入,但更容易创建列表列,输出的内容也非常容易理解:

tibble(
    x = list(1:3, 3:5),
    y = c("1, 2", "3, 4, 5")
)
#> # A tibble: 2 x 2
#>   x         y      
#>   <list>    <chr>  
#> 1 <int [3]> 1, 2   
#> 2 <int [3]> 3, 4, 5

使用tribble()函数则更容易,它可以自动识别:

tribble(
    ~x, ~y,
    1:3, "1, 2",
    3:5, "3, 4, 5"
    
)
#> # A tibble: 2 x 2
#>   x         y      
#>   <list>    <chr>  
#> 1 <int [3]> 1, 2   
#> 2 <int [3]> 3, 4, 5

列表列的最大用处是作为一种中间数据结构。想要直接处理列表列比较困难,因为多数R函数都只处理原子向量或数据框。但列表列可以将相关条目统一保存在一个数据框中,光这一点就值得我们学习它。

创建列表列

一般我们会使用下面3种方式创建列表列。

  • 使用tidyr::nest()函数将分组数据转换为嵌套数据框,嵌套数据框中会包含列表列。
  • 使用mutate()函数以及能够返回列表的向量化函数
  • 使用summarize()函数以及能够返回多个结果的摘要函数

使用嵌套

nest()函数有两种使用方式。

一是当用于分组数据时,nest()函数会保留用于分组的列,而其他所有数据归并到列表中。

二是可以在未分组的数据上使用nest()函数,此时需要指定嵌套哪些列。

使用向量化函数

一些常用函数接受一个原子向量并返回一个列表。例如stringr::str_split()函数接受一个字符向量,并返回字符向量的一个列表。如果在mutate()中使用该函数,就会得到列表列。

df = tribble(
    ~x1,
    "a,b,c",
    "d,e,f,g"
)

df %>% 
    mutate(x2 = stringr::str_split(x1, ","))
#> # A tibble: 2 x 2
#>   x1      x2       
#>   <chr>   <list>   
#> 1 a,b,c   <chr [3]>
#> 2 d,e,f,g <chr [4]>

unnest()函数知道如何处理这些向量列表。

df %>% 
    mutate(x2 = stringr::str_split(x1, ",")) %>% 
    unnest()
#> # A tibble: 7 x 2
#>   x1      x2   
#>   <chr>   <chr>
#> 1 a,b,c   a    
#> 2 a,b,c   b    
#> 3 a,b,c   c    
#> 4 d,e,f,g d    
#> 5 d,e,f,g e    
#> 6 d,e,f,g f    
#> 7 d,e,f,g g

另一个示例是使用purrr包中的map()、map2()以及pmap()函数。

sim = tribble(
    ~f, ~params,
    "runif", list(min = -1, max = -1),
    "rnorm", list(sd = 5),
    "rpois", list(lambda = 10)
)

sim %>% 
    mutate(sims = invoke_map(f, params, n = 10))
#> # A tibble: 3 x 3
#>   f     params     sims      
#>   <chr> <list>     <list>    
#> 1 runif <list [2]> <dbl [10]>
#> 2 rnorm <list [1]> <dbl [10]>
#> 3 rpois <list [1]> <int [10]>

使用多值摘要

summarize()函数的一个局限性是,只能使用返回单一值的摘要函数。这意味着我们不能使用像quantile()这样的函数,因为它会返回任意长度的向量:

mtcars %>% 
    group_by(cyl) %>% 
    summarize(q = quantile(mpg))
#> Error in summarise_impl(.data, dots): Column `q` must be length 1 (a summary value), not 5

然而,我们可以将结果包装在一个列表中!这样操作的画,返回的结果就是长度为1的列表(向量)了。

mtcars %>% 
    group_by(cyl) %>% 
    summarise(q = list(quantile(mpg)))
#> # A tibble: 3 x 2
#>     cyl q        
#>   <dbl> <list>   
#> 1     4 <dbl [5]>
#> 2     6 <dbl [5]>
#> 3     8 <dbl [5]>

想要unnest()函数的结果更可用,添加一个概览列:

probs = c(0.01, 0.25, 0.5, 0.75, 0.99)

mtcars %>% 
    group_by(cyl) %>% 
    summarise(p = list(probs), q = list(quantile(mpg, probs))) %>% 
    unnest()
#> # A tibble: 15 x 3
#>      cyl     p     q
#>    <dbl> <dbl> <dbl>
#>  1     4  0.01  21.4
#>  2     4  0.25  22.8
#>  3     4  0.5   26  
#>  4     4  0.75  30.4
#>  5     4  0.99  33.8
#>  6     6  0.01  17.8
#>  7     6  0.25  18.6
#>  8     6  0.5   19.7
#>  9     6  0.75  21  
#> 10     6  0.99  21.4
#> 11     8  0.01  10.4
#> 12     8  0.25  14.4
#> 13     8  0.5   15.2
#> 14     8  0.75  16.2
#> 15     8  0.99  19.1

使用命名列表

带有列表列的数据框可以解决一种常见问题:如何同时对列表的元素及元素的内容进行迭代?相对于将所有元素内容塞进一个对象,更容易的方法是创建一个数据框:一列包含元素名称,另一列包含元素中的列表内容。我们可以使用enframe()函数实现该数据框。

x = list(
    a = 1:5,
    b = 3:4,
    c = 5:6
)

df = enframe(x)
df
#> # A tibble: 3 x 2
#>   name  value    
#>   <chr> <list>   
#> 1 a     <int [5]>
#> 2 b     <int [2]>
#> 3 c     <int [2]>

如果想对名称和值进行列表,使用map2()函数。

df %>% 
    mutate(
        smry = map2_chr(
            name,
            value,
            ~ stringr::str_c(.x, ": ", .y[1])
        )
    )
#> # A tibble: 3 x 3
#>   name  value     smry 
#>   <chr> <list>    <chr>
#> 1 a     <int [5]> a: 1 
#> 2 b     <int [2]> b: 3 
#> 3 c     <int [2]> c: 5

简化列表列

  • 如果想得到单个值,就使用mutate()以及map_lgl()、map_int()、map_dbl()和map_chr()函数来创建一个原子向量。
  • 如果想得到多个值,就使用unnest()函数将列表列还原为普通列,这样可以按需要将行执行多次重复

列表转换为向量

如果可以将列表缩减为一个原子向量,那么这个原子向量就可以作为普通列。

df = tribble(
    ~x,
    letters[1:5],
    1:3,
    runif(5)
)

df %>% mutate(
    type = map_chr(x, typeof),
    length = map_int(x, length)
)
#> # A tibble: 3 x 3
#>   x         type      length
#>   <list>    <chr>      <int>
#> 1 <chr [5]> character      5
#> 2 <int [3]> integer        3
#> 3 <dbl [5]> double         5

我们还可以使用map_*快捷方式,例如使用map_chr(x, “apple”)从x的每个元素中提取apple中的内容,如果元素存在缺失值,可以使用.null参数提供一个返回值(不是返回NULL):

df = tribble(
    ~x,
    list(a=1, b=2),
    list(a=2, c=4)
)

df %>% 
    mutate(a = map_dbl(x, "a"),
           b = map_dbl(x, "b", .null = NA_real_))
#> # A tibble: 2 x 3
#>   x              a     b
#>   <list>     <dbl> <dbl>
#> 1 <list [2]>     1     2
#> 2 <list [2]>     2    NA

嵌套还原

unnest()函数将列表列中的每个元素都重复一次为普通列。例如下面的例子第1行重复了4次,而第2行只重复1次:

tibble(x = 1:2, y = list(1:4, 1)) %>% unnest(y)
#> # A tibble: 5 x 2
#>       x     y
#>   <int> <dbl>
#> 1     1     1
#> 2     1     2
#> 3     1     3
#> 4     1     4
#> 5     2     1

注意,每行中数据框的行数都要相同,这样才能同时还原多个列表列。

df2 = tribble(
    ~x, ~y, ~z,
    1, "a", 1:2,
    2, c("b", "c"), 3
)

df2
#> # A tibble: 2 x 3
#>       x y         z        
#>   <dbl> <list>    <list>   
#> 1     1 <chr [1]> <int [2]>
#> 2     2 <chr [2]> <dbl [1]>

df2 %>% unnest(y, z)
#> Error: All nested columns must have the same number of elements.

使用broom生成整洁数据

broom包提供了3种常用工具,用于将模型转换为整洁数据框。

  • glance(model) 为每个模型返回一行数据,其中每一列都是模型的一个摘要统计量:要么是模型质量的度量方式,要么是模型复杂度,又或者是两者的结合。
  • tidy(model) 为模型的每个系数返回一行数据,每一列都是系数的估计值或变异指标。
  • augment(model, data) 返回data中的每一行,但会添加一些额外信息,如残差以及其他一些有影响的统计量

大部分的模型broom都支持,所以十分有用。

md = lm(mpg ~ cyl, data = mtcars)
broom::glance(md)
#> # 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.726         0.717  3.21      79.6 6.11e-10     2  -81.7  169.  174.
#> # ... with 2 more variables: deviance <dbl>, df.residual <int>

文章作者 王诗翔

上次更新 2018-10-15

许可协议 CC BY-NC-ND 4.0

purrr broom

modelr——基础模型实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,898评论 2 89
  • 为[爱心]阅读100天 D75 今天阅读的是卡梅拉系列之《我得北极大冒险》、奇先生妙小姐之《生日先生》、《圣诞先生...
  • 最近在做分布式任务调度系统,遇到分布式id的问题,我们需要一个全局唯一的id,但是服务又部署在多台服务器上面。因为...
    疯狂的哈丘阅读 972评论 0 22
  • 注:这是瓷心鱼的第8篇推荐。 一、关键词:选择。 总有些人喜欢各种天马行空、各种想入非非,总是在幻想自己什么时候中...
    瓷心鱼阅读 390评论 0 0
  • 2018/4/19 首先进入系统偏好设置 ↓ 用户与群组 ↓ 打开左下角的小锁,如下图: ↓ 选中当前的用户,右...
    aggie1024阅读 3,983评论 0 0