For many of you, this is your first time doing any serious coding, so
we’ll start with the fundamentals. This lecture covers what EDA is, why
it’s a crucial first step in any data analysis project, and how we can
use visualizations to understand the individual characteristics of our
variables.
What is Exploratory Data Analysis?
EDA isn’t about running complex models or finding final answers.
Think of it as data detective work. You’re generating
questions, looking for clues in your data through visualizations and
transformations, and then using what you find to ask even better
questions. It’s an iterative, creative process.
The main goal of EDA is to develop an understanding of your
data. You’re building a mental map of what’s in your dataset. Are
there missing values? Are there strange outliers? What’s the typical
range of values for each variable? You’re using your curiosity and
skepticism to guide this investigation.
To do this, we’ll focus on two key types of questions:
What type of variation occurs within my variables? (The topic for
today)
What type of covariation occurs between my variables? (The topic
for next time)
Let’s define a few key terms to keep us on the same page.
A variable is a quantity or quality you can
measure.
A value is a specific measurement of a
variable.
An observation is a set of measurements made
under similar conditions. It’s often a single row in your
dataset.
Tabular data is a dataset arranged in rows and
columns. We’ll be working with tidy data, where each variable is a
column, each observation is a row, and each value has its own cell. This
is the format that works best with the tidyverse tools we’ll be
using.
Understanding Variation in Your Data
Variation is the tendency for a variable’s values to change from one
observation to the next. Every variable has a unique pattern of
variation, and the best way to understand this pattern is to visualize
the distribution of its values.
We’ll use a new dataset today that’s more relevant to business: a
fictional employee attrition dataset. Let’s load the tidyverse and the
data.
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))
Visualizing Distributions of Different Variable Types
The plot you use depends on the variable type.
Categorical Variables
A categorical variable can only take on a small set of values. In R,
these are often character
vectors or
factors
.
To visualize the distribution of a categorical variable, we use a
bar chart (geom_bar
). The height of each
bar represents the count of observations for that category. Let’s look
at the JobRole
variable in our dataset.
ggplot(data = attrition_data) +
geom_bar(mapping = aes(x = JobRole))

This plot is a bit cluttered because of the long names. Let’s make it
more readable by flipping the coordinates.
ggplot(data = attrition_data) +
geom_bar(mapping = aes(x = JobRole)) +
coord_flip()

You can manually get these counts using dplyr::count():
attrition_data %>%
count(JobRole)
Continuous Variables
A continuous variable can take on any value within an interval. Think
of things like salary, age, or employee tenure.
To visualize the distribution of a continuous variable, we use a
histogram (geom_histogram
). A histogram
divides the data range into bins and then counts how many observations
fall into each bin. Let’s look at the MonthlyIncome of our
employees.
ggplot(data = attrition_data) +
geom_histogram(mapping = aes(x = MonthlyIncome), binwidth = 1000)

The binwidth argument is crucial. Changing it can reveal different
patterns in your data. It’s always a good idea to try a few different
binwidth values.
Interpreting Visualizations: Typical & Unusual Values
Once you have your visualizations, the real detective work begins.
We’re looking for patterns, common values, and anything that stands
out.
Finding Typical Values
Tall bars in a bar chart or a high
frequency in a histogram show us the most common
values. For example, in our JobRole
bar chart,
Sales
, Executive
, and
Research Scientist
appear to be the most common
roles.
Clusters of values in a histogram can suggest
there are underlying subgroups in your data. You might
have a group of new hires with low TotalWorkingYears
and a
group of senior employees with many years of experience. We’ll explore
this more next week.
Identifying Unusual Values (Outliers)
Outliers are observations that don’t fit the general
pattern. They could be data entry errors (like a salary of $0) or they
could be real, interesting observations (a CEO with a massive
salary).
Outliers can be hard to see in histograms, especially if there are a
lot of data points. Let’s look at the DailyRate
variable.
ggplot(data = attrition_data) +
geom_histogram(mapping = aes(x = DailyRate))

The plot looks okay But what if there’s a typo, say an employee with
a DailyRate of 99999? Let’s add that to the data and see what
happens.
attrition_data_outlier <- attrition_data %>%
add_row(DailyRate = 99999)
ggplot(data = attrition_data_outlier) +
geom_histogram(mapping = aes(x = DailyRate), binwidth = 100)

The one outlier makes the rest of the plot unreadable! The single
outlier is so far out that the bins for the typical values are too short
to see.
This is where coord_cartesian()
comes in handy. It lets
us zoom in without throwing out the data.
ggplot(data = attrition_data_outlier) +
geom_histogram(mapping = aes(x = DailyRate), binwidth = 100) +
coord_cartesian(xlim = c(200, 1600))

This reveals the typical distribution again while still acknowledging
that the outlier exists.
Handling Outliers and Missing Values
When you find an outlier, the first step is to investigate it. Is it
a mistake? If so, you might want to replace it with a missing value (NA)
using ifelse()
or case_when()
.
Let’s imagine we know that any DailyRate
below 100 or
above 1600 is a data error.
attrition_data_cleaned <- attrition_data_outlier %>%
mutate(DailyRate = ifelse(DailyRate < 100 | DailyRate > 1600, NA, DailyRate))
ggplot(data = attrition_data_cleaned) +
geom_histogram(mapping = aes(x = DailyRate))

Notice how ggplot2
automatically gives us a warning that
it removed missing values. To get rid of that warning, you can add
na.rm = TRUE
.
LS0tDQp0aXRsZTogIiBJbnRyb2R1Y3Rpb24gdG8gRURBLCBWYXJpYXRpb24sIGFuZCBWaXN1YWxpemluZyBEaXN0cmlidXRpb25zIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCi0tLQ0KDQpGb3IgbWFueSBvZiB5b3UsIHRoaXMgaXMgeW91ciBmaXJzdCB0aW1lIGRvaW5nIGFueSBzZXJpb3VzIGNvZGluZywgc28gd2UnbGwgc3RhcnQgd2l0aCB0aGUgZnVuZGFtZW50YWxzLiBUaGlzIGxlY3R1cmUgY292ZXJzIHdoYXQgRURBIGlzLCB3aHkgaXQncyBhIGNydWNpYWwgZmlyc3Qgc3RlcCBpbiBhbnkgZGF0YSBhbmFseXNpcyBwcm9qZWN0LCBhbmQgaG93IHdlIGNhbiB1c2UgdmlzdWFsaXphdGlvbnMgdG8gdW5kZXJzdGFuZCB0aGUgaW5kaXZpZHVhbCBjaGFyYWN0ZXJpc3RpY3Mgb2Ygb3VyIHZhcmlhYmxlcy4NCg0KIyMgV2hhdCBpcyBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzPw0KRURBIGlzbid0IGFib3V0IHJ1bm5pbmcgY29tcGxleCBtb2RlbHMgb3IgZmluZGluZyBmaW5hbCBhbnN3ZXJzLiBUaGluayBvZiBpdCBhcyBfZGF0YSBkZXRlY3RpdmUgd29ya18uIFlvdSdyZSBnZW5lcmF0aW5nIHF1ZXN0aW9ucywgbG9va2luZyBmb3IgY2x1ZXMgaW4geW91ciBkYXRhIHRocm91Z2ggdmlzdWFsaXphdGlvbnMgYW5kIHRyYW5zZm9ybWF0aW9ucywgYW5kIHRoZW4gdXNpbmcgd2hhdCB5b3UgZmluZCB0byBhc2sgZXZlbiBiZXR0ZXIgcXVlc3Rpb25zLiBJdCdzIGFuIGl0ZXJhdGl2ZSwgY3JlYXRpdmUgcHJvY2Vzcy4NCg0KVGhlIG1haW4gZ29hbCBvZiBFREEgaXMgdG8gX2RldmVsb3AgYW4gdW5kZXJzdGFuZGluZyBvZiB5b3VyIGRhdGFfLiBZb3UncmUgYnVpbGRpbmcgYSBtZW50YWwgbWFwIG9mIHdoYXQncyBpbiB5b3VyIGRhdGFzZXQuIEFyZSB0aGVyZSBtaXNzaW5nIHZhbHVlcz8gQXJlIHRoZXJlIHN0cmFuZ2Ugb3V0bGllcnM/IFdoYXQncyB0aGUgdHlwaWNhbCByYW5nZSBvZiB2YWx1ZXMgZm9yIGVhY2ggdmFyaWFibGU/IFlvdSdyZSB1c2luZyB5b3VyIGN1cmlvc2l0eSBhbmQgc2tlcHRpY2lzbSB0byBndWlkZSB0aGlzIGludmVzdGlnYXRpb24uDQoNClRvIGRvIHRoaXMsIHdlJ2xsIGZvY3VzIG9uIHR3byBrZXkgdHlwZXMgb2YgcXVlc3Rpb25zOg0KDQoxLiBXaGF0IHR5cGUgb2YgdmFyaWF0aW9uIG9jY3VycyB3aXRoaW4gbXkgdmFyaWFibGVzPyAoVGhlIHRvcGljIGZvciB0b2RheSkNCg0KMi4gV2hhdCB0eXBlIG9mIGNvdmFyaWF0aW9uIG9jY3VycyBiZXR3ZWVuIG15IHZhcmlhYmxlcz8gKFRoZSB0b3BpYyBmb3IgbmV4dCB0aW1lKQ0KDQpMZXQncyBkZWZpbmUgYSBmZXcga2V5IHRlcm1zIHRvIGtlZXAgdXMgb24gdGhlIHNhbWUgcGFnZS4NCg0KIC0gQSBfX3ZhcmlhYmxlX18gaXMgYSBxdWFudGl0eSBvciBxdWFsaXR5IHlvdSBjYW4gbWVhc3VyZS4NCg0KIC0gQSBfX3ZhbHVlX18gaXMgYSBzcGVjaWZpYyBtZWFzdXJlbWVudCBvZiBhIHZhcmlhYmxlLg0KDQogLSBBbiBfX29ic2VydmF0aW9uX18gaXMgYSBzZXQgb2YgbWVhc3VyZW1lbnRzIG1hZGUgdW5kZXIgc2ltaWxhciBjb25kaXRpb25zLiBJdCdzIG9mdGVuIGEgc2luZ2xlIHJvdyBpbiB5b3VyIGRhdGFzZXQuDQoNCiAtIF9fVGFidWxhciBkYXRhX18gaXMgYSBkYXRhc2V0IGFycmFuZ2VkIGluIHJvd3MgYW5kIGNvbHVtbnMuIFdlJ2xsIGJlIHdvcmtpbmcgd2l0aCB0aWR5IGRhdGEsIHdoZXJlIGVhY2ggdmFyaWFibGUgaXMgYSBjb2x1bW4sIGVhY2ggb2JzZXJ2YXRpb24gaXMgYSByb3csIGFuZCBlYWNoIHZhbHVlIGhhcyBpdHMgb3duIGNlbGwuIFRoaXMgaXMgdGhlIGZvcm1hdCB0aGF0IHdvcmtzIGJlc3Qgd2l0aCB0aGUgdGlkeXZlcnNlIHRvb2xzIHdlJ2xsIGJlIHVzaW5nLg0KDQojIyBVbmRlcnN0YW5kaW5nIFZhcmlhdGlvbiBpbiBZb3VyIERhdGENClZhcmlhdGlvbiBpcyB0aGUgdGVuZGVuY3kgZm9yIGEgdmFyaWFibGUncyB2YWx1ZXMgdG8gY2hhbmdlIGZyb20gb25lIG9ic2VydmF0aW9uIHRvIHRoZSBuZXh0LiBFdmVyeSB2YXJpYWJsZSBoYXMgYSB1bmlxdWUgcGF0dGVybiBvZiB2YXJpYXRpb24sIGFuZCB0aGUgYmVzdCB3YXkgdG8gdW5kZXJzdGFuZCB0aGlzIHBhdHRlcm4gaXMgdG8gdmlzdWFsaXplIHRoZSBkaXN0cmlidXRpb24gb2YgaXRzIHZhbHVlcy4NCg0KV2UnbGwgdXNlIGEgbmV3IGRhdGFzZXQgdG9kYXkgdGhhdCdzIG1vcmUgcmVsZXZhbnQgdG8gYnVzaW5lc3M6IGEgZmljdGlvbmFsIGVtcGxveWVlIGF0dHJpdGlvbiBkYXRhc2V0LiBMZXQncyBsb2FkIHRoZSB0aWR5dmVyc2UgYW5kIHRoZSBkYXRhLg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KYXR0cml0aW9uX2RhdGEgPC0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wcGxvbnNraS9kYXRhc2V0cy1mb3Itc3RhcnQvcmVmcy9oZWFkcy9tYXN0ZXIvZW1wbG95ZWVfYXR0cml0aW9uL0hSLUVtcGxveWVlLUF0dHJpdGlvbi1BbGwuY3N2IikNCiNDb252ZXJ0IGNociBjb2x1bW5zIHRvIGZhY3Rvcg0KYXR0cml0aW9uX2RhdGEgPC0gYXR0cml0aW9uX2RhdGEgJT4lIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMuY2hhcmFjdGVyKSwgYXMuZmFjdG9yKSkNCmBgYA0KIyMjIFZpc3VhbGl6aW5nIERpc3RyaWJ1dGlvbnMgb2YgRGlmZmVyZW50IFZhcmlhYmxlIFR5cGVzDQoNClRoZSBwbG90IHlvdSB1c2UgZGVwZW5kcyBvbiB0aGUgdmFyaWFibGUgdHlwZS4NCg0KIyMjIyBDYXRlZ29yaWNhbCBWYXJpYWJsZXMNCkEgY2F0ZWdvcmljYWwgdmFyaWFibGUgY2FuIG9ubHkgdGFrZSBvbiBhIHNtYWxsIHNldCBvZiB2YWx1ZXMuIEluIFIsIHRoZXNlIGFyZSBvZnRlbiBgY2hhcmFjdGVyYCB2ZWN0b3JzIG9yIGBmYWN0b3JzYC4NCg0KVG8gdmlzdWFsaXplIHRoZSBkaXN0cmlidXRpb24gb2YgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSwgd2UgdXNlIGEgX19iYXIgY2hhcnRfXyAoYGdlb21fYmFyYCkuIFRoZSBoZWlnaHQgb2YgZWFjaCBiYXIgcmVwcmVzZW50cyB0aGUgY291bnQgb2Ygb2JzZXJ2YXRpb25zIGZvciB0aGF0IGNhdGVnb3J5LiBMZXQncyBsb29rIGF0IHRoZSBgSm9iUm9sZWAgdmFyaWFibGUgaW4gb3VyIGRhdGFzZXQuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBhdHRyaXRpb25fZGF0YSkgKw0KICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBKb2JSb2xlKSkNCmBgYA0KDQpUaGlzIHBsb3QgaXMgYSBiaXQgY2x1dHRlcmVkIGJlY2F1c2Ugb2YgdGhlIGxvbmcgbmFtZXMuIExldCdzIG1ha2UgaXQgbW9yZSByZWFkYWJsZSBieSBmbGlwcGluZyB0aGUgY29vcmRpbmF0ZXMuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBhdHRyaXRpb25fZGF0YSkgKw0KICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBKb2JSb2xlKSkgKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQpZb3UgY2FuIG1hbnVhbGx5IGdldCB0aGVzZSBjb3VudHMgdXNpbmcgZHBseXI6OmNvdW50KCk6DQoNCmBgYHtyfQ0KYXR0cml0aW9uX2RhdGEgJT4lDQogIGNvdW50KEpvYlJvbGUpDQpgYGANCg0KIyMjIyBDb250aW51b3VzIFZhcmlhYmxlcw0KQSBjb250aW51b3VzIHZhcmlhYmxlIGNhbiB0YWtlIG9uIGFueSB2YWx1ZSB3aXRoaW4gYW4gaW50ZXJ2YWwuIFRoaW5rIG9mIHRoaW5ncyBsaWtlIHNhbGFyeSwgYWdlLCBvciBlbXBsb3llZSB0ZW51cmUuDQoNClRvIHZpc3VhbGl6ZSB0aGUgZGlzdHJpYnV0aW9uIG9mIGEgY29udGludW91cyB2YXJpYWJsZSwgd2UgdXNlIGEgX19oaXN0b2dyYW1fXyAoYGdlb21faGlzdG9ncmFtYCkuIEEgaGlzdG9ncmFtIGRpdmlkZXMgdGhlIGRhdGEgcmFuZ2UgaW50byBiaW5zIGFuZCB0aGVuIGNvdW50cyBob3cgbWFueSBvYnNlcnZhdGlvbnMgZmFsbCBpbnRvIGVhY2ggYmluLiBMZXQncyBsb29rIGF0IHRoZSBNb250aGx5SW5jb21lIG9mIG91ciBlbXBsb3llZXMuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBhdHRyaXRpb25fZGF0YSkgKw0KICBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nID0gYWVzKHggPSBNb250aGx5SW5jb21lKSwgYmlud2lkdGggPSAxMDAwKQ0KYGBgDQoNClRoZSBiaW53aWR0aCBhcmd1bWVudCBpcyBjcnVjaWFsLiBDaGFuZ2luZyBpdCBjYW4gcmV2ZWFsIGRpZmZlcmVudCBwYXR0ZXJucyBpbiB5b3VyIGRhdGEuIEl0J3MgYWx3YXlzIGEgZ29vZCBpZGVhIHRvIHRyeSBhIGZldyBkaWZmZXJlbnQgYmlud2lkdGggdmFsdWVzLg0KDQojIyBJbnRlcnByZXRpbmcgVmlzdWFsaXphdGlvbnM6IFR5cGljYWwgJiBVbnVzdWFsIFZhbHVlcw0KT25jZSB5b3UgaGF2ZSB5b3VyIHZpc3VhbGl6YXRpb25zLCB0aGUgcmVhbCBkZXRlY3RpdmUgd29yayBiZWdpbnMuIFdlJ3JlIGxvb2tpbmcgZm9yIHBhdHRlcm5zLCBjb21tb24gdmFsdWVzLCBhbmQgYW55dGhpbmcgdGhhdCBzdGFuZHMgb3V0Lg0KDQojIyMgRmluZGluZyBUeXBpY2FsIFZhbHVlcw0KIC0gX19UYWxsIGJhcnMgaW4gYSBiYXIgY2hhcnRfXyBvciBhIF9faGlnaCBmcmVxdWVuY3kgaW4gYSBoaXN0b2dyYW1fXyBzaG93IHVzIF9fdGhlIG1vc3QgY29tbW9uIHZhbHVlc19fLiBGb3IgZXhhbXBsZSwgaW4gb3VyIGBKb2JSb2xlYCBiYXIgY2hhcnQsIGBTYWxlc2AsIGBFeGVjdXRpdmVgLCBhbmQgYFJlc2VhcmNoIFNjaWVudGlzdGAgYXBwZWFyIHRvIGJlIHRoZSBtb3N0IGNvbW1vbiByb2xlcy4NCg0KIC0gX19DbHVzdGVyc19fIG9mIHZhbHVlcyBpbiBhIGhpc3RvZ3JhbSBjYW4gc3VnZ2VzdCB0aGVyZSBhcmUgdW5kZXJseWluZyBfX3N1Ymdyb3Vwc19fIGluIHlvdXIgZGF0YS4gWW91IG1pZ2h0IGhhdmUgYSBncm91cCBvZiBuZXcgaGlyZXMgd2l0aCBsb3cgYFRvdGFsV29ya2luZ1llYXJzYCBhbmQgYSBncm91cCBvZiBzZW5pb3IgZW1wbG95ZWVzIHdpdGggbWFueSB5ZWFycyBvZiBleHBlcmllbmNlLiBXZSdsbCBleHBsb3JlIHRoaXMgbW9yZSBuZXh0IHdlZWsuDQoNCiMjIyBJZGVudGlmeWluZyBVbnVzdWFsIFZhbHVlcyAoT3V0bGllcnMpDQpfX091dGxpZXJzX18gYXJlIG9ic2VydmF0aW9ucyB0aGF0IGRvbid0IGZpdCB0aGUgZ2VuZXJhbCBwYXR0ZXJuLiBUaGV5IGNvdWxkIGJlIGRhdGEgZW50cnkgZXJyb3JzIChsaWtlIGEgc2FsYXJ5IG9mICQwKSBvciB0aGV5IGNvdWxkIGJlIHJlYWwsIGludGVyZXN0aW5nIG9ic2VydmF0aW9ucyAoYSBDRU8gd2l0aCBhIG1hc3NpdmUgc2FsYXJ5KS4NCg0KT3V0bGllcnMgY2FuIGJlIGhhcmQgdG8gc2VlIGluIGhpc3RvZ3JhbXMsIGVzcGVjaWFsbHkgaWYgdGhlcmUgYXJlIGEgbG90IG9mIGRhdGEgcG9pbnRzLiBMZXQncyBsb29rIGF0IHRoZSBgRGFpbHlSYXRlYCB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGF0dHJpdGlvbl9kYXRhKSArDQogIGdlb21faGlzdG9ncmFtKG1hcHBpbmcgPSBhZXMoeCA9IERhaWx5UmF0ZSkpDQpgYGANCg0KVGhlIHBsb3QgbG9va3Mgb2theSBCdXQgd2hhdCBpZiB0aGVyZSdzIGEgdHlwbywgc2F5IGFuIGVtcGxveWVlIHdpdGggYSBEYWlseVJhdGUgb2YgOTk5OTk/IExldCdzIGFkZCB0aGF0IHRvIHRoZSBkYXRhIGFuZCBzZWUgd2hhdCBoYXBwZW5zLg0KDQpgYGB7cn0NCmF0dHJpdGlvbl9kYXRhX291dGxpZXIgPC0gYXR0cml0aW9uX2RhdGEgJT4lDQogIGFkZF9yb3coRGFpbHlSYXRlID0gOTk5OTkpDQoNCmdncGxvdChkYXRhID0gYXR0cml0aW9uX2RhdGFfb3V0bGllcikgKw0KICBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nID0gYWVzKHggPSBEYWlseVJhdGUpLCBiaW53aWR0aCA9IDEwMCkNCmBgYA0KDQpUaGUgb25lIG91dGxpZXIgbWFrZXMgdGhlIHJlc3Qgb2YgdGhlIHBsb3QgdW5yZWFkYWJsZSEgVGhlIHNpbmdsZSBvdXRsaWVyIGlzIHNvIGZhciBvdXQgdGhhdCB0aGUgYmlucyBmb3IgdGhlIHR5cGljYWwgdmFsdWVzIGFyZSB0b28gc2hvcnQgdG8gc2VlLg0KDQpUaGlzIGlzIHdoZXJlIGBjb29yZF9jYXJ0ZXNpYW4oKWAgY29tZXMgaW4gaGFuZHkuIEl0IGxldHMgdXMgem9vbSBpbiB3aXRob3V0IHRocm93aW5nIG91dCB0aGUgZGF0YS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGF0dHJpdGlvbl9kYXRhX291dGxpZXIpICsNCiAgZ2VvbV9oaXN0b2dyYW0obWFwcGluZyA9IGFlcyh4ID0gRGFpbHlSYXRlKSwgYmlud2lkdGggPSAxMDApICsNCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKDIwMCwgMTYwMCkpDQpgYGANCg0KVGhpcyByZXZlYWxzIHRoZSB0eXBpY2FsIGRpc3RyaWJ1dGlvbiBhZ2FpbiB3aGlsZSBzdGlsbCBhY2tub3dsZWRnaW5nIHRoYXQgdGhlIG91dGxpZXIgZXhpc3RzLg0KDQojIyMjIEhhbmRsaW5nIE91dGxpZXJzIGFuZCBNaXNzaW5nIFZhbHVlcw0KV2hlbiB5b3UgZmluZCBhbiBvdXRsaWVyLCB0aGUgZmlyc3Qgc3RlcCBpcyB0byBpbnZlc3RpZ2F0ZSBpdC4gSXMgaXQgYSBtaXN0YWtlPyBJZiBzbywgeW91IG1pZ2h0IHdhbnQgdG8gcmVwbGFjZSBpdCB3aXRoIGEgbWlzc2luZyB2YWx1ZSAoTkEpIHVzaW5nIGBpZmVsc2UoKWAgb3IgYGNhc2Vfd2hlbigpYC4NCg0KTGV0J3MgaW1hZ2luZSB3ZSBrbm93IHRoYXQgYW55IGBEYWlseVJhdGVgIGJlbG93IDEwMCBvciBhYm92ZSAxNjAwIGlzIGEgZGF0YSBlcnJvci4NCg0KYGBge3J9DQphdHRyaXRpb25fZGF0YV9jbGVhbmVkIDwtIGF0dHJpdGlvbl9kYXRhX291dGxpZXIgJT4lDQogIG11dGF0ZShEYWlseVJhdGUgPSBpZmVsc2UoRGFpbHlSYXRlIDwgMTAwIHwgRGFpbHlSYXRlID4gMTYwMCwgTkEsIERhaWx5UmF0ZSkpDQoNCmdncGxvdChkYXRhID0gYXR0cml0aW9uX2RhdGFfY2xlYW5lZCkgKw0KICBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nID0gYWVzKHggPSBEYWlseVJhdGUpKQ0KYGBgDQoNCk5vdGljZSBob3cgYGdncGxvdDJgIGF1dG9tYXRpY2FsbHkgZ2l2ZXMgdXMgYSB3YXJuaW5nIHRoYXQgaXQgcmVtb3ZlZCBtaXNzaW5nIHZhbHVlcy4gVG8gZ2V0IHJpZCBvZiB0aGF0IHdhcm5pbmcsIHlvdSBjYW4gYWRkIGBuYS5ybSA9IFRSVUVgLg0K