Last time, we focused on understanding variation within a single variable. This time, we’re understanding covariation, which is the relationship between two or more variables. This is where we start to ask more complex questions, like “Do employees with higher salaries tend to have more years of experience?”

library(tidyverse)
attrition_data <- read_csv("https://raw.githubusercontent.com/pplonski/datasets-for-start/refs/heads/master/employee_attrition/HR-Employee-Attrition-All.csv")
#Convert chr columns to factor
attrition_data <- attrition_data %>% mutate(across(where(is.character), as.factor))

Understanding Covariation

Covariation is the tendency for the values of two or more variables to vary together in a related way. It’s the core of most statistical analysis. When two variables covary, you can predict one using the other. For example, if you know an employee’s MonthlyIncome, you have a better idea of their JobLevel.

Visualizing Covariation: Continuous & Categorical Variables

A very common task is to explore how a continuous variable’s distribution changes across different categories.

Boxplots (geom_boxplot)

While frequency polygons (geom_freqpoly) are good for comparing densities, boxplots are a fantastic visual shorthand for comparing continuous distributions across categories. They are compact and highlight the median, the interquartile range (IQR), and outliers.

Let’s compare the MonthlyIncome across different JobLevels.

ggplot(data = attrition_data, mapping = aes(x = factor(JobLevel), y = MonthlyIncome)) +
  geom_boxplot()

The plot shows a clear positive relationship: as JobLevel increases, the median MonthlyIncome also increases.

Reordering and Flipping

Sometimes your categorical variable is unordered, and the default alphabetical order isn’t the most informative. We can use reorder() to arrange the categories based on a summary statistic of the continuous variable, like the median. Let’s compare MonthlyIncome across different EducationFields.

ggplot(data = attrition_data, mapping = aes(x = reorder(EducationField, MonthlyIncome, FUN = median), y = MonthlyIncome)) +
  geom_boxplot() +
  coord_flip()

Flipping the coordinates (coord_flip()) is useful when the category names are long, making the labels more readable.

Visualizing Covariation: Two Categorical Variables

To visualize the relationship between two categorical variables, we need to count how many observations fall into each combination of categories.

geom_count()

This is the simplest way. geom_count() automatically calculates the counts and represents them with the size of the circles. Let’s look at the relationship between JobRole and Department.

ggplot(data = attrition_data) +
  geom_count(mapping = aes(x = Department, y = JobRole))

Manual Counting with dplyr and geom_tile()

For more control, we can manually count the combinations using dplyr and then use a heat map (geom_tile()) to visualize the results. This is often more flexible.

attrition_data %>%
  count(Department, JobRole) %>%
  ggplot(mapping = aes(x = Department, y = JobRole)) +
  geom_tile(mapping = aes(fill = n))

The darker the tile, the more employees in that Department/JobRole combination. This tells us, for example, that the Research & Development department has a large number of Research Scientist and Laboratory Technician roles, which makes perfect sense.

Visualizing Covariation: Two Continuous Variables

The classic way to visualize the relationship between two continuous variables is with a scatterplot using geom_point(). Let’s explore the relationship between MonthlyIncome and TotalWorkingYears.

ggplot(data = attrition_data) +
  geom_point(mapping = aes(x = TotalWorkingYears, y = MonthlyIncome))

The Overplotting Problem

For large datasets, scatterplots can suffer from overplotting, where many points overlap and create a big black blob, hiding the true distribution.

Let’s pretend our dataset is much larger. Solutions include:

  • Transparency (alpha): Make the points semi-transparent so you can see where they are most dense.
ggplot(data = attrition_data) +
  geom_point(mapping = aes(x = TotalWorkingYears, y = MonthlyIncome), alpha = 1/5)

  • 2D Binning (geom_bin2d or geom_hex): Divide the plot into 2D bins and use color to represent the number of points in each bin. This is like a 2D histogram.
ggplot(data = attrition_data) +
  geom_bin2d(mapping = aes(x = TotalWorkingYears, y = MonthlyIncome))

This reveals that most employees are clustered at lower TotalWorkingYears and lower MonthlyIncome.

Patterns and Models

The patterns we see in our data give us clues. A strong relationship between TotalWorkingYears and MonthlyIncome makes sense—we’d expect more experienced employees to earn more.

Sometimes, a dominant relationship can hide other, more subtle ones. This is where models can be useful during EDA. We can use a model to “remove” the effect of a variable and then look at the residuals (what’s left over).

Let’s model the relationship between MonthlyIncome and JobLevel.

library(modelr)
mod <- lm(MonthlyIncome ~ JobLevel, data = attrition_data)

attrition_data_cleaned <- attrition_data %>%
  add_residuals(mod)

ggplot(data = attrition_data_cleaned) +
  geom_boxplot(mapping = aes(x = JobRole, y = resid)) +
  coord_flip()

By plotting the residuals against JobRole, we are now looking at the relative salary differences, after accounting for JobLevel. This might reveal which JobRoles are over or underpaid relative to their JobLevel.

ggplot2 Conciseness

As you become more comfortable, you can write more concise code by omitting argument names like data = and mapping =.

Example: Instead of ggplot(data = attrition_data, mapping = aes(x = JobRole)) + geom_bar(), you can write ggplot(attrition_data, aes(x = JobRole)) + geom_bar().

You’ll also see a lot of pipes (%>%) leading into a ggplot2 call.

attrition_data %>%
  group_by(Education) %>%
  summarise(MedianIncome = median(MonthlyIncome)) %>%
  ggplot(aes(x = reorder(Education, MedianIncome), y = MedianIncome)) +
  geom_bar(stat = "identity")

This is a very common workflow in R, and it’s a powerful way to transition from data manipulation to visualization.

Tools

G

LS0tDQp0aXRsZTogIkNvdmFyaWF0aW9uIGFuZCBBZHZhbmNlZCBWaXN1YWxpemF0aW9uIFRlY2huaXF1ZXMiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KLS0tDQoNCltMYXN0IHRpbWVdKGh0dHBzOi8vcnB1YnMuY29tL3VreTk5NC8xMzQzNjgyKSwgd2UgZm9jdXNlZCBvbiB1bmRlcnN0YW5kaW5nIHZhcmlhdGlvbiB3aXRoaW4gYSBzaW5nbGUgdmFyaWFibGUuIFRoaXMgdGltZSwgd2UncmUgdW5kZXJzdGFuZGluZyBjb3ZhcmlhdGlvbiwgd2hpY2ggaXMgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHR3byBvciBtb3JlIHZhcmlhYmxlcy4gVGhpcyBpcyB3aGVyZSB3ZSBzdGFydCB0byBhc2sgbW9yZSBjb21wbGV4IHF1ZXN0aW9ucywgbGlrZSAiRG8gZW1wbG95ZWVzIHdpdGggaGlnaGVyIHNhbGFyaWVzIHRlbmQgdG8gaGF2ZSBtb3JlIHllYXJzIG9mIGV4cGVyaWVuY2U/Ig0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KYXR0cml0aW9uX2RhdGEgPC0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wcGxvbnNraS9kYXRhc2V0cy1mb3Itc3RhcnQvcmVmcy9oZWFkcy9tYXN0ZXIvZW1wbG95ZWVfYXR0cml0aW9uL0hSLUVtcGxveWVlLUF0dHJpdGlvbi1BbGwuY3N2IikNCiNDb252ZXJ0IGNociBjb2x1bW5zIHRvIGZhY3Rvcg0KYXR0cml0aW9uX2RhdGEgPC0gYXR0cml0aW9uX2RhdGEgJT4lIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMuY2hhcmFjdGVyKSwgYXMuZmFjdG9yKSkNCmBgYA0KDQojIyBVbmRlcnN0YW5kaW5nIENvdmFyaWF0aW9uDQpDb3ZhcmlhdGlvbiBpcyB0aGUgdGVuZGVuY3kgZm9yIHRoZSB2YWx1ZXMgb2YgdHdvIG9yIG1vcmUgdmFyaWFibGVzIHRvIHZhcnkgdG9nZXRoZXIgaW4gYSByZWxhdGVkIHdheS4gSXQncyB0aGUgY29yZSBvZiBtb3N0IHN0YXRpc3RpY2FsIGFuYWx5c2lzLiBXaGVuIHR3byB2YXJpYWJsZXMgY292YXJ5LCB5b3UgY2FuIHByZWRpY3Qgb25lIHVzaW5nIHRoZSBvdGhlci4gRm9yIGV4YW1wbGUsIGlmIHlvdSBrbm93IGFuIGVtcGxveWVlJ3MgYE1vbnRobHlJbmNvbWVgLCB5b3UgaGF2ZSBhIGJldHRlciBpZGVhIG9mIHRoZWlyIGBKb2JMZXZlbGAuDQoNCiMjIFZpc3VhbGl6aW5nIENvdmFyaWF0aW9uOiBDb250aW51b3VzICYgQ2F0ZWdvcmljYWwgVmFyaWFibGVzDQpBIHZlcnkgY29tbW9uIHRhc2sgaXMgdG8gZXhwbG9yZSBob3cgYSBjb250aW51b3VzIHZhcmlhYmxlJ3MgZGlzdHJpYnV0aW9uIGNoYW5nZXMgYWNyb3NzIGRpZmZlcmVudCBjYXRlZ29yaWVzLg0KDQojIyMgQm94cGxvdHMgKGBnZW9tX2JveHBsb3RgKQ0KV2hpbGUgZnJlcXVlbmN5IHBvbHlnb25zIChgZ2VvbV9mcmVxcG9seWApIGFyZSBnb29kIGZvciBjb21wYXJpbmcgZGVuc2l0aWVzLCBib3hwbG90cyBhcmUgYSBmYW50YXN0aWMgdmlzdWFsIHNob3J0aGFuZCBmb3IgY29tcGFyaW5nIGNvbnRpbnVvdXMgZGlzdHJpYnV0aW9ucyBhY3Jvc3MgY2F0ZWdvcmllcy4gVGhleSBhcmUgY29tcGFjdCBhbmQgaGlnaGxpZ2h0IHRoZSBtZWRpYW4sIHRoZSBpbnRlcnF1YXJ0aWxlIHJhbmdlIChJUVIpLCBhbmQgb3V0bGllcnMuDQoNCkxldCdzIGNvbXBhcmUgdGhlIGBNb250aGx5SW5jb21lYCBhY3Jvc3MgZGlmZmVyZW50IGBKb2JMZXZlbHNgLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gYXR0cml0aW9uX2RhdGEsIG1hcHBpbmcgPSBhZXMoeCA9IGZhY3RvcihKb2JMZXZlbCksIHkgPSBNb250aGx5SW5jb21lKSkgKw0KICBnZW9tX2JveHBsb3QoKQ0KYGBgDQoNClRoZSBwbG90IHNob3dzIGEgY2xlYXIgcG9zaXRpdmUgcmVsYXRpb25zaGlwOiBhcyBKb2JMZXZlbCBpbmNyZWFzZXMsIHRoZSBtZWRpYW4gTW9udGhseUluY29tZSBhbHNvIGluY3JlYXNlcy4NCg0KIyMjIyBSZW9yZGVyaW5nIGFuZCBGbGlwcGluZw0KU29tZXRpbWVzIHlvdXIgY2F0ZWdvcmljYWwgdmFyaWFibGUgaXMgdW5vcmRlcmVkLCBhbmQgdGhlIGRlZmF1bHQgYWxwaGFiZXRpY2FsIG9yZGVyIGlzbid0IHRoZSBtb3N0IGluZm9ybWF0aXZlLiBXZSBjYW4gdXNlIGByZW9yZGVyKClgIHRvIGFycmFuZ2UgdGhlIGNhdGVnb3JpZXMgYmFzZWQgb24gYSBzdW1tYXJ5IHN0YXRpc3RpYyBvZiB0aGUgY29udGludW91cyB2YXJpYWJsZSwgbGlrZSB0aGUgbWVkaWFuLiBMZXQncyBjb21wYXJlIGBNb250aGx5SW5jb21lYCBhY3Jvc3MgZGlmZmVyZW50IGBFZHVjYXRpb25GaWVsZHNgLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gYXR0cml0aW9uX2RhdGEsIG1hcHBpbmcgPSBhZXMoeCA9IHJlb3JkZXIoRWR1Y2F0aW9uRmllbGQsIE1vbnRobHlJbmNvbWUsIEZVTiA9IG1lZGlhbiksIHkgPSBNb250aGx5SW5jb21lKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCkZsaXBwaW5nIHRoZSBjb29yZGluYXRlcyAoYGNvb3JkX2ZsaXAoKWApIGlzIHVzZWZ1bCB3aGVuIHRoZSBjYXRlZ29yeSBuYW1lcyBhcmUgbG9uZywgbWFraW5nIHRoZSBsYWJlbHMgbW9yZSByZWFkYWJsZS4NCg0KIyMgVmlzdWFsaXppbmcgQ292YXJpYXRpb246IFR3byBDYXRlZ29yaWNhbCBWYXJpYWJsZXMNClRvIHZpc3VhbGl6ZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdHdvIGNhdGVnb3JpY2FsIHZhcmlhYmxlcywgd2UgbmVlZCB0byBjb3VudCBob3cgbWFueSBvYnNlcnZhdGlvbnMgZmFsbCBpbnRvIGVhY2ggY29tYmluYXRpb24gb2YgY2F0ZWdvcmllcy4NCg0KIyMjIGBnZW9tX2NvdW50KClgDQpUaGlzIGlzIHRoZSBzaW1wbGVzdCB3YXkuIGdlb21fY291bnQoKSBhdXRvbWF0aWNhbGx5IGNhbGN1bGF0ZXMgdGhlIGNvdW50cyBhbmQgcmVwcmVzZW50cyB0aGVtIHdpdGggdGhlIHNpemUgb2YgdGhlIGNpcmNsZXMuIExldCdzIGxvb2sgYXQgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIEpvYlJvbGUgYW5kIERlcGFydG1lbnQuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBhdHRyaXRpb25fZGF0YSkgKw0KICBnZW9tX2NvdW50KG1hcHBpbmcgPSBhZXMoeCA9IERlcGFydG1lbnQsIHkgPSBKb2JSb2xlKSkNCmBgYA0KDQojIyMgTWFudWFsIENvdW50aW5nIHdpdGggYGRwbHlyYCBhbmQgYGdlb21fdGlsZSgpYA0KRm9yIG1vcmUgY29udHJvbCwgd2UgY2FuIG1hbnVhbGx5IGNvdW50IHRoZSBjb21iaW5hdGlvbnMgdXNpbmcgYGRwbHlyYCBhbmQgdGhlbiB1c2UgYSBoZWF0IG1hcCAoYGdlb21fdGlsZSgpYCkgdG8gdmlzdWFsaXplIHRoZSByZXN1bHRzLiBUaGlzIGlzIG9mdGVuIG1vcmUgZmxleGlibGUuDQoNCmBgYHtyfQ0KYXR0cml0aW9uX2RhdGEgJT4lDQogIGNvdW50KERlcGFydG1lbnQsIEpvYlJvbGUpICU+JQ0KICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gRGVwYXJ0bWVudCwgeSA9IEpvYlJvbGUpKSArDQogIGdlb21fdGlsZShtYXBwaW5nID0gYWVzKGZpbGwgPSBuKSkNCmBgYA0KDQpUaGUgZGFya2VyIHRoZSB0aWxlLCB0aGUgbW9yZSBlbXBsb3llZXMgaW4gdGhhdCBgRGVwYXJ0bWVudGAvYEpvYlJvbGVgIGNvbWJpbmF0aW9uLiBUaGlzIHRlbGxzIHVzLCBmb3IgZXhhbXBsZSwgdGhhdCB0aGUgYFJlc2VhcmNoICYgRGV2ZWxvcG1lbnRgIGRlcGFydG1lbnQgaGFzIGEgbGFyZ2UgbnVtYmVyIG9mIGBSZXNlYXJjaCBTY2llbnRpc3RgIGFuZCBgTGFib3JhdG9yeSBUZWNobmljaWFuYCByb2xlcywgd2hpY2ggbWFrZXMgcGVyZmVjdCBzZW5zZS4NCg0KIyMgVmlzdWFsaXppbmcgQ292YXJpYXRpb246IFR3byBDb250aW51b3VzIFZhcmlhYmxlcw0KVGhlIGNsYXNzaWMgd2F5IHRvIHZpc3VhbGl6ZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdHdvIGNvbnRpbnVvdXMgdmFyaWFibGVzIGlzIHdpdGggYSBzY2F0dGVycGxvdCB1c2luZyBgZ2VvbV9wb2ludCgpYC4gTGV0J3MgZXhwbG9yZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYE1vbnRobHlJbmNvbWVgIGFuZCBgVG90YWxXb3JraW5nWWVhcnNgLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gYXR0cml0aW9uX2RhdGEpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBUb3RhbFdvcmtpbmdZZWFycywgeSA9IE1vbnRobHlJbmNvbWUpKQ0KYGBgDQoNCiMjIyMgVGhlIE92ZXJwbG90dGluZyBQcm9ibGVtDQpGb3IgbGFyZ2UgZGF0YXNldHMsIHNjYXR0ZXJwbG90cyBjYW4gc3VmZmVyIGZyb20gb3ZlcnBsb3R0aW5nLCB3aGVyZSBtYW55IHBvaW50cyBvdmVybGFwIGFuZCBjcmVhdGUgYSBiaWcgYmxhY2sgYmxvYiwgaGlkaW5nIHRoZSB0cnVlIGRpc3RyaWJ1dGlvbi4NCg0KTGV0J3MgcHJldGVuZCBvdXIgZGF0YXNldCBpcyBtdWNoIGxhcmdlci4gU29sdXRpb25zIGluY2x1ZGU6DQoNCiAtIF9fVHJhbnNwYXJlbmN5X18gKGBhbHBoYWApOiBNYWtlIHRoZSBwb2ludHMgc2VtaS10cmFuc3BhcmVudCBzbyB5b3UgY2FuIHNlZSB3aGVyZSB0aGV5IGFyZSBtb3N0IGRlbnNlLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gYXR0cml0aW9uX2RhdGEpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBUb3RhbFdvcmtpbmdZZWFycywgeSA9IE1vbnRobHlJbmNvbWUpLCBhbHBoYSA9IDEvNSkNCmBgYA0KDQogLSBfXzJEIEJpbm5pbmcgKGBnZW9tX2JpbjJkYCBvciBgZ2VvbV9oZXhgKV9fOiBEaXZpZGUgdGhlIHBsb3QgaW50byAyRCBiaW5zIGFuZCB1c2UgY29sb3IgdG8gcmVwcmVzZW50IHRoZSBudW1iZXIgb2YgcG9pbnRzIGluIGVhY2ggYmluLiBUaGlzIGlzIGxpa2UgYSAyRCBoaXN0b2dyYW0uDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBhdHRyaXRpb25fZGF0YSkgKw0KICBnZW9tX2JpbjJkKG1hcHBpbmcgPSBhZXMoeCA9IFRvdGFsV29ya2luZ1llYXJzLCB5ID0gTW9udGhseUluY29tZSkpDQpgYGANCg0KVGhpcyByZXZlYWxzIHRoYXQgbW9zdCBlbXBsb3llZXMgYXJlIGNsdXN0ZXJlZCBhdCBsb3dlciBgVG90YWxXb3JraW5nWWVhcnNgIGFuZCBsb3dlciBgTW9udGhseUluY29tZWAuDQoNCiMjIFBhdHRlcm5zIGFuZCBNb2RlbHMNClRoZSBwYXR0ZXJucyB3ZSBzZWUgaW4gb3VyIGRhdGEgZ2l2ZSB1cyBjbHVlcy4gQSBzdHJvbmcgcmVsYXRpb25zaGlwIGJldHdlZW4gYFRvdGFsV29ya2luZ1llYXJzYCBhbmQgYE1vbnRobHlJbmNvbWVgIG1ha2VzIHNlbnNl4oCUd2UnZCBleHBlY3QgbW9yZSBleHBlcmllbmNlZCBlbXBsb3llZXMgdG8gZWFybiBtb3JlLg0KDQpTb21ldGltZXMsIGEgZG9taW5hbnQgcmVsYXRpb25zaGlwIGNhbiBoaWRlIG90aGVyLCBtb3JlIHN1YnRsZSBvbmVzLiBUaGlzIGlzIHdoZXJlIG1vZGVscyBjYW4gYmUgdXNlZnVsIGR1cmluZyBFREEuIFdlIGNhbiB1c2UgYSBtb2RlbCB0byAicmVtb3ZlIiB0aGUgZWZmZWN0IG9mIGEgdmFyaWFibGUgYW5kIHRoZW4gbG9vayBhdCB0aGUgcmVzaWR1YWxzICh3aGF0J3MgbGVmdCBvdmVyKS4NCg0KTGV0J3MgbW9kZWwgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIE1vbnRobHlJbmNvbWUgYW5kIEpvYkxldmVsLg0KDQpgYGB7cn0NCmxpYnJhcnkobW9kZWxyKQ0KbW9kIDwtIGxtKE1vbnRobHlJbmNvbWUgfiBKb2JMZXZlbCwgZGF0YSA9IGF0dHJpdGlvbl9kYXRhKQ0KDQphdHRyaXRpb25fZGF0YV9jbGVhbmVkIDwtIGF0dHJpdGlvbl9kYXRhICU+JQ0KICBhZGRfcmVzaWR1YWxzKG1vZCkNCg0KZ2dwbG90KGRhdGEgPSBhdHRyaXRpb25fZGF0YV9jbGVhbmVkKSArDQogIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSBKb2JSb2xlLCB5ID0gcmVzaWQpKSArDQogIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCkJ5IHBsb3R0aW5nIHRoZSByZXNpZHVhbHMgYWdhaW5zdCBgSm9iUm9sZWAsIHdlIGFyZSBub3cgbG9va2luZyBhdCB0aGUgcmVsYXRpdmUgc2FsYXJ5IGRpZmZlcmVuY2VzLCBhZnRlciBhY2NvdW50aW5nIGZvciBgSm9iTGV2ZWxgLiBUaGlzIG1pZ2h0IHJldmVhbCB3aGljaCBgSm9iUm9sZXNgIGFyZSBvdmVyIG9yIHVuZGVycGFpZCByZWxhdGl2ZSB0byB0aGVpciBgSm9iTGV2ZWxgLg0KDQojIyBgZ2dwbG90MmAgQ29uY2lzZW5lc3MgIA0KQXMgeW91IGJlY29tZSBtb3JlIGNvbWZvcnRhYmxlLCB5b3UgY2FuIHdyaXRlIG1vcmUgY29uY2lzZSBjb2RlIGJ5IG9taXR0aW5nIGFyZ3VtZW50IG5hbWVzIGxpa2UgYGRhdGEgPWAgYW5kIGBtYXBwaW5nID1gLg0KDQpFeGFtcGxlOg0KSW5zdGVhZCBvZiBgZ2dwbG90KGRhdGEgPSBhdHRyaXRpb25fZGF0YSwgbWFwcGluZyA9IGFlcyh4ID0gSm9iUm9sZSkpICsgZ2VvbV9iYXIoKWAsIHlvdSBjYW4gd3JpdGUgYGdncGxvdChhdHRyaXRpb25fZGF0YSwgYWVzKHggPSBKb2JSb2xlKSkgKyBnZW9tX2JhcigpYC4NCg0KWW91J2xsIGFsc28gc2VlIGEgbG90IG9mIHBpcGVzIChgJT4lYCkgbGVhZGluZyBpbnRvIGEgZ2dwbG90MiBjYWxsLg0KDQpgYGB7cn0NCmF0dHJpdGlvbl9kYXRhICU+JQ0KICBncm91cF9ieShFZHVjYXRpb24pICU+JQ0KICBzdW1tYXJpc2UoTWVkaWFuSW5jb21lID0gbWVkaWFuKE1vbnRobHlJbmNvbWUpKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcihFZHVjYXRpb24sIE1lZGlhbkluY29tZSksIHkgPSBNZWRpYW5JbmNvbWUpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKQ0KYGBgDQoNClRoaXMgaXMgYSB2ZXJ5IGNvbW1vbiB3b3JrZmxvdyBpbiBSLCBhbmQgaXQncyBhIHBvd2VyZnVsIHdheSB0byB0cmFuc2l0aW9uIGZyb20gZGF0YSBtYW5pcHVsYXRpb24gdG8gdmlzdWFsaXphdGlvbi4NCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KVG9vbHMNCg0KRw==