tidyverse 体系下的整合与重构:R base cheatsheet.pdf

处理缺失值

删除含缺失值的行 drop_na()

tidyr::drop_na() 可以对数据框指定一到多个变量,删去指定的变量有缺失值的行。不指定变量时有任何变量缺失的行都会被删去,此时功能同 na.omit()

填充缺失值

  • tidyr::fill(data, ..., direction=c("down", "up")),填充缺失值,填充值根据参数是缺失值附近的数值

  • tidyr::replace_na(data, replace=list(), ...),将数据中的缺失值替换为别的值

拆分、合并数据列

拆分列

tidyr::separate(),有时应该放在不同列的数据用分隔符分隔后放在同一列中了,应拆开。一般可用于日志数据或日期时间型数据的拆分。

separate(data, col, into, sep = "[^[:alnum:]]+", remove = TRUE, convert = FALSE, extra = "warn", fill = "warn", ...)
  • col:需要被拆分的列,列名或位置索引均可
  • into:拆分后新建的列名,为字符串向量
  • sep:被拆分列的分隔符
  • remove:是否删除被分割的列
  • convert=TRUE 要求自动将分割后的值转换为适当的类型
d.sep <- read_csv("testid, succ/total
1, 1/10
2, 3/5
3, 2/8
")
d.sep
#> # A tibble: 3 x 2
#>   testid `succ/total`
#>    <dbl> <chr>       
#> 1      1 1/10        
#> 2      2 3/5         
#> 3      3 2/8

d.sep %>%
  separate(
    `succ/total`,
    into = c("succ", "total"),
    sep = "/",
    convert = TRUE
  )
#> # A tibble: 3 x 3
#>   testid  succ total
#>    <dbl> <int> <int>
#> 1      1     1    10
#> 2      2     3     5
#> 3      3     2     8

拆分行

tidyr::separate_rows(data, ..., sep),将某列数据中的每个值拆分为同一列的两行

  • data 原始数据
  • … 待分数据所在的列(字段、变量)
  • sep 分割数据时使用的风格符

合并列

tidyr::unite() 函数可以将同一行的两列或多列的内容合并成一列。是 separate() 的反向操作

unite(data, col, …, sep = “_”, remove = TRUE)
  • col:合并的新列名称
  • …:指定哪些列需要被组合
  • sep:组合列之间的连接符,默认为下划线
  • remove:是否删除被组合的各列
d.sep %>%
  separate(`succ/total`, into = c("succ", "total"), 
           sep = "/", convert = TRUE) %>%
  unite(ratio, succ, total, sep = ":")
#> # A tibble: 3 x 2
#>   testid ratio
#>    <dbl> <chr>
#> 1      1 1:10 
#> 2      2 3:5  
#> 3      3 2:8

## 另外一个例子
# 先虚构一数据框
set.seed(1)
date <- as.Date('2016-11-01') + 0:14
hour <- sample(1:24, 15)
min <- sample(1:60, 15)
second <- sample(1:60, 15)
event <- sample(letters, 15)
data <- data.table(date, hour, min, second, event)

# 把date,hour,min和second列合并为新列datetime
# R中的日期时间格式为"Year-Month-Day Hour:Min:Second"
dataNew <- data %>% unite(datehour, date, hour, sep = ' ') %>% unite(datetime, datehour, min, second, sep = ':')
dataNew
#>                datetime event
#>  1:  2016-11-01 4:15:35     w
#>  2:   2016-11-02 7:21:6     x
#>  3:  2016-11-03 1:37:10     f
#>  4:  2016-11-04 2:41:42     g
#>  5: 2016-11-05 11:25:38     s
#>  6: 2016-11-06 14:46:47     j
#>  7: 2016-11-07 18:58:20     y
#>  8: 2016-11-08 22:54:28     n
#>  9:  2016-11-09 5:34:54     b
#> 10: 2016-11-10 16:42:44     m
#> 11: 2016-11-11 10:56:23     r
#> 12:  2016-11-12 6:44:59     t
#> 13: 2016-11-13 19:60:40     v
#> 14: 2016-11-14 23:33:51     o
#> 15:  2016-11-15 9:20:25     a

数据表变形:长宽数据转换

基础包

# input data
mydata <- read.table(header = TRUE, sep = " ", text = "
ID Time X1 X2
1 1 5 6
1 2 3 5
2 1 6 1
2 2 2 4
")
mydata
#>   ID Time X1 X2
#> 1  1    1  5  6
#> 2  1    2  3  5
#> 3  2    1  6  1
#> 4  2    2  2  4

宽转长 melt()

将宽数据转换长数据,id 参数接受一个列名向量,其中的列会保留。其余列的列名会变为新variable列的值,值会变成新value列的值。

# id参数中的变量维持属性地位,其他变量的值都成为value,因此其他变量被合并成一列variable
md <- melt(mydata, id = c("ID", "Time")) #or md <- melt(mydata, id=1:2)
md
#>   ID Time variable value
#> 1  1    1       X1     5
#> 2  1    2       X1     3
#> 3  2    1       X1     6
#> 4  2    2       X1     2
#> 5  1    1       X2     6
#> 6  1    2       X2     5
#> 7  2    1       X2     1
#> 8  2    2       X2     4

长转宽 *cast()

dcast()返回一个数据框,acast()返回一个向量/矩阵/数组

熔化与重铸

# dcast(md, formula, fun.aggregate)
# formula参数中,~左边的变量保持不变,右边的变量作为一个因子,各取值水平分别成为一个变量名。
dcast(md, ID+Time~variable)
#>   ID Time X1 X2
#> 1  1    1  5  6
#> 2  1    2  3  5
#> 3  2    1  6  1
#> 4  2    2  2  4
dcast(md, ID+variable~Time)
#>   ID variable 1 2
#> 1  1       X1 5 3
#> 2  1       X2 6 5
#> 3  2       X1 6 2
#> 4  2       X2 1 4
dcast(md, ID~variable+Time)
#>   ID X1_1 X1_2 X2_1 X2_2
#> 1  1    5    3    6    5
#> 2  2    6    2    1    4
dcast(md, ID~Time+variable)
#>   ID 1_X1 1_X2 2_X1 2_X2
#> 1  1    5    6    3    5
#> 2  2    6    1    2    4
# formula中没有涉及的变量将从数据中消失,由fun.aggregate参数执行整合功能。
dcast(md, ID~Time,mean)
#>   ID   1 2
#> 1  1 5.5 4
#> 2  2 3.5 3
dcast(md, ID~variable,mean)
#>   ID X1  X2
#> 1  1  4 5.5
#> 2  2  4 2.5

tidyr 包

新函数

  • tidyr::pivot_longer(cols, names_to, values_to),宽转长
  • tidyr::pivot_wider(names_from, values_from),长转宽
library(tidyverse)

plant_height <- data.frame(
  Day = 1:5,
  A = c(0.7, 1.0, 1.5, 1.8, 2.2),
  B = c(0.5, 0.7, 0.9, 1.3, 1.8),
  C = c(0.3, 0.6, 1.0, 1.2, 2.2),
  D = c(0.4, 0.7, 1.2, 1.5, 3.2)
)
plant_height
#>   Day   A   B   C   D
#> 1   1 0.7 0.5 0.3 0.4
#> 2   2 1.0 0.7 0.6 0.7
#> 3   3 1.5 0.9 1.0 1.2
#> 4   4 1.8 1.3 1.2 1.5
#> 5   5 2.2 1.8 2.2 3.2

long <- plant_height %>%
  pivot_longer(
    cols = A:D,
    names_to = "plant",
    values_to = "height"
  )
long
#> # A tibble: 20 x 3
#>      Day plant height
#>    <int> <chr>  <dbl>
#>  1     1 A        0.7
#>  2     1 B        0.5
#>  3     1 C        0.3
#>  4     1 D        0.4
#>  5     2 A        1  
#>  6     2 B        0.7
#>  7     2 C        0.6
#>  8     2 D        0.7
#>  9     3 A        1.5
#> 10     3 B        0.9
#> 11     3 C        1  
#> 12     3 D        1.2
#> 13     4 A        1.8
#> 14     4 B        1.3
#> 15     4 C        1.2
#> 16     4 D        1.5
#> 17     5 A        2.2
#> 18     5 B        1.8
#> 19     5 C        2.2
#> 20     5 D        3.2

long %>%
  ggplot(aes(x = Day, y = height, color = plant)) +
  geom_line()


wide <- long %>%
  pivot_wider(
    names_from = "plant",
    values_from = "height"
  )
wide
#> # A tibble: 5 x 5
#>     Day     A     B     C     D
#>   <int> <dbl> <dbl> <dbl> <dbl>
#> 1     1   0.7   0.5   0.3   0.4
#> 2     2   1     0.7   0.6   0.7
#> 3     3   1.5   0.9   1     1.2
#> 4     4   1.8   1.3   1.2   1.5
#> 5     5   2.2   1.8   2.2   3.2

高级应用

plant_record <- data.frame(
  day = c(1L, 2L, 3L, 4L, 5L),
  A_height = c(1.1, 1.2, 1.3, 1.4, 1.5),
  A_width = c(2.1, 2.2, 2.3, 2.4, 2.5),
  A_depth = c(3.1, 3.2, 3.3, 3.4, 3.5),
  B_height = c(4.1, 4.2, 4.3, 4.4, 4.5),
  B_width = c(5.1, 5.2, 5.3, 5.4, 5.5),
  B_depth = c(6.1, 6.2, 6.3, 6.4, 6.5),
  C_height = c(7.1, 7.2, 7.3, 7.4, 7.5),
  C_width = c(8.1, 8.2, 8.3, 8.4, 8.5),
  C_depth = c(9.1, 9.2, 9.3, 9.4, 9.5)
)
plant_record
#>   day A_height A_width A_depth B_height B_width B_depth C_height C_width
#> 1   1      1.1     2.1     3.1      4.1     5.1     6.1      7.1     8.1
#> 2   2      1.2     2.2     3.2      4.2     5.2     6.2      7.2     8.2
#> 3   3      1.3     2.3     3.3      4.3     5.3     6.3      7.3     8.3
#> 4   4      1.4     2.4     3.4      4.4     5.4     6.4      7.4     8.4
#> 5   5      1.5     2.5     3.5      4.5     5.5     6.5      7.5     8.5
#>   C_depth
#> 1     9.1
#> 2     9.2
#> 3     9.3
#> 4     9.4
#> 5     9.5

plant_record %>%
  tidyr::pivot_longer(
    cols = !day,
    names_to = c("species", "parameter"),
    names_pattern = "(.*)_(.*)",
    values_to = "value"
  )
#> # A tibble: 45 x 4
#>      day species parameter value
#>    <int> <chr>   <chr>     <dbl>
#>  1     1 A       height      1.1
#>  2     1 A       width       2.1
#>  3     1 A       depth       3.1
#>  4     1 B       height      4.1
#>  5     1 B       width       5.1
#>  6     1 B       depth       6.1
#>  7     1 C       height      7.1
#>  8     1 C       width       8.1
#>  9     1 C       depth       9.1
#> 10     2 A       height      1.2
#> # ... with 35 more rows

plant_record_longer <- plant_record %>%
  tidyr::pivot_longer(
    cols = !day,
    # 注意时 .value, 不是一个列
    names_to = c("species", ".value"),
    names_pattern = "(.*)_(.*)"
  )
plant_record_longer
#> # A tibble: 15 x 5
#>      day species height width depth
#>    <int> <chr>    <dbl> <dbl> <dbl>
#>  1     1 A          1.1   2.1   3.1
#>  2     1 B          4.1   5.1   6.1
#>  3     1 C          7.1   8.1   9.1
#>  4     2 A          1.2   2.2   3.2
#>  5     2 B          4.2   5.2   6.2
#>  6     2 C          7.2   8.2   9.2
#>  7     3 A          1.3   2.3   3.3
#>  8     3 B          4.3   5.3   6.3
#>  9     3 C          7.3   8.3   9.3
#> 10     4 A          1.4   2.4   3.4
#> 11     4 B          4.4   5.4   6.4
#> 12     4 C          7.4   8.4   9.4
#> 13     5 A          1.5   2.5   3.5
#> 14     5 B          4.5   5.5   6.5
#> 15     5 C          7.5   8.5   9.5


us_rent_income %>%
  pivot_wider(
    names_from = variable,
    names_glue = "{variable}_{.value}",
    values_from = c(estimate, moe)
  )
#> # A tibble: 52 x 6
#>    GEOID NAME                 income_estimate rent_estimate income_moe rent_moe
#>    <chr> <chr>                          <dbl>         <dbl>      <dbl>    <dbl>
#>  1 01    Alabama                        24476           747        136        3
#>  2 02    Alaska                         32940          1200        508       13
#>  3 04    Arizona                        27517           972        148        4
#>  4 05    Arkansas                       23789           709        165        5
#>  5 06    California                     29454          1358        109        3
#>  6 08    Colorado                       32401          1125        109        5
#>  7 09    Connecticut                    35326          1123        195        5
#>  8 10    Delaware                       31560          1076        247       10
#>  9 11    District of Columbia           43198          1424        681       17
#> 10 12    Florida                        25952          1077         70        3
#> # ... with 42 more rows

plant_record_longer %>%
  tidyr::pivot_wider(
    names_from = species,
    values_from = c(height, width, depth),
    names_glue = "{species}_{.value}"
  )
#> # A tibble: 5 x 10
#>     day A_height B_height C_height A_width B_width C_width A_depth B_depth
#>   <int>    <dbl>    <dbl>    <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
#> 1     1      1.1      4.1      7.1     2.1     5.1     8.1     3.1     6.1
#> 2     2      1.2      4.2      7.2     2.2     5.2     8.2     3.2     6.2
#> 3     3      1.3      4.3      7.3     2.3     5.3     8.3     3.3     6.3
#> 4     4      1.4      4.4      7.4     2.4     5.4     8.4     3.4     6.4
#> 5     5      1.5      4.5      7.5     2.5     5.5     8.5     3.5     6.5
#> # ... with 1 more variable: C_depth <dbl>

宽转长 tidyr::gather()

gather(data, key, value, …, na.rm = FALSE, convert = FALSE)

  • key:将不打算保留的列的列名赋给一个新变量key
  • value:将不打算保留的列的值赋给一个新变量value
  • …:选中要gather的列(不打算保留的列),若此参数不赋值则gather所有列。
  • na.rm:是否删除缺失值
mtcars <- head(datasets::mtcars)

# 为方便处理,在数据集中增加一列car
mtcars$car <- rownames(mtcars)    
#将添加的一列从最后一列移到最前列
mtcars <- mtcars[, c(12, 1:11)]   
head(mtcars)
#>                                 car  mpg cyl disp  hp drat    wt  qsec vs am
#> Mazda RX4                 Mazda RX4 21.0   6  160 110 3.90 2.620 16.46  0  1
#> Mazda RX4 Wag         Mazda RX4 Wag 21.0   6  160 110 3.90 2.875 17.02  0  1
#> Datsun 710               Datsun 710 22.8   4  108  93 3.85 2.320 18.61  1  1
#> Hornet 4 Drive       Hornet 4 Drive 21.4   6  258 110 3.08 3.215 19.44  1  0
#> Hornet Sportabout Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0
#> Valiant                     Valiant 18.1   6  225 105 2.76 3.460 20.22  1  0
#>                   gear carb
#> Mazda RX4            4    4
#> Mazda RX4 Wag        4    4
#> Datsun 710           4    1
#> Hornet 4 Drive       3    1
#> Hornet Sportabout    3    2
#> Valiant              3    1

# 除了car列外,其余列gather成两列,分别命名为attribute和value
mtcarsNew <- mtcars %>% 
  gather(key = attribute, value = value, -car)
head(mtcarsNew)
#>                 car attribute value
#> 1         Mazda RX4       mpg  21.0
#> 2     Mazda RX4 Wag       mpg  21.0
#> 3        Datsun 710       mpg  22.8
#> 4    Hornet 4 Drive       mpg  21.4
#> 5 Hornet Sportabout       mpg  18.7
#> 6           Valiant       mpg  18.1

# gather在map和gear之间的所有列,从而保持carb和car列不变
mtcarsNew <- mtcars %>% gather(attribute, value, mpg:gear)
head(mtcarsNew)
#>                 car carb attribute value
#> 1         Mazda RX4    4       mpg  21.0
#> 2     Mazda RX4 Wag    4       mpg  21.0
#> 3        Datsun 710    1       mpg  22.8
#> 4    Hornet 4 Drive    1       mpg  21.4
#> 5 Hornet Sportabout    2       mpg  18.7
#> 6           Valiant    1       mpg  18.1

# 只gather() `gear`和`carb`两列
mtcarsNew <- mtcars %>% 
  gather(key = attribute, value = value, gear, carb)
head(mtcarsNew)
#>                 car  mpg cyl disp  hp drat    wt  qsec vs am attribute value
#> 1         Mazda RX4 21.0   6  160 110 3.90 2.620 16.46  0  1      gear     4
#> 2     Mazda RX4 Wag 21.0   6  160 110 3.90 2.875 17.02  0  1      gear     4
#> 3        Datsun 710 22.8   4  108  93 3.85 2.320 18.61  1  1      gear     4
#> 4    Hornet 4 Drive 21.4   6  258 110 3.08 3.215 19.44  1  0      gear     3
#> 5 Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0      gear     3
#> 6           Valiant 18.1   6  225 105 2.76 3.460 20.22  1  0      gear     3
unique(mtcarsNew$attribute)
#> [1] "gear" "carb"


## 或用上一节的例子
md <- mydata %>% gather(variable, value, X1, X2)
md
#>   ID Time variable value
#> 1  1    1       X1     5
#> 2  1    2       X1     3
#> 3  2    1       X1     6
#> 4  2    2       X1     2
#> 5  1    1       X2     6
#> 6  1    2       X2     5
#> 7  2    1       X2     1
#> 8  2    2       X2     4

长转宽 tidyr::spread()

spread(data, key, value, fill = NA, convert = FALSE, drop = TRUE)

  • key:需要将变量值拓展为字段的变量名
  • value:需要分散的值的变量名
  • fill:对于缺失值,可将fill的值赋值给被转型后的缺失值
mtcarsSpread <- mtcarsNew %>% 
  spread(key = attribute, value = value)
head(mtcarsSpread)
#>                 car  mpg cyl disp  hp drat    wt  qsec vs am carb gear
#> 1        Datsun 710 22.8   4  108  93 3.85 2.320 18.61  1  1    1    4
#> 2    Hornet 4 Drive 21.4   6  258 110 3.08 3.215 19.44  1  0    1    3
#> 3 Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    2    3
#> 4         Mazda RX4 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
#> 5     Mazda RX4 Wag 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
#> 6           Valiant 18.1   6  225 105 2.76 3.460 20.22  1  0    1    3

## 或用上一节的例子
md %>% spread(variable, value)
#>   ID Time X1 X2
#> 1  1    1  5  6
#> 2  1    2  3  5
#> 3  2    1  6  1
#> 4  2    2  2  4

数据表的简单合并

  • bind_cols(table1, table2) 纵向合并
  • bind_rows(table1, table2) 横向合并
    • bind_rows("tableName1" = table1, "tableName2" = table2, .id = NULL) ,可选参数 .id 不为NULL而接收一个字符串时,可以添加一列,列名为 .id 的参数,用以标识各行数据的来源。新添加的这一列的各行值为 tableName1tableName2
LS0tDQp0aXRsZTogIuaVsOaNrua4hea0l+WSjOmHjeaVtCINCnN1YnRpdGxlOiAnJw0KYXV0aG9yOiAiSHVtb29uIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY3NzOiBbIi4uL2Nzcy9zdHlsZS5jc3MiXQ0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICB0aGVtZTogdW5pdGVkDQogICAgaGlnaGxpZ2h0OiBoYWRkb2NrDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiB5ZXMNCiAgICAgIHNtb290aF9zY3JvbGw6IHllcw0KZG9jdW1lbnRjbGFzczogY3RleGFydA0KY2xhc3NvcHRpb246IGh5cGVycmVmLA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9DQpzb3VyY2UoIi4uL1JtYXJrZG93bi10ZW1wbGF0ZS9SbWFya2Rvd25fY29uZmlnLlIiKQ0KDQojIyBnbG9iYWwgb3B0aW9ucyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICB3aWR0aCA9IGNvbmZpZyR3aWR0aCwNCiAgZmlnLndpZHRoID0gY29uZmlnJGZpZy53aWR0aCwNCiAgZmlnLmFzcCA9IGNvbmZpZyRmaWcuYXNwLA0KICBvdXQud2lkdGggPSBjb25maWckb3V0LndpZHRoLA0KICBmaWcuYWxpZ24gPSBjb25maWckZmlnLmFsaWduLA0KICBmaWcucGF0aCA9IGNvbmZpZyRmaWcucGF0aCwNCiAgZmlnLnNob3cgPSBjb25maWckZmlnLnNob3csDQogIHdhcm4gPSBjb25maWckd2FybiwNCiAgd2FybmluZyA9IGNvbmZpZyR3YXJuaW5nLA0KICBtZXNzYWdlID0gY29uZmlnJG1lc3NhZ2UsDQogIGVjaG8gPSBjb25maWckZWNobywgDQogIGV2YWwgPSBjb25maWckZXZhbCwgDQogIHRpZHkgPSBjb25maWckdGlkeSwgDQogIGNvbW1lbnQgPSBjb25maWckY29tbWVudCwgDQogIGNvbGxhcHNlID0gY29uZmlnJGNvbGxhcHNlLCANCiAgY2FjaGUgPSBjb25maWckY2FjaGUsDQogIGNhY2hlLmNvbW1lbnRzID0gY29uZmlnJGNhY2hlLmNvbW1lbnRzLA0KICBhdXRvZGVwID0gY29uZmlnJGF1dG9kZXANCikNCg0KIyMgdXNlIG5lY2Vzc2FyeSBwYWNrYWdlcyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShodG1sd2lkZ2V0cykNCmBgYA0KDQp0aWR5dmVyc2Ug5L2T57O75LiL55qE5pW05ZCI5LiO6YeN5p6E77yaPGEgaHJlZj0iLi4vcGRmL2NoZWF0c2hlZXQtdGlkeXIucGRmIj4qUiBiYXNlIGNoZWF0c2hlZXQucGRmKjwvYT4NCg0KPG9iamVjdCBkYXRhPSIuLi9wZGYvY2hlYXRzaGVldC10aWR5ci5wZGYiIHR5cGU9ImFwcGxpY2F0aW9uL3BkZiIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSI+PC9vYmplY3Q+DQoNCg0KIyMg5aSE55CG57y65aSx5YC8DQoNCiMjIyDliKDpmaTlkKvnvLrlpLHlgLznmoTooYwgYGRyb3BfbmEoKWANCg0KYHRpZHlyOjpkcm9wX25hKClgIOWPr+S7peWvueaVsOaNruahhuaMh+WumuS4gOWIsOWkmuS4quWPmOmHj++8jOWIoOWOu+aMh+WumueahOWPmOmHj+aciee8uuWkseWAvOeahOihjOOAgioq5LiN5oyH5a6a5Y+Y6YeP5pe25pyJ5Lu75L2V5Y+Y6YeP57y65aSx55qE6KGM6YO95Lya6KKr5Yig5Y67KirvvIzmraTml7blip/og73lkIwgYG5hLm9taXQoKWDjgIINCg0KIyMjIOWhq+WFhee8uuWkseWAvA0KDQotIGB0aWR5cjo6ZmlsbChkYXRhLCAuLi4sIGRpcmVjdGlvbj1jKCJkb3duIiwgInVwIikpYO+8jOWhq+WFhee8uuWkseWAvO+8jOWhq+WFheWAvOagueaNruWPguaVsOaYr+e8uuWkseWAvOmZhOi/keeahOaVsOWAvA0KDQotIGB0aWR5cjo6cmVwbGFjZV9uYShkYXRhLCByZXBsYWNlPWxpc3QoKSwgLi4uKWDvvIzlsIbmlbDmja7kuK3nmoTnvLrlpLHlgLzmm7/mjaLkuLrliKvnmoTlgLwNCg0KDQojIyDmi4bliIbjgIHlkIjlubbmlbDmja7liJcgDQoNCiMjIyDmi4bliIbliJcNCg0KYHRpZHlyOjpzZXBhcmF0ZSgpYO+8jOacieaXtuW6lOivpeaUvuWcqOS4jeWQjOWIl+eahOaVsOaNrueUqOWIhumalOespuWIhumalOWQjuaUvuWcqOWQjOS4gOWIl+S4reS6hu+8jOW6lOaLhuW8gOOAguS4gOiIrOWPr+eUqOS6juaXpeW/l+aVsOaNruaIluaXpeacn+aXtumXtOWei+aVsOaNrueahOaLhuWIhuOAgg0KDQpgYGByDQpzZXBhcmF0ZShkYXRhLCBjb2wsIGludG8sIHNlcCA9ICJbXls6YWxudW06XV0rIiwgcmVtb3ZlID0gVFJVRSwgY29udmVydCA9IEZBTFNFLCBleHRyYSA9ICJ3YXJuIiwgZmlsbCA9ICJ3YXJuIiwgLi4uKQ0KYGBgDQoNCi0gY29s77ya6ZyA6KaB6KKr5ouG5YiG55qE5YiX77yM5YiX5ZCN5oiW5L2N572u57Si5byV5Z2H5Y+vDQotIGludG/vvJrmi4bliIblkI7mlrDlu7rnmoTliJflkI3vvIzkuLrlrZfnrKbkuLLlkJHph48NCi0gc2Vw77ya6KKr5ouG5YiG5YiX55qE5YiG6ZqU56ymDQotIHJlbW92Ze+8muaYr+WQpuWIoOmZpOiiq+WIhuWJsueahOWIlyANCi0gY29udmVydD1UUlVFIOimgeaxguiHquWKqOWwhuWIhuWJsuWQjueahOWAvOi9rOaNouS4uumAguW9k+eahOexu+Weiw0KDQpgYGB7cn0NCmQuc2VwIDwtIHJlYWRfY3N2KCJ0ZXN0aWQsIHN1Y2MvdG90YWwNCjEsIDEvMTANCjIsIDMvNQ0KMywgMi84DQoiKQ0KZC5zZXANCg0KZC5zZXAgJT4lDQogIHNlcGFyYXRlKA0KICAgIGBzdWNjL3RvdGFsYCwNCiAgICBpbnRvID0gYygic3VjYyIsICJ0b3RhbCIpLA0KICAgIHNlcCA9ICIvIiwNCiAgICBjb252ZXJ0ID0gVFJVRQ0KICApDQpgYGANCg0KIyMjIOaLhuWIhuihjA0KDQpgdGlkeXI6OnNlcGFyYXRlX3Jvd3MoZGF0YSwgLi4uLCBzZXApYO+8jOWwhuafkOWIl+aVsOaNruS4reeahOavj+S4quWAvOaLhuWIhuS4uuWQjOS4gOWIl+eahOS4pOihjA0KDQotIGRhdGEg5Y6f5aeL5pWw5o2uDQotIC4uLiDlvoXliIbmlbDmja7miYDlnKjnmoTliJco5a2X5q6144CB5Y+Y6YePKQ0KLSBzZXAg5YiG5Ymy5pWw5o2u5pe25L2/55So55qE6aOO5qC856ymDQoNCg0KIyMjIOWQiOW5tuWIlw0KDQpgdGlkeXI6OnVuaXRlKClgIOWHveaVsOWPr+S7peWwhuWQjOS4gOihjOeahOS4pOWIl+aIluWkmuWIl+eahOWGheWuueWQiOW5tuaIkOS4gOWIl+OAguaYryBgc2VwYXJhdGUoKWAg55qE5Y+N5ZCR5pON5L2cDQoNCmBgYHINCnVuaXRlKGRhdGEsIGNvbCwg4oCmLCBzZXAgPSDigJxf4oCdLCByZW1vdmUgPSBUUlVFKQ0KYGBgDQoNCi0gY29s77ya5ZCI5bm255qE5paw5YiX5ZCN56ewDQotIOKApu+8muaMh+WumuWTquS6m+WIl+mcgOimgeiiq+e7hOWQiA0KLSBzZXDvvJrnu4TlkIjliJfkuYvpl7TnmoTov57mjqXnrKbvvIzpu5jorqTkuLrkuIvliJLnur8NCi0gcmVtb3Zl77ya5piv5ZCm5Yig6Zmk6KKr57uE5ZCI55qE5ZCE5YiXDQoNCmBgYHtyfQ0KZC5zZXAgJT4lDQogIHNlcGFyYXRlKGBzdWNjL3RvdGFsYCwgaW50byA9IGMoInN1Y2MiLCAidG90YWwiKSwgDQogICAgICAgICAgIHNlcCA9ICIvIiwgY29udmVydCA9IFRSVUUpICU+JQ0KICB1bml0ZShyYXRpbywgc3VjYywgdG90YWwsIHNlcCA9ICI6IikNCg0KIyMg5Y+m5aSW5LiA5Liq5L6L5a2QDQojIOWFiOiZmuaehOS4gOaVsOaNruahhg0Kc2V0LnNlZWQoMSkNCmRhdGUgPC0gYXMuRGF0ZSgnMjAxNi0xMS0wMScpICsgMDoxNA0KaG91ciA8LSBzYW1wbGUoMToyNCwgMTUpDQptaW4gPC0gc2FtcGxlKDE6NjAsIDE1KQ0Kc2Vjb25kIDwtIHNhbXBsZSgxOjYwLCAxNSkNCmV2ZW50IDwtIHNhbXBsZShsZXR0ZXJzLCAxNSkNCmRhdGEgPC0gZGF0YS50YWJsZShkYXRlLCBob3VyLCBtaW4sIHNlY29uZCwgZXZlbnQpDQoNCiMg5oqKZGF0Ze+8jGhvdXLvvIxtaW7lkoxzZWNvbmTliJflkIjlubbkuLrmlrDliJdkYXRldGltZQ0KIyBS5Lit55qE5pel5pyf5pe26Ze05qC85byP5Li6IlllYXItTW9udGgtRGF5IEhvdXI6TWluOlNlY29uZCINCmRhdGFOZXcgPC0gZGF0YSAlPiUgdW5pdGUoZGF0ZWhvdXIsIGRhdGUsIGhvdXIsIHNlcCA9ICcgJykgJT4lIHVuaXRlKGRhdGV0aW1lLCBkYXRlaG91ciwgbWluLCBzZWNvbmQsIHNlcCA9ICc6JykNCmRhdGFOZXcNCmBgYA0KDQoNCg0KIyMg5pWw5o2u6KGo5Y+Y5b2i77ya6ZW/5a695pWw5o2u6L2s5o2iDQoNCiMjIyDln7rnoYDljIUNCg0KYGBge3IgZGF0YX0NCiMgaW5wdXQgZGF0YQ0KbXlkYXRhIDwtIHJlYWQudGFibGUoaGVhZGVyID0gVFJVRSwgc2VwID0gIiAiLCB0ZXh0ID0gIg0KSUQgVGltZSBYMSBYMg0KMSAxIDUgNg0KMSAyIDMgNQ0KMiAxIDYgMQ0KMiAyIDIgNA0KIikNCm15ZGF0YQ0KYGBgDQoNCiMjIyMg5a696L2s6ZW/IGBtZWx0KClgDQoNCuWwhuWuveaVsOaNrui9rOaNoumVv+aVsOaNru+8jGlkIOWPguaVsOaOpeWPl+S4gOS4quWIl+WQjeWQkemHj++8jOWFtuS4reeahOWIl+S8muS/neeVmeOAguWFtuS9meWIl+eahOWIl+WQjeS8muWPmOS4uuaWsHZhcmlhYmxl5YiX55qE5YC877yM5YC85Lya5Y+Y5oiQ5pawdmFsdWXliJfnmoTlgLzjgIINCg0KYGBge3IgbWVsdCwgZGVwZW5kc29uPSdkYXRhJ30NCiMgaWTlj4LmlbDkuK3nmoTlj5jph4/nu7TmjIHlsZ7mgKflnLDkvY3vvIzlhbbku5blj5jph4/nmoTlgLzpg73miJDkuLp2YWx1Ze+8jOWboOatpOWFtuS7luWPmOmHj+iiq+WQiOW5tuaIkOS4gOWIl3ZhcmlhYmxlDQptZCA8LSBtZWx0KG15ZGF0YSwgaWQgPSBjKCJJRCIsICJUaW1lIikpICNvciBtZCA8LSBtZWx0KG15ZGF0YSwgaWQ9MToyKQ0KbWQNCmBgYA0KDQojIyMjIOmVv+i9rOWuvSBgKmNhc3QoKWANCg0KYGRjYXN0KClg6L+U5Zue5LiA5Liq5pWw5o2u5qGG77yMYGFjYXN0KClg6L+U5Zue5LiA5Liq5ZCR6YePL+efqemYtS/mlbDnu4QNCg0KIVvnhpTljJbkuI7ph43pk7hdKGh0dHA6Ly9odW1vb24taW1hZ2UtaG9zdGluZy1zZXJ2aWNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS9pbWcvdHlwb3JhLzIwMjIv54aU5YyW5LiO6YeN6ZO4LnBuZykNCg0KYGBge3IgY2FzdCwgZGVwZW5kc29uPSdtZWx0J30NCiMgZGNhc3QobWQsIGZvcm11bGEsIGZ1bi5hZ2dyZWdhdGUpDQojIGZvcm11bGHlj4LmlbDkuK3vvIx+5bem6L6555qE5Y+Y6YeP5L+d5oyB5LiN5Y+Y77yM5Y+z6L6555qE5Y+Y6YeP5L2c5Li65LiA5Liq5Zug5a2Q77yM5ZCE5Y+W5YC85rC05bmz5YiG5Yir5oiQ5Li65LiA5Liq5Y+Y6YeP5ZCN44CCDQpkY2FzdChtZCwgSUQrVGltZX52YXJpYWJsZSkNCmRjYXN0KG1kLCBJRCt2YXJpYWJsZX5UaW1lKQ0KZGNhc3QobWQsIElEfnZhcmlhYmxlK1RpbWUpDQpkY2FzdChtZCwgSUR+VGltZSt2YXJpYWJsZSkNCiMgZm9ybXVsYeS4reayoeaciea2ieWPiueahOWPmOmHj+WwhuS7juaVsOaNruS4rea2iOWkse+8jOeUsWZ1bi5hZ2dyZWdhdGXlj4LmlbDmiafooYzmlbTlkIjlip/og73jgIINCmRjYXN0KG1kLCBJRH5UaW1lLG1lYW4pDQpkY2FzdChtZCwgSUR+dmFyaWFibGUsbWVhbikNCmBgYA0KDQojIyMgdGlkeXIg5YyFDQoNCiMjIyMg5paw5Ye95pWwDQoNCi0gYHRpZHlyOjpwaXZvdF9sb25nZXIoY29scywgbmFtZXNfdG8sIHZhbHVlc190bylg77yM5a696L2s6ZW/DQotIGB0aWR5cjo6cGl2b3Rfd2lkZXIobmFtZXNfZnJvbSwgdmFsdWVzX2Zyb20pYO+8jOmVv+i9rOWuvQ0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQpwbGFudF9oZWlnaHQgPC0gZGF0YS5mcmFtZSgNCiAgRGF5ID0gMTo1LA0KICBBID0gYygwLjcsIDEuMCwgMS41LCAxLjgsIDIuMiksDQogIEIgPSBjKDAuNSwgMC43LCAwLjksIDEuMywgMS44KSwNCiAgQyA9IGMoMC4zLCAwLjYsIDEuMCwgMS4yLCAyLjIpLA0KICBEID0gYygwLjQsIDAuNywgMS4yLCAxLjUsIDMuMikNCikNCnBsYW50X2hlaWdodA0KDQpsb25nIDwtIHBsYW50X2hlaWdodCAlPiUNCiAgcGl2b3RfbG9uZ2VyKA0KICAgIGNvbHMgPSBBOkQsDQogICAgbmFtZXNfdG8gPSAicGxhbnQiLA0KICAgIHZhbHVlc190byA9ICJoZWlnaHQiDQogICkNCmxvbmcNCg0KbG9uZyAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gRGF5LCB5ID0gaGVpZ2h0LCBjb2xvciA9IHBsYW50KSkgKw0KICBnZW9tX2xpbmUoKQ0KDQp3aWRlIDwtIGxvbmcgJT4lDQogIHBpdm90X3dpZGVyKA0KICAgIG5hbWVzX2Zyb20gPSAicGxhbnQiLA0KICAgIHZhbHVlc19mcm9tID0gImhlaWdodCINCiAgKQ0Kd2lkZQ0KYGBgDQoNCiMjIyMg6auY57qn5bqU55SoDQoNCmBgYHtyfQ0KcGxhbnRfcmVjb3JkIDwtIGRhdGEuZnJhbWUoDQogIGRheSA9IGMoMUwsIDJMLCAzTCwgNEwsIDVMKSwNCiAgQV9oZWlnaHQgPSBjKDEuMSwgMS4yLCAxLjMsIDEuNCwgMS41KSwNCiAgQV93aWR0aCA9IGMoMi4xLCAyLjIsIDIuMywgMi40LCAyLjUpLA0KICBBX2RlcHRoID0gYygzLjEsIDMuMiwgMy4zLCAzLjQsIDMuNSksDQogIEJfaGVpZ2h0ID0gYyg0LjEsIDQuMiwgNC4zLCA0LjQsIDQuNSksDQogIEJfd2lkdGggPSBjKDUuMSwgNS4yLCA1LjMsIDUuNCwgNS41KSwNCiAgQl9kZXB0aCA9IGMoNi4xLCA2LjIsIDYuMywgNi40LCA2LjUpLA0KICBDX2hlaWdodCA9IGMoNy4xLCA3LjIsIDcuMywgNy40LCA3LjUpLA0KICBDX3dpZHRoID0gYyg4LjEsIDguMiwgOC4zLCA4LjQsIDguNSksDQogIENfZGVwdGggPSBjKDkuMSwgOS4yLCA5LjMsIDkuNCwgOS41KQ0KKQ0KcGxhbnRfcmVjb3JkDQoNCnBsYW50X3JlY29yZCAlPiUNCiAgdGlkeXI6OnBpdm90X2xvbmdlcigNCiAgICBjb2xzID0gIWRheSwNCiAgICBuYW1lc190byA9IGMoInNwZWNpZXMiLCAicGFyYW1ldGVyIiksDQogICAgbmFtZXNfcGF0dGVybiA9ICIoLiopXyguKikiLA0KICAgIHZhbHVlc190byA9ICJ2YWx1ZSINCiAgKQ0KDQpwbGFudF9yZWNvcmRfbG9uZ2VyIDwtIHBsYW50X3JlY29yZCAlPiUNCiAgdGlkeXI6OnBpdm90X2xvbmdlcigNCiAgICBjb2xzID0gIWRheSwNCiAgICAjIOazqOaEj+aXtiAudmFsdWUsIOS4jeaYr+S4gOS4quWIlw0KICAgIG5hbWVzX3RvID0gYygic3BlY2llcyIsICIudmFsdWUiKSwNCiAgICBuYW1lc19wYXR0ZXJuID0gIiguKilfKC4qKSINCiAgKQ0KcGxhbnRfcmVjb3JkX2xvbmdlcg0KDQoNCnVzX3JlbnRfaW5jb21lICU+JQ0KICBwaXZvdF93aWRlcigNCiAgICBuYW1lc19mcm9tID0gdmFyaWFibGUsDQogICAgbmFtZXNfZ2x1ZSA9ICJ7dmFyaWFibGV9X3sudmFsdWV9IiwNCiAgICB2YWx1ZXNfZnJvbSA9IGMoZXN0aW1hdGUsIG1vZSkNCiAgKQ0KDQpwbGFudF9yZWNvcmRfbG9uZ2VyICU+JQ0KICB0aWR5cjo6cGl2b3Rfd2lkZXIoDQogICAgbmFtZXNfZnJvbSA9IHNwZWNpZXMsDQogICAgdmFsdWVzX2Zyb20gPSBjKGhlaWdodCwgd2lkdGgsIGRlcHRoKSwNCiAgICBuYW1lc19nbHVlID0gIntzcGVjaWVzfV97LnZhbHVlfSINCiAgKQ0KYGBgDQoNCg0KIyMjIyDlrr3ovazplb8gYHRpZHlyOjpnYXRoZXIoKWANCg0KYGdhdGhlcihkYXRhLCBrZXksIHZhbHVlLCDigKYsIG5hLnJtID0gRkFMU0UsIGNvbnZlcnQgPSBGQUxTRSlgDQoNCi0ga2V577ya5bCG5LiN5omT566X5L+d55WZ55qE5YiX55qE5YiX5ZCN6LWL57uZ5LiA5Liq5paw5Y+Y6YePa2V5DQotIHZhbHVl77ya5bCG5LiN5omT566X5L+d55WZ55qE5YiX55qE5YC86LWL57uZ5LiA5Liq5paw5Y+Y6YePdmFsdWUNCi0g4oCm77ya6YCJ5Lit6KaBZ2F0aGVy55qE5YiX77yI5LiN5omT566X5L+d55WZ55qE5YiX77yJ77yM6Iul5q2k5Y+C5pWw5LiN6LWL5YC85YiZZ2F0aGVy5omA5pyJ5YiX44CCDQotIG5hLnJt77ya5piv5ZCm5Yig6Zmk57y65aSx5YC8DQoNCmBgYHtyfQ0KbXRjYXJzIDwtIGhlYWQoZGF0YXNldHM6Om10Y2FycykNCg0KIyDkuLrmlrnkvr/lpITnkIbvvIzlnKjmlbDmja7pm4bkuK3lop7liqDkuIDliJdjYXINCm10Y2FycyRjYXIgPC0gcm93bmFtZXMobXRjYXJzKSAgICANCiPlsIbmt7vliqDnmoTkuIDliJfku47mnIDlkI7kuIDliJfnp7vliLDmnIDliY3liJcNCm10Y2FycyA8LSBtdGNhcnNbLCBjKDEyLCAxOjExKV0gICANCmhlYWQobXRjYXJzKQ0KDQojIOmZpOS6hmNhcuWIl+Wklu+8jOWFtuS9meWIl2dhdGhlcuaIkOS4pOWIl++8jOWIhuWIq+WRveWQjeS4umF0dHJpYnV0ZeWSjHZhbHVlDQptdGNhcnNOZXcgPC0gbXRjYXJzICU+JSANCiAgZ2F0aGVyKGtleSA9IGF0dHJpYnV0ZSwgdmFsdWUgPSB2YWx1ZSwgLWNhcikNCmhlYWQobXRjYXJzTmV3KQ0KDQojIGdhdGhlcuWcqG1hcOWSjGdlYXLkuYvpl7TnmoTmiYDmnInliJfvvIzku47ogIzkv53mjIFjYXJi5ZKMY2Fy5YiX5LiN5Y+YDQptdGNhcnNOZXcgPC0gbXRjYXJzICU+JSBnYXRoZXIoYXR0cmlidXRlLCB2YWx1ZSwgbXBnOmdlYXIpDQpoZWFkKG10Y2Fyc05ldykNCg0KIyDlj6pnYXRoZXIoKSBgZ2VhcmDlkoxgY2FyYmDkuKTliJcNCm10Y2Fyc05ldyA8LSBtdGNhcnMgJT4lIA0KICBnYXRoZXIoa2V5ID0gYXR0cmlidXRlLCB2YWx1ZSA9IHZhbHVlLCBnZWFyLCBjYXJiKQ0KaGVhZChtdGNhcnNOZXcpDQp1bmlxdWUobXRjYXJzTmV3JGF0dHJpYnV0ZSkNCg0KDQojIyDmiJbnlKjkuIrkuIDoioLnmoTkvovlrZANCm1kIDwtIG15ZGF0YSAlPiUgZ2F0aGVyKHZhcmlhYmxlLCB2YWx1ZSwgWDEsIFgyKQ0KbWQNCmBgYA0KDQojIyMjIOmVv+i9rOWuvSBgdGlkeXI6OnNwcmVhZCgpYA0KDQpgc3ByZWFkKGRhdGEsIGtleSwgdmFsdWUsIGZpbGwgPSBOQSwgY29udmVydCA9IEZBTFNFLCBkcm9wID0gVFJVRSlgDQoNCi0ga2V577ya6ZyA6KaB5bCG5Y+Y6YeP5YC85ouT5bGV5Li65a2X5q6155qE5Y+Y6YeP5ZCNDQotIHZhbHVl77ya6ZyA6KaB5YiG5pWj55qE5YC855qE5Y+Y6YeP5ZCNDQotIGZpbGzvvJrlr7nkuo7nvLrlpLHlgLzvvIzlj6/lsIZmaWxs55qE5YC86LWL5YC857uZ6KKr6L2s5Z6L5ZCO55qE57y65aSx5YC8DQoNCmBgYHtyfQ0KbXRjYXJzU3ByZWFkIDwtIG10Y2Fyc05ldyAlPiUgDQogIHNwcmVhZChrZXkgPSBhdHRyaWJ1dGUsIHZhbHVlID0gdmFsdWUpDQpoZWFkKG10Y2Fyc1NwcmVhZCkNCg0KIyMg5oiW55So5LiK5LiA6IqC55qE5L6L5a2QDQptZCAlPiUgc3ByZWFkKHZhcmlhYmxlLCB2YWx1ZSkNCmBgYA0KDQoNCiMjIOaVsOaNruihqOeahOeugOWNleWQiOW5tg0KDQotIGBiaW5kX2NvbHModGFibGUxLCB0YWJsZTIpYCDnurXlkJHlkIjlubYNCi0gYGJpbmRfcm93cyh0YWJsZTEsIHRhYmxlMilgIOaoquWQkeWQiOW5tg0KICAtIGBiaW5kX3Jvd3MoInRhYmxlTmFtZTEiID0gdGFibGUxLCAidGFibGVOYW1lMiIgPSB0YWJsZTIsIC5pZCA9IE5VTEwpYCDvvIzlj6/pgInlj4LmlbAgYC5pZGAg5LiN5Li6TlVMTOiAjOaOpeaUtuS4gOS4quWtl+espuS4suaXtu+8jOWPr+S7pea3u+WKoOS4gOWIl++8jOWIl+WQjeS4uiBgLmlkYCDnmoTlj4LmlbDvvIznlKjku6XmoIfor4blkITooYzmlbDmja7nmoTmnaXmupDjgILmlrDmt7vliqDnmoTov5nkuIDliJfnmoTlkITooYzlgLzkuLogYHRhYmxlTmFtZTFgIOWSjCBgdGFibGVOYW1lMmA=