This notebook lecture will cover multivariable logistic regression in R, using the Titanic survival dataset as an example.

Introduction

Univariable models are insufficient for understanding complex phenomena because they do not account for the interconnectedness of multiple factors. Multivariable logistic regression is a more realistic approach for understanding binary outcomes, such as survival.

This lecture will cover how to: - Visualize predicted survival probabilities - Validate a model by checking its assumptions - Extract and interpret odds ratios - Identify the most important factors influencing survival - Running a Logistic Regression in R

The data

We’ll use the Survival of Passengers on the Titanic dataset which has information on the survival status, sex, age, and passenger class of 1309 passengers in the Titanic disaster of 1912.

# Load the Titanic dataset
data(TitanicSurvival)
Warning: data set ‘TitanicSurvival’ not found

It’s always a good idea to explore new datasets to get a feel for the number of observations, number of variable, and missing values

library(DataExplorer)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'rmarkdown':
  method         from
  print.paged_df     
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
## View basic description for TitanicSurvival data
t(introduce(TitanicSurvival))
                      [,1]
rows                  1309
columns                  4
discrete_columns         3
continuous_columns       1
all_missing_columns      0
total_missing_values     0
complete_rows         1309
total_observations    5236
memory_usage         29536
## Plot basic description for TitanicSurvival data
plot_intro(TitanicSurvival)

## View missing value distribution for TitanicSurvival data
plot_missing(TitanicSurvival)

## frequency distribution of all discrete variables
plot_bar(TitanicSurvival)

## View histogram of all continuous variables
plot_histogram(TitanicSurvival)

## View overall correlation heatmap
plot_correlation(TitanicSurvival)

## View bivariate continuous distribution based on `cut`
plot_boxplot(TitanicSurvival, by = "survived")

Those missing values for age are a problem. The glm function will listwise delete these variables, but let’s explore imputation.

# Impute missing values (for instance, using median or mean imputation):
library(dplyr)

Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
# Replace NA values in the age column with the median age
TitanicSurvival <- TitanicSurvival %>%
  mutate(age = ifelse(is.na(age), median(age, na.rm = TRUE), age))
m <- glm(formula = survived ~ sex + age + passengerClass,
         data = d,
         family = binomial)

The glm() function is used to run a logistic regression in R. It stands for “generalized linear model.” The family = binomial argument is used to specify that the outcome is binary. The formula for a logistic regression has two parts: - The left side specifies the binary outcome variable. - The right side specifies the predictor variables.
Finally, the data argument specifies the dataset to be used.

# Load necessary packages
library(carData)

Attaching package: ‘carData’

The following object is masked _by_ ‘.GlobalEnv’:

    TitanicSurvival
library(car)

Attaching package: ‘car’

The following object is masked from ‘package:dplyr’:

    recode
library(performance)
library(ggeffects)
library(sjPlot)
library(gtsummary)
library(equatiomatic)
library(vip)

Attaching package: ‘vip’

The following object is masked from ‘package:utils’:

    vi
library(pROC)
Type 'citation("pROC")' for a citation.

Attaching package: ‘pROC’

The following objects are masked from ‘package:stats’:

    cov, smooth, var
# Run the logistic regression
model <- glm(survived ~ sex + age + passengerClass, data = TitanicSurvival, family = binomial)

Handling Nonlinearity

Numeric variables, such as age, can have nonlinear relationships with the outcome. It’s important to check for nonlinearity using methods such as generalized additive models.

The gam() function can be used to fit a generalized additive model.

# Fit a generalized additive model
library(mgcv)
Loading required package: nlme

Attaching package: ‘nlme’

The following object is masked from ‘package:dplyr’:

    collapse

This is mgcv 1.9-1. For overview type 'help("mgcv-package")'.
gam_model <- gam(survived ~ s(age) + sex + passengerClass, data = TitanicSurvival, family = binomial)

# Plot the generalized additive model
plot(gam_model)

If a nonlinear relationship is found, it needs to be addressed appropriately.

Checking Model Assumptions

Checking model assumptions is crucial to ensure the validity of the results. The check_model() function from the performance package automatically checks all relevant assumptions for the specified model type.

It checks assumptions such as: - The distribution of residuals - Multicollinearity - Influential outliers

The independence of observations assumption needs to be checked separately, especially if the data includes repeated measures.

The check_model() function can also handle mixed effects logistic regression models.

# Check model assumptions
check_model(model)

Visualizing Model Results

The ggpredict() function from the ggeffects package can be used to obtain predicted probabilities for numeric variables. It provides multiple values to illustrate trends, rather than just the average. Averages are calculated across all categories of other predictors. Averaging across all categories is important because many predictive functions default to the reference levels for categorical predictors. This can lead to misleading conclusions if not considered carefully. The plot() function can be used to visualize the estimated survival probabilities for each predictor separately. The plot_grid() function from the sjPlot package can be used to combine multiple plots into a single figure.

library(ggeffects)
library(cowplot)

Attaching package: ‘cowplot’

The following objects are masked from ‘package:sjPlot’:

    plot_grid, save_plot

The following object is masked from ‘package:ggeffects’:

    get_title
# Obtain predicted probabilities for each term separately
predictions_age <- ggpredict(model, terms = "age")
Data were 'prettified'. Consider using `terms="age [all]"` to get smooth plots.
predictions_sex <- ggpredict(model, terms = "sex")
predictions_passengerClass <- ggpredict(model, terms = "passengerClass")

# Plot each term individually
plot_age <- plot(predictions_age)
plot_sex <- plot(predictions_sex)
plot_passengerClass <- plot(predictions_passengerClass)

# Combine plots into a single figure
plot_grid(plot_age, plot_sex, plot_passengerClass)

# extract predictions
library(ggeffects)
ggeffect(model)
$sex
# Predicted probabilities of survived

sex    | Predicted |     95% CI
-------------------------------
female |      0.72 | 0.68, 0.77
male   |      0.18 | 0.15, 0.21


$age
# Predicted probabilities of survived

age | Predicted |     95% CI
----------------------------
  0 |      0.58 | 0.48, 0.66
 10 |      0.50 | 0.43, 0.56
 20 |      0.42 | 0.37, 0.46
 30 |      0.34 | 0.31, 0.37
 40 |      0.27 | 0.24, 0.31
 50 |      0.21 | 0.17, 0.27
 60 |      0.16 | 0.12, 0.23
 80 |      0.09 | 0.05, 0.16

Not all rows are shown in the output. Use `print(..., n = Inf)` to show all rows.

$passengerClass
# Predicted probabilities of survived

passengerClass | Predicted |     95% CI
---------------------------------------
1st            |      0.68 | 0.62, 0.74
2nd            |      0.40 | 0.33, 0.47
3rd            |      0.20 | 0.17, 0.24


attr(,"class")
[1] "ggalleffects" "list"        
attr(,"model.name")
[1] "model"

Creating a Table of Odds Ratios

Odds ratios and their confidence intervals and p-values are essential for confirming the statistical significance of differences in survival rates. The tbl_regression() function from the gtsummary package can be used to create a table of odds ratios. The exponentiate = TRUE argument converts log odds ratios to odds ratios. The add_pairwise_contrasts = TRUE argument compares all categories with each other. The table can be saved in various formats, such as Microsoft Word or PNG.

# Create a table of odds ratios
fancy_table <- tbl_regression(
  model, 
  exponentiate = TRUE, 
  add_pairwise_contrasts = TRUE,
  # up your table game
  contrasts_adjust = "bonferroni", # none
  pairwise_reverse = FALSE,
  pvalue_fun = ~style_pvalue(.x, digits = 3)) %>%
  add_significance_stars(hide_p = F, hide_se = T,
                         hide_ci = F) %>%
  bold_p()

fancy_table
Characteristic OR1,2 95% CI2 p-value
sex


    female / male 12.2*** 9.09, 16.3 <0.001
age 0.97*** 0.96, 0.98 <0.001
passengerClass


    1st / 2nd 3.26*** 1.97, 5.40 <0.001
    1st / 3rd 8.65*** 5.42, 13.8 <0.001
    2nd / 3rd 2.65*** 1.72, 4.09 <0.001
1 *p<0.05; **p<0.01; ***p<0.001
2 OR = Odds Ratio, CI = Confidence Interval

Writing Equations for Multivariable Models

The extract_eq() function from the equatiomatic package converts a model to LaTeX format, making it easier to include equations in documents.

# Extract the model equation
extract_eq(model)
$$
\log\left[ \frac { P( \operatorname{survived} = \operatorname{yes} ) }{ 1 - P( \operatorname{survived} = \operatorname{yes} ) } \right] = \alpha + \beta_{1}(\operatorname{sex}_{\operatorname{male}}) + \beta_{2}(\operatorname{age}) + \beta_{3}(\operatorname{passengerClass}_{\operatorname{2nd}}) + \beta_{4}(\operatorname{passengerClass}_{\operatorname{3rd}})
$$

Interpreting Model Results

summary(model)

Call:
glm(formula = survived ~ sex + age + passengerClass, family = binomial, 
    data = TitanicSurvival)

Coefficients:
                   Estimate Std. Error z value Pr(>|z|)    
(Intercept)        3.332795   0.299152  11.141  < 2e-16 ***
sexmale           -2.498210   0.148571 -16.815  < 2e-16 ***
age               -0.032159   0.006107  -5.266 1.39e-07 ***
passengerClass2nd -1.183165   0.209954  -5.635 1.75e-08 ***
passengerClass3rd -2.157681   0.195160 -11.056  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1741.0  on 1308  degrees of freedom
Residual deviance: 1228.1  on 1304  degrees of freedom
AIC: 1238.1

Number of Fisher Scoring iterations: 4

The contrast_adjust argument in the tbl_regression() function allows for different p-value adjustments for multiple comparisons, such as Bonferroni or Tukey. The Bonferroni correction is conservative and can increase p-values, making it harder to reach significance. Other corrections or no correction may be more appropriate depending on the data and research question. The pairwise_reverse = FALSE argument in the tbl_regression() function can be used to interpret odds ratios greater than one instead of less than one.

Variable Importance

With large sample sizes, all p-values may be significant, making it difficult to determine which predictors are most important.

Two methods for ranking variable importance are: - The Anova() function from the car package, which provides likelihood ratio chi-square values. Higher chi-square values indicate greater importance. - Random forest classification, using the vip() package to create a variable importance plot.

# Assess variable importance using ANOVA
Anova(model)
Analysis of Deviance Table (Type II tests)

Response: survived
               LR Chisq Df Pr(>Chisq)    
sex              340.74  1  < 2.2e-16 ***
age               29.10  1  6.876e-08 ***
passengerClass   139.55  2  < 2.2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
# Run the random forest model
library(randomForest)
randomForest 4.7-1.2
Type rfNews() to see new features/changes/bug fixes.

Attaching package: ‘randomForest’

The following object is masked from ‘package:dplyr’:

    combine
rf_model <- randomForest(survived ~ sex + age + passengerClass, data = TitanicSurvival)

# Create a variable importance plot
vip(rf_model)

Model Fit

# Obtain model performance metrics
performance(model)
# Indices of model performance

AIC      |     AICc |      BIC | Tjur's R2 |  RMSE | Sigma | Log_loss | Score_log | Score_spherical |   PCP
-----------------------------------------------------------------------------------------------------------
1238.123 | 1238.169 | 1264.008 |     0.362 | 0.387 | 1.000 |    0.469 |      -Inf |       9.369e-04 | 0.699
# Interpret R-squared value
library(effectsize)
library(performance)

# Calculate R-squared
r2_value <- r2(model)$R2
# Interpret the R-squared value
interpret_r2(r2_value)
    Tjur's R2 
"substantial" 
(Rules: cohen1988)

Receiver Operating Characteristic (ROC) Curve

The ROC curve visualizes a model’s ability to distinguish between positive and negative cases. Area under the curve (AUC): A higher AUC indicates better model performance. The roc() function from the pROC package can be used to create an ROC curve. The confusion matrix provides additional insights into model performance, including metrics such as specificity, sensitivity, and accuracy.

# Create an ROC curve
roc_curve <- roc(survived ~ fitted.values(model), data=TitanicSurvival,
                 plot = TRUE, legacy.axes = TRUE,
                 print.auc = TRUE, ci = TRUE)
Setting levels: control = no, case = yes
Setting direction: controls < cases

# Plot the ROC curve
plot(roc_curve)


# Print the AUC
auc(roc_curve)
Area under the curve: 0.8339

Comparing Multivariable and Univariable Models

Multivariable models provide a more realistic understanding of relationships than univariable models because they account for spurious correlations. Analyzing variables in isolation can lead to misleading conclusions.

Conclusion

This notebook lecture has covered the key concepts and techniques of multivariable logistic regression in R. You have learned how to run and interpret a model, check assumptions, visualize results, assess variable importance, evaluate model fit, and understand the importance of considering multiple variables simultaneously.install

LS0tDQp0aXRsZTogIk11bHRpdmFyaWFibGUgTG9naXN0aWMgUmVncmVzc2lvbiBpbiBSIE5vdGVib29rIExlY3R1cmUiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICB0b2M6IHRydWUNCi0tLQ0KDQpUaGlzIG5vdGVib29rIGxlY3R1cmUgd2lsbCBjb3ZlciBtdWx0aXZhcmlhYmxlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gaW4gUiwgdXNpbmcgdGhlIFRpdGFuaWMgc3Vydml2YWwgZGF0YXNldCBhcyBhbiBleGFtcGxlLg0KDQojIyBJbnRyb2R1Y3Rpb24NClVuaXZhcmlhYmxlIG1vZGVscyBhcmUgaW5zdWZmaWNpZW50IGZvciB1bmRlcnN0YW5kaW5nIGNvbXBsZXggcGhlbm9tZW5hIGJlY2F1c2UgdGhleSBkbyBub3QgYWNjb3VudCBmb3IgdGhlIGludGVyY29ubmVjdGVkbmVzcyBvZiBtdWx0aXBsZSBmYWN0b3JzLiBNdWx0aXZhcmlhYmxlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgYSBtb3JlIHJlYWxpc3RpYyBhcHByb2FjaCBmb3IgdW5kZXJzdGFuZGluZyBiaW5hcnkgb3V0Y29tZXMsIHN1Y2ggYXMgc3Vydml2YWwuIA0KDQpUaGlzIGxlY3R1cmUgd2lsbCBjb3ZlciBob3cgdG86IA0KIC0gVmlzdWFsaXplIHByZWRpY3RlZCBzdXJ2aXZhbCBwcm9iYWJpbGl0aWVzDQogLSBWYWxpZGF0ZSBhIG1vZGVsIGJ5IGNoZWNraW5nIGl0cyBhc3N1bXB0aW9ucw0KIC0gRXh0cmFjdCBhbmQgaW50ZXJwcmV0IG9kZHMgcmF0aW9zDQogLSBJZGVudGlmeSB0aGUgbW9zdCBpbXBvcnRhbnQgZmFjdG9ycyBpbmZsdWVuY2luZyBzdXJ2aXZhbA0KIC0gUnVubmluZyBhIExvZ2lzdGljIFJlZ3Jlc3Npb24gaW4gUg0KIA0KIyMgVGhlIGRhdGENCldlJ2xsIHVzZSB0aGUgU3Vydml2YWwgb2YgUGFzc2VuZ2VycyBvbiB0aGUgVGl0YW5pYyBkYXRhc2V0IHdoaWNoIGhhcyBpbmZvcm1hdGlvbiBvbiB0aGUgc3Vydml2YWwgc3RhdHVzLCBzZXgsIGFnZSwgYW5kIHBhc3NlbmdlciBjbGFzcyBvZiAxMzA5IHBhc3NlbmdlcnMgaW4gdGhlIFRpdGFuaWMgZGlzYXN0ZXIgb2YgMTkxMi4NCg0KYGBge3J9DQojIExvYWQgdGhlIFRpdGFuaWMgZGF0YXNldA0KZGF0YShUaXRhbmljU3Vydml2YWwpDQpgYGANCg0KSXQncyBhbHdheXMgYSBnb29kIGlkZWEgdG8gZXhwbG9yZSBuZXcgZGF0YXNldHMgdG8gZ2V0IGEgZmVlbCBmb3IgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMsIG51bWJlciBvZiB2YXJpYWJsZSwgYW5kIG1pc3NpbmcgdmFsdWVzDQoNCmBgYHtyfQ0KbGlicmFyeShEYXRhRXhwbG9yZXIpDQojIyBWaWV3IGJhc2ljIGRlc2NyaXB0aW9uIGZvciBUaXRhbmljU3Vydml2YWwgZGF0YQ0KdChpbnRyb2R1Y2UoVGl0YW5pY1N1cnZpdmFsKSkNCmBgYA0KYGBge3J9DQojIyBQbG90IGJhc2ljIGRlc2NyaXB0aW9uIGZvciBUaXRhbmljU3Vydml2YWwgZGF0YQ0KcGxvdF9pbnRybyhUaXRhbmljU3Vydml2YWwpDQpgYGANCg0KYGBge3J9DQojIyBWaWV3IG1pc3NpbmcgdmFsdWUgZGlzdHJpYnV0aW9uIGZvciBUaXRhbmljU3Vydml2YWwgZGF0YQ0KcGxvdF9taXNzaW5nKFRpdGFuaWNTdXJ2aXZhbCkNCmBgYA0KDQoNCmBgYHtyfQ0KIyMgZnJlcXVlbmN5IGRpc3RyaWJ1dGlvbiBvZiBhbGwgZGlzY3JldGUgdmFyaWFibGVzDQpwbG90X2JhcihUaXRhbmljU3Vydml2YWwpDQpgYGANCmBgYHtyfQ0KIyMgVmlldyBoaXN0b2dyYW0gb2YgYWxsIGNvbnRpbnVvdXMgdmFyaWFibGVzDQpwbG90X2hpc3RvZ3JhbShUaXRhbmljU3Vydml2YWwpDQpgYGANCmBgYHtyfQ0KIyMgVmlldyBvdmVyYWxsIGNvcnJlbGF0aW9uIGhlYXRtYXANCnBsb3RfY29ycmVsYXRpb24oVGl0YW5pY1N1cnZpdmFsKQ0KYGBgDQoNCmBgYHtyfQ0KIyMgVmlldyBiaXZhcmlhdGUgY29udGludW91cyBkaXN0cmlidXRpb24gYmFzZWQgb24gYGN1dGANCnBsb3RfYm94cGxvdChUaXRhbmljU3Vydml2YWwsIGJ5ID0gInN1cnZpdmVkIikNCmBgYA0KDQpUaG9zZSBtaXNzaW5nIHZhbHVlcyBmb3IgYGFnZWAgYXJlIGEgcHJvYmxlbS4gVGhlIGBnbG1gIGZ1bmN0aW9uIHdpbGwgbGlzdHdpc2UgZGVsZXRlIHRoZXNlIHZhcmlhYmxlcywgYnV0IGxldCdzIGV4cGxvcmUgaW1wdXRhdGlvbi4NCg0KYGBge3J9DQojIEltcHV0ZSBtaXNzaW5nIHZhbHVlcyAoZm9yIGluc3RhbmNlLCB1c2luZyBtZWRpYW4gb3IgbWVhbiBpbXB1dGF0aW9uKToNCmxpYnJhcnkoZHBseXIpDQoNCiMgUmVwbGFjZSBOQSB2YWx1ZXMgaW4gdGhlIGFnZSBjb2x1bW4gd2l0aCB0aGUgbWVkaWFuIGFnZQ0KVGl0YW5pY1N1cnZpdmFsIDwtIFRpdGFuaWNTdXJ2aXZhbCAlPiUNCiAgbXV0YXRlKGFnZSA9IGlmZWxzZShpcy5uYShhZ2UpLCBtZWRpYW4oYWdlLCBuYS5ybSA9IFRSVUUpLCBhZ2UpKQ0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KbSA8LSBnbG0oZm9ybXVsYSA9IHN1cnZpdmVkIH4gc2V4ICsgYWdlICsgcGFzc2VuZ2VyQ2xhc3MsDQogICAgICAgICBkYXRhID0gZCwNCiAgICAgICAgIGZhbWlseSA9IGJpbm9taWFsKQ0KYGBgDQoNClRoZSBgZ2xtKClgIGZ1bmN0aW9uIGlzIHVzZWQgdG8gcnVuIGEgbG9naXN0aWMgcmVncmVzc2lvbiBpbiBSLiBJdCBzdGFuZHMgZm9yICJnZW5lcmFsaXplZCBsaW5lYXIgbW9kZWwuIiBUaGUgYGZhbWlseSA9IGJpbm9taWFsYCBhcmd1bWVudCBpcyB1c2VkIHRvIHNwZWNpZnkgdGhhdCB0aGUgb3V0Y29tZSBpcyBiaW5hcnkuIFRoZSBmb3JtdWxhIGZvciBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gaGFzIHR3byBwYXJ0czogDQogLSBUaGUgbGVmdCBzaWRlIHNwZWNpZmllcyB0aGUgYmluYXJ5IG91dGNvbWUgdmFyaWFibGUuIA0KIC0gVGhlIHJpZ2h0IHNpZGUgc3BlY2lmaWVzIHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzLiAgDQogRmluYWxseSwgdGhlIGRhdGEgYXJndW1lbnQgc3BlY2lmaWVzIHRoZSBkYXRhc2V0IHRvIGJlIHVzZWQuIA0KIA0KYGBge3J9IA0KIyBMb2FkIG5lY2Vzc2FyeSBwYWNrYWdlcw0KbGlicmFyeShjYXJEYXRhKQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KHBlcmZvcm1hbmNlKQ0KbGlicmFyeShnZ2VmZmVjdHMpDQpsaWJyYXJ5KHNqUGxvdCkNCmxpYnJhcnkoZ3RzdW1tYXJ5KQ0KbGlicmFyeShlcXVhdGlvbWF0aWMpDQpsaWJyYXJ5KHZpcCkNCmxpYnJhcnkocFJPQykNCmBgYA0KDQpgYGB7cn0NCiMgUnVuIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uDQptb2RlbCA8LSBnbG0oc3Vydml2ZWQgfiBzZXggKyBhZ2UgKyBwYXNzZW5nZXJDbGFzcywgZGF0YSA9IFRpdGFuaWNTdXJ2aXZhbCwgZmFtaWx5ID0gYmlub21pYWwpDQpgYGANCg0KIyMgSGFuZGxpbmcgTm9ubGluZWFyaXR5ICANCk51bWVyaWMgdmFyaWFibGVzLCBzdWNoIGFzIGFnZSwgY2FuIGhhdmUgbm9ubGluZWFyIHJlbGF0aW9uc2hpcHMgd2l0aCB0aGUgb3V0Y29tZS4gDQpJdCdzIGltcG9ydGFudCB0byBjaGVjayBmb3Igbm9ubGluZWFyaXR5IHVzaW5nIG1ldGhvZHMgc3VjaCBhcyBnZW5lcmFsaXplZCBhZGRpdGl2ZSBtb2RlbHMuDQoNClRoZSBgZ2FtKClgIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIHRvIGZpdCBhIGdlbmVyYWxpemVkIGFkZGl0aXZlIG1vZGVsLg0KDQpgYGB7cn0NCiMgRml0IGEgZ2VuZXJhbGl6ZWQgYWRkaXRpdmUgbW9kZWwNCmxpYnJhcnkobWdjdikNCmdhbV9tb2RlbCA8LSBnYW0oc3Vydml2ZWQgfiBzKGFnZSkgKyBzZXggKyBwYXNzZW5nZXJDbGFzcywgZGF0YSA9IFRpdGFuaWNTdXJ2aXZhbCwgZmFtaWx5ID0gYmlub21pYWwpDQoNCiMgUGxvdCB0aGUgZ2VuZXJhbGl6ZWQgYWRkaXRpdmUgbW9kZWwNCnBsb3QoZ2FtX21vZGVsKQ0KYGBgDQoNCklmIGEgbm9ubGluZWFyIHJlbGF0aW9uc2hpcCBpcyBmb3VuZCwgaXQgbmVlZHMgdG8gYmUgYWRkcmVzc2VkIGFwcHJvcHJpYXRlbHkuIA0KDQojIyBDaGVja2luZyBNb2RlbCBBc3N1bXB0aW9ucyAgDQpDaGVja2luZyBtb2RlbCBhc3N1bXB0aW9ucyBpcyBjcnVjaWFsIHRvIGVuc3VyZSB0aGUgdmFsaWRpdHkgb2YgdGhlIHJlc3VsdHMuIFRoZSBgY2hlY2tfbW9kZWwoKWAgZnVuY3Rpb24gZnJvbSB0aGUgcGVyZm9ybWFuY2UgcGFja2FnZSBhdXRvbWF0aWNhbGx5IGNoZWNrcyBhbGwgcmVsZXZhbnQgYXNzdW1wdGlvbnMgZm9yIHRoZSBzcGVjaWZpZWQgbW9kZWwgdHlwZS4gDQoNCkl0IGNoZWNrcyBhc3N1bXB0aW9ucyBzdWNoIGFzOiANCiAtIFRoZSBkaXN0cmlidXRpb24gb2YgcmVzaWR1YWxzDQogLSBNdWx0aWNvbGxpbmVhcml0eQ0KIC0gSW5mbHVlbnRpYWwgb3V0bGllcnMNCg0KVGhlIGluZGVwZW5kZW5jZSBvZiBvYnNlcnZhdGlvbnMgYXNzdW1wdGlvbiBuZWVkcyB0byBiZSBjaGVja2VkIHNlcGFyYXRlbHksIGVzcGVjaWFsbHkgaWYgdGhlIGRhdGEgaW5jbHVkZXMgcmVwZWF0ZWQgbWVhc3VyZXMuDQoNClRoZSBgY2hlY2tfbW9kZWwoKWAgZnVuY3Rpb24gY2FuIGFsc28gaGFuZGxlIG1peGVkIGVmZmVjdHMgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMuIA0KDQpgYGB7cn0NCiMgQ2hlY2sgbW9kZWwgYXNzdW1wdGlvbnMNCmNoZWNrX21vZGVsKG1vZGVsKQ0KYGBgDQoNCiMjIFZpc3VhbGl6aW5nIE1vZGVsIFJlc3VsdHMNClRoZSBgZ2dwcmVkaWN0KClgIGZ1bmN0aW9uIGZyb20gdGhlIFtnZ2VmZmVjdHMgcGFja2FnZV0oaHR0cHM6Ly9zdHJlbmdlamFja2UuZ2l0aHViLmlvL2dnZWZmZWN0cy8pIGNhbiBiZSB1c2VkIHRvIG9idGFpbiBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBmb3IgbnVtZXJpYyB2YXJpYWJsZXMuICBJdCBwcm92aWRlcyBtdWx0aXBsZSB2YWx1ZXMgdG8gaWxsdXN0cmF0ZSB0cmVuZHMsIHJhdGhlciB0aGFuIGp1c3QgdGhlIGF2ZXJhZ2UuIEF2ZXJhZ2VzIGFyZSBjYWxjdWxhdGVkIGFjcm9zcyBhbGwgY2F0ZWdvcmllcyBvZiBvdGhlciBwcmVkaWN0b3JzLiBBdmVyYWdpbmcgYWNyb3NzIGFsbCBjYXRlZ29yaWVzIGlzIGltcG9ydGFudCBiZWNhdXNlIG1hbnkgcHJlZGljdGl2ZSBmdW5jdGlvbnMgZGVmYXVsdCB0byB0aGUgcmVmZXJlbmNlIGxldmVscyBmb3IgY2F0ZWdvcmljYWwgcHJlZGljdG9ycy4gVGhpcyBjYW4gbGVhZCB0byBtaXNsZWFkaW5nIGNvbmNsdXNpb25zIGlmIG5vdCBjb25zaWRlcmVkIGNhcmVmdWxseS4gVGhlIGBwbG90KClgIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIHRvIHZpc3VhbGl6ZSB0aGUgZXN0aW1hdGVkIHN1cnZpdmFsIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggcHJlZGljdG9yIHNlcGFyYXRlbHkuIA0KVGhlIGBwbG90X2dyaWQoKWAgZnVuY3Rpb24gZnJvbSB0aGUgc2pQbG90IHBhY2thZ2UgY2FuIGJlIHVzZWQgdG8gY29tYmluZSBtdWx0aXBsZSBwbG90cyBpbnRvIGEgc2luZ2xlIGZpZ3VyZS4NCg0KYGBge3J9DQpsaWJyYXJ5KGdnZWZmZWN0cykNCmxpYnJhcnkoY293cGxvdCkNCg0KIyBPYnRhaW4gcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggdGVybSBzZXBhcmF0ZWx5DQpwcmVkaWN0aW9uc19hZ2UgPC0gZ2dwcmVkaWN0KG1vZGVsLCB0ZXJtcyA9ICJhZ2UiKQ0KcHJlZGljdGlvbnNfc2V4IDwtIGdncHJlZGljdChtb2RlbCwgdGVybXMgPSAic2V4IikNCnByZWRpY3Rpb25zX3Bhc3NlbmdlckNsYXNzIDwtIGdncHJlZGljdChtb2RlbCwgdGVybXMgPSAicGFzc2VuZ2VyQ2xhc3MiKQ0KDQojIFBsb3QgZWFjaCB0ZXJtIGluZGl2aWR1YWxseQ0KcGxvdF9hZ2UgPC0gcGxvdChwcmVkaWN0aW9uc19hZ2UpDQpwbG90X3NleCA8LSBwbG90KHByZWRpY3Rpb25zX3NleCkNCnBsb3RfcGFzc2VuZ2VyQ2xhc3MgPC0gcGxvdChwcmVkaWN0aW9uc19wYXNzZW5nZXJDbGFzcykNCg0KIyBDb21iaW5lIHBsb3RzIGludG8gYSBzaW5nbGUgZmlndXJlDQpwbG90X2dyaWQocGxvdF9hZ2UsIHBsb3Rfc2V4LCBwbG90X3Bhc3NlbmdlckNsYXNzKQ0KYGBgDQpgYGB7cn0NCiMgZXh0cmFjdCBwcmVkaWN0aW9ucw0KbGlicmFyeShnZ2VmZmVjdHMpDQpnZ2VmZmVjdChtb2RlbCkNCmBgYA0KDQojIyBDcmVhdGluZyBhIFRhYmxlIG9mIE9kZHMgUmF0aW9zICANCk9kZHMgcmF0aW9zIGFuZCB0aGVpciBjb25maWRlbmNlIGludGVydmFscyBhbmQgKnAqLXZhbHVlcyBhcmUgZXNzZW50aWFsIGZvciBjb25maXJtaW5nIHRoZSBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2Ugb2YgZGlmZmVyZW5jZXMgaW4gc3Vydml2YWwgcmF0ZXMuIFRoZSBgdGJsX3JlZ3Jlc3Npb24oKWAgZnVuY3Rpb24gZnJvbSB0aGUgZ3RzdW1tYXJ5IHBhY2thZ2UgY2FuIGJlIHVzZWQgdG8gY3JlYXRlIGEgdGFibGUgb2Ygb2RkcyByYXRpb3MuIA0KVGhlIGBleHBvbmVudGlhdGUgPSBUUlVFYCBhcmd1bWVudCBjb252ZXJ0cyBsb2cgb2RkcyByYXRpb3MgdG8gb2RkcyByYXRpb3MuIA0KVGhlIGBhZGRfcGFpcndpc2VfY29udHJhc3RzID0gVFJVRWAgYXJndW1lbnQgY29tcGFyZXMgYWxsIGNhdGVnb3JpZXMgd2l0aCBlYWNoIG90aGVyLiANClRoZSB0YWJsZSBjYW4gYmUgc2F2ZWQgaW4gdmFyaW91cyBmb3JtYXRzLCBzdWNoIGFzIE1pY3Jvc29mdCBXb3JkIG9yIFBORy4gDQoNCmBgYHtyfQ0KIyBDcmVhdGUgYSB0YWJsZSBvZiBvZGRzIHJhdGlvcw0KZmFuY3lfdGFibGUgPC0gdGJsX3JlZ3Jlc3Npb24oDQogIG1vZGVsLCANCiAgZXhwb25lbnRpYXRlID0gVFJVRSwgDQogIGFkZF9wYWlyd2lzZV9jb250cmFzdHMgPSBUUlVFLA0KICAjIHVwIHlvdXIgdGFibGUgZ2FtZQ0KICBjb250cmFzdHNfYWRqdXN0ID0gImJvbmZlcnJvbmkiLCAjIG5vbmUNCiAgcGFpcndpc2VfcmV2ZXJzZSA9IEZBTFNFLA0KICBwdmFsdWVfZnVuID0gfnN0eWxlX3B2YWx1ZSgueCwgZGlnaXRzID0gMykpICU+JQ0KICBhZGRfc2lnbmlmaWNhbmNlX3N0YXJzKGhpZGVfcCA9IEYsIGhpZGVfc2UgPSBULA0KICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGVfY2kgPSBGKSAlPiUNCiAgYm9sZF9wKCkNCg0KZmFuY3lfdGFibGUNCmBgYA0KDQojIyBXcml0aW5nIEVxdWF0aW9ucyBmb3IgTXVsdGl2YXJpYWJsZSBNb2RlbHMNClRoZSBgZXh0cmFjdF9lcSgpYCBmdW5jdGlvbiBmcm9tIHRoZSBbZXF1YXRpb21hdGljIHBhY2thZ2VdKGh0dHBzOi8vZGF0YWxvcmF4LmdpdGh1Yi5pby9lcXVhdGlvbWF0aWMvKSBjb252ZXJ0cyBhIG1vZGVsIHRvIExhVGVYIGZvcm1hdCwgbWFraW5nIGl0IGVhc2llciB0byBpbmNsdWRlIGVxdWF0aW9ucyBpbiBkb2N1bWVudHMuIA0KDQpgYGB7cn0NCiMgRXh0cmFjdCB0aGUgbW9kZWwgZXF1YXRpb24NCmV4dHJhY3RfZXEobW9kZWwpDQpgYGANCg0KIyMgSW50ZXJwcmV0aW5nIE1vZGVsIFJlc3VsdHMNCiAtICoqTnVtZXJpYyBwcmVkaWN0b3JzOioqIFRoZSBvZGRzIHJhdGlvIHJlcHJlc2VudHMgdGhlIGNoYW5nZSBpbiBvZGRzIG9mIHN1cnZpdmFsIGZvciBhIG9uZS11bml0IGNoYW5nZSBpbiB0aGUgcHJlZGljdG9yLCBob2xkaW5nIG90aGVyIHByZWRpY3RvcnMgY29uc3RhbnQuICANCiAtICoqQ2F0ZWdvcmljYWwgcHJlZGljdG9yczoqKiBUaGUgb2RkcyByYXRpbyByZXByZXNlbnRzIHRoZSBvZGRzIG9mIHN1cnZpdmFsIGZvciBvbmUgY2F0ZWdvcnkgZGl2aWRlZCBieSB0aGUgb2RkcyBvZiBzdXJ2aXZhbCBmb3IgYW5vdGhlciBjYXRlZ29yeSwgaG9sZGluZyBvdGhlciBwcmVkaWN0b3JzIGNvbnN0YW50LiANCg0KYGBge3J9DQpzdW1tYXJ5KG1vZGVsKQ0KYGBgDQogDQpUaGUgYGNvbnRyYXN0X2FkanVzdGAgYXJndW1lbnQgaW4gdGhlIGB0YmxfcmVncmVzc2lvbigpYCBmdW5jdGlvbiBhbGxvd3MgZm9yIGRpZmZlcmVudCAqcCotdmFsdWUgYWRqdXN0bWVudHMgZm9yIG11bHRpcGxlIGNvbXBhcmlzb25zLCBzdWNoIGFzIEJvbmZlcnJvbmkgb3IgVHVrZXkuIFRoZSBCb25mZXJyb25pIGNvcnJlY3Rpb24gaXMgY29uc2VydmF0aXZlIGFuZCBjYW4gaW5jcmVhc2UgKnAqLXZhbHVlcywgbWFraW5nIGl0IGhhcmRlciB0byByZWFjaCBzaWduaWZpY2FuY2UuIE90aGVyIGNvcnJlY3Rpb25zIG9yIG5vIGNvcnJlY3Rpb24gbWF5IGJlIG1vcmUgYXBwcm9wcmlhdGUgZGVwZW5kaW5nIG9uIHRoZSBkYXRhIGFuZCByZXNlYXJjaCBxdWVzdGlvbi4gVGhlIGBwYWlyd2lzZV9yZXZlcnNlID0gRkFMU0VgIGFyZ3VtZW50IGluIHRoZSBgdGJsX3JlZ3Jlc3Npb24oKWAgZnVuY3Rpb24gY2FuIGJlIHVzZWQgdG8gaW50ZXJwcmV0IG9kZHMgcmF0aW9zIGdyZWF0ZXIgdGhhbiBvbmUgaW5zdGVhZCBvZiBsZXNzIHRoYW4gb25lLiANCg0KIyMgVmFyaWFibGUgSW1wb3J0YW5jZQ0KV2l0aCBsYXJnZSBzYW1wbGUgc2l6ZXMsIGFsbCAqcCotdmFsdWVzIG1heSBiZSBzaWduaWZpY2FudCwgbWFraW5nIGl0IGRpZmZpY3VsdCB0byBkZXRlcm1pbmUgd2hpY2ggcHJlZGljdG9ycyBhcmUgbW9zdCBpbXBvcnRhbnQuIA0KDQpUd28gbWV0aG9kcyBmb3IgcmFua2luZyB2YXJpYWJsZSBpbXBvcnRhbmNlIGFyZTogDQogLSBUaGUgYEFub3ZhKClgIGZ1bmN0aW9uIGZyb20gdGhlIGNhciBwYWNrYWdlLCB3aGljaCBwcm92aWRlcyBsaWtlbGlob29kIHJhdGlvIGNoaS1zcXVhcmUgdmFsdWVzLiBIaWdoZXIgY2hpLXNxdWFyZSB2YWx1ZXMgaW5kaWNhdGUgZ3JlYXRlciBpbXBvcnRhbmNlLiANCiAtIFJhbmRvbSBmb3Jlc3QgY2xhc3NpZmljYXRpb24sIHVzaW5nIHRoZSBgdmlwKClgIHBhY2thZ2UgdG8gY3JlYXRlIGEgdmFyaWFibGUgaW1wb3J0YW5jZSBwbG90LiANCg0KYGBge3J9IA0KIyBBc3Nlc3MgdmFyaWFibGUgaW1wb3J0YW5jZSB1c2luZyBBTk9WQQ0KQW5vdmEobW9kZWwpDQoNCg0KIyBSdW4gdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KcmZfbW9kZWwgPC0gcmFuZG9tRm9yZXN0KHN1cnZpdmVkIH4gc2V4ICsgYWdlICsgcGFzc2VuZ2VyQ2xhc3MsIGRhdGEgPSBUaXRhbmljU3Vydml2YWwpDQoNCiMgQ3JlYXRlIGEgdmFyaWFibGUgaW1wb3J0YW5jZSBwbG90DQp2aXAocmZfbW9kZWwpDQpgYGANCg0KIyMgTW9kZWwgRml0DQogLSAqKlItc3F1YXJlZDoqKiBUaGUgcGVyZm9ybWFuY2UoKSBmdW5jdGlvbiBmcm9tIHRoZSBwZXJmb3JtYW5jZSBwYWNrYWdlIHByb3ZpZGVzIHZhcmlvdXMgcGVyZm9ybWFuY2UgbWV0cmljcywgaW5jbHVkaW5nIFItc3F1YXJlZC4gSGlnaGVyIFItc3F1YXJlZCB2YWx1ZXMgaW5kaWNhdGUgYmV0dGVyIG1vZGVsIGZpdC4gDQogLSAqKkVmZmVjdCBzaXplOioqIFRoZSBlZmZlY3RzaXplIHBhY2thZ2UgY2FuIGJlIHVzZWQgdG8gaW50ZXJwcmV0IFItc3F1YXJlZCB2YWx1ZXMuIA0KIA0KYGBge3J9DQojIE9idGFpbiBtb2RlbCBwZXJmb3JtYW5jZSBtZXRyaWNzDQpwZXJmb3JtYW5jZShtb2RlbCkNCg0KIyBJbnRlcnByZXQgUi1zcXVhcmVkIHZhbHVlDQpsaWJyYXJ5KGVmZmVjdHNpemUpDQpsaWJyYXJ5KHBlcmZvcm1hbmNlKQ0KDQojIENhbGN1bGF0ZSBSLXNxdWFyZWQNCnIyX3ZhbHVlIDwtIHIyKG1vZGVsKSRSMg0KYGBgDQoNCmBgYHtyfQ0KIyBJbnRlcnByZXQgdGhlIFItc3F1YXJlZCB2YWx1ZQ0KaW50ZXJwcmV0X3IyKHIyX3ZhbHVlKQ0KYGBgDQoNCiMjIFJlY2VpdmVyIE9wZXJhdGluZyBDaGFyYWN0ZXJpc3RpYyAoUk9DKSBDdXJ2ZQ0KVGhlIFJPQyBjdXJ2ZSB2aXN1YWxpemVzIGEgbW9kZWwncyBhYmlsaXR5IHRvIGRpc3Rpbmd1aXNoIGJldHdlZW4gcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIGNhc2VzLiANCkFyZWEgdW5kZXIgdGhlIGN1cnZlIChBVUMpOiBBIGhpZ2hlciBBVUMgaW5kaWNhdGVzIGJldHRlciBtb2RlbCBwZXJmb3JtYW5jZS4gDQpUaGUgcm9jKCkgZnVuY3Rpb24gZnJvbSB0aGUgcFJPQyBwYWNrYWdlIGNhbiBiZSB1c2VkIHRvIGNyZWF0ZSBhbiBST0MgY3VydmUuIA0KVGhlIGNvbmZ1c2lvbiBtYXRyaXggcHJvdmlkZXMgYWRkaXRpb25hbCBpbnNpZ2h0cyBpbnRvIG1vZGVsIHBlcmZvcm1hbmNlLCBpbmNsdWRpbmcgbWV0cmljcyBzdWNoIGFzIHNwZWNpZmljaXR5LCBzZW5zaXRpdml0eSwgYW5kIGFjY3VyYWN5LiANCg0KYGBge3J9DQojIENyZWF0ZSBhbiBST0MgY3VydmUNCnJvY19jdXJ2ZSA8LSByb2Moc3Vydml2ZWQgfiBmaXR0ZWQudmFsdWVzKG1vZGVsKSwgZGF0YT1UaXRhbmljU3Vydml2YWwsDQogICAgICAgICAgICAgICAgIHBsb3QgPSBUUlVFLCBsZWdhY3kuYXhlcyA9IFRSVUUsDQogICAgICAgICAgICAgICAgIHByaW50LmF1YyA9IFRSVUUsIGNpID0gVFJVRSkNCg0KIyBQbG90IHRoZSBST0MgY3VydmUNCnBsb3Qocm9jX2N1cnZlKQ0KDQojIFByaW50IHRoZSBBVUMNCmF1Yyhyb2NfY3VydmUpDQpgYGANCg0KIyMgQ29tcGFyaW5nIE11bHRpdmFyaWFibGUgYW5kIFVuaXZhcmlhYmxlIE1vZGVscw0KTXVsdGl2YXJpYWJsZSBtb2RlbHMgcHJvdmlkZSBhIG1vcmUgcmVhbGlzdGljIHVuZGVyc3RhbmRpbmcgb2YgcmVsYXRpb25zaGlwcyB0aGFuIHVuaXZhcmlhYmxlIG1vZGVscyBiZWNhdXNlIHRoZXkgYWNjb3VudCBmb3Igc3B1cmlvdXMgY29ycmVsYXRpb25zLiANCkFuYWx5emluZyB2YXJpYWJsZXMgaW4gaXNvbGF0aW9uIGNhbiBsZWFkIHRvIG1pc2xlYWRpbmcgY29uY2x1c2lvbnMuIA0KDQojIyBDb25jbHVzaW9uDQpUaGlzIG5vdGVib29rIGxlY3R1cmUgaGFzIGNvdmVyZWQgdGhlIGtleSBjb25jZXB0cyBhbmQgdGVjaG5pcXVlcyBvZiBtdWx0aXZhcmlhYmxlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gaW4gUi4gWW91IGhhdmUgbGVhcm5lZCBob3cgdG8gcnVuIGFuZCBpbnRlcnByZXQgYSBtb2RlbCwgY2hlY2sgYXNzdW1wdGlvbnMsIHZpc3VhbGl6ZSByZXN1bHRzLCBhc3Nlc3MgdmFyaWFibGUgaW1wb3J0YW5jZSwgZXZhbHVhdGUgbW9kZWwgZml0LCBhbmQgdW5kZXJzdGFuZCB0aGUgaW1wb3J0YW5jZSBvZiBjb25zaWRlcmluZyBtdWx0aXBsZSB2YXJpYWJsZXMgc2ltdWx0YW5lb3VzbHkuaW5zdGFsbA==