Coefficient plots are often more useful than tables 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:
- raw coefficients
- standardized coefficients
- meaningfully scaled coefficients
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:
coefplot() includes a dashed vertical reference line at
x = 0 by default, but modelplot() does
not.
- The \(Y\) axis is ordered in
reverse to what appears in tabular output.
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.
IycgLS0tDQojJyB0aXRsZTogIkNvZWZmaWNpZW50IHBsb3RzIGZvciBsaW5lYXIgbW9kZWxzIg0KIycgYXV0aG9yOiAiTWljaGFlbCBGcmllbmRseSINCiMnIGRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSlgIg0KIycgb3V0cHV0Og0KIycgICBodG1sX2RvY3VtZW50Og0KIycgICAgIHRoZW1lOiByZWFkYWJsZQ0KIycgICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiMnIC0tLQ0KDQoNCiMrIGVjaG89RkFMU0UNCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nPUZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlPUZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgICBlcnJvciA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIFIub3B0aW9ucz1saXN0KGRpZ2l0cz00KSkNCg0KIycgQ29lZmZpY2llbnQgcGxvdHMgYXJlIG9mdGVuIG1vcmUgdXNlZnVsIHRoYW4gdGFibGVzW14xXQ0KIycgYnV0IHBsb3R0aW5nIF9yYXcgY29lZmZpY2llbnRzXyBjYW4gYmUgbWlzbGVhZGluZyB3aGVuIHRoZSBwcmVkaWN0b3JzIGFyZQ0KIycgb24gZGlmZmVyZW50IHNjYWxlcy4NCiMnIA0KIycgW14xXTogS2FzdGVsbGVjLCBKLiBQLiwgJiBMZW9uaSwgRS4gTC4gKDIwMDcpLiBVc2luZyBHcmFwaHMgSW5zdGVhZCBvZiBUYWJsZXMgaW4gUG9saXRpY2FsIFNjaWVuY2UuIF9QZXJzcGVjdGl2ZXMgb24gUG9saXRpY3NfLCA1LCA3NTXigJM3NzEuDQojJyANCiMnIFRoZSBwYWNrYWdlcyBbYGFybWBdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3BhY2thZ2U9YXJtKSBhbmQgDQojJyBbYG1vZGVsc3VtbWFyeWBdKGh0dHBzOi8vbW9kZWxzdW1tYXJ5LmNvbS8pLCBhbmQgbGF0ZXIsDQojJyBbYGdnc3RhdHNgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2dzdGF0cy92aWduZXR0ZXMvZ2djb2VmX21vZGVsLmh0bWwpDQojJyBhcmUgdXNlZCB0byBpbGx1c3RyYXRlIHRoZXNlIHBsb3RzLg0KIycgDQojJyBJbiB0aGUgcHJvY2VzcywgSSBkaXNjb3ZlciBzb21lIG90aGVyIHByb2JsZW1zIHdpdGggbmFpdmUgdXNlIG9mIGNvZWZmaWNpZW50IHBsb3RzLg0KIycgSSBjb21wYXJlIHBsb3R0aW5nOg0KIycNCiMnICogcmF3IGNvZWZmaWNpZW50cw0KIycgKiBzdGFuZGFyZGl6ZWQgY29lZmZpY2llbnRzDQojJyAqIG1lYW5pbmdmdWxseSBzY2FsZWQgY29lZmZpY2llbnRzDQojJyANCg0KIycgIyMjIExvYWQgcGFja2FnZXMNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGFybSkgICAgICAgICAgICMgY29lZnBsb3QoKQ0KbGlicmFyeShtb2RlbHN1bW1hcnkpICAjIG1vZGVscGxvdCgpDQpsaWJyYXJ5KGdnc3RhdHMpICAgICAgICMgZ2djb2VmX21vZGVsKCkNCg0KDQojJyAjIyBPY2N1cGF0aW9uYWwgcHJlc3RpZ2UgZGF0YQ0KIycgSG93IGRvZXMgb2NjdXBhdGlvbmFsIHByZXN0aWdlIGRlcGVuZCBvbiBlZHVjYXRpb24gKHllYXJzKSwgaW5jb21lICgkKSwgJXdvbWVuLCAuLi4/DQojJyANCiMnIEkgdXNlIGEgY2xhc3NpYyBleGFtcGxlIGZyb20gdGhlIGBjYXJEYXRhYCBwYWNrYWdlLiBUaGUgb2JzZXJ2YXRpb25zIGFyZSBhdmVyYWdlcyBmb3Igb2NjdXBhdGlvbmFsDQojJyBjYXRlZ29yaWVzIHJhdGhlciB0aGFuIGluZGl2aWR1YWxzLiBUaGUgZGF0YSBjb21lIGZyb20gdGhlIDE5NzEgQ2FuYWRpYW4gY2Vuc3VzLg0KZGF0YShQcmVzdGlnZSwgcGFja2FnZT0iY2FyRGF0YSIpDQpoZWFkKFByZXN0aWdlLCA1KQ0KDQojJyAjIyBGaXQgYSBzaW1wbGUgbW9kZWwNCm1vZDAgPC0gbG0ocHJlc3RpZ2UgfiBlZHVjYXRpb24gKyBpbmNvbWUgKyB3b21lbiwNCiAgICAgICAgICAgZGF0YT1QcmVzdGlnZSkNCnN1bW1hcnkobW9kMCkNCg0KIycgIyMjIERpc3BsYXlpbmcgY29lZmZpY2llbnRzDQojJyBJJ20gaW50ZXJlc3RlZCBpbiB0aGUgY29lZmZpY2llbnRzIGluIHRoaXMgbW9kZWwsIHNvIGhlcmUgaXMganVzdCBhIHJlbWluZGVyIHRoYXQgeW91IGNhbiBleHRyYWN0DQojJyB0aGUgbnVtZXJpY2FsIHZhbHVlcyB1c2luZyBgY29lZigpYCwgb3IgZGlzcGxheSB0aGVtIGluIGEgdGFibGUgc2ltaWxhciB0byB0aGUgIkNvZWZmaWNpZW50cyINCiMnIHBvcnRpb24gYWJvdmUgdXNpbmcgYGJyb29tOjp0aWR5KClgIG9yIGBhcm06OmRpc3BsYXkoKWA6DQoNCmNvZWYobW9kMCkNCmJyb29tOjp0aWR5KG1vZDApDQphcm06OmRpc3BsYXkobW9kMCwgZGV0YWlsID0gVFJVRSkNCg0KIycgIyMgVmlzdWFsaXplIGNvZWZmaWNpZW50cw0KIycgVGhlIHBsb3RzIGJlbG93IHNob3cgZGVmYXVsdCBjb2VmZmljaWVudCBwbG90cyBmb3IgdGhpcyBtb2RlbCB1c2luZyBgbW9kZWxzdW1tYXJ5Ojptb2RlbHBsb3QoKWANCiMnIGFuZCBgYXJtOjpjb2VmcGxvdCgpYC4gQXQgdGhlIGVuZCwgSSBzaG93IHNvbWUgZXhhbXBsZXMgdXNpbmcgdGhlIGBnZ3N0YXRzYCBwYWNrYWdlLg0KIycgDQojJyBUaGV5IGFyZSBkaXNhcHBvaW50aW5nIGFuZCBtaXNsZWFkaW5nIGJlY2F1c2UgdGhlc2Ugc2hvdyB0aGUgKipyYXcqKiBjb2VmZmljaWVudHMuIA0KIycgSXQgbG9va3MgbGlrZSBvbmx5IGBlZHVjYXRpb25gIGhhcyBhIG5vbi16ZXJvIGVmZmVjdCwgYnV0IHRoZSBlZmZlY3Qgb2YgYGluY29tZWAgaXMgYWxzbw0KIycgaGlnaGx5IHNpZ25pZmljYW50Lg0KIycgDQojJyBUaGVzZSBwbG90cyBhcmUgbWlzbGVhZGluZyBiZWNhdXNlIHRoZSBwcmVkaWN0b3JzDQojJyBhcmUgb24gZGlmZmVyZW50IHNjYWxlcywgc28gaXQgbWFrZXMgbGl0dGxlIHNlbnNlIHRvIGNvbXBhcmUgdGhlIGNoYW5nZSBpbiBgcHJlc3RpZ2VgIGZvcg0KIycgYSAxIHllYXIgaW5jcmVhc2UgaW4gYGVkdWNhdGlvbmAgd2l0aCB0aGUgY2hhbmdlIGZvciBhICQxIGluY3JlYXNlIGluIGBpbmNvbWVgLg0KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTQpKQ0KbW9kZWxwbG90KG1vZDAsIA0KICAgICAgICAgIGNvZWZfb21pdD0iSW50ZXJjZXB0IiwgDQogICAgICAgICAgY29sb3I9ImJsdWUiLCBzaXplPTEpICsNCiAgbGFicyh0aXRsZT0iUmF3IGNvZWZmaWNpZW50cyBmb3IgbW9kMCIpDQoNCmFybTo6Y29lZnBsb3QobW9kMCwgY29sLnB0cz0icmVkIiwgY2V4LnB0cz0xLjUpDQoNCiMnIE90aGVyIGdyYXBoaWNhbCBmZWF0dXJlcyB0byBub3RlIGhlcmU6DQojJyANCiMnICogYGNvZWZwbG90KClgIGluY2x1ZGVzIGEgZGFzaGVkIHZlcnRpY2FsIHJlZmVyZW5jZSBsaW5lIGF0IGB4ID0gMGAgYnkgZGVmYXVsdCwgYnV0IGBtb2RlbHBsb3QoKWAgZG9lcyBub3QuDQojJyAqIFRoZSAkWSQgYXhpcyBpcyBvcmRlcmVkIGluIHJldmVyc2UgdG8gd2hhdCBhcHBlYXJzIGluIHRhYnVsYXIgb3V0cHV0Lg0KIycgIA0KIycgIyMgRml0IGEgbW9yZSBjb21wbGV4IG1vZGVsDQojJyBGb3IgdGhpcyBleGFtcGxlLCBncmFwaGljYWwgYW5hbHlzaXMgc3VnZ2VzdGVkIHRoYXQgdGhlIHJlbGF0aW9uIGJldHdlZW4gYHByZXN0aWdlYCBhbmQgYGluY29tZWAgd2FzDQojJyBub24tbGluZWFyLCB3aGljaCBjb3VsZCBiZSBjb3JyZWN0ZWQgdXNpbmcgYGxvZyhpbmNvbWUpYC4gQXMgd2VsbCwgb3RoZXIgcGxvdHMgc3VnZ2VzdGVkIGFuDQojJyBpbnRlcmFjdGlvbiB3aXRoIGB0eXBlYCBvZiBvY2N1cGF0aW9uLg0KbW9kMSA8LSBsbShwcmVzdGlnZSB+IGVkdWNhdGlvbiArIHdvbWVuICsNCiAgICAgICAgICAgICBsb2coaW5jb21lKSp0eXBlLCBkYXRhPVByZXN0aWdlKQ0Kc3VtbWFyeShtb2QxKQ0KDQojJyAjIyBDb21wYXJlIG1vZGVscw0KIycgYGFybTo6Y29lZnBsb3QoKWAgYWxsb3dzIHlvdSB0byBjb21wYXJlIHNldmVyYWwgbW9kZWxzIGluIHRoZSBzYW1lIHBsb3QsIHVzaW5nIGBhZGQ9VFJVRWAgZm9yIHRoZSBzZWNvbmQgYW5kIHN1YnNlcXVlbnQgb25lcy4NCiMnIFRoZSBpbnRlcmNlcHQgaXMgaWdub3JlZCBieSBkZWZhdWx0Lg0KYXJtOjpjb2VmcGxvdChtb2QwLCBjb2wucHRzPSJyZWQiLCBjZXgucHRzPTEuNSkNCmFybTo6Y29lZnBsb3QobW9kMSwgYWRkPVRSVUUsIGNvbC5wdHM9ImJsdWUiLCBjZXgucHRzPTEuNSkNCg0KIycgQnV0ICoqV0FJVCoqOiBgaW5jb21lYCB3YXMgZW50ZXJlZCBhcyBgbG9nKGluY29tZSlgIGluIGBtb2QxYC4gVGhlIHBsb3QgYWJvdmUgaXMgc2lsZW50bHkNCiMnIHdyb25nLCAgYmVjYXVzZSBpdCBkb2Vzbid0IGluY2x1ZGUgYGxvZyhpbmNvbWUpYCBvciB0aGUgaW50ZXJhY3Rpb24gdGVybXMuDQojJyBJdCBpcyB3YXMgcHJvYmFibHkgc3R1cGlkIHRvDQojJyB0cnkgdG8gY29tcGFyZSB0aGUgY29lZmZpY2llbnRzLCBhbmQgdGhpcyBzaG91bGQgaGF2ZSByYWlzZWQgYSB3YXJuaW5nIG9yIGVycm9yLg0KIycgDQojJw0KIycgQWxsIHRoZSB0ZXJtcyB3aWxsIGFwcGVhciBpZiB0aGUgbW9zdCBjb21wbGV4IG1vZGVsIGlzIHBsb3R0ZWQgZmlyc3QuDQphcm06OmNvZWZwbG90KG1vZDEsIGNvbC5wdHM9InJlZCIsIGNleC5wdHM9MS41KQ0KYXJtOjpjb2VmcGxvdChtb2QwLCBhZGQ9VFJVRSwgY29sLnB0cz0iYmx1ZSIsIGNleC5wdHM9MS41KQ0KDQoNCiMnICMjIFN0YW5kYXJkaXplIHRoZSB2YXJpYWJsZXMsIGdpdmluZyAkXGJldGEkIGNvZWZmaWNpZW50cw0KIycgQW4gYWx0ZXJuYXRpdmUgaXMgdG8gcHJlc2VudCB0aGUgc3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cy4gVGhlc2UgYXJlIGludGVycHJldGVkDQojJyBhcyB0aGUgc3RhbmRhcmRpemVkIGNoYW5nZSBpbiBwcmVzdGlnZSBmb3IgYSBvbmUgc3RhbmRhcmQgZGV2aWF0aW9uIGNoYW5nZSBpbiB0aGUNCiMnIHByZWRpY3RvcnMuDQojJyANCiMnIE9uZSB3YXkgdG8gZG8gdGhpcyBpcyB0byB0cmFuc2Zvcm0gYWxsIHZhcmlhYmxlcyB0byBzdGFuZGFyZGl6ZWQgKCR6JCkgc2NvcmVzLg0KIycgVGhlIHN5bnRheCB1bHRpbWF0ZWx5IHVzZXMgYHNjYWxlYCB0byB0cmFuc2Zvcm0gYWxsIHRoZSBudW1lcmljIHZhcmlhYmxlcy4NCiMnIA0KDQpQcmVzdGlnZV9zdGQgPC0gUHJlc3RpZ2UgfD4NCiAgYXNfdGliYmxlKCkgfD4NCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgc2NhbGUpKQ0KDQojJyAjIyMgRml0IHRoZSBzYW1lIG1vZGVscyB0byB0aGUgc3RhbmRhcmRpemVkIHZhcmlhYmxlcw0KIycgVGhpcyBhY2NvcmRzIGJldHRlciB3aXRoIHRoZSBmaXR0ZWQgKHN0YW5kYXJkaXplZCkgY29lZmZpY2llbnRzLiBgaW5jb21lYCBhbmQgYGVkdWNhdGlvbmANCiMnIGFyZSBub3cgYm90aCBsYXJnZSBhbmQgc2lnbmlmaWNhbnQgY29tcGFyZWQgd2l0aCB0aGUgZWZmZWN0IG9mIGB3b21lbmAuDQojJyANCiMnIEJ1dCBhIG5hZ2dpbmcgZG91YnQgcmVtYWluczogIEhvdyBjYW4gSSBpbnRlcnByZXQgdGhlIG51bWVyaWNhbCAqKnNpemUqKiBvZiBjb2VmZmljaWVudHM/DQojJyBUaGUgZGlyZWN0IGFuc3dlciBpcyB0aGF0IHRoZXkgZ2l2ZSB0aGUgZXhwZWN0ZWQgY2hhbmdlIGluIHN0YW5kYXJkIGRldmlhdGlvbnMgb2YNCiMnIGBwcmVzdGlnZWAgZm9yIGEgb25lIHN0YW5kYXJkIGRldmlhdGlvbg0KIycgY2hhbmdlIGluIGVhY2ggb2YgdGhlIHByZWRpY3RvcnMuDQoNCm1vZDBfc3RkIDwtIGxtKHByZXN0aWdlIH4gZWR1Y2F0aW9uICsgaW5jb21lICsgd29tZW4sDQogICAgICAgICAgICAgICBkYXRhPVByZXN0aWdlX3N0ZCkNCg0KbW9kMV9zdGQgPC0gbG0ocHJlc3RpZ2UgfiBlZHVjYXRpb24gKyB3b21lbiArDQogICAgICAgICAgICAgICBsb2coaW5jb21lKSp0eXBlLCANCiAgICAgICAgICAgICAgIGRhdGE9UHJlc3RpZ2Vfc3RkKQ0KDQojJyAjIyBQbG90IHRoZSBzdGFuZGFyZGl6ZWQgY29lZmZpY2llbnRzDQojJyBUaGVzZSBwbG90cyBsb29rIG1vcmUgbGlrZSB3aGF0IEkgd2FzIGV4cGVjdGluZyB0byBzaG93IHRoZSByZWxhdGl2ZSBtYWduaXR1ZGVzIG9mIHRoZSBjb2VmZmljaWVudHMNCiMnIGFuZCBDSXMgc28gb25lIGNvdWxkIHNlZSB3aGljaCBkaWZmZXIgZnJvbSB6ZXJvLg0KIycgDQptb2RlbHBsb3QobW9kMV9zdGQsIA0KICAgICAgICAgIGNvZWZfb21pdD0iSW50ZXJjZXB0IiwgY29sb3I9ImJsdWUiLCBzaXplPTEpICsNCiAgbGFicyh0aXRsZT0iU3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cyBmb3IgbW9kMCIpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAyKSANCiAgDQoNCiMnICMjIFVzaW5nIGBzdGFuZGFyZGl6ZSA9ICJyZWZpdCJgDQojJyBJdCB0dXJucyBvdXQgdGhlcmUgaXMgYW4gZWFzaWVyIHdheSB0byBnZXQgcGxvdHMgb2Ygc3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cy4NCiMnIGBtb2RlbHN1bW1hcnkoKWAgZXh0cmFjdHMgY29lZmZpY2llbnRzIGZyb20gbW9kZWwgb2JqZWN0cyB1c2luZyB0aGUgYHBhcmFtZXRlcnNgIHBhY2thZ2UsIGFuZCB0aGF0IHBhY2thZ2Ugb2ZmZXJzIHNldmVyYWwgb3B0aW9ucyBmb3Igc3RhbmRhcmRpemF0aW9uOiANCiMnIFNlZSBbbW9kZWwgcGFyYW1ldGVycyBkb2N1bWVudGF0aW9uXShodHRwczovL2Vhc3lzdGF0cy5naXRodWIuaW8vcGFyYW1ldGVycy9yZWZlcmVuY2UvbW9kZWxfcGFyYW1ldGVycy5kZWZhdWx0Lmh0bWwpLg0KIycgIFdlIGNhbiBwYXNzIHRoZSBgc3RhbmRhcmRpemU9InJlZml0ImAgKG9yIG90aGVyKSBhcmd1bWVudCBkaXJlY3RseSB0byBgbW9kZWxzdW1tYXJ5KClgIG9yIGBtb2RlbHBsb3QoKWAsIGFuZCB0aGF0IGFyZ3VtZW50IHdpbGwgYmUgZm9yd2FyZGVkIHRvIGBwYXJhbWV0ZXJzYC4gDQojJyBUbyBjb21wYXJlIG1vZGVscywgcGFzcyB0aGVtIHRvIGBtb2RlbHBsb3RgIGFzIGEgbGlzdC4NCm1vZGVscGxvdChsaXN0KG1vZDAgPSBtb2QwLCBtb2QxID0gbW9kMSksDQogICAgICAgICAgc3RhbmRhcmRpemUgPSAicmVmaXQiLA0KICAgICAgICAgIGNvZWZfb21pdD0iSW50ZXJjZXB0Iiwgc2l6ZT0xKSArDQogIGxhYnModGl0bGU9IlN0YW5kYXJkaXplZCBjb2VmZmljaWVudHMgZm9yIG1vZDAgYW5kIG1vZDEiKSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gMikgDQogIA0KIycgQSBzbWFsbCBhbm5veWFuY2U6IFRoaXMgZ2l2ZXMgYSB3YXJuaW5nIGJlY2F1c2UgYHN0YW5kYXJkaXplYCBpcyBoYW5kbGVkIHZpYSBgLi4uYA0KIycgYW5kIHRoYXQgaXMgcHVzaGVkIHRvIGJvdGggYHBhcmFtZXRlcnNgIGFuZCBgZ2dwbG90MmAuDQoNCiMnICMjIE1vcmUgbWVhbmluZ2Z1bCB1bml0cw0KIycgQSBiZXR0ZXIgc3Vic3RhbnRhdGl2ZSBjb21wYXJpc29uIG9mIHRoZSBjb2VmZmljaWVudHMgY291bGQgdXNlIHVuZGVyc3RhbmRhYmxlIHNjYWxlcyBmb3IgdGhlDQojJyBwcmVkaWN0b3JzLCBlLmcuLCBtb250aHMgb2YgZWR1Y2F0aW9uLCAkNTAsMDAwIGluY29tZSBvciAxMCUgb2Ygd29tZW4ncyBwYXJ0aWNpcGF0aW9uLg0KIycgDQojJyBOb3RlIHRoYXQgdGhlIGVmZmVjdCBvZiB0aGlzIGlzIGp1c3QgdG8gbXVsdGlwbHkgdGhlIGNvZWZmaWNpZW50cyBhbmQgdGhlaXIgc3RhbmRhcmQgZXJyb3JzIGJ5IGEgZmFjdG9yLiANCiMnIFRoZSBzdGF0aXN0aWNhbCBjb25jbHVzaW9ucyBvZiBzaWduaWZpY2FuY2UgYXJlIHVuY2hhbmdlZC4NCg0KUHJlc3RpZ2Vfc2NhbGVkIDwtIFByZXN0aWdlIHw+DQogIG11dGF0ZShlZHVjYXRpb24gPSAxMiAqIGVkdWNhdGlvbiwNCiAgICAgICAgIGluY29tZSA9IGluY29tZSAvIDUwLA0KICAgICAgICAgd29tZW4gPSB3b21lbiAvIDEwKQ0KDQptb2QwX3NjYWxlZCA8LSBsbShwcmVzdGlnZSB+IGVkdWNhdGlvbiArIGluY29tZSArIHdvbWVuLA0KICAgICAgICAgICAgICAgZGF0YT1QcmVzdGlnZV9zY2FsZWQpDQoNCmFybTo6ZGlzcGxheShtb2QwX3NjYWxlZCwgZGV0YWlsID0gVFJVRSkNCg0KYXJtOjpjb2VmcGxvdChtb2QwX3NjYWxlZCwgY29sLnB0cz0icmVkIiwgY2V4LnB0cz0xLjUsDQogICAgICAgICAgICAgIG1haW4gPSAiQ29lZmZpY2llbnRzIGZvciBwcmVzdGlnZSB3aXRoIHNjYWxlZCBwcmVkaWN0b3JzIiwNCiAgICAgICAgICAgICAgdmFybmFtZXM9YygiaW50ZXJjZXB0IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgImVkdWNhdGlvblxuKC9tb250aCkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJpbmNvbWVcbigvJDUwSykiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJ3b21lblxuKC8xMCUpIikNCiAgICAgICAgICAgICAgICkNCg0KIycgIyMgUmUtb3JkZXJpbmcgdGVybXMNCiMnIFRoZXJlIGlzIGEgbWlzbWF0Y2ggYmV0d2VlbiB0aGUgdGFidWxhciBkaXNwbGF5cyBvZiBjb2VmZmljaWVudHMsIHdoZXJlIHRlcm1zIGFyZSBsaXN0ZWQgaW4gdGhlaXINCiMnIG9yZGVyIGluIHRoZSBtb2RlbCwgYW5kIHRoZSBwbG90cyBzaG93biBhYm92ZSwgd2hlcmUgdGVybXMgYXBwZWFyIG9uIHRoZSB2ZXJ0aWNhbCBheGlzIGluIHRoZSByZXZlcnNlIG9yZGVyLA0KIycgYmVjYXVzZSB0aGUgJFkkIGF4aXMgc3RhcnRzIGF0IDAuIFdpdGggYG1vZGVscGxvdCgpYCwgd2UgY2FuIHJlb3JkZXIgdGhlIHRlcm1zIHVzaW5nIGBzY2FsZV95X2Rpc2NyZXRlKClgLg0KIycgDQoNCm1vZGVscGxvdChsaXN0KG1vZDAgPSBtb2QwLCBtb2QxID0gbW9kMSksDQogICAgICAgICAgc3RhbmRhcmRpemUgPSAicmVmaXQiLA0KICAgICAgICAgIGNvZWZfb21pdD0iSW50ZXJjZXB0Iiwgc2l6ZT0xKSArDQogIGxhYnModGl0bGU9IlN0YW5kYXJkaXplZCBjb2VmZmljaWVudHMgZm9yIG1vZDAgYW5kIG1vZDEiKSArDQogIHNjYWxlX3lfZGlzY3JldGUobGltaXRzID0gcmV2KSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gMikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwgMC4yKSkNCg0KIycgIyMgRmFjdG9yIGxldmVscw0KIycgVGhlcmUgYXJlIHNldmVyYWwgZmVhdHVyZXMgb2YgdGhlIHBsb3RzIHNob3duIGFib3ZlIHRoYXQgYXJlIGhhbmRsZWQgYmV0dGVyIGluIG90aGVyIHBhY2thZ2VzLg0KIycgQSBtYWluIG9uZSBpcyBob3cgdGVybXMgaW52b2x2aW5nIGZhY3RvciB2YXJpYWJsZXMgYXJlIGhhbmRsZWQ6IA0KIycgSW4gdGhlIGBQcmVzdGlnZWAgZGF0YSwgb2NjdXBhdGlvbiBgdHlwZWAgaXMgYSBmYWN0b3IsIHdpdGggbGV2ZWxzIGAiYmMiLCAicHJvZiIsICJ3YyJgDQojJyBtZWFuaW5nIGJsdWUtY29sbGFyLCBwcm9mZXNzaW9uYWwgYW5kIHdoaXRlLWNvbGxhciBqb2JzLiANCiMnIA0KIycgQnkgZGVmYXVsdCwgYGxtKClgIHRyZWF0cyB0aGUgZmlyc3QNCiMnIGxldmVsIChgImJjImApIGFzIHRoZSBiYXNlbGluZSBjYXRlZ29yeSwgc28gdGhlIGNvZWZmaWNpZW50cyBmb3IgYHR5cGVgIGFyZSBsYWJlbGVkDQojJyBgdHlwZXByb2ZgIGFuZCBgdHlwZXdjYC4gVGhpcyBpcyB1Z2x5IGFuZCBub24taW5mb3JtYXRpdmUgZm9yIHByZXNlbnRhdGlvbiBwdXJwb3NlcywNCiMnIHdoZXJlIGl0IHdvdWxkIGJlIGJldHRlciB0byBncm91cCB0aGUgY29lZmZpY2llbnRzIGZvciB0aGUgbGV2ZWxzIHVuZGVyIHRoZSBgdHlwZWAgZmFjdG9yLg0KIycgDQojJyBUbyBpbGx1c3RyYXRlLCBJIGlnbm9yZSB1c2luZyBgbG9nKGluY29tZSlgLCBhbmQgc3BlY2lmeSBzb21lIG1vZGVscyB1c2luZyBgdHlwZWAgYW5kIGl0cw0KIycgaW50ZXJhY3Rpb25zLg0KIycgDQptb2QwIDwtIGxtKHByZXN0aWdlIH4gZWR1Y2F0aW9uICsgaW5jb21lICsgd29tZW4sDQogICAgICAgICAgZGF0YT1QcmVzdGlnZV9zdGQpDQptb2QyIDwtIHVwZGF0ZShtb2QwLCB+IC4gKyB0eXBlKQ0KbW9kMyA8LSB1cGRhdGUobW9kMCwgfiAuICsgdHlwZSArIHR5cGUgKiBpbmNvbWUpDQoNCiMnIFRoZSBbYGdnc3RhdHNgXShodHRwczovL2xhcm1hcmFuZ2UuZ2l0aHViLmlvL2dnc3RhdHMvKSBwYWNrYWdlIHByb3ZpZGVzIHNvbWUgbmljZXIgdmVyc2lvbnMNCiMnIG9mIGNvZWZmaWNpZW50IHBsb3RzIHRoYXQgaGFuZGxlIGZhY3RvcnMgaW4gYSBtb3JlIHJlYXNvbmFibGUgd2F5LCBhcyBsZXZlbHMgbmVzdGVkIHdpdGhpbg0KIycgdGhlIGZhY3Rvci4gQnkgZGVmYXVsdCBpdCBzaG93cyAkcCQtdmFsdWVzIGFuZCBzaWduaWZpY2FuY2Ugc3RhcnMgaW4gdGhlIGxhYmVscywgYW5kIA0KIycgKHJlZHVuZGFudGx5KSB1c2VzIGRpZmZlcmVudCBzeW1ib2xzIGZvciB0ZXJtcyBzaWduaWZpY2FudCBhdCAkcCBcbGUgMC4wNSQgb3Igbm90Lg0KbGlicmFyeShnZ3N0YXRzKQ0KZ2djb2VmX21vZGVsKG1vZDIpICsNCiAgbGFicyh4ID0gIlN0YW5kYXJpemVkIENvZWZmaWNpZW50IikNCg0KIycgSSBmaW5kIHRoZSAkcCQtdmFsdWVzIGFuZCBzaWduaWZpY2FuY2Ugc3RhcnMgZGlzdHJhY3RpbmcgaW4gdGhlIHBsb3QsIGJ1dCB0aGVzZSBjYW4gYmUgc3VwcHJlc3NlZA0KIycgdXNpbmcgb3B0aW9ucyBgc2hvd19wX3ZhbHVlcyA9IEZBTFNFYCBhbmQgYHNpZ25pZl9zdGFycyA9IEZBTFNFYC4NCiMnDQojJyAjIyBDb21wYXJpbmcgbW9kZWxzDQojJyBUbyBjb21wYXJlIHNldmVyYWwgbW9kZWxzLCB1c2UgYGdnY29lZl9jb21wYXJlKClgLCBzdXBwbGllZCB3aXRoIGEgbGlzdCBvZiBtb2RlbHMuDQojJyBUaGlzIGRvZXMgYSBtdWNoIG5pY2VyIGpvYiBvZiBvcmdhbml6aW5nIHRoZSB0ZXJtcyBmb3IgbWFpbiBlZmZlY3RzIGFuZCBpbnRlcmFjdGlvbnMNCiMnIHNvIHRoZXkgbWF5IGJlIG1vcmUgcmVhZGlseSBjb21wYXJlZC4NCm1vZGVscyA8LSBsaXN0KA0KICAiQmFzZSBtb2RlbCIgICAgICA9IG1vZDAsDQogICJBZGQgdHlwZSIgICAgICAgID0gbW9kMiwNCiAgIkFkZCBpbnRlcmFjdGlvbiIgPSBtb2QzKQ0KDQpnZ2NvZWZfY29tcGFyZShtb2RlbHMpICsNCiAgbGFicyh4ID0gIlN0YW5kYXJpemVkIENvZWZmaWNpZW50IikNCg0KDQojJyAjIyBXcmFwdXANCg0KIycgQXQgZmlyc3QgZ2xhbmNlLCBwcmludGluZyBhbmQgcGxvdHRpbmcgY29lZmZpY2llbnRzIGZyb20gc3RhdGlzdGljYWwgbW9kZWxzIHNlZW1zIGxpa2UgYW4NCiMnIGVhc3kgdGFzayBmb3Igd2hpY2ggdGhlcmUgc2hvdWxkIGJlIHN0cmFpZ2h0Zm9yd2FyZCBzb2x1dGlvbnMsIHdpdGggcmVhc29uYWJsZSBkZWZhdWx0cw0KIycgYW5kIHRoZSBhYmlsaXR5IHRvIGN1c3RvbWl6ZSBkaXNwbGF5cyBlYXNpbHkuDQojJyANCiMnIGBtb2RlbHBsb3QoKWAgaXMgbmljZSBpbiB0aGF0IHlvdSBjYW4gYXNrIGl0IHRvIHBsb3Qgc3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cywgYnV0IGl0IGRvZXNuJ3QNCiMnIGhhbmRsZSBmYWN0b3JzIHdlbGwuIEkgdGhpbmsgdGhhdCwgYnkgZGVmYXVsdCBpdCBzaG91bGQgcmV2ZXJzZSB0aGUgb3JkZXIgb2YgY29lZmZpY2llbnRzDQojJyBvbiB0aGUgdmVydGljYWwgYXhpcyBhbmQgcGxvdCB0aGUgdmVydGljYWwgcmVmZXJlbmNlIGxpbmUgYXQgMC4NCiMnIA0K