Data Exploration
Exploration of the raw data - base and dplyr
bmi_1 = read_excel("bmi.xlsx", sheet = 2)
class(bmi_1)
[1] "tbl_df" "tbl" "data.frame"
- Check the dimensions of bmi:
dim(bmi_1)
[1] 199 2
- View the column names of bmi:
colnames(bmi_1)
[1] "Country" "BMI_1980"
str(bmi_1)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 199 obs. of 2 variables:
$ Country : chr "Afghanistan" "Albania" "Algeria" "Andorra" ...
$ BMI_1980: num 20.4 25.2 23.7 25.7 20.1 ...
# library(dplyr), install.packages(“dplyr“)
glimpse(bmi_1)
Observations: 199
Variables: 2
$ Country [3m[38;5;246m<chr>[39m[23m "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", "Armenia"...
$ BMI_1980 [3m[38;5;246m<dbl>[39m[23m 20.44348, 25.17427, 23.67764, 25.67324, 20.06763, 24.22235, 23.84436, 25.77727, 23.63058, 23.90340, 26....
summary(bmi_1)
Country BMI_1980
Length:199 Min. :18.47
Class :character 1st Qu.:21.38
Mode :character Median :23.98
Mean :23.55
3rd Qu.:25.40
Max. :28.28
head(bmi_1, n = 10)
tail(bmi_1, n = 10)
Exploration of the raw data - psych
# library(psych), install.packages("psych")
- Check the structure of bmi, the psych way:
describe(bmi_1)
- Check the structure of bmi, the psych way - by group:
bmi_2 <- bmi_1 %>%
group_by(Country) %>%
mutate(Health = if_else(BMI_1980 <= 18.5 | BMI_1980 >= 24.9,
"Unhealthy", "Normal"))
describe.by(bmi_2$BMI_1980, group = factor(bmi_2$Health))
Descriptive statistics by group
group: Normal
---------------------------------------------------------------------------------------------
group: Unhealthy
Introduction to Data Wrangling
Infrastructure <- read.csv2("Infrastructure.csv")
- Preview Infrastructure with str():
str(Infrastructure)
'data.frame': 133 obs. of 7 variables:
$ Country : Factor w/ 133 levels "Afghanistan",..: 126 97 26 49 40 125 56 120 43 35 ...
$ ISO3 : Factor w/ 133 levels "AFG","ALB","ALG",..: 127 97 25 49 40 124 57 121 43 35 ...
$ Rank : int 1 2 3 4 5 6 7 8 9 10 ...
$ Ports : int 24 7 15 7 14 14 10 9 13 7 ...
$ Roadway_Coverage: int 6586610 982000 3860800 3320410 951200 394428 1210251 352046 644480 65050 ...
$ Railway_Coverage: int 224792 87157 86000 63974 29640 16454 27182 8699 41981 5083 ...
$ Airports : int 13513 1218 507 346 464 460 175 98 539 83 ...
- Coerce Country to character:
Infrastructure$Country <- as.character(Infrastructure$Country)
Infrastructure$Rank <- as.character(Infrastructure$Rank)
- Look at Infrastructure once more with str():
str(Infrastructure)
'data.frame': 133 obs. of 7 variables:
$ Country : chr "United States" "Russia" "China" "India" ...
$ ISO3 : Factor w/ 133 levels "AFG","ALB","ALG",..: 127 97 25 49 40 124 57 121 43 35 ...
$ Rank : chr "1" "2" "3" "4" ...
$ Ports : int 24 7 15 7 14 14 10 9 13 7 ...
$ Roadway_Coverage: int 6586610 982000 3860800 3320410 951200 394428 1210251 352046 644480 65050 ...
$ Railway_Coverage: int 224792 87157 86000 63974 29640 16454 27182 8699 41981 5083 ...
$ Airports : int 13513 1218 507 346 464 460 175 98 539 83 ...
Strings
- Load the stringr package:
# library("stringr") install.packages("stringr")
- Trim all leading and trailing whitespace:
name = c(" Filip ", "Nick ", " Jonathan")
str_trim(name)
[1] "Filip" "Nick" "Jonathan"
- Pad these strings with leading zeros:
pad = c("23485W", "8823453Q", "994Z")
str_pad(pad, width = 9, side = "left", pad = "0")
[1] "00023485W" "08823453Q" "00000994Z"
- Print state abbreviations:
head(Manpower$Country)
[1] United States Russia China India France United Kingdom
133 Levels: Afghanistan Albania Algeria Angola Argentina Armenia Australia Austria Azerbaijan Bahrain Bangladesh ... Zimbabwe
- Make states all uppercase and save result:
# to states_upper
states_upper <- toupper(Manpower$Country)
head(states_upper)
[1] "UNITED STATES" "RUSSIA" "CHINA" "INDIA" "FRANCE" "UNITED KINGDOM"
- Make states_upper all lowercase again:
states_lower <- tolower(Manpower$Country)
head(states_lower)
[1] "united states" "russia" "china" "india" "france" "united kingdom"
- Look at the head of Infrastructure:
head(Infrastructure)
- Detect all “Republic” in Country:
str_detect(Infrastructure$Country, "Republic")
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[21] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[41] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[61] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE
[81] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
[101] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE
[121] FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE
- In the Country column, replace “Republic” with “R”…:
Infrastructure$Country <- str_replace(Infrastructure$Country, "Republic", "R")
Valid Values
- In R, represented as NA
- May appear in other forms
- N/A (Excel)
- Single dot (SPSS, SAS)
- Empty string
- Inf - “Infinite value” (indicative of outliers?):
1/0
[1] Inf
1/0 + 1/0
[1] Inf
33333^33333
[1] Inf
- NaN - “Not a number” (rethink a variable?):
0/0
[1] NaN
1/0 - 1/0
[1] NaN
"treatment", "123", "A"
23.44, 120, NaN, Inf
4L, 1123L
factor("Hello"), factor(8)
TRUE, FALSE, NA
Missing Values
name = c("Jerry", "Beth", "Rick", "Morty")
n_friends = c(NaN, NA, Inf, 2)
status = c("Listening to human music", "Happy Family", "Garage", "")
social_df = data.frame(cbind(name, n_friends, status))
- Call is.na() on the full social_df to spot all NAs:
is.na(social_df)
name n_friends status
[1,] FALSE FALSE FALSE
[2,] FALSE TRUE FALSE
[3,] FALSE FALSE FALSE
[4,] FALSE FALSE FALSE
- Use the any() function to ask whether there are any NAs in the data:
any(is.na(social_df))
[1] TRUE
- View a summary() of the dataset:
summary(social_df)
name n_friends status
Beth :1 2 :1 :1
Jerry:1 Inf :1 Garage :1
Morty:1 NaN :1 Happy Family :1
Rick :1 NA's:1 Listening to human music:1
- Call table() on the status column:
table(social_df$status)
Garage Happy Family Listening to human music
1 1 1 1
- Replace all empty strings in status with NA:
social_df$status[social_df$status == ""] <- NA
- Print social_df to the console:
social_df
- Use complete.cases() to see which rows have no missing values:
complete.cases(social_df)
[1] TRUE FALSE TRUE FALSE
- Use na.omit() to remove all rows with any missing values:
na.omit(social_df)
Outliers
Infrastructure = read.csv2("Infrastructure.csv")
hist(Infrastructure$Ports)
boxplot(Infrastructure$Airports)
plot(Infrastructure$Railway_Coverage, Infrastructure$Roadway_Coverage)
LS0tDQp0aXRsZTogIioqMDQuIERhdGEgQ2xlYW5pbmcqKiINCnN1YnRpdGxlOiAiUjEwMSINCmF1dGhvcjogIlbDrXQgR2FicmhlbCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgdGhlbWU6IHlldGkgICAgDQogICAgY29kZV9mb2xkaW5nOiAic2hvdyINCi0tLQ0KLS0tDQojIFNldHRpbmcgdXAgdGhlIGxpYnJhcnkNCjxicj4NCg0KKiAqKlBhY2thZ2VzKio6DQpgYGB7ciBlY2hvID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZz1GQUxTRSwgZXJyb3I9RkFMU0V9DQojIExvYWQgcGFja2FnZXMgdmlhIGZ1bmN0aW9uDQppcGFrIDwtIGZ1bmN0aW9uKHBrZyl7DQogIG5ldy5wa2cgPC0gcGtnWyEocGtnICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLCAiUGFja2FnZSJdKV0NCiAgaWYgKGxlbmd0aChuZXcucGtnKSkgDQogICAgaW5zdGFsbC5wYWNrYWdlcyhuZXcucGtnLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQ0KICBzYXBwbHkocGtnLCByZXF1aXJlLCBjaGFyYWN0ZXIub25seSA9IFRSVUUpDQp9DQoNCnBhY2thZ2VzIDwtIGMoImdncGxvdDIiLCAiZHBseXIiLCAicHN5Y2giLCANCiAgICAgICAgICAgICAgInN1bW1hcnl0b29scyIsICJyZWFkeGwiLCAibHVicmlkYXRlIiwNCiAgICAgICAgICAgICAgInN0cmluZ3IiKQ0KDQppcGFrKHBhY2thZ2VzKQ0KYGBgDQo8YnI+DQoNCiMgRGF0YSBFeHBsb3JhdGlvbg0KIyMgKkV4cGxvcmF0aW9uIG9mIHRoZSByYXcgZGF0YSAtIFtiYXNlXShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvYmFzZS9odG1sLzAwSW5kZXguaHRtbCkgYW5kIFtkcGx5cl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2RwbHlyL2RwbHlyLnBkZikqDQo8YnI+DQoNCiogKipEYXRhKiogZW50cnk6DQpgYGB7cn0NCmJtaV8xID0gcmVhZF9leGNlbCgiYm1pLnhsc3giLCBzaGVldCA9IDIpDQpgYGANCjxicj4NCg0KKiBDaGVjayB0aGUgKipjbGFzcyoqIG9mICpibWkqOg0KYGBge3J9DQpjbGFzcyhibWlfMSkNCmBgYA0KPGJyPg0KDQoqIENoZWNrIHRoZSAqKmRpbWVuc2lvbnMqKiBvZiAqYm1pKjoNCmBgYHtyfQ0KZGltKGJtaV8xKQ0KYGBgDQo8YnI+DQoNCiogVmlldyB0aGUgKipjb2x1bW4gbmFtZXMqKiBvZiAqYm1pKjoNCmBgYHtyfQ0KY29sbmFtZXMoYm1pXzEpDQpgYGANCjxicj4NCg0KKiAqKlN0cnVjdHVyZSoqIG9mIHRoZSBkYXRhOg0KYGBge3J9DQpzdHIoYm1pXzEpDQpgYGANCjxicj4NCg0KKiAqKkdsaW1wc2UqKjoNCmBgYHtyfQ0KIyBsaWJyYXJ5KGRwbHlyKSwgaW5zdGFsbC5wYWNrYWdlcyjigJxkcGx5cuKAnCkNCmdsaW1wc2UoYm1pXzEpDQpgYGANCjxicj4NCg0KKiAqKlN1bW1hcnkqKjoNCmBgYHtyfQ0Kc3VtbWFyeShibWlfMSkNCmBgYA0KPGJyPg0KDQoqICoqRmlyc3QqKiAxMCByb3dzOg0KYGBge3J9DQpoZWFkKGJtaV8xLCBuID0gMTApDQpgYGANCjxicj4NCg0KKiAqKkxhc3QqKiAxMCByb3dzOg0KYGBge3J9DQp0YWlsKGJtaV8xLCBuID0gMTApDQpgYGANCjxicj4NCg0KIyMgKkV4cGxvcmF0aW9uIG9mIHRoZSByYXcgZGF0YSAtIFtwc3ljaF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3BzeWNoL3BzeWNoLnBkZikqDQo8YnI+DQoNCiogTG9hZCBwc3ljaDoNCmBgYHtyfQ0KIyBsaWJyYXJ5KHBzeWNoKSwgaW5zdGFsbC5wYWNrYWdlcygicHN5Y2giKQ0KYGBgDQo8YnI+DQoNCiogQ2hlY2sgdGhlICoqc3RydWN0dXJlKiogb2YgKmJtaSosIHRoZSAqKnBzeWNoKiogd2F5Og0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpkZXNjcmliZShibWlfMSkNCmBgYA0KPGJyPg0KDQoqIENoZWNrIHRoZSAqKnN0cnVjdHVyZSoqIG9mICpibWkqLCB0aGUgKipwc3ljaCoqIHdheSAtICoqYnkgZ3JvdXAqKjoNCmBgYHtyLCB3YXJuaW5nID0gRkFMU0V9DQpibWlfMiA8LSBibWlfMSAlPiUNCiAgICAgICAgIGdyb3VwX2J5KENvdW50cnkpICU+JQ0KICAgICAgICAgbXV0YXRlKEhlYWx0aCA9IGlmX2Vsc2UoQk1JXzE5ODAgPD0gMTguNSB8IEJNSV8xOTgwID49IDI0LjksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlVuaGVhbHRoeSIsICJOb3JtYWwiKSkNCg0KDQpkZXNjcmliZS5ieShibWlfMiRCTUlfMTk4MCwgZ3JvdXAgPSBmYWN0b3IoYm1pXzIkSGVhbHRoKSkNCmBgYA0KPGJyPg0KDQojIyAqRXhwbG9yYXRpb24gb2YgdGhlIHJhdyBkYXRhIC0gW3N1bW1hcnl0b29sc10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3N1bW1hcnl0b29scy9zdW1tYXJ5dG9vbHMucGRmKSoNCjxicj4NCg0KKiBMb2FkIHN1bW1hcnl0b29sczoNCmBgYHtyfQ0KIyBsaWJyYXJ5KHN1bW1hcnl0b29scyksIGluc3RhbGwucGFja2FnZXMoInN1bW1hcnl0b29scyIpDQpgYGANCjxicj4NCg0KKiBEYXRhOg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpNYW5wb3dlciA8LSByZWFkLmNzdignTWFucG93ZXIuY3N2JykNCmBgYA0KPGJyPg0KDQoqIENoZWNrIHRoZSAqKnN0cnVjdHVyZSoqIG9mICpibWkqLCB0aGUgKipzdW1tYXJ5dG9vbHMqKiB3YXk6DQpgYGB7ciwgd2FybmluZz1GQUxTRSwgZXZhbD1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnZpZXcoZGZTdW1tYXJ5KE1hbnBvd2VyKSkNCmBgYA0KDQojIEludHJvZHVjdGlvbiB0byBEYXRhIFdyYW5nbGluZw0KPGJyPg0KDQoqIERhdGE6DQpgYGB7cn0NCkluZnJhc3RydWN0dXJlIDwtIHJlYWQuY3N2MigiSW5mcmFzdHJ1Y3R1cmUuY3N2IikNCmBgYA0KPGJyPg0KDQoqIFByZXZpZXcgKkluZnJhc3RydWN0dXJlKiB3aXRoICoqc3RyKCkqKjoNCmBgYHtyfQ0Kc3RyKEluZnJhc3RydWN0dXJlKQ0KYGBgDQo8YnI+DQoNCiogQ29lcmNlICpDb3VudHJ5KiB0byAqKmNoYXJhY3RlcioqOg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpJbmZyYXN0cnVjdHVyZSRDb3VudHJ5IDwtIGFzLmNoYXJhY3RlcihJbmZyYXN0cnVjdHVyZSRDb3VudHJ5KQ0KYGBgDQo8YnI+DQoNCiogQ29lcmNlICpSYW5rKiB0byAqKmZhY3RvcioqOg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpJbmZyYXN0cnVjdHVyZSRSYW5rIDwtIGFzLmNoYXJhY3RlcihJbmZyYXN0cnVjdHVyZSRSYW5rKQ0KYGBgDQo8YnI+DQoNCiogTG9vayBhdCAqSW5mcmFzdHJ1Y3R1cmUqIG9uY2UgbW9yZSB3aXRoICoqc3RyKCkqKjoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0Kc3RyKEluZnJhc3RydWN0dXJlKQ0KYGBgDQo8YnI+DQoNCiMjICpTdHJpbmdzKg0KPGJyPg0KDQoqIExvYWQgdGhlIHN0cmluZ3IgcGFja2FnZToNCmBgYHtyfQ0KIyBsaWJyYXJ5KCJzdHJpbmdyIikgaW5zdGFsbC5wYWNrYWdlcygic3RyaW5nciIpDQpgYGANCjxicj4NCg0KKiAqKlRyaW0qKiBhbGwgbGVhZGluZyBhbmQgdHJhaWxpbmcgd2hpdGVzcGFjZToNCmBgYHtyfQ0KbmFtZSA9IGMoIiBGaWxpcCAiLCAiTmljayAiLCAiIEpvbmF0aGFuIikNCnN0cl90cmltKG5hbWUpDQpgYGANCjxicj4NCg0KKiAqKlBhZCoqIHRoZXNlIHN0cmluZ3Mgd2l0aCBsZWFkaW5nIHplcm9zOg0KYGBge3J9DQpwYWQgPSBjKCIyMzQ4NVciLCAiODgyMzQ1M1EiLCAiOTk0WiIpDQoNCnN0cl9wYWQocGFkLCB3aWR0aCA9IDksIHNpZGUgPSAibGVmdCIsIHBhZCA9ICIwIikNCmBgYA0KPGJyPg0KDQoqICoqUHJpbnQqKiBzdGF0ZSBhYmJyZXZpYXRpb25zOg0KYGBge3J9DQpoZWFkKE1hbnBvd2VyJENvdW50cnkpDQpgYGANCjxicj4NCg0KKiBNYWtlIHN0YXRlcyBhbGwgKip1cHBlcmNhc2UqKiBhbmQgc2F2ZSByZXN1bHQ6DQpgYGB7cn0NCiMgdG8gc3RhdGVzX3VwcGVyDQpzdGF0ZXNfdXBwZXIgPC0gdG91cHBlcihNYW5wb3dlciRDb3VudHJ5KQ0KaGVhZChzdGF0ZXNfdXBwZXIpDQpgYGANCjxicj4NCg0KKiBNYWtlIHN0YXRlc191cHBlciBhbGwgKipsb3dlcmNhc2UqKiBhZ2FpbjoNCmBgYHtyfQ0Kc3RhdGVzX2xvd2VyIDwtIHRvbG93ZXIoTWFucG93ZXIkQ291bnRyeSkNCmhlYWQoc3RhdGVzX2xvd2VyKQ0KYGBgDQo8YnI+DQoNCiogTG9vayBhdCB0aGUgKipoZWFkKiogb2YgKkluZnJhc3RydWN0dXJlKjoNCmBgYHtyfQ0KaGVhZChJbmZyYXN0cnVjdHVyZSkNCmBgYA0KPGJyPg0KDQoqICoqRGV0ZWN0KiogYWxsICJSZXB1YmxpYyIgaW4gKkNvdW50cnkqOg0KYGBge3J9DQpzdHJfZGV0ZWN0KEluZnJhc3RydWN0dXJlJENvdW50cnksICJSZXB1YmxpYyIpDQpgYGANCjxicj4NCg0KKiBJbiB0aGUgQ291bnRyeSBjb2x1bW4sICoqcmVwbGFjZSoqICJSZXB1YmxpYyIgd2l0aCAiUiIuLi46DQpgYGB7cn0NCkluZnJhc3RydWN0dXJlJENvdW50cnkgPC0gc3RyX3JlcGxhY2UoSW5mcmFzdHJ1Y3R1cmUkQ291bnRyeSwgIlJlcHVibGljIiwgIlIiKQ0KYGBgDQo8YnI+DQoNCiMjICpWYWxpZCBWYWx1ZXMqDQoNCiogSW4gUiwgcmVwcmVzZW50ZWQgYXMgTkENCiogTWF5IGFwcGVhciBpbiBvdGhlciBmb3Jtcw0KICArIE4vQSAoRXhjZWwpDQogICsgU2luZ2xlIGRvdCAoU1BTUywgU0FTKQ0KICArIEVtcHR5IHN0cmluZw0KICANCjxicj4NCg0KKiAqKkluZioqIC0gIkluZmluaXRlIHZhbHVlIiAoaW5kaWNhdGl2ZSBvZiBvdXRsaWVycz8pOg0KYGBge3J9DQoxLzANCg0KMS8wICsgMS8wDQoNCjMzMzMzXjMzMzMzDQpgYGANCjxicj4NCg0KKiAqKk5hTioqIC0gIk5vdCBhIG51bWJlciIgKHJldGhpbmsgYSB2YXJpYWJsZT8pOg0KYGBge3J9DQowLzANCg0KMS8wIC0gMS8wDQpgYGANCjxicj4NCg0KKiAqKmNoYXJhY3RlcioqOg0KYGBge3IgZXZhbD1GQUxTRX0NCiJ0cmVhdG1lbnQiLCAiMTIzIiwgIkEiDQpgYGANCjxicj4NCg0KKiAqKm51bWVyaWMqKjogDQpgYGB7ciBldmFsPUZBTFNFfQ0KMjMuNDQsIDEyMCwgTmFOLCBJbmYNCmBgYA0KPGJyPg0KDQoqICoqaW50ZWdlcioqOiANCmBgYHtyIGV2YWw9RkFMU0V9DQo0TCwgMTEyM0wNCmBgYA0KPGJyPg0KDQoqICoqZmFjdG9yKio6DQpgYGB7ciBldmFsPUZBTFNFfQ0KZmFjdG9yKCJIZWxsbyIpLCBmYWN0b3IoOCkNCmBgYA0KPGJyPg0KDQoqICoqbG9naWNhbCoqOg0KYGBge3IgZXZhbD1GQUxTRX0NClRSVUUsIEZBTFNFLCBOQQ0KYGBgDQoNCjxicj4NCg0KIyMgKk1pc3NpbmcgVmFsdWVzKg0KDQo8YnI+DQoNCiogKipEYXRhKio6DQpgYGB7cn0NCm5hbWUgPSBjKCJKZXJyeSIsICJCZXRoIiwgIlJpY2siLCAiTW9ydHkiKQ0Kbl9mcmllbmRzID0gYyhOYU4sIE5BLCBJbmYsIDIpDQpzdGF0dXMgPSBjKCJMaXN0ZW5pbmcgdG8gaHVtYW4gbXVzaWMiLCAiSGFwcHkgRmFtaWx5IiwgIkdhcmFnZSIsICIiKQ0Kc29jaWFsX2RmID0gZGF0YS5mcmFtZShjYmluZChuYW1lLCBuX2ZyaWVuZHMsIHN0YXR1cykpDQpgYGANCg0KPGJyPg0KDQoqIENhbGwgKippcy5uYSgpKiogb24gdGhlICoqZnVsbCoqICpzb2NpYWxfZGYqIHRvICoqc3BvdCBhbGwgTkFzKio6DQpgYGB7cn0NCmlzLm5hKHNvY2lhbF9kZikNCmBgYA0KDQo8YnI+DQoNCiogVXNlIHRoZSAqKmFueSgpKiogZnVuY3Rpb24gdG8gYXNrIHdoZXRoZXIgdGhlcmUgYXJlICoqYW55IE5BcyoqIGluIHRoZSBkYXRhOg0KYGBge3J9DQphbnkoaXMubmEoc29jaWFsX2RmKSkNCmBgYA0KDQo8YnI+DQoNCiogVmlldyBhICoqc3VtbWFyeSgpKiogb2YgdGhlIGRhdGFzZXQ6DQpgYGB7cn0NCnN1bW1hcnkoc29jaWFsX2RmKQ0KYGBgDQoNCjxicj4NCg0KKiBDYWxsICoqdGFibGUoKSoqIG9uIHRoZSAqc3RhdHVzKiBjb2x1bW46DQpgYGB7cn0NCnRhYmxlKHNvY2lhbF9kZiRzdGF0dXMpDQpgYGANCg0KPGJyPg0KDQoqICoqUmVwbGFjZSBhbGwgZW1wdHkgc3RyaW5ncyoqIGluICpzdGF0dXMqIHdpdGggKipOQSoqOg0KYGBge3J9DQpzb2NpYWxfZGYkc3RhdHVzW3NvY2lhbF9kZiRzdGF0dXMgPT0gIiJdIDwtIE5BDQpgYGANCg0KPGJyPg0KDQoqICoqUHJpbnQqKiAqc29jaWFsX2RmKiB0byB0aGUgY29uc29sZToNCmBgYHtyfQ0Kc29jaWFsX2RmDQpgYGANCg0KPGJyPg0KDQoqIFVzZSAqKmNvbXBsZXRlLmNhc2VzKCkqKiB0byBzZWUgd2hpY2ggKipyb3dzIGhhdmUgbm8gbWlzc2luZyB2YWx1ZXMqKjoNCmBgYHtyfQ0KY29tcGxldGUuY2FzZXMoc29jaWFsX2RmKQ0KYGBgDQoNCjxicj4NCg0KKiBVc2UgKipuYS5vbWl0KCkqKiB0byAqKnJlbW92ZSBhbGwgcm93cyoqIHdpdGggKiphbnkgbWlzc2luZyB2YWx1ZXMqKjoNCmBgYHtyfQ0KbmEub21pdChzb2NpYWxfZGYpDQpgYGANCg0KIyMgKk91dGxpZXJzKg0KDQo8YnI+DQoNCiogKipEYXRhKio6DQpgYGB7cn0NCkluZnJhc3RydWN0dXJlID0gcmVhZC5jc3YyKCJJbmZyYXN0cnVjdHVyZS5jc3YiKQ0KYGBgDQoNCjxicj4NCg0KKiAqKkhpc3RvZ3JhbSoqOg0KYGBge3J9DQpoaXN0KEluZnJhc3RydWN0dXJlJFBvcnRzKQ0KYGBgDQoNCiogKipCb3hwbG90Kio6DQpgYGB7cn0NCmJveHBsb3QoSW5mcmFzdHJ1Y3R1cmUkQWlycG9ydHMpDQpgYGANCg0KKiAqKlNjYXR0ZXJwbG90Kio6DQpgYGB7cn0NCnBsb3QoSW5mcmFzdHJ1Y3R1cmUkUmFpbHdheV9Db3ZlcmFnZSwgSW5mcmFzdHJ1Y3R1cmUkUm9hZHdheV9Db3ZlcmFnZSkNCmBgYA0KDQojIyBSZXNvdXJjZXMNCg0KW0RhdGFDYW1wJ3MgRnJlZSBDbGFzc3Jvb20gTW9kZWwgVXNlZCBpbiAxODAgQ291bnRyaWVzXShodHRwczovL3d3dy5kYXRhY2FtcC5jb20vY29tbXVuaXR5L2Jsb2cvZGF0YWNhbXBzLWZyZWUtY2xhc3Nyb29tLW1vZGVsLXVzZWQtaW4tMTgwLWNvdW50cmllcz9mYmNsaWQ9SXdBUjFCbzBCNC1EMk03TERBR1VnNkxWYllLeWVKbjRPNkZZeEtzMHdxRWdBN3UwRXpNM2NVT3pIU0dLWSkNCjxicj4NCg0KW1RoaXMgUiBEYXRhIEltcG9ydCBUdXRvcmlhbCBJcyBFdmVyeXRoaW5nIFlvdSBOZWVkXShodHRwczovL3d3dy5kYXRhY2FtcC5jb20vY29tbXVuaXR5L3R1dG9yaWFscy9yLWRhdGEtaW1wb3J0LXR1dG9yaWFsKQ0KPGJyPg0KDQpbVGhlIExhbmRzY2FwZSBvZiBSIFBhY2thZ2VzIGZvcg0KQXV0b21hdGVkIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMNCl0oaHR0cHM6Ly9qb3VybmFsLnItcHJvamVjdC5vcmcvYXJjaGl2ZS8yMDE5L1JKLTIwMTktMDMzL1JKLTIwMTktMDMzLnBkZikNCg0KDQoNCg==