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:

  1. What type of variation occurs within my variables? (The topic for today)

  2. 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