Coefficient plots are often more useful than tables1 but plotting raw coefficients can be misleading when the predictors are on different scales.

The packages arm and modelsummary, and later, ggstats are used to illustrate these plots.

In the process, I discover some other problems with naive use of coefficient plots. I compare plotting:

Load packages

library(dplyr)
library(ggplot2)
library(arm)           # coefplot()
library(modelsummary)  # modelplot()
library(ggstats)       # ggcoef_model()

Occupational prestige data

How does occupational prestige depend on education (years), income ($), %women, …?

I use a classic example from the carData package. The observations are averages for occupational categories rather than individuals. The data come from the 1971 Canadian census.

data(Prestige, package="carData")
head(Prestige, 5)
##                     education income women prestige census type
## gov.administrators      13.11  12351 11.16     68.8   1113 prof
## general.managers        12.26  25879  4.02     69.1   1130 prof
## accountants             12.77   9271 15.70     63.4   1171 prof
## purchasing.officers     11.42   8865  9.11     56.8   1175 prof
## chemists                14.62   8403 11.68     73.5   2111 prof

Fit a simple model

mod0 <- lm(prestige ~ education + income + women,
           data=Prestige)
summary(mod0)
## 
## Call:
## lm(formula = prestige ~ education + income + women, data = Prestige)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -19.825  -5.333  -0.136   5.159  17.504 
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -6.794334   3.239089   -2.10    0.039 *  
## education    4.186637   0.388701   10.77  < 2e-16 ***
## income       0.001314   0.000278    4.73  7.6e-06 ***
## women       -0.008905   0.030407   -0.29    0.770    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 7.85 on 98 degrees of freedom
## Multiple R-squared:  0.798,  Adjusted R-squared:  0.792 
## F-statistic:  129 on 3 and 98 DF,  p-value: <2e-16

Displaying coefficients

I’m interested in the coefficients in this model, so here is just a reminder that you can extract the numerical values using coef(), or display them in a table similar to the “Coefficients” portion above using broom::tidy() or arm::display():

coef(mod0)
## (Intercept)   education      income       women 
##   -6.794334    4.186637    0.001314   -0.008905
broom::tidy(mod0)
## # A tibble: 4 × 5
##   term        estimate std.error statistic  p.value
##   <chr>          <dbl>     <dbl>     <dbl>    <dbl>
## 1 (Intercept) -6.79     3.24        -2.10  3.85e- 2
## 2 education    4.19     0.389       10.8   2.59e-18
## 3 income       0.00131  0.000278     4.73  7.58e- 6
## 4 women       -0.00891  0.0304      -0.293 7.70e- 1
arm::display(mod0, detail = TRUE)
## lm(formula = prestige ~ education + income + women, data = Prestige)
##             coef.est coef.se t value Pr(>|t|)
## (Intercept) -6.79     3.24   -2.10    0.04   
## education    4.19     0.39   10.77    0.00   
## income       0.00     0.00    4.73    0.00   
## women       -0.01     0.03   -0.29    0.77   
## ---
## n = 102, k = 4
## residual sd = 7.85, R-Squared = 0.80

Visualize coefficients

The plots below show default coefficient plots for this model using modelsummary::modelplot() and arm::coefplot(). At the end, I show some examples using the ggstats package.

They are disappointing and misleading because these show the raw coefficients. It looks like only education has a non-zero effect, but the effect of income is also highly significant.

These plots are misleading because the predictors are on different scales, so it makes little sense to compare the change in prestige for a 1 year increase in education with the change for a $1 increase in income.

theme_set(theme_minimal(base_size = 14))
modelplot(mod0, 
          coef_omit="Intercept", 
          color="blue", size=1) +
  labs(title="Raw coefficients for mod0")

arm::coefplot(mod0, col.pts="red", cex.pts=1.5)

Other graphical features to note here:

Fit a more complex model

For this example, graphical analysis suggested that the relation between prestige and income was non-linear, which could be corrected using log(income). As well, other plots suggested an interaction with type of occupation.

mod1 <- lm(prestige ~ education + women +
             log(income)*type, data=Prestige)
summary(mod1)
## 
## Call:
## lm(formula = prestige ~ education + women + log(income) * type, 
##     data = Prestige)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -13.814  -4.685   0.656   3.966  16.569 
## 
## Coefficients:
##                       Estimate Std. Error t value Pr(>|t|)    
## (Intercept)          -152.2059    23.2499   -6.55  3.5e-09 ***
## education               2.9282     0.5883    4.98  3.1e-06 ***
## women                   0.0883     0.0323    2.73   0.0076 ** 
## log(income)            18.9819     2.8285    6.71  1.7e-09 ***
## typeprof               85.2642    30.4582    2.80   0.0063 ** 
## typewc                 29.4133    36.5075    0.81   0.4226    
## log(income):typeprof   -9.0124     3.4102   -2.64   0.0097 ** 
## log(income):typewc     -3.8334     4.2603   -0.90   0.3706    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 6.27 on 90 degrees of freedom
##   (4 observations deleted due to missingness)
## Multiple R-squared:  0.875,  Adjusted R-squared:  0.865 
## F-statistic: 90.1 on 7 and 90 DF,  p-value: <2e-16

Compare models

arm::coefplot() allows you to compare several models in the same plot, using add=TRUE for the second and subsequent ones. The intercept is ignored by default.

arm::coefplot(mod0, col.pts="red", cex.pts=1.5)
arm::coefplot(mod1, add=TRUE, col.pts="blue", cex.pts=1.5)

But WAIT: income was entered as log(income) in mod1. The plot above is silently wrong, because it doesn’t include log(income) or the interaction terms. It is was probably stupid to try to compare the coefficients, and this should have raised a warning or error.

All the terms will appear if the most complex model is plotted first.

arm::coefplot(mod1, col.pts="red", cex.pts=1.5)
arm::coefplot(mod0, add=TRUE, col.pts="blue", cex.pts=1.5)

Standardize the variables, giving \(\beta\) coefficients

An alternative is to present the standardized coefficients. These are interpreted as the standardized change in prestige for a one standard deviation change in the predictors.

One way to do this is to transform all variables to standardized (\(z\)) scores. The syntax ultimately uses scale to transform all the numeric variables.

Prestige_std <- Prestige |>
  as_tibble() |>
  mutate(across(where(is.numeric), scale))

Fit the same models to the standardized variables

This accords better with the fitted (standardized) coefficients. income and education are now both large and significant compared with the effect of women.

But a nagging doubt remains: How can I interpret the numerical size of coefficients? The direct answer is that they give the expected change in standard deviations of prestige for a one standard deviation change in each of the predictors.

mod0_std <- lm(prestige ~ education + income + women,
               data=Prestige_std)

mod1_std <- lm(prestige ~ education + women +
               log(income)*type, 
               data=Prestige_std)

Plot the standardized coefficients

These plots look more like what I was expecting to show the relative magnitudes of the coefficients and CIs so one could see which differ from zero.

modelplot(mod1_std, 
          coef_omit="Intercept", color="blue", size=1) +
  labs(title="Standardized coefficients for mod0") +
  geom_vline(xintercept = 0, linetype = 2) 

Using standardize = "refit"

It turns out there is an easier way to get plots of standardized coefficients. modelsummary() extracts coefficients from model objects using the parameters package, and that package offers several options for standardization: See model parameters documentation. We can pass the standardize="refit" (or other) argument directly to modelsummary() or modelplot(), and that argument will be forwarded to parameters. To compare models, pass them to modelplot as a list.

modelplot(list(mod0 = mod0, mod1 = mod1),
          standardize = "refit",
          coef_omit="Intercept", size=1) +
  labs(title="Standardized coefficients for mod0 and mod1") +
  geom_vline(xintercept = 0, linetype = 2) 

A small annoyance: This gives a warning because standardize is handled via ... and that is pushed to both parameters and ggplot2. ## More meaningful units A better substantative comparison of the coefficients could use understandable scales for the predictors, e.g., months of education, $50,000 income or 10% of women’s participation.

Note that the effect of this is just to multiply the coefficients and their standard errors by a factor. The statistical conclusions of significance are unchanged.

Prestige_scaled <- Prestige |>
  mutate(education = 12 * education,
         income = income / 50,
         women = women / 10)

mod0_scaled <- lm(prestige ~ education + income + women,
               data=Prestige_scaled)

arm::display(mod0_scaled, detail = TRUE)
## lm(formula = prestige ~ education + income + women, data = Prestige_scaled)
##             coef.est coef.se t value Pr(>|t|)
## (Intercept) -6.79     3.24   -2.10    0.04   
## education    0.35     0.03   10.77    0.00   
## income       0.07     0.01    4.73    0.00   
## women       -0.09     0.30   -0.29    0.77   
## ---
## n = 102, k = 4
## residual sd = 7.85, R-Squared = 0.80
arm::coefplot(mod0_scaled, col.pts="red", cex.pts=1.5,
              main = "Coefficients for prestige with scaled predictors",
              varnames=c("intercept", 
                         "education\n(/month)",
                         "income\n(/$50K)",
                         "women\n(/10%)")
               )

Re-ordering terms

There is a mismatch between the tabular displays of coefficients, where terms are listed in their order in the model, and the plots shown above, where terms appear on the vertical axis in the reverse order, because the \(Y\) axis starts at 0. With modelplot(), we can reorder the terms using scale_y_discrete().

modelplot(list(mod0 = mod0, mod1 = mod1),
          standardize = "refit",
          coef_omit="Intercept", size=1) +
  labs(title="Standardized coefficients for mod0 and mod1") +
  scale_y_discrete(limits = rev) +
  geom_vline(xintercept = 0, linetype = 2) +
  theme(legend.position = c(0.9, 0.2))

Factor levels

There are several features of the plots shown above that are handled better in other packages. A main one is how terms involving factor variables are handled: In the Prestige data, occupation type is a factor, with levels "bc", "prof", "wc" meaning blue-collar, professional and white-collar jobs.

By default, lm() treats the first level ("bc") as the baseline category, so the coefficients for type are labeled typeprof and typewc. This is ugly and non-informative for presentation purposes, where it would be better to group the coefficients for the levels under the type factor.

To illustrate, I ignore using log(income), and specify some models using type and its interactions.

mod0 <- lm(prestige ~ education + income + women,
          data=Prestige_std)
mod2 <- update(mod0, ~ . + type)
mod3 <- update(mod0, ~ . + type + type * income)

The ggstats package provides some nicer versions of coefficient plots that handle factors in a more reasonable way, as levels nested within the factor. By default it shows \(p\)-values and significance stars in the labels, and (redundantly) uses different symbols for terms significant at \(p \le 0.05\) or not.

library(ggstats)
ggcoef_model(mod2) +
  labs(x = "Standarized Coefficient")

I find the \(p\)-values and significance stars distracting in the plot, but these can be suppressed using options show_p_values = FALSE and signif_stars = FALSE.

Comparing models

To compare several models, use ggcoef_compare(), supplied with a list of models. This does a much nicer job of organizing the terms for main effects and interactions so they may be more readily compared.

models <- list(
  "Base model"      = mod0,
  "Add type"        = mod2,
  "Add interaction" = mod3)

ggcoef_compare(models) +
  labs(x = "Standarized Coefficient")

Wrapup

At first glance, printing and plotting coefficients from statistical models seems like an easy task for which there should be straightforward solutions, with reasonable defaults and the ability to customize displays easily.

modelplot() is nice in that you can ask it to plot standardized coefficients, but it doesn’t handle factors well. I think that, by default it should reverse the order of coefficients on the vertical axis and plot the vertical reference line at 0.


  1. Kastellec, J. P., & Leoni, E. L. (2007). Using Graphs Instead of Tables in Political Science. Perspectives on Politics, 5, 755–771.↩︎

IycgLS0tDQojJyB0aXRsZTogIkNvZWZmaWNpZW50IHBsb3RzIGZvciBsaW5lYXIgbW9kZWxzIg0KIycgYXV0aG9yOiAiTWljaGFlbCBGcmllbmRseSINCiMnIGRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSlgIg0KIycgb3V0cHV0Og0KIycgICBodG1sX2RvY3VtZW50Og0KIycgICAgIHRoZW1lOiByZWFkYWJsZQ0KIycgICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiMnIC0tLQ0KDQoNCiMrIGVjaG89RkFMU0UNCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nPUZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlPUZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgICBlcnJvciA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIFIub3B0aW9ucz1saXN0KGRpZ2l0cz00KSkNCg0KIycgQ29lZmZpY2llbnQgcGxvdHMgYXJlIG9mdGVuIG1vcmUgdXNlZnVsIHRoYW4gdGFibGVzW14xXQ0KIycgYnV0IHBsb3R0aW5nIF9yYXcgY29lZmZpY2llbnRzXyBjYW4gYmUgbWlzbGVhZGluZyB3aGVuIHRoZSBwcmVkaWN0b3JzIGFyZQ0KIycgb24gZGlmZmVyZW50IHNjYWxlcy4NCiMnIA0KIycgW14xXTogS2FzdGVsbGVjLCBKLiBQLiwgJiBMZW9uaSwgRS4gTC4gKDIwMDcpLiBVc2luZyBHcmFwaHMgSW5zdGVhZCBvZiBUYWJsZXMgaW4gUG9saXRpY2FsIFNjaWVuY2UuIF9QZXJzcGVjdGl2ZXMgb24gUG9saXRpY3NfLCA1LCA3NTXigJM3NzEuDQojJyANCiMnIFRoZSBwYWNrYWdlcyBbYGFybWBdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3BhY2thZ2U9YXJtKSBhbmQgDQojJyBbYG1vZGVsc3VtbWFyeWBdKGh0dHBzOi8vbW9kZWxzdW1tYXJ5LmNvbS8pLCBhbmQgbGF0ZXIsDQojJyBbYGdnc3RhdHNgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2dzdGF0cy92aWduZXR0ZXMvZ2djb2VmX21vZGVsLmh0bWwpDQojJyBhcmUgdXNlZCB0byBpbGx1c3RyYXRlIHRoZXNlIHBsb3RzLg0KIycgDQojJyBJbiB0aGUgcHJvY2VzcywgSSBkaXNjb3ZlciBzb21lIG90aGVyIHByb2JsZW1zIHdpdGggbmFpdmUgdXNlIG9mIGNvZWZmaWNpZW50IHBsb3RzLg0KIycgSSBjb21wYXJlIHBsb3R0aW5nOg0KIycNCiMnICogcmF3IGNvZWZmaWNpZW50cw0KIycgKiBzdGFuZGFyZGl6ZWQgY29lZmZpY2llbnRzDQojJyAqIG1lYW5pbmdmdWxseSBzY2FsZWQgY29lZmZpY2llbnRzDQojJyANCg0KIycgIyMjIExvYWQgcGFja2FnZXMNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGFybSkgICAgICAgICAgICMgY29lZnBsb3QoKQ0KbGlicmFyeShtb2RlbHN1bW1hcnkpICAjIG1vZGVscGxvdCgpDQpsaWJyYXJ5KGdnc3RhdHMpICAgICAgICMgZ2djb2VmX21vZGVsKCkNCg0KDQojJyAjIyBPY2N1cGF0aW9uYWwgcHJlc3RpZ2UgZGF0YQ0KIycgSG93IGRvZXMgb2NjdXBhdGlvbmFsIHByZXN0aWdlIGRlcGVuZCBvbiBlZHVjYXRpb24gKHllYXJzKSwgaW5jb21lICgkKSwgJXdvbWVuLCAuLi4/DQojJyANCiMnIEkgdXNlIGEgY2xhc3NpYyBleGFtcGxlIGZyb20gdGhlIGBjYXJEYXRhYCBwYWNrYWdlLiBUaGUgb2JzZXJ2YXRpb25zIGFyZSBhdmVyYWdlcyBmb3Igb2NjdXBhdGlvbmFsDQojJyBjYXRlZ29yaWVzIHJhdGhlciB0aGFuIGluZGl2aWR1YWxzLiBUaGUgZGF0YSBjb21lIGZyb20gdGhlIDE5NzEgQ2FuYWRpYW4gY2Vuc3VzLg0KZGF0YShQcmVzdGlnZSwgcGFja2FnZT0iY2FyRGF0YSIpDQpoZWFkKFByZXN0aWdlLCA1KQ0KDQojJyAjIyBGaXQgYSBzaW1wbGUgbW9kZWwNCm1vZDAgPC0gbG0ocHJlc3RpZ2UgfiBlZHVjYXRpb24gKyBpbmNvbWUgKyB3b21lbiwNCiAgICAgICAgICAgZGF0YT1QcmVzdGlnZSkNCnN1bW1hcnkobW9kMCkNCg0KIycgIyMjIERpc3BsYXlpbmcgY29lZmZpY2llbnRzDQojJyBJJ20gaW50ZXJlc3RlZCBpbiB0aGUgY29lZmZpY2llbnRzIGluIHRoaXMgbW9kZWwsIHNvIGhlcmUgaXMganVzdCBhIHJlbWluZGVyIHRoYXQgeW91IGNhbiBleHRyYWN0DQojJyB0aGUgbnVtZXJpY2FsIHZhbHVlcyB1c2luZyBgY29lZigpYCwgb3IgZGlzcGxheSB0aGVtIGluIGEgdGFibGUgc2ltaWxhciB0byB0aGUgIkNvZWZmaWNpZW50cyINCiMnIHBvcnRpb24gYWJvdmUgdXNpbmcgYGJyb29tOjp0aWR5KClgIG9yIGBhcm06OmRpc3BsYXkoKWA6DQoNCmNvZWYobW9kMCkNCmJyb29tOjp0aWR5KG1vZDApDQphcm06OmRpc3BsYXkobW9kMCwgZGV0YWlsID0gVFJVRSkNCg0KIycgIyMgVmlzdWFsaXplIGNvZWZmaWNpZW50cw0KIycgVGhlIHBsb3RzIGJlbG93IHNob3cgZGVmYXVsdCBjb2VmZmljaWVudCBwbG90cyBmb3IgdGhpcyBtb2RlbCB1c2luZyBgbW9kZWxzdW1tYXJ5Ojptb2RlbHBsb3QoKWANCiMnIGFuZCBgYXJtOjpjb2VmcGxvdCgpYC4gQXQgdGhlIGVuZCwgSSBzaG93IHNvbWUgZXhhbXBsZXMgdXNpbmcgdGhlIGBnZ3N0YXRzYCBwYWNrYWdlLg0KIycgDQojJyBUaGV5IGFyZSBkaXNhcHBvaW50aW5nIGFuZCBtaXNsZWFkaW5nIGJlY2F1c2UgdGhlc2Ugc2hvdyB0aGUgKipyYXcqKiBjb2VmZmljaWVudHMuIA0KIycgSXQgbG9va3MgbGlrZSBvbmx5IGBlZHVjYXRpb25gIGhhcyBhIG5vbi16ZXJvIGVmZmVjdCwgYnV0IHRoZSBlZmZlY3Qgb2YgYGluY29tZWAgaXMgYWxzbw0KIycgaGlnaGx5IHNpZ25pZmljYW50Lg0KIycgDQojJyBUaGVzZSBwbG90cyBhcmUgbWlzbGVhZGluZyBiZWNhdXNlIHRoZSBwcmVkaWN0b3JzDQojJyBhcmUgb24gZGlmZmVyZW50IHNjYWxlcywgc28gaXQgbWFrZXMgbGl0dGxlIHNlbnNlIHRvIGNvbXBhcmUgdGhlIGNoYW5nZSBpbiBgcHJlc3RpZ2VgIGZvcg0KIycgYSAxIHllYXIgaW5jcmVhc2UgaW4gYGVkdWNhdGlvbmAgd2l0aCB0aGUgY2hhbmdlIGZvciBhICQxIGluY3JlYXNlIGluIGBpbmNvbWVgLg0KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTQpKQ0KbW9kZWxwbG90KG1vZDAsIA0KICAgICAgICAgIGNvZWZfb21pdD0iSW50ZXJjZXB0IiwgDQogICAgICAgICAgY29sb3I9ImJsdWUiLCBzaXplPTEpICsNCiAgbGFicyh0aXRsZT0iUmF3IGNvZWZmaWNpZW50cyBmb3IgbW9kMCIpDQoNCmFybTo6Y29lZnBsb3QobW9kMCwgY29sLnB0cz0icmVkIiwgY2V4LnB0cz0xLjUpDQoNCiMnIE90aGVyIGdyYXBoaWNhbCBmZWF0dXJlcyB0byBub3RlIGhlcmU6DQojJyANCiMnICogYGNvZWZwbG90KClgIGluY2x1ZGVzIGEgZGFzaGVkIHZlcnRpY2FsIHJlZmVyZW5jZSBsaW5lIGF0IGB4ID0gMGAgYnkgZGVmYXVsdCwgYnV0IGBtb2RlbHBsb3QoKWAgZG9lcyBub3QuDQojJyAqIFRoZSAkWSQgYXhpcyBpcyBvcmRlcmVkIGluIHJldmVyc2UgdG8gd2hhdCBhcHBlYXJzIGluIHRhYnVsYXIgb3V0cHV0Lg0KIycgIA0KIycgIyMgRml0IGEgbW9yZSBjb21wbGV4IG1vZGVsDQojJyBGb3IgdGhpcyBleGFtcGxlLCBncmFwaGljYWwgYW5hbHlzaXMgc3VnZ2VzdGVkIHRoYXQgdGhlIHJlbGF0aW9uIGJldHdlZW4gYHByZXN0aWdlYCBhbmQgYGluY29tZWAgd2FzDQojJyBub24tbGluZWFyLCB3aGljaCBjb3VsZCBiZSBjb3JyZWN0ZWQgdXNpbmcgYGxvZyhpbmNvbWUpYC4gQXMgd2VsbCwgb3RoZXIgcGxvdHMgc3VnZ2VzdGVkIGFuDQojJyBpbnRlcmFjdGlvbiB3aXRoIGB0eXBlYCBvZiBvY2N1cGF0aW9uLg0KbW9kMSA8LSBsbShwcmVzdGlnZSB+IGVkdWNhdGlvbiArIHdvbWVuICsNCiAgICAgICAgICAgICBsb2coaW5jb21lKSp0eXBlLCBkYXRhPVByZXN0aWdlKQ0Kc3VtbWFyeShtb2QxKQ0KDQojJyAjIyBDb21wYXJlIG1vZGVscw0KIycgYGFybTo6Y29lZnBsb3QoKWAgYWxsb3dzIHlvdSB0byBjb21wYXJlIHNldmVyYWwgbW9kZWxzIGluIHRoZSBzYW1lIHBsb3QsIHVzaW5nIGBhZGQ9VFJVRWAgZm9yIHRoZSBzZWNvbmQgYW5kIHN1YnNlcXVlbnQgb25lcy4NCiMnIFRoZSBpbnRlcmNlcHQgaXMgaWdub3JlZCBieSBkZWZhdWx0Lg0KYXJtOjpjb2VmcGxvdChtb2QwLCBjb2wucHRzPSJyZWQiLCBjZXgucHRzPTEuNSkNCmFybTo6Y29lZnBsb3QobW9kMSwgYWRkPVRSVUUsIGNvbC5wdHM9ImJsdWUiLCBjZXgucHRzPTEuNSkNCg0KIycgQnV0ICoqV0FJVCoqOiBgaW5jb21lYCB3YXMgZW50ZXJlZCBhcyBgbG9nKGluY29tZSlgIGluIGBtb2QxYC4gVGhlIHBsb3QgYWJvdmUgaXMgc2lsZW50bHkNCiMnIHdyb25nLCAgYmVjYXVzZSBpdCBkb2Vzbid0IGluY2x1ZGUgYGxvZyhpbmNvbWUpYCBvciB0aGUgaW50ZXJhY3Rpb24gdGVybXMuDQojJyBJdCBpcyB3YXMgcHJvYmFibHkgc3R1cGlkIHRvDQojJyB0cnkgdG8gY29tcGFyZSB0aGUgY29lZmZpY2llbnRzLCBhbmQgdGhpcyBzaG91bGQgaGF2ZSByYWlzZWQgYSB3YXJuaW5nIG9yIGVycm9yLg0KIycgDQojJw0KIycgQWxsIHRoZSB0ZXJtcyB3aWxsIGFwcGVhciBpZiB0aGUgbW9zdCBjb21wbGV4IG1vZGVsIGlzIHBsb3R0ZWQgZmlyc3QuDQphcm06OmNvZWZwbG90KG1vZDEsIGNvbC5wdHM9InJlZCIsIGNleC5wdHM9MS41KQ0KYXJtOjpjb2VmcGxvdChtb2QwLCBhZGQ9VFJVRSwgY29sLnB0cz0iYmx1ZSIsIGNleC5wdHM9MS41KQ0KDQoNCiMnICMjIFN0YW5kYXJkaXplIHRoZSB2YXJpYWJsZXMsIGdpdmluZyAkXGJldGEkIGNvZWZmaWNpZW50cw0KIycgQW4gYWx0ZXJuYXRpdmUgaXMgdG8gcHJlc2VudCB0aGUgc3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cy4gVGhlc2UgYXJlIGludGVycHJldGVkDQojJyBhcyB0aGUgc3RhbmRhcmRpemVkIGNoYW5nZSBpbiBwcmVzdGlnZSBmb3IgYSBvbmUgc3RhbmRhcmQgZGV2aWF0aW9uIGNoYW5nZSBpbiB0aGUNCiMnIHByZWRpY3RvcnMuDQojJyANCiMnIE9uZSB3YXkgdG8gZG8gdGhpcyBpcyB0byB0cmFuc2Zvcm0gYWxsIHZhcmlhYmxlcyB0byBzdGFuZGFyZGl6ZWQgKCR6JCkgc2NvcmVzLg0KIycgVGhlIHN5bnRheCB1bHRpbWF0ZWx5IHVzZXMgYHNjYWxlYCB0byB0cmFuc2Zvcm0gYWxsIHRoZSBudW1lcmljIHZhcmlhYmxlcy4NCiMnIA0KDQpQcmVzdGlnZV9zdGQgPC0gUHJlc3RpZ2UgfD4NCiAgYXNfdGliYmxlKCkgfD4NCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgc2NhbGUpKQ0KDQojJyAjIyMgRml0IHRoZSBzYW1lIG1vZGVscyB0byB0aGUgc3RhbmRhcmRpemVkIHZhcmlhYmxlcw0KIycgVGhpcyBhY2NvcmRzIGJldHRlciB3aXRoIHRoZSBmaXR0ZWQgKHN0YW5kYXJkaXplZCkgY29lZmZpY2llbnRzLiBgaW5jb21lYCBhbmQgYGVkdWNhdGlvbmANCiMnIGFyZSBub3cgYm90aCBsYXJnZSBhbmQgc2lnbmlmaWNhbnQgY29tcGFyZWQgd2l0aCB0aGUgZWZmZWN0IG9mIGB3b21lbmAuDQojJyANCiMnIEJ1dCBhIG5hZ2dpbmcgZG91YnQgcmVtYWluczogIEhvdyBjYW4gSSBpbnRlcnByZXQgdGhlIG51bWVyaWNhbCAqKnNpemUqKiBvZiBjb2VmZmljaWVudHM/DQojJyBUaGUgZGlyZWN0IGFuc3dlciBpcyB0aGF0IHRoZXkgZ2l2ZSB0aGUgZXhwZWN0ZWQgY2hhbmdlIGluIHN0YW5kYXJkIGRldmlhdGlvbnMgb2YNCiMnIGBwcmVzdGlnZWAgZm9yIGEgb25lIHN0YW5kYXJkIGRldmlhdGlvbg0KIycgY2hhbmdlIGluIGVhY2ggb2YgdGhlIHByZWRpY3RvcnMuDQoNCm1vZDBfc3RkIDwtIGxtKHByZXN0aWdlIH4gZWR1Y2F0aW9uICsgaW5jb21lICsgd29tZW4sDQogICAgICAgICAgICAgICBkYXRhPVByZXN0aWdlX3N0ZCkNCg0KbW9kMV9zdGQgPC0gbG0ocHJlc3RpZ2UgfiBlZHVjYXRpb24gKyB3b21lbiArDQogICAgICAgICAgICAgICBsb2coaW5jb21lKSp0eXBlLCANCiAgICAgICAgICAgICAgIGRhdGE9UHJlc3RpZ2Vfc3RkKQ0KDQojJyAjIyBQbG90IHRoZSBzdGFuZGFyZGl6ZWQgY29lZmZpY2llbnRzDQojJyBUaGVzZSBwbG90cyBsb29rIG1vcmUgbGlrZSB3aGF0IEkgd2FzIGV4cGVjdGluZyB0byBzaG93IHRoZSByZWxhdGl2ZSBtYWduaXR1ZGVzIG9mIHRoZSBjb2VmZmljaWVudHMNCiMnIGFuZCBDSXMgc28gb25lIGNvdWxkIHNlZSB3aGljaCBkaWZmZXIgZnJvbSB6ZXJvLg0KIycgDQptb2RlbHBsb3QobW9kMV9zdGQsIA0KICAgICAgICAgIGNvZWZfb21pdD0iSW50ZXJjZXB0IiwgY29sb3I9ImJsdWUiLCBzaXplPTEpICsNCiAgbGFicyh0aXRsZT0iU3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cyBmb3IgbW9kMCIpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAyKSANCiAgDQoNCiMnICMjIFVzaW5nIGBzdGFuZGFyZGl6ZSA9ICJyZWZpdCJgDQojJyBJdCB0dXJucyBvdXQgdGhlcmUgaXMgYW4gZWFzaWVyIHdheSB0byBnZXQgcGxvdHMgb2Ygc3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cy4NCiMnIGBtb2RlbHN1bW1hcnkoKWAgZXh0cmFjdHMgY29lZmZpY2llbnRzIGZyb20gbW9kZWwgb2JqZWN0cyB1c2luZyB0aGUgYHBhcmFtZXRlcnNgIHBhY2thZ2UsIGFuZCB0aGF0IHBhY2thZ2Ugb2ZmZXJzIHNldmVyYWwgb3B0aW9ucyBmb3Igc3RhbmRhcmRpemF0aW9uOiANCiMnIFNlZSBbbW9kZWwgcGFyYW1ldGVycyBkb2N1bWVudGF0aW9uXShodHRwczovL2Vhc3lzdGF0cy5naXRodWIuaW8vcGFyYW1ldGVycy9yZWZlcmVuY2UvbW9kZWxfcGFyYW1ldGVycy5kZWZhdWx0Lmh0bWwpLg0KIycgIFdlIGNhbiBwYXNzIHRoZSBgc3RhbmRhcmRpemU9InJlZml0ImAgKG9yIG90aGVyKSBhcmd1bWVudCBkaXJlY3RseSB0byBgbW9kZWxzdW1tYXJ5KClgIG9yIGBtb2RlbHBsb3QoKWAsIGFuZCB0aGF0IGFyZ3VtZW50IHdpbGwgYmUgZm9yd2FyZGVkIHRvIGBwYXJhbWV0ZXJzYC4gDQojJyBUbyBjb21wYXJlIG1vZGVscywgcGFzcyB0aGVtIHRvIGBtb2RlbHBsb3RgIGFzIGEgbGlzdC4NCm1vZGVscGxvdChsaXN0KG1vZDAgPSBtb2QwLCBtb2QxID0gbW9kMSksDQogICAgICAgICAgc3RhbmRhcmRpemUgPSAicmVmaXQiLA0KICAgICAgICAgIGNvZWZfb21pdD0iSW50ZXJjZXB0Iiwgc2l6ZT0xKSArDQogIGxhYnModGl0bGU9IlN0YW5kYXJkaXplZCBjb2VmZmljaWVudHMgZm9yIG1vZDAgYW5kIG1vZDEiKSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gMikgDQogIA0KIycgQSBzbWFsbCBhbm5veWFuY2U6IFRoaXMgZ2l2ZXMgYSB3YXJuaW5nIGJlY2F1c2UgYHN0YW5kYXJkaXplYCBpcyBoYW5kbGVkIHZpYSBgLi4uYA0KIycgYW5kIHRoYXQgaXMgcHVzaGVkIHRvIGJvdGggYHBhcmFtZXRlcnNgIGFuZCBgZ2dwbG90MmAuDQoNCiMnICMjIE1vcmUgbWVhbmluZ2Z1bCB1bml0cw0KIycgQSBiZXR0ZXIgc3Vic3RhbnRhdGl2ZSBjb21wYXJpc29uIG9mIHRoZSBjb2VmZmljaWVudHMgY291bGQgdXNlIHVuZGVyc3RhbmRhYmxlIHNjYWxlcyBmb3IgdGhlDQojJyBwcmVkaWN0b3JzLCBlLmcuLCBtb250aHMgb2YgZWR1Y2F0aW9uLCAkNTAsMDAwIGluY29tZSBvciAxMCUgb2Ygd29tZW4ncyBwYXJ0aWNpcGF0aW9uLg0KIycgDQojJyBOb3RlIHRoYXQgdGhlIGVmZmVjdCBvZiB0aGlzIGlzIGp1c3QgdG8gbXVsdGlwbHkgdGhlIGNvZWZmaWNpZW50cyBhbmQgdGhlaXIgc3RhbmRhcmQgZXJyb3JzIGJ5IGEgZmFjdG9yLiANCiMnIFRoZSBzdGF0aXN0aWNhbCBjb25jbHVzaW9ucyBvZiBzaWduaWZpY2FuY2UgYXJlIHVuY2hhbmdlZC4NCg0KUHJlc3RpZ2Vfc2NhbGVkIDwtIFByZXN0aWdlIHw+DQogIG11dGF0ZShlZHVjYXRpb24gPSAxMiAqIGVkdWNhdGlvbiwNCiAgICAgICAgIGluY29tZSA9IGluY29tZSAvIDUwLA0KICAgICAgICAgd29tZW4gPSB3b21lbiAvIDEwKQ0KDQptb2QwX3NjYWxlZCA8LSBsbShwcmVzdGlnZSB+IGVkdWNhdGlvbiArIGluY29tZSArIHdvbWVuLA0KICAgICAgICAgICAgICAgZGF0YT1QcmVzdGlnZV9zY2FsZWQpDQoNCmFybTo6ZGlzcGxheShtb2QwX3NjYWxlZCwgZGV0YWlsID0gVFJVRSkNCg0KYXJtOjpjb2VmcGxvdChtb2QwX3NjYWxlZCwgY29sLnB0cz0icmVkIiwgY2V4LnB0cz0xLjUsDQogICAgICAgICAgICAgIG1haW4gPSAiQ29lZmZpY2llbnRzIGZvciBwcmVzdGlnZSB3aXRoIHNjYWxlZCBwcmVkaWN0b3JzIiwNCiAgICAgICAgICAgICAgdmFybmFtZXM9YygiaW50ZXJjZXB0IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgImVkdWNhdGlvblxuKC9tb250aCkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJpbmNvbWVcbigvJDUwSykiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJ3b21lblxuKC8xMCUpIikNCiAgICAgICAgICAgICAgICkNCg0KIycgIyMgUmUtb3JkZXJpbmcgdGVybXMNCiMnIFRoZXJlIGlzIGEgbWlzbWF0Y2ggYmV0d2VlbiB0aGUgdGFidWxhciBkaXNwbGF5cyBvZiBjb2VmZmljaWVudHMsIHdoZXJlIHRlcm1zIGFyZSBsaXN0ZWQgaW4gdGhlaXINCiMnIG9yZGVyIGluIHRoZSBtb2RlbCwgYW5kIHRoZSBwbG90cyBzaG93biBhYm92ZSwgd2hlcmUgdGVybXMgYXBwZWFyIG9uIHRoZSB2ZXJ0aWNhbCBheGlzIGluIHRoZSByZXZlcnNlIG9yZGVyLA0KIycgYmVjYXVzZSB0aGUgJFkkIGF4aXMgc3RhcnRzIGF0IDAuIFdpdGggYG1vZGVscGxvdCgpYCwgd2UgY2FuIHJlb3JkZXIgdGhlIHRlcm1zIHVzaW5nIGBzY2FsZV95X2Rpc2NyZXRlKClgLg0KIycgDQoNCm1vZGVscGxvdChsaXN0KG1vZDAgPSBtb2QwLCBtb2QxID0gbW9kMSksDQogICAgICAgICAgc3RhbmRhcmRpemUgPSAicmVmaXQiLA0KICAgICAgICAgIGNvZWZfb21pdD0iSW50ZXJjZXB0Iiwgc2l6ZT0xKSArDQogIGxhYnModGl0bGU9IlN0YW5kYXJkaXplZCBjb2VmZmljaWVudHMgZm9yIG1vZDAgYW5kIG1vZDEiKSArDQogIHNjYWxlX3lfZGlzY3JldGUobGltaXRzID0gcmV2KSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gMikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwgMC4yKSkNCg0KIycgIyMgRmFjdG9yIGxldmVscw0KIycgVGhlcmUgYXJlIHNldmVyYWwgZmVhdHVyZXMgb2YgdGhlIHBsb3RzIHNob3duIGFib3ZlIHRoYXQgYXJlIGhhbmRsZWQgYmV0dGVyIGluIG90aGVyIHBhY2thZ2VzLg0KIycgQSBtYWluIG9uZSBpcyBob3cgdGVybXMgaW52b2x2aW5nIGZhY3RvciB2YXJpYWJsZXMgYXJlIGhhbmRsZWQ6IA0KIycgSW4gdGhlIGBQcmVzdGlnZWAgZGF0YSwgb2NjdXBhdGlvbiBgdHlwZWAgaXMgYSBmYWN0b3IsIHdpdGggbGV2ZWxzIGAiYmMiLCAicHJvZiIsICJ3YyJgDQojJyBtZWFuaW5nIGJsdWUtY29sbGFyLCBwcm9mZXNzaW9uYWwgYW5kIHdoaXRlLWNvbGxhciBqb2JzLiANCiMnIA0KIycgQnkgZGVmYXVsdCwgYGxtKClgIHRyZWF0cyB0aGUgZmlyc3QNCiMnIGxldmVsIChgImJjImApIGFzIHRoZSBiYXNlbGluZSBjYXRlZ29yeSwgc28gdGhlIGNvZWZmaWNpZW50cyBmb3IgYHR5cGVgIGFyZSBsYWJlbGVkDQojJyBgdHlwZXByb2ZgIGFuZCBgdHlwZXdjYC4gVGhpcyBpcyB1Z2x5IGFuZCBub24taW5mb3JtYXRpdmUgZm9yIHByZXNlbnRhdGlvbiBwdXJwb3NlcywNCiMnIHdoZXJlIGl0IHdvdWxkIGJlIGJldHRlciB0byBncm91cCB0aGUgY29lZmZpY2llbnRzIGZvciB0aGUgbGV2ZWxzIHVuZGVyIHRoZSBgdHlwZWAgZmFjdG9yLg0KIycgDQojJyBUbyBpbGx1c3RyYXRlLCBJIGlnbm9yZSB1c2luZyBgbG9nKGluY29tZSlgLCBhbmQgc3BlY2lmeSBzb21lIG1vZGVscyB1c2luZyBgdHlwZWAgYW5kIGl0cw0KIycgaW50ZXJhY3Rpb25zLg0KIycgDQptb2QwIDwtIGxtKHByZXN0aWdlIH4gZWR1Y2F0aW9uICsgaW5jb21lICsgd29tZW4sDQogICAgICAgICAgZGF0YT1QcmVzdGlnZV9zdGQpDQptb2QyIDwtIHVwZGF0ZShtb2QwLCB+IC4gKyB0eXBlKQ0KbW9kMyA8LSB1cGRhdGUobW9kMCwgfiAuICsgdHlwZSArIHR5cGUgKiBpbmNvbWUpDQoNCiMnIFRoZSBbYGdnc3RhdHNgXShodHRwczovL2xhcm1hcmFuZ2UuZ2l0aHViLmlvL2dnc3RhdHMvKSBwYWNrYWdlIHByb3ZpZGVzIHNvbWUgbmljZXIgdmVyc2lvbnMNCiMnIG9mIGNvZWZmaWNpZW50IHBsb3RzIHRoYXQgaGFuZGxlIGZhY3RvcnMgaW4gYSBtb3JlIHJlYXNvbmFibGUgd2F5LCBhcyBsZXZlbHMgbmVzdGVkIHdpdGhpbg0KIycgdGhlIGZhY3Rvci4gQnkgZGVmYXVsdCBpdCBzaG93cyAkcCQtdmFsdWVzIGFuZCBzaWduaWZpY2FuY2Ugc3RhcnMgaW4gdGhlIGxhYmVscywgYW5kIA0KIycgKHJlZHVuZGFudGx5KSB1c2VzIGRpZmZlcmVudCBzeW1ib2xzIGZvciB0ZXJtcyBzaWduaWZpY2FudCBhdCAkcCBcbGUgMC4wNSQgb3Igbm90Lg0KbGlicmFyeShnZ3N0YXRzKQ0KZ2djb2VmX21vZGVsKG1vZDIpICsNCiAgbGFicyh4ID0gIlN0YW5kYXJpemVkIENvZWZmaWNpZW50IikNCg0KIycgSSBmaW5kIHRoZSAkcCQtdmFsdWVzIGFuZCBzaWduaWZpY2FuY2Ugc3RhcnMgZGlzdHJhY3RpbmcgaW4gdGhlIHBsb3QsIGJ1dCB0aGVzZSBjYW4gYmUgc3VwcHJlc3NlZA0KIycgdXNpbmcgb3B0aW9ucyBgc2hvd19wX3ZhbHVlcyA9IEZBTFNFYCBhbmQgYHNpZ25pZl9zdGFycyA9IEZBTFNFYC4NCiMnDQojJyAjIyBDb21wYXJpbmcgbW9kZWxzDQojJyBUbyBjb21wYXJlIHNldmVyYWwgbW9kZWxzLCB1c2UgYGdnY29lZl9jb21wYXJlKClgLCBzdXBwbGllZCB3aXRoIGEgbGlzdCBvZiBtb2RlbHMuDQojJyBUaGlzIGRvZXMgYSBtdWNoIG5pY2VyIGpvYiBvZiBvcmdhbml6aW5nIHRoZSB0ZXJtcyBmb3IgbWFpbiBlZmZlY3RzIGFuZCBpbnRlcmFjdGlvbnMNCiMnIHNvIHRoZXkgbWF5IGJlIG1vcmUgcmVhZGlseSBjb21wYXJlZC4NCm1vZGVscyA8LSBsaXN0KA0KICAiQmFzZSBtb2RlbCIgICAgICA9IG1vZDAsDQogICJBZGQgdHlwZSIgICAgICAgID0gbW9kMiwNCiAgIkFkZCBpbnRlcmFjdGlvbiIgPSBtb2QzKQ0KDQpnZ2NvZWZfY29tcGFyZShtb2RlbHMpICsNCiAgbGFicyh4ID0gIlN0YW5kYXJpemVkIENvZWZmaWNpZW50IikNCg0KDQojJyAjIyBXcmFwdXANCg0KIycgQXQgZmlyc3QgZ2xhbmNlLCBwcmludGluZyBhbmQgcGxvdHRpbmcgY29lZmZpY2llbnRzIGZyb20gc3RhdGlzdGljYWwgbW9kZWxzIHNlZW1zIGxpa2UgYW4NCiMnIGVhc3kgdGFzayBmb3Igd2hpY2ggdGhlcmUgc2hvdWxkIGJlIHN0cmFpZ2h0Zm9yd2FyZCBzb2x1dGlvbnMsIHdpdGggcmVhc29uYWJsZSBkZWZhdWx0cw0KIycgYW5kIHRoZSBhYmlsaXR5IHRvIGN1c3RvbWl6ZSBkaXNwbGF5cyBlYXNpbHkuDQojJyANCiMnIGBtb2RlbHBsb3QoKWAgaXMgbmljZSBpbiB0aGF0IHlvdSBjYW4gYXNrIGl0IHRvIHBsb3Qgc3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cywgYnV0IGl0IGRvZXNuJ3QNCiMnIGhhbmRsZSBmYWN0b3JzIHdlbGwuIEkgdGhpbmsgdGhhdCwgYnkgZGVmYXVsdCBpdCBzaG91bGQgcmV2ZXJzZSB0aGUgb3JkZXIgb2YgY29lZmZpY2llbnRzDQojJyBvbiB0aGUgdmVydGljYWwgYXhpcyBhbmQgcGxvdCB0aGUgdmVydGljYWwgcmVmZXJlbmNlIGxpbmUgYXQgMC4NCiMnIA0K