You just got hired as a Data Analyst at the Census Bureau, which collects census data and finds interesting insights from it.
The person who previously had your job left you all the data they had for the most recent census. The data is spread across multiple csv files. They didn’t use R, and they would manually look through these csv files whenever they wanted to find something. Sometimes they would copy and paste certain numbers into Excel for analysis.
The thought of it makes you shiver. This is not scalable or repeatable.
Your boss wants you to dig into the data and find some insights by the end of the day. Can you get this data into R and into reasonable shape so that you can perform your analysis?
# load libraries
library(dplyr)
library(readr)
library(tidyr)
It will be easier to inspect the data stored in these files once you have it in a data frame. You can’t even call head() on these csvs! How are you supposed to read them?
Begin by creating a variable called files and set it equal to the list.files() of all of the csv files you want to import.
Read each file in files into a data frame using lapply() and save the result to df_list.
Concatenate all of the data frames in df_list into one data frame called us_census.
# load CSVs
files <- list.files(pattern = "states_.*csv")
df_list <- lapply(files, read_csv)
us_census <- bind_rows(df_list)
Inspect the us_census data frame by printing the column names, looking at the data types with str(), and viewing the head().
What columns have symbols that will prevent calculations? What are the data types of the columns? Do any columns contain multiple kinds of information?
# inspect data
str(us_census)
Classes ‘spec_tbl_df’, ‘tbl_df’, ‘tbl’ and 'data.frame': 61 obs. of 11 variables:
$ X1 : num 0 1 2 3 4 5 0 1 2 3 ...
$ State : chr "Alabama" "Alaska" "Arizona" "Arkansas" ...
$ TotalPop : num 4830620 733375 6641928 2958208 38421464 ...
$ Hispanic : chr "3.7516156462584975%" "5.909580838323351%" "29.565921052631502%" "6.215474452554738%" ...
$ White : chr "61.878656462585%" "60.910179640718574%" "57.120000000000026%" "71.13781021897813%" ...
$ Black : chr "31.25297619047618%" "2.8485029940119775%" "3.8509868421052658%" "18.968759124087573%" ...
$ Native : chr "0.4532312925170065%" "16.39101796407186%" "4.35506578947368%" "0.5229197080291965%" ...
$ Asian : chr "1.0502551020408146%" "5.450299401197604%" "2.876578947368419%" "1.1423357664233578%" ...
$ Pacific : chr "0.03435374149659865%" "1.0586826347305378%" "0.16763157894736833%" "0.14686131386861315%" ...
$ Income : chr "$43296.35860306644" "$70354.74390243902" "$54207.82095490716" "$41935.63396778917" ...
$ GenderPop: chr "2341093M_2489527F" "384160M_349215F" "3299088M_3342840F" "1451913M_1506295F" ...
head(us_census)
colnames(us_census)
[1] "X1" "State" "TotalPop" "Hispanic" "White"
[6] "Black" "Native" "Asian" "Pacific" "Income"
[11] "GenderPop"
When inspecting us_census you notice a column X1 that stores meaningless information. Drop the X1 column from us_census, and save the resulting data frame to us_census. View the head of us_census.
# drop X1 column
us_census <- us_census%>%
select(-X1)
head(us_census)
NA
You notice that there are 6 columns representing the population percentage for different races. The columns include the percent symbol %. Remove the percent symbol % from each of the race columns (Hispanic,White,Black,Native,Asian,Pacific). Save the resulting data frame to us_census, and view the head.
# remove % from race columns
us_census <- us_census%>%
mutate(Hispanic=gsub('\\%','',Hispanic),
White=gsub('\\%','',White),
Black=gsub('\\%','',Black),
Native=gsub('\\%','',Native),
Asian=gsub('\\%','',Asian),
Pacific=gsub('\\%','',Pacific))
head(us_census)
NA
The Income column also incudes a $ symbol along with the number representing median income for a state. Remove the $ from the Income column. Save the resulting data frame to us_census. View the head of us_census.
# remove $ from Income column
us_census <- us_census%>%
mutate(Income=gsub('\\$','',Income))
head(us_census)
The GenderPop column appears to hold the male and female population counts. Separate this column at the _ character to create two new columns: male_pop and female_pop. Save the resulting data frame to us_census, and view the head.
# separate GenderPop column
us_census <- us_census%>%
separate(GenderPop,c('male_pop', 'female_pop'), '_')
head(us_census)
You notice the new male_pop and female_pop columns contain extra characters M and F, respectively. Remove these extra characters from the columns. Save the resulting data frame to us_census, and view the head.
# clean male and female population columns
us_census <- us_census%>%
mutate(male_pop=gsub('M','',male_pop),
female_pop=gsub('F','',female_pop))
head(us_census)
Now that you have removed extra symbols from many of the columns that contain numerical data, you notice that the data type for these columns is still chr, or character. Convert all of these columns (Hispanic,White,Black,Native,Asian,Pacific,Income,male_pop,female_pop) to have a data type of numeric. Save the resulting data frame to us_census, and view the head.
# update column data types
us_census <- us_census%>%
mutate(Hispanic = as.numeric(Hispanic),
White = as.numeric(White),
Black = as.numeric(Black),
Native = as.numeric(Native),
Asian = as.numeric(Asian),
Pacific = as.numeric(Pacific),
Income = as.numeric(Income),
male_pop = as.numeric(male_pop),
female_pop = as.numeric(female_pop))
head(us_census)
Take a second to look back at the Hispanic, White, Black, Native, Asian, and Pacific columns. The columns represent the population percentage for each race. Earlier you removed the % symbol, and then you just converted the column to numeric type. To make calculations easier, the column should now represent percentages in decimal form, where 50% is equivalent to 0.50. Update the values of these columns to be in decimal form, and save the resulting data frame to us_census. View the head of us_census.
# update values of race columns
us_census <- us_census%>%
mutate(Hispanic = Hispanic/100,
White = White/100,
Black = Black/100,
Native = Native/100,
Asian = Asian/100,
Pacific = Pacific/100)
head(us_census)
It’s always a good idea to check if there are duplicate rows of data in a data set. Pipe us_census into the duplicated() function to see which rows are duplicated. Then pipe the result into table() to get a count of the duplicated rows.
# check for duplicate rows
duplicates <- us_census%>%
duplicated() %>%
table()
duplicates
.
FALSE TRUE
52 9
Since there are duplicates, update the value of us_census to be the us_census data frame with only unique/distinct rows.
# remove duplicate rows
us_census <- us_census%>%
distinct()
Confirm that there are no more duplicated rows in us_census. Pipe us_census into the duplicated() function to see which rows are duplicated. Then pipe the result into table() to get a count of the duplicated rows.
You should expect to see no TRUEs!
# cduplicates <- us_census%>%
duplicates <- us_census%>%
duplicated() %>%
table()
duplicates
.
FALSE
52
View the head() of us_census. The data frame is all clean and ready for analysis! What do you want to find out?
# clean data frame
head(us_census)
LS0tDQp0aXRsZTogIkNsZWFuaW5nIFVTIENlbnN1cyBEYXRhIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KWW91IGp1c3QgZ290IGhpcmVkIGFzIGEgRGF0YSBBbmFseXN0IGF0IHRoZSBDZW5zdXMgQnVyZWF1LCB3aGljaCBjb2xsZWN0cyBjZW5zdXMgZGF0YSBhbmQgZmluZHMgaW50ZXJlc3RpbmcgaW5zaWdodHMgZnJvbSBpdC4NCg0KVGhlIHBlcnNvbiB3aG8gcHJldmlvdXNseSBoYWQgeW91ciBqb2IgbGVmdCB5b3UgYWxsIHRoZSBkYXRhIHRoZXkgaGFkIGZvciB0aGUgbW9zdCByZWNlbnQgY2Vuc3VzLiBUaGUgZGF0YSBpcyBzcHJlYWQgYWNyb3NzIG11bHRpcGxlIGNzdiBmaWxlcy4gVGhleSBkaWRu4oCZdCB1c2UgUiwgYW5kIHRoZXkgd291bGQgbWFudWFsbHkgbG9vayB0aHJvdWdoIHRoZXNlIGNzdiBmaWxlcyB3aGVuZXZlciB0aGV5IHdhbnRlZCB0byBmaW5kIHNvbWV0aGluZy4gU29tZXRpbWVzIHRoZXkgd291bGQgY29weSBhbmQgcGFzdGUgY2VydGFpbiBudW1iZXJzIGludG8gRXhjZWwgZm9yIGFuYWx5c2lzLg0KDQpUaGUgdGhvdWdodCBvZiBpdCBtYWtlcyB5b3Ugc2hpdmVyLiBUaGlzIGlzIG5vdCBzY2FsYWJsZSBvciByZXBlYXRhYmxlLg0KDQpZb3VyIGJvc3Mgd2FudHMgeW91IHRvIGRpZyBpbnRvIHRoZSBkYXRhIGFuZCBmaW5kIHNvbWUgaW5zaWdodHMgYnkgdGhlIGVuZCBvZiB0aGUgZGF5LiBDYW4geW91IGdldCB0aGlzIGRhdGEgaW50byBSIGFuZCBpbnRvIHJlYXNvbmFibGUgc2hhcGUgc28gdGhhdCB5b3UgY2FuIHBlcmZvcm0geW91ciBhbmFseXNpcz8NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXJyb3I9VFJVRX0NCiMgbG9hZCBsaWJyYXJpZXMNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeSh0aWR5cikNCmBgYA0KDQpJdCB3aWxsIGJlIGVhc2llciB0byBpbnNwZWN0IHRoZSBkYXRhIHN0b3JlZCBpbiB0aGVzZSBmaWxlcyBvbmNlIHlvdSBoYXZlIGl0IGluIGEgZGF0YSBmcmFtZS4gWW91IGNhbuKAmXQgZXZlbiBjYWxsIGhlYWQoKSBvbiB0aGVzZSBjc3ZzISBIb3cgYXJlIHlvdSBzdXBwb3NlZCB0byByZWFkIHRoZW0/DQoNCkJlZ2luIGJ5IGNyZWF0aW5nIGEgdmFyaWFibGUgY2FsbGVkIGZpbGVzIGFuZCBzZXQgaXQgZXF1YWwgdG8gdGhlIGxpc3QuZmlsZXMoKSBvZiBhbGwgb2YgdGhlIGNzdiBmaWxlcyB5b3Ugd2FudCB0byBpbXBvcnQuDQoNCg0KUmVhZCBlYWNoIGZpbGUgaW4gZmlsZXMgaW50byBhIGRhdGEgZnJhbWUgdXNpbmcgbGFwcGx5KCkgYW5kIHNhdmUgdGhlIHJlc3VsdCB0byBkZl9saXN0Lg0KDQoNCkNvbmNhdGVuYXRlIGFsbCBvZiB0aGUgZGF0YSBmcmFtZXMgaW4gZGZfbGlzdCBpbnRvIG9uZSBkYXRhIGZyYW1lIGNhbGxlZCB1c19jZW5zdXMuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVycm9yPVRSVUV9DQojIGxvYWQgQ1NWcw0KZmlsZXMgPC0gbGlzdC5maWxlcyhwYXR0ZXJuID0gInN0YXRlc18uKmNzdiIpDQpkZl9saXN0IDwtIGxhcHBseShmaWxlcywgcmVhZF9jc3YpDQp1c19jZW5zdXMgPC0gYmluZF9yb3dzKGRmX2xpc3QpDQoNCmBgYA0KSW5zcGVjdCB0aGUgdXNfY2Vuc3VzIGRhdGEgZnJhbWUgYnkgcHJpbnRpbmcgdGhlIGNvbHVtbiBuYW1lcywgbG9va2luZyBhdCB0aGUgZGF0YSB0eXBlcyB3aXRoIHN0cigpLCBhbmQgdmlld2luZyB0aGUgaGVhZCgpLg0KDQpXaGF0IGNvbHVtbnMgaGF2ZSBzeW1ib2xzIHRoYXQgd2lsbCBwcmV2ZW50IGNhbGN1bGF0aW9ucz8gV2hhdCBhcmUgdGhlIGRhdGEgdHlwZXMgb2YgdGhlIGNvbHVtbnM/IERvIGFueSBjb2x1bW5zIGNvbnRhaW4gbXVsdGlwbGUga2luZHMgb2YgaW5mb3JtYXRpb24/DQpgYGB7ciBlcnJvcj1UUlVFfQ0KIyBpbnNwZWN0IGRhdGENCnN0cih1c19jZW5zdXMpDQpoZWFkKHVzX2NlbnN1cykNCmNvbG5hbWVzKHVzX2NlbnN1cykNCg0KYGBgDQpXaGVuIGluc3BlY3RpbmcgdXNfY2Vuc3VzIHlvdSBub3RpY2UgYSBjb2x1bW4gWDEgdGhhdCBzdG9yZXMgbWVhbmluZ2xlc3MgaW5mb3JtYXRpb24uIERyb3AgdGhlIFgxIGNvbHVtbiBmcm9tIHVzX2NlbnN1cywgYW5kIHNhdmUgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lIHRvIHVzX2NlbnN1cy4gVmlldyB0aGUgaGVhZCBvZiB1c19jZW5zdXMuDQpgYGB7ciBlcnJvcj1UUlVFfQ0KIyBkcm9wIFgxIGNvbHVtbg0KdXNfY2Vuc3VzIDwtIHVzX2NlbnN1cyU+JQ0KICBzZWxlY3QoLVgxKQ0KaGVhZCh1c19jZW5zdXMpDQoNCmBgYA0KWW91IG5vdGljZSB0aGF0IHRoZXJlIGFyZSA2IGNvbHVtbnMgcmVwcmVzZW50aW5nIHRoZSBwb3B1bGF0aW9uIHBlcmNlbnRhZ2UgZm9yIGRpZmZlcmVudCByYWNlcy4gVGhlIGNvbHVtbnMgaW5jbHVkZSB0aGUgcGVyY2VudCBzeW1ib2wgJS4gUmVtb3ZlIHRoZSBwZXJjZW50IHN5bWJvbCAlIGZyb20gZWFjaCBvZiB0aGUgcmFjZSBjb2x1bW5zIChIaXNwYW5pYyxXaGl0ZSxCbGFjayxOYXRpdmUsQXNpYW4sUGFjaWZpYykuIFNhdmUgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lIHRvIHVzX2NlbnN1cywgYW5kIHZpZXcgdGhlIGhlYWQuDQpgYGB7ciBlcnJvcj1UUlVFfQ0KIyByZW1vdmUgJSBmcm9tIHJhY2UgY29sdW1ucw0KdXNfY2Vuc3VzIDwtIHVzX2NlbnN1cyU+JQ0KICBtdXRhdGUoSGlzcGFuaWM9Z3N1YignXFwlJywnJyxIaXNwYW5pYyksDQogICAgICAgIFdoaXRlPWdzdWIoJ1xcJScsJycsV2hpdGUpLA0KICAgICAgICBCbGFjaz1nc3ViKCdcXCUnLCcnLEJsYWNrKSwNCiAgICAgICAgTmF0aXZlPWdzdWIoJ1xcJScsJycsTmF0aXZlKSwNCiAgICAgICAgQXNpYW49Z3N1YignXFwlJywnJyxBc2lhbiksDQogICAgICAgIFBhY2lmaWM9Z3N1YignXFwlJywnJyxQYWNpZmljKSkNCmhlYWQodXNfY2Vuc3VzKQ0KDQpgYGANCg0KVGhlIEluY29tZSBjb2x1bW4gYWxzbyBpbmN1ZGVzIGEgXCQgc3ltYm9sIGFsb25nIHdpdGggdGhlIG51bWJlciByZXByZXNlbnRpbmcgbWVkaWFuIGluY29tZSBmb3IgYSBzdGF0ZS4gUmVtb3ZlIHRoZSBcJCBmcm9tIHRoZSBJbmNvbWUgY29sdW1uLiBTYXZlIHRoZSByZXN1bHRpbmcgZGF0YSBmcmFtZSB0byB1c19jZW5zdXMuIFZpZXcgdGhlIGhlYWQgb2YgdXNfY2Vuc3VzLg0KYGBge3IgZXJyb3I9VFJVRX0NCiMgcmVtb3ZlICQgZnJvbSBJbmNvbWUgY29sdW1uDQp1c19jZW5zdXMgPC0gdXNfY2Vuc3VzJT4lDQogIG11dGF0ZShJbmNvbWU9Z3N1YignXFwkJywnJyxJbmNvbWUpKQ0KaGVhZCh1c19jZW5zdXMpDQpgYGANCg0KVGhlIEdlbmRlclBvcCBjb2x1bW4gYXBwZWFycyB0byBob2xkIHRoZSBtYWxlIGFuZCBmZW1hbGUgcG9wdWxhdGlvbiBjb3VudHMuIFNlcGFyYXRlIHRoaXMgY29sdW1uIGF0IHRoZSBfIGNoYXJhY3RlciB0byBjcmVhdGUgdHdvIG5ldyBjb2x1bW5zOiBtYWxlX3BvcCBhbmQgZmVtYWxlX3BvcC4gU2F2ZSB0aGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUgdG8gdXNfY2Vuc3VzLCBhbmQgdmlldyB0aGUgaGVhZC4NCmBgYHtyIGVycm9yPVRSVUV9DQojIHNlcGFyYXRlIEdlbmRlclBvcCBjb2x1bW4NCnVzX2NlbnN1cyA8LSB1c19jZW5zdXMlPiUNCiAgc2VwYXJhdGUoR2VuZGVyUG9wLGMoJ21hbGVfcG9wJywgJ2ZlbWFsZV9wb3AnKSwgJ18nKQ0KaGVhZCh1c19jZW5zdXMpDQpgYGANCllvdSBub3RpY2UgdGhlIG5ldyBtYWxlX3BvcCBhbmQgZmVtYWxlX3BvcCBjb2x1bW5zIGNvbnRhaW4gZXh0cmEgY2hhcmFjdGVycyBNIGFuZCBGLCByZXNwZWN0aXZlbHkuIFJlbW92ZSB0aGVzZSBleHRyYSBjaGFyYWN0ZXJzIGZyb20gdGhlIGNvbHVtbnMuIFNhdmUgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lIHRvIHVzX2NlbnN1cywgYW5kIHZpZXcgdGhlIGhlYWQuDQpgYGB7ciBlcnJvcj1UUlVFfQ0KIyBjbGVhbiBtYWxlIGFuZCBmZW1hbGUgcG9wdWxhdGlvbiBjb2x1bW5zDQp1c19jZW5zdXMgPC0gdXNfY2Vuc3VzJT4lDQogIG11dGF0ZShtYWxlX3BvcD1nc3ViKCdNJywnJyxtYWxlX3BvcCksDQogICAgICAgIGZlbWFsZV9wb3A9Z3N1YignRicsJycsZmVtYWxlX3BvcCkpDQpoZWFkKHVzX2NlbnN1cykNCmBgYA0KTm93IHRoYXQgeW91IGhhdmUgcmVtb3ZlZCBleHRyYSBzeW1ib2xzIGZyb20gbWFueSBvZiB0aGUgY29sdW1ucyB0aGF0IGNvbnRhaW4gbnVtZXJpY2FsIGRhdGEsIHlvdSBub3RpY2UgdGhhdCB0aGUgZGF0YSB0eXBlIGZvciB0aGVzZSBjb2x1bW5zIGlzIHN0aWxsIGNociwgb3IgY2hhcmFjdGVyLiBDb252ZXJ0IGFsbCBvZiB0aGVzZSBjb2x1bW5zIChIaXNwYW5pYyxXaGl0ZSxCbGFjayxOYXRpdmUsQXNpYW4sUGFjaWZpYyxJbmNvbWUsbWFsZV9wb3AsZmVtYWxlX3BvcCkgdG8gaGF2ZSBhIGRhdGEgdHlwZSBvZiBudW1lcmljLiBTYXZlIHRoZSByZXN1bHRpbmcgZGF0YSBmcmFtZSB0byB1c19jZW5zdXMsIGFuZCB2aWV3IHRoZSBoZWFkLg0KYGBge3IgZXJyb3I9VFJVRX0NCiMgdXBkYXRlIGNvbHVtbiBkYXRhIHR5cGVzDQp1c19jZW5zdXMgPC0gdXNfY2Vuc3VzJT4lDQogIG11dGF0ZShIaXNwYW5pYyA9IGFzLm51bWVyaWMoSGlzcGFuaWMpLA0KICAgICAgICBXaGl0ZSA9IGFzLm51bWVyaWMoV2hpdGUpLA0KICAgICAgICBCbGFjayA9IGFzLm51bWVyaWMoQmxhY2spLA0KICAgICAgICBOYXRpdmUgPSBhcy5udW1lcmljKE5hdGl2ZSksDQogICAgICAgIEFzaWFuID0gYXMubnVtZXJpYyhBc2lhbiksDQogICAgICAgIFBhY2lmaWMgPSBhcy5udW1lcmljKFBhY2lmaWMpLA0KICAgICAgICBJbmNvbWUgPSBhcy5udW1lcmljKEluY29tZSksDQogICAgICAgIG1hbGVfcG9wID0gYXMubnVtZXJpYyhtYWxlX3BvcCksDQogICAgICAgIGZlbWFsZV9wb3AgPSBhcy5udW1lcmljKGZlbWFsZV9wb3ApKQ0KaGVhZCh1c19jZW5zdXMpDQpgYGANClRha2UgYSBzZWNvbmQgdG8gbG9vayBiYWNrIGF0IHRoZSBIaXNwYW5pYywgV2hpdGUsIEJsYWNrLCBOYXRpdmUsIEFzaWFuLCBhbmQgUGFjaWZpYyBjb2x1bW5zLiBUaGUgY29sdW1ucyByZXByZXNlbnQgdGhlIHBvcHVsYXRpb24gcGVyY2VudGFnZSBmb3IgZWFjaCByYWNlLiBFYXJsaWVyIHlvdSByZW1vdmVkIHRoZSAlIHN5bWJvbCwgYW5kIHRoZW4geW91IGp1c3QgY29udmVydGVkIHRoZSBjb2x1bW4gdG8gbnVtZXJpYyB0eXBlLiBUbyBtYWtlIGNhbGN1bGF0aW9ucyBlYXNpZXIsIHRoZSBjb2x1bW4gc2hvdWxkIG5vdyByZXByZXNlbnQgcGVyY2VudGFnZXMgaW4gZGVjaW1hbCBmb3JtLCB3aGVyZSA1MCUgaXMgZXF1aXZhbGVudCB0byAwLjUwLiBVcGRhdGUgdGhlIHZhbHVlcyBvZiB0aGVzZSBjb2x1bW5zIHRvIGJlIGluIGRlY2ltYWwgZm9ybSwgYW5kIHNhdmUgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lIHRvIHVzX2NlbnN1cy4gVmlldyB0aGUgaGVhZCBvZiB1c19jZW5zdXMuDQpgYGB7ciBlcnJvcj1UUlVFfQ0KIyB1cGRhdGUgdmFsdWVzIG9mIHJhY2UgY29sdW1ucw0KdXNfY2Vuc3VzIDwtIHVzX2NlbnN1cyU+JQ0KICBtdXRhdGUoSGlzcGFuaWMgPSBIaXNwYW5pYy8xMDAsDQogICAgICAgICBXaGl0ZSA9IFdoaXRlLzEwMCwNCiAgICAgICAgIEJsYWNrID0gQmxhY2svMTAwLA0KICAgICAgICAgTmF0aXZlID0gTmF0aXZlLzEwMCwNCiAgICAgICAgIEFzaWFuID0gQXNpYW4vMTAwLA0KICAgICAgICAgUGFjaWZpYyA9IFBhY2lmaWMvMTAwKQ0KaGVhZCh1c19jZW5zdXMpDQpgYGANCkl04oCZcyBhbHdheXMgYSBnb29kIGlkZWEgdG8gY2hlY2sgaWYgdGhlcmUgYXJlIGR1cGxpY2F0ZSByb3dzIG9mIGRhdGEgaW4gYSBkYXRhIHNldC4gUGlwZSB1c19jZW5zdXMgaW50byB0aGUgZHVwbGljYXRlZCgpIGZ1bmN0aW9uIHRvIHNlZSB3aGljaCByb3dzIGFyZSBkdXBsaWNhdGVkLiBUaGVuIHBpcGUgdGhlIHJlc3VsdCBpbnRvIHRhYmxlKCkgdG8gZ2V0IGEgY291bnQgb2YgdGhlIGR1cGxpY2F0ZWQgcm93cy4NCmBgYHtyIGVycm9yPVRSVUV9DQojIGNoZWNrIGZvciBkdXBsaWNhdGUgcm93cw0KZHVwbGljYXRlcyA8LSB1c19jZW5zdXMlPiUNCiAgZHVwbGljYXRlZCgpICU+JQ0KICB0YWJsZSgpDQpkdXBsaWNhdGVzDQpgYGANClNpbmNlIHRoZXJlIGFyZSBkdXBsaWNhdGVzLCB1cGRhdGUgdGhlIHZhbHVlIG9mIHVzX2NlbnN1cyB0byBiZSB0aGUgdXNfY2Vuc3VzIGRhdGEgZnJhbWUgd2l0aCBvbmx5IHVuaXF1ZS9kaXN0aW5jdCByb3dzLg0KYGBge3IgZXJyb3I9VFJVRX0NCiMgcmVtb3ZlIGR1cGxpY2F0ZSByb3dzDQp1c19jZW5zdXMgPC0gdXNfY2Vuc3VzJT4lDQogIGRpc3RpbmN0KCkNCmBgYA0KQ29uZmlybSB0aGF0IHRoZXJlIGFyZSBubyBtb3JlIGR1cGxpY2F0ZWQgcm93cyBpbiB1c19jZW5zdXMuIFBpcGUgdXNfY2Vuc3VzIGludG8gdGhlIGR1cGxpY2F0ZWQoKSBmdW5jdGlvbiB0byBzZWUgd2hpY2ggcm93cyBhcmUgZHVwbGljYXRlZC4gVGhlbiBwaXBlIHRoZSByZXN1bHQgaW50byB0YWJsZSgpIHRvIGdldCBhIGNvdW50IG9mIHRoZSBkdXBsaWNhdGVkIHJvd3MuDQoNCllvdSBzaG91bGQgZXhwZWN0IHRvIHNlZSBubyBUUlVFcyENCmBgYHtyIGVycm9yPVRSVUV9DQojIGNkdXBsaWNhdGVzIDwtIHVzX2NlbnN1cyU+JQ0KZHVwbGljYXRlcyA8LSB1c19jZW5zdXMlPiUNCiAgZHVwbGljYXRlZCgpICU+JQ0KICB0YWJsZSgpDQpkdXBsaWNhdGVzDQpgYGANClZpZXcgdGhlIGhlYWQoKSBvZiB1c19jZW5zdXMuIFRoZSBkYXRhIGZyYW1lIGlzIGFsbCBjbGVhbiBhbmQgcmVhZHkgZm9yIGFuYWx5c2lzISBXaGF0IGRvIHlvdSB3YW50IHRvIGZpbmQgb3V0Pw0KYGBge3IgZXJyb3I9VFJVRX0NCiMgY2xlYW4gZGF0YSBmcmFtZQ0KaGVhZCh1c19jZW5zdXMpDQpgYGA=