How to use this document. Run the code along with
me. Comments start with #. If you see errors, read them!
They usually tell you the fix.
Today’s plan
- Fresh start + packages
- Import & understand the School data
- Count unique schools
- Summaries: zero-suspension and attendance rates
- “Top” schools: highest suspension, graduation, most graduates, most
students
- Correlations
- Regression refresher (
lm) + residuals
- Quick practice
Fresh Start +
Packages
rm(list = ls()) # (optional) clean environment
# Load (every session)
library(tidyverse) # data tools (ggplot2, dplyr, readr, etc.)
library(dplyr) # data wrangling (filter, select, summarize, mutate)
library(ggplot2) # plotting (geom_*)
In the live session we set the working directory with
setwd(...). In this document the data file
SchoolData.csv sits next to the .Rmd, so no
setwd() is needed.
Import & Understand
the School Data
schooldata <- read.csv("SchoolData.csv")
# Quick peek
dim(schooldata) # rows, columns
#> [1] 213 23
names(schooldata) # variable names
#> [1] "year_academic" "id_school" "name_school" "sector"
#> [5] "category" "group" "oss_denom" "oss_0_numer"
#> [9] "oss_0_pct" "oss_1_numer" "oss_1_pct" "oss_2_numer"
#> [13] "oss_2_pct" "oss_3_numer" "oss_3_pct" "oss_4_numer"
#> [17] "oss_4_pct" "att_denom" "att_95_numer" "att_95_pct"
#> [21] "grad_denom" "grad_4_numer" "grad_4_pct"
# In RStudio you would run View(schooldata); here we preview the first rows.
knitr::kable(head(schooldata, 10), caption = "First rows of the School data")
First rows of the School data
| 2018-2019 |
1010 |
John Bartram High School |
District |
All Students |
All Students |
683 |
579 |
84.77 |
84 |
12.30 |
14 |
2.05 |
6 |
0.88 |
0 |
0.00 |
683 |
174 |
25.48 |
159 |
94 |
59.12 |
| 2018-2019 |
1020 |
West Philadelphia High School |
District |
All Students |
All Students |
501 |
421 |
84.03 |
61 |
12.18 |
12 |
2.40 |
4 |
0.80 |
3 |
0.60 |
501 |
68 |
13.57 |
114 |
70 |
61.40 |
| 2018-2019 |
1030 |
High School of the Future |
District |
All Students |
All Students |
561 |
507 |
90.37 |
40 |
7.13 |
12 |
2.14 |
1 |
0.18 |
1 |
0.18 |
561 |
251 |
44.74 |
107 |
88 |
82.24 |
| 2018-2019 |
1050 |
Paul Robeson High School for Human Services |
District |
All Students |
All Students |
323 |
308 |
95.36 |
14 |
4.33 |
1 |
0.31 |
0 |
0.00 |
0 |
0.00 |
323 |
122 |
37.77 |
76 |
71 |
93.42 |
| 2018-2019 |
1100 |
William L. Sayre High School |
District |
All Students |
All Students |
539 |
418 |
77.55 |
83 |
15.40 |
30 |
5.57 |
6 |
1.11 |
2 |
0.37 |
539 |
84 |
15.58 |
103 |
55 |
53.40 |
| 2018-2019 |
1130 |
William T. Tilden School |
District |
All Students |
All Students |
457 |
362 |
79.21 |
59 |
12.91 |
16 |
3.50 |
9 |
1.97 |
11 |
2.41 |
457 |
233 |
50.98 |
NA |
NA |
NA |
| 2018-2019 |
1190 |
Motivation High School |
District |
All Students |
All Students |
417 |
383 |
91.85 |
31 |
7.43 |
3 |
0.72 |
0 |
0.00 |
0 |
0.00 |
417 |
327 |
78.42 |
88 |
75 |
85.23 |
| 2018-2019 |
1200 |
John Barry School |
District |
All Students |
All Students |
764 |
722 |
94.50 |
35 |
4.58 |
5 |
0.65 |
2 |
0.26 |
0 |
0.00 |
764 |
211 |
27.62 |
NA |
NA |
NA |
| 2018-2019 |
1230 |
William C. Bryant School |
District |
All Students |
All Students |
525 |
483 |
92.00 |
29 |
5.52 |
13 |
2.48 |
0 |
0.00 |
0 |
0.00 |
525 |
119 |
22.67 |
NA |
NA |
NA |
| 2018-2019 |
1250 |
Joseph W. Catharine School |
District |
All Students |
All Students |
536 |
534 |
99.63 |
1 |
0.19 |
1 |
0.19 |
0 |
0.00 |
0 |
0.00 |
536 |
246 |
45.90 |
NA |
NA |
NA |
Extended codebook (suspensions, attendance,
graduation)
OSS = Out-of-School Suspension (a student kept out of school for up
to 10 days).
- Suspension denominator (population base)
oss_denom = total number of students in the school
(denominator for suspension %).
- Counts (numerators) by suspension frequency
oss_0_numer … oss_4_numer = # of students
with exactly 0, 1, 2, 3, or 4+ OSS.
- Percentages by suspension frequency (relative to
oss_denom)
oss_0_pct … oss_4_pct = % of students with
exactly 0, 1, 2, 3, or 4+ OSS (oss_4_pct is the “highest
suspension rate”).
- Attendance (\(\geq\) 95% of
days)
att_denom, att_95_numer,
att_95_pct.
- Graduation (4-year on-time)
grad_denom, grad_4_numer,
grad_4_pct.
- IDs and labels
year_academic, id_school,
name_school.
Notes: _numer columns are counts; _pct
columns are percentages (0–100), typically computed as
100 * (numerator / denominator). Each row is usually a
school-year record.
Count Unique
Schools
nrow(schooldata) # if each row = a school
#> [1] 213
n_distinct(schooldata$id_school) # safer - counts unique IDs
#> [1] 213
n_distinct(schooldata$name_school)
#> [1] 213
Summaries: Zero
Suspensions & Attendance
Average % of students with ZERO suspensions.
summarize() collapses the dataset into a tiny summary
table.
schooldata %>%
summarize(avg_zero_suspensions = mean(oss_0_pct, na.rm = TRUE),
median_zero_sus = median(oss_0_pct, na.rm = TRUE),
max = max(oss_0_pct, na.rm = TRUE))
Average attendance % (att_95_pct).
schooldata %>%
summarize(avg_attendance_rate = mean(att_95_pct, na.rm = TRUE))
“Top” Schools
School with the HIGHEST suspension rate. Start with
the data, then filter, then select.
school_highest_suspension <-
schooldata %>%
filter(oss_4_pct == max(oss_4_pct, na.rm = TRUE)) %>%
select(name_school, oss_4_pct)
school_highest_suspension
School with the HIGHEST graduation rate.
school_highest_graduation <-
schooldata %>%
filter(grad_4_pct == max(grad_4_pct, na.rm = TRUE)) %>%
select(name_school, grad_4_pct)
school_highest_graduation
Schools with “perfect attendance.”
perfect_attendance <-
schooldata %>%
filter(att_95_pct == 100) %>% # how I first define "perfect"
select(name_school, att_95_pct)
perfect_attendance
Practice — redefine “perfect attendance.” Start with
att_95_pct >= 95, and lower the threshold (90, 85, 80,
…) until schools appear.
perfect_attendance <-
schooldata %>%
filter(att_95_pct >= 85) %>%
select(name_school, att_95_pct)
perfect_attendance
School with the MOST graduates.
school_most_graduates <-
schooldata %>%
filter(grad_4_numer == max(grad_4_numer, na.rm = TRUE)) %>%
select(name_school, grad_4_numer)
school_most_graduates
School with the MOST students.
school_most_students <-
schooldata %>%
filter(oss_denom == max(oss_denom, na.rm = TRUE)) %>%
select(name_school, oss_denom)
school_most_students
Correlations
Correlation between the total number of students and the number of
students who graduated in four years.
schooldata %>%
summarize(total_vs_grad_num =
cor(oss_denom, grad_4_numer, use = "complete.obs"))
Practice. Correlation between attendance percentage
and graduation percentage — what does it tell you about their
relationship?
schooldata %>%
summarize(att_vs_grad =
cor(att_95_pct, grad_4_pct, use = "complete.obs"))
Regression Refresher
(lm)
Fit a regression: attendance % as a function of zero-suspension
%.
m1 <- lm(att_95_pct ~ oss_0_pct, data = schooldata)
summary(m1)
#>
#> Call:
#> lm(formula = att_95_pct ~ oss_0_pct, data = schooldata)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -27.171 -10.417 -1.643 7.405 35.349
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) -90.7972 14.2159 -6.387 1.06e-09 ***
#> oss_0_pct 1.4575 0.1515 9.622 < 2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 14.09 on 211 degrees of freedom
#> Multiple R-squared: 0.305, Adjusted R-squared: 0.3017
#> F-statistic: 92.58 on 1 and 211 DF, p-value: < 2.2e-16
Scatterplot with a loess curve (red) and a regression line
(blue).
ggplot(schooldata, aes(x = oss_0_pct, y = att_95_pct)) +
geom_point() +
geom_smooth(color = "red") + # loess curve
geom_smooth(method = lm, color = "blue") + # regression line
labs(title = "Zero Suspensions (%) vs Attendance (%)",
x = "% with 0 Suspensions",
y = "% with 95%+ Attendance")

Residuals
Create a new column of residuals, then plot them. A good residual
plot is centered around 0 with no pattern.
schooldata <- schooldata %>%
mutate(Residuals = residuals(m1)) # residuals for the model
ggplot(schooldata, aes(x = oss_0_pct, y = Residuals)) +
geom_point() + # residual vs predictor
geom_hline(yintercept = 0, color = "red") + # perfect-prediction line
labs(title = "Residual Plot",
x = "% with 0 Suspensions",
y = "Residuals")

Quick Practice
P1) On average, what percentage of students had exactly 1
suspension across all schools?
# base R way
mean(schooldata$oss_1_pct)
#> [1] 4.327606
# tidyverse
schooldata %>%
summarize(avg_oss1_pct = mean(oss_1_pct, na.rm = TRUE))
P2) Which school had the largest number of students with
exactly 2 suspensions?
schooldata %>%
filter(oss_2_numer == max(oss_2_numer, na.rm = TRUE)) %>%
select(name_school, oss_2_numer, year_academic)
P3) Is there a correlation between attendance rate and the
total number of students in the school?
cor(schooldata$att_95_pct, schooldata$oss_denom, use = "complete.obs")
#> [1] 0.1182958
P4) Fit a regression predicting attendance % from total
number of students. What is the slope?
m2 <- lm(att_95_pct ~ oss_denom, data = schooldata)
summary(m2)
#>
#> Call:
#> lm(formula = att_95_pct ~ oss_denom, data = schooldata)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -33.252 -13.552 -0.786 11.294 40.604
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 42.339385 2.240539 18.90 <2e-16 ***
#> oss_denom 0.005096 0.002945 1.73 0.085 .
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Residual standard error: 16.78 on 211 degrees of freedom
#> Multiple R-squared: 0.01399, Adjusted R-squared: 0.009321
#> F-statistic: 2.995 on 1 and 211 DF, p-value: 0.085
P5) Add residuals from the P4 model and make a residual
plot.
schooldata$resid_m2 <- residuals(m2)
ggplot(schooldata, aes(x = att_95_pct, y = resid_m2)) +
geom_point(alpha = 0.6) +
geom_hline(yintercept = 0, color = "red")

Interpretation. The residuals form a strong diagonal line —
almost like another regression line. This means the model is not
capturing the relationship correctly and suggests model assumptions
(linearity or independence) are violated.
P6) Make a histogram of residuals.
ggplot(schooldata, aes(x = resid_m2)) +
geom_histogram(bins = 30, color = "white", fill = "steelblue") +
labs(title = "Residuals Histogram", x = "Residuals", y = "Count")

LS0tCnRpdGxlOiAiTGFiIDQg4oCUIFNjaG9vbCBEYXRhOiBTdW1tYXJpZXMsIFwiVG9wXCIgU2Nob29scyAmIFJlZ3Jlc3Npb24iCnN1YnRpdGxlOiAiUXVhbnRpdGF0aXZlIFJlYXNvbmluZyDCtyBMQUIgNDEzIgphdXRob3I6ICJJbnN0cnVjdG9yOiBTdWJpbiBOYSIKZGF0ZTogIlNlcHRlbWJlciAyNCwgMjAyNSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogZmxhdGx5CiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLAogIGZpZy5hbGlnbiA9ICJjZW50ZXIiLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gNC4yLAogIGNvbW1lbnQgPSAiIz4iCikKYGBgCgo+ICoqSG93IHRvIHVzZSB0aGlzIGRvY3VtZW50LioqIFJ1biB0aGUgY29kZSBhbG9uZyB3aXRoIG1lLiBDb21tZW50cyBzdGFydCB3aXRoIGAjYC4KPiBJZiB5b3Ugc2VlIGVycm9ycywgcmVhZCB0aGVtISBUaGV5IHVzdWFsbHkgdGVsbCB5b3UgdGhlIGZpeC4KCioqVG9kYXkncyBwbGFuKioKCjEuIEZyZXNoIHN0YXJ0ICsgcGFja2FnZXMKMi4gSW1wb3J0ICYgdW5kZXJzdGFuZCB0aGUgU2Nob29sIGRhdGEKMy4gQ291bnQgdW5pcXVlIHNjaG9vbHMKNC4gU3VtbWFyaWVzOiB6ZXJvLXN1c3BlbnNpb24gYW5kIGF0dGVuZGFuY2UgcmF0ZXMKNS4gIlRvcCIgc2Nob29sczogaGlnaGVzdCBzdXNwZW5zaW9uLCBncmFkdWF0aW9uLCBtb3N0IGdyYWR1YXRlcywgbW9zdCBzdHVkZW50cwo2LiBDb3JyZWxhdGlvbnMKNy4gUmVncmVzc2lvbiByZWZyZXNoZXIgKGBsbWApICsgcmVzaWR1YWxzCjguIFF1aWNrIHByYWN0aWNlCgotLS0KCiMgRnJlc2ggU3RhcnQgKyBQYWNrYWdlcwoKYGBge3IgY2xlYW4tc2V0dXB9CnJtKGxpc3QgPSBscygpKSAgICMgKG9wdGlvbmFsKSBjbGVhbiBlbnZpcm9ubWVudAoKIyBMb2FkIChldmVyeSBzZXNzaW9uKQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICMgZGF0YSB0b29scyAoZ2dwbG90MiwgZHBseXIsIHJlYWRyLCBldGMuKQpsaWJyYXJ5KGRwbHlyKSAgICAgICMgZGF0YSB3cmFuZ2xpbmcgKGZpbHRlciwgc2VsZWN0LCBzdW1tYXJpemUsIG11dGF0ZSkKbGlicmFyeShnZ3Bsb3QyKSAgICAjIHBsb3R0aW5nIChnZW9tXyopCmBgYAoKPiBJbiB0aGUgbGl2ZSBzZXNzaW9uIHdlIHNldCB0aGUgd29ya2luZyBkaXJlY3Rvcnkgd2l0aCBgc2V0d2QoLi4uKWAuIEluIHRoaXMKPiBkb2N1bWVudCB0aGUgZGF0YSBmaWxlIGBTY2hvb2xEYXRhLmNzdmAgc2l0cyBuZXh0IHRvIHRoZSBgLlJtZGAsIHNvIG5vCj4gYHNldHdkKClgIGlzIG5lZWRlZC4KCiMgSW1wb3J0ICYgVW5kZXJzdGFuZCB0aGUgU2Nob29sIERhdGEKCmBgYHtyIGltcG9ydH0Kc2Nob29sZGF0YSA8LSByZWFkLmNzdigiU2Nob29sRGF0YS5jc3YiKQoKIyBRdWljayBwZWVrCmRpbShzY2hvb2xkYXRhKSAgICAgICMgcm93cywgY29sdW1ucwpuYW1lcyhzY2hvb2xkYXRhKSAgICAjIHZhcmlhYmxlIG5hbWVzCgojIEluIFJTdHVkaW8geW91IHdvdWxkIHJ1biBWaWV3KHNjaG9vbGRhdGEpOyBoZXJlIHdlIHByZXZpZXcgdGhlIGZpcnN0IHJvd3MuCmtuaXRyOjprYWJsZShoZWFkKHNjaG9vbGRhdGEsIDEwKSwgY2FwdGlvbiA9ICJGaXJzdCByb3dzIG9mIHRoZSBTY2hvb2wgZGF0YSIpCmBgYAoKKipFeHRlbmRlZCBjb2RlYm9vayoqIChzdXNwZW5zaW9ucywgYXR0ZW5kYW5jZSwgZ3JhZHVhdGlvbikKCk9TUyA9IE91dC1vZi1TY2hvb2wgU3VzcGVuc2lvbiAoYSBzdHVkZW50IGtlcHQgb3V0IG9mIHNjaG9vbCBmb3IgdXAgdG8gMTAgZGF5cykuCgotICoqU3VzcGVuc2lvbiBkZW5vbWluYXRvciAocG9wdWxhdGlvbiBiYXNlKSoqCiAgLSBgb3NzX2Rlbm9tYCA9IHRvdGFsIG51bWJlciBvZiBzdHVkZW50cyBpbiB0aGUgc2Nob29sIChkZW5vbWluYXRvciBmb3Igc3VzcGVuc2lvbiAlKS4KLSAqKkNvdW50cyAobnVtZXJhdG9ycykgYnkgc3VzcGVuc2lvbiBmcmVxdWVuY3kqKgogIC0gYG9zc18wX251bWVyYCDigKYgYG9zc180X251bWVyYCA9ICMgb2Ygc3R1ZGVudHMgd2l0aCBleGFjdGx5IDAsIDEsIDIsIDMsIG9yIDQrIE9TUy4KLSAqKlBlcmNlbnRhZ2VzIGJ5IHN1c3BlbnNpb24gZnJlcXVlbmN5KiogKHJlbGF0aXZlIHRvIGBvc3NfZGVub21gKQogIC0gYG9zc18wX3BjdGAg4oCmIGBvc3NfNF9wY3RgID0gJSBvZiBzdHVkZW50cyB3aXRoIGV4YWN0bHkgMCwgMSwgMiwgMywgb3IgNCsgT1NTCiAgICAoYG9zc180X3BjdGAgaXMgdGhlICJoaWdoZXN0IHN1c3BlbnNpb24gcmF0ZSIpLgotICoqQXR0ZW5kYW5jZSAoJFxnZXEkIDk1JSBvZiBkYXlzKSoqCiAgLSBgYXR0X2Rlbm9tYCwgYGF0dF85NV9udW1lcmAsIGBhdHRfOTVfcGN0YC4KLSAqKkdyYWR1YXRpb24gKDQteWVhciBvbi10aW1lKSoqCiAgLSBgZ3JhZF9kZW5vbWAsIGBncmFkXzRfbnVtZXJgLCBgZ3JhZF80X3BjdGAuCi0gKipJRHMgYW5kIGxhYmVscyoqCiAgLSBgeWVhcl9hY2FkZW1pY2AsIGBpZF9zY2hvb2xgLCBgbmFtZV9zY2hvb2xgLgoKPiBOb3RlczogYF9udW1lcmAgY29sdW1ucyBhcmUgY291bnRzOyBgX3BjdGAgY29sdW1ucyBhcmUgcGVyY2VudGFnZXMgKDDigJMxMDApLAo+IHR5cGljYWxseSBjb21wdXRlZCBhcyBgMTAwICogKG51bWVyYXRvciAvIGRlbm9taW5hdG9yKWAuIEVhY2ggcm93IGlzIHVzdWFsbHkgYQo+IHNjaG9vbC15ZWFyIHJlY29yZC4KCiMgQ291bnQgVW5pcXVlIFNjaG9vbHMKCmBgYHtyIHVuaXF1ZS1zY2hvb2xzfQpucm93KHNjaG9vbGRhdGEpICAgICAgICAgICAgICAgICAgICAgICMgaWYgZWFjaCByb3cgPSBhIHNjaG9vbAoKbl9kaXN0aW5jdChzY2hvb2xkYXRhJGlkX3NjaG9vbCkgICAgICAjIHNhZmVyIC0gY291bnRzIHVuaXF1ZSBJRHMKbl9kaXN0aW5jdChzY2hvb2xkYXRhJG5hbWVfc2Nob29sKQpgYGAKCiMgU3VtbWFyaWVzOiBaZXJvIFN1c3BlbnNpb25zICYgQXR0ZW5kYW5jZQoKKipBdmVyYWdlICUgb2Ygc3R1ZGVudHMgd2l0aCBaRVJPIHN1c3BlbnNpb25zLioqIGBzdW1tYXJpemUoKWAgY29sbGFwc2VzIHRoZQpkYXRhc2V0IGludG8gYSB0aW55IHN1bW1hcnkgdGFibGUuCgpgYGB7ciBzdW1tLXplcm99CnNjaG9vbGRhdGEgJT4lCiAgc3VtbWFyaXplKGF2Z196ZXJvX3N1c3BlbnNpb25zID0gbWVhbihvc3NfMF9wY3QsIG5hLnJtID0gVFJVRSksCiAgICAgICAgICAgIG1lZGlhbl96ZXJvX3N1cyAgICAgID0gbWVkaWFuKG9zc18wX3BjdCwgbmEucm0gPSBUUlVFKSwKICAgICAgICAgICAgbWF4ICAgICAgICAgICAgICAgICAgPSBtYXgob3NzXzBfcGN0LCBuYS5ybSA9IFRSVUUpKQpgYGAKCioqQXZlcmFnZSBhdHRlbmRhbmNlICUgKGBhdHRfOTVfcGN0YCkuKioKCmBgYHtyIHN1bW0tYXR0fQpzY2hvb2xkYXRhICU+JQogIHN1bW1hcml6ZShhdmdfYXR0ZW5kYW5jZV9yYXRlID0gbWVhbihhdHRfOTVfcGN0LCBuYS5ybSA9IFRSVUUpKQpgYGAKCiMgIlRvcCIgU2Nob29scwoKKipTY2hvb2wgd2l0aCB0aGUgSElHSEVTVCBzdXNwZW5zaW9uIHJhdGUuKiogU3RhcnQgd2l0aCB0aGUgZGF0YSwgdGhlbiBgZmlsdGVyYCwKdGhlbiBgc2VsZWN0YC4KCmBgYHtyIGhpZ2hlc3Qtc3VzcGVuc2lvbn0Kc2Nob29sX2hpZ2hlc3Rfc3VzcGVuc2lvbiA8LQogIHNjaG9vbGRhdGEgJT4lCiAgZmlsdGVyKG9zc180X3BjdCA9PSBtYXgob3NzXzRfcGN0LCBuYS5ybSA9IFRSVUUpKSAlPiUKICBzZWxlY3QobmFtZV9zY2hvb2wsIG9zc180X3BjdCkKCnNjaG9vbF9oaWdoZXN0X3N1c3BlbnNpb24KYGBgCgoqKlNjaG9vbCB3aXRoIHRoZSBISUdIRVNUIGdyYWR1YXRpb24gcmF0ZS4qKgoKYGBge3IgaGlnaGVzdC1ncmFkdWF0aW9ufQpzY2hvb2xfaGlnaGVzdF9ncmFkdWF0aW9uIDwtCiAgc2Nob29sZGF0YSAlPiUKICBmaWx0ZXIoZ3JhZF80X3BjdCA9PSBtYXgoZ3JhZF80X3BjdCwgbmEucm0gPSBUUlVFKSkgJT4lCiAgc2VsZWN0KG5hbWVfc2Nob29sLCBncmFkXzRfcGN0KQoKc2Nob29sX2hpZ2hlc3RfZ3JhZHVhdGlvbgpgYGAKCioqU2Nob29scyB3aXRoICJwZXJmZWN0IGF0dGVuZGFuY2UuIioqCgpgYGB7ciBwZXJmZWN0LWF0dGVuZGFuY2UtMTAwfQpwZXJmZWN0X2F0dGVuZGFuY2UgPC0KICBzY2hvb2xkYXRhICU+JQogIGZpbHRlcihhdHRfOTVfcGN0ID09IDEwMCkgJT4lICAjIGhvdyBJIGZpcnN0IGRlZmluZSAicGVyZmVjdCIKICBzZWxlY3QobmFtZV9zY2hvb2wsIGF0dF85NV9wY3QpCgpwZXJmZWN0X2F0dGVuZGFuY2UKYGBgCgo+ICoqUHJhY3RpY2Ug4oCUIHJlZGVmaW5lICJwZXJmZWN0IGF0dGVuZGFuY2UuIioqIFN0YXJ0IHdpdGggYGF0dF85NV9wY3QgPj0gOTVgLAo+IGFuZCBsb3dlciB0aGUgdGhyZXNob2xkICg5MCwgODUsIDgwLCDigKYpIHVudGlsIHNjaG9vbHMgYXBwZWFyLgoKYGBge3IgcGVyZmVjdC1hdHRlbmRhbmNlLTg1fQpwZXJmZWN0X2F0dGVuZGFuY2UgPC0KICBzY2hvb2xkYXRhICU+JQogIGZpbHRlcihhdHRfOTVfcGN0ID49IDg1KSAlPiUKICBzZWxlY3QobmFtZV9zY2hvb2wsIGF0dF85NV9wY3QpCgpwZXJmZWN0X2F0dGVuZGFuY2UKYGBgCgoqKlNjaG9vbCB3aXRoIHRoZSBNT1NUIGdyYWR1YXRlcy4qKgoKYGBge3IgbW9zdC1ncmFkdWF0ZXN9CnNjaG9vbF9tb3N0X2dyYWR1YXRlcyA8LQogIHNjaG9vbGRhdGEgJT4lCiAgZmlsdGVyKGdyYWRfNF9udW1lciA9PSBtYXgoZ3JhZF80X251bWVyLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBzZWxlY3QobmFtZV9zY2hvb2wsIGdyYWRfNF9udW1lcikKCnNjaG9vbF9tb3N0X2dyYWR1YXRlcwpgYGAKCioqU2Nob29sIHdpdGggdGhlIE1PU1Qgc3R1ZGVudHMuKioKCmBgYHtyIG1vc3Qtc3R1ZGVudHN9CnNjaG9vbF9tb3N0X3N0dWRlbnRzIDwtCiAgc2Nob29sZGF0YSAlPiUKICBmaWx0ZXIob3NzX2Rlbm9tID09IG1heChvc3NfZGVub20sIG5hLnJtID0gVFJVRSkpICU+JQogIHNlbGVjdChuYW1lX3NjaG9vbCwgb3NzX2Rlbm9tKQoKc2Nob29sX21vc3Rfc3R1ZGVudHMKYGBgCgojIENvcnJlbGF0aW9ucwoKQ29ycmVsYXRpb24gYmV0d2VlbiB0aGUgdG90YWwgbnVtYmVyIG9mIHN0dWRlbnRzIGFuZCB0aGUgbnVtYmVyIG9mIHN0dWRlbnRzIHdobwpncmFkdWF0ZWQgaW4gZm91ciB5ZWFycy4KCmBgYHtyIGNvci10b3RhbC1ncmFkfQpzY2hvb2xkYXRhICU+JQogIHN1bW1hcml6ZSh0b3RhbF92c19ncmFkX251bSA9CiAgICAgICAgICAgICAgY29yKG9zc19kZW5vbSwgZ3JhZF80X251bWVyLCB1c2UgPSAiY29tcGxldGUub2JzIikpCmBgYAoKPiAqKlByYWN0aWNlLioqIENvcnJlbGF0aW9uIGJldHdlZW4gYXR0ZW5kYW5jZSBwZXJjZW50YWdlIGFuZCBncmFkdWF0aW9uCj4gcGVyY2VudGFnZSDigJQgd2hhdCBkb2VzIGl0IHRlbGwgeW91IGFib3V0IHRoZWlyIHJlbGF0aW9uc2hpcD8KCmBgYHtyIGNvci1hdHQtZ3JhZH0Kc2Nob29sZGF0YSAlPiUKICBzdW1tYXJpemUoYXR0X3ZzX2dyYWQgPQogICAgICAgICAgICAgIGNvcihhdHRfOTVfcGN0LCBncmFkXzRfcGN0LCB1c2UgPSAiY29tcGxldGUub2JzIikpCmBgYAoKIyBSZWdyZXNzaW9uIFJlZnJlc2hlciAoYGxtYCkKCkZpdCBhIHJlZ3Jlc3Npb246IGF0dGVuZGFuY2UgJSBhcyBhIGZ1bmN0aW9uIG9mIHplcm8tc3VzcGVuc2lvbiAlLgoKYGBge3IgbG0tZml0fQptMSA8LSBsbShhdHRfOTVfcGN0IH4gb3NzXzBfcGN0LCBkYXRhID0gc2Nob29sZGF0YSkKc3VtbWFyeShtMSkKYGBgCgpTY2F0dGVycGxvdCB3aXRoIGEgbG9lc3MgY3VydmUgKHJlZCkgYW5kIGEgcmVncmVzc2lvbiBsaW5lIChibHVlKS4KCmBgYHtyIGxtLXBsb3R9CmdncGxvdChzY2hvb2xkYXRhLCBhZXMoeCA9IG9zc18wX3BjdCwgeSA9IGF0dF85NV9wY3QpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChjb2xvciA9ICJyZWQiKSArICAgICAgICAgICAgICAgICAgIyBsb2VzcyBjdXJ2ZQogIGdlb21fc21vb3RoKG1ldGhvZCA9IGxtLCBjb2xvciA9ICJibHVlIikgKyAgICAjIHJlZ3Jlc3Npb24gbGluZQogIGxhYnModGl0bGUgPSAiWmVybyBTdXNwZW5zaW9ucyAoJSkgdnMgQXR0ZW5kYW5jZSAoJSkiLAogICAgICAgeCA9ICIlIHdpdGggMCBTdXNwZW5zaW9ucyIsCiAgICAgICB5ID0gIiUgd2l0aCA5NSUrIEF0dGVuZGFuY2UiKQpgYGAKCiMgUmVzaWR1YWxzCgpDcmVhdGUgYSBuZXcgY29sdW1uIG9mIHJlc2lkdWFscywgdGhlbiBwbG90IHRoZW0uIEEgZ29vZCByZXNpZHVhbCBwbG90IGlzCmNlbnRlcmVkIGFyb3VuZCAwIHdpdGggbm8gcGF0dGVybi4KCmBgYHtyIHJlc2lkdWFsc30Kc2Nob29sZGF0YSA8LSBzY2hvb2xkYXRhICU+JQogIG11dGF0ZShSZXNpZHVhbHMgPSByZXNpZHVhbHMobTEpKSAjIHJlc2lkdWFscyBmb3IgdGhlIG1vZGVsCgpnZ3Bsb3Qoc2Nob29sZGF0YSwgYWVzKHggPSBvc3NfMF9wY3QsIHkgPSBSZXNpZHVhbHMpKSArCiAgZ2VvbV9wb2ludCgpICsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyByZXNpZHVhbCB2cyBwcmVkaWN0b3IKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArICAjIHBlcmZlY3QtcHJlZGljdGlvbiBsaW5lCiAgbGFicyh0aXRsZSA9ICJSZXNpZHVhbCBQbG90IiwKICAgICAgIHggPSAiJSB3aXRoIDAgU3VzcGVuc2lvbnMiLAogICAgICAgeSA9ICJSZXNpZHVhbHMiKQpgYGAKCiMgUXVpY2sgUHJhY3RpY2UKCioqUDEpIE9uIGF2ZXJhZ2UsIHdoYXQgcGVyY2VudGFnZSBvZiBzdHVkZW50cyBoYWQgZXhhY3RseSAxIHN1c3BlbnNpb24gYWNyb3NzIGFsbCBzY2hvb2xzPyoqCgpgYGB7ciBwMX0KIyBiYXNlIFIgd2F5Cm1lYW4oc2Nob29sZGF0YSRvc3NfMV9wY3QpCgojIHRpZHl2ZXJzZQpzY2hvb2xkYXRhICU+JQogIHN1bW1hcml6ZShhdmdfb3NzMV9wY3QgPSBtZWFuKG9zc18xX3BjdCwgbmEucm0gPSBUUlVFKSkKYGBgCgoqKlAyKSBXaGljaCBzY2hvb2wgaGFkIHRoZSBsYXJnZXN0IG51bWJlciBvZiBzdHVkZW50cyB3aXRoIGV4YWN0bHkgMiBzdXNwZW5zaW9ucz8qKgoKYGBge3IgcDJ9CnNjaG9vbGRhdGEgJT4lCiAgZmlsdGVyKG9zc18yX251bWVyID09IG1heChvc3NfMl9udW1lciwgbmEucm0gPSBUUlVFKSkgJT4lCiAgc2VsZWN0KG5hbWVfc2Nob29sLCBvc3NfMl9udW1lciwgeWVhcl9hY2FkZW1pYykKYGBgCgoqKlAzKSBJcyB0aGVyZSBhIGNvcnJlbGF0aW9uIGJldHdlZW4gYXR0ZW5kYW5jZSByYXRlIGFuZCB0aGUgdG90YWwgbnVtYmVyIG9mIHN0dWRlbnRzIGluIHRoZSBzY2hvb2w/KioKCmBgYHtyIHAzfQpjb3Ioc2Nob29sZGF0YSRhdHRfOTVfcGN0LCBzY2hvb2xkYXRhJG9zc19kZW5vbSwgdXNlID0gImNvbXBsZXRlLm9icyIpCmBgYAoKKipQNCkgRml0IGEgcmVncmVzc2lvbiBwcmVkaWN0aW5nIGF0dGVuZGFuY2UgJSBmcm9tIHRvdGFsIG51bWJlciBvZiBzdHVkZW50cy4gV2hhdCBpcyB0aGUgc2xvcGU/KioKCmBgYHtyIHA0fQptMiA8LSBsbShhdHRfOTVfcGN0IH4gb3NzX2Rlbm9tLCBkYXRhID0gc2Nob29sZGF0YSkKc3VtbWFyeShtMikKYGBgCgoqKlA1KSBBZGQgcmVzaWR1YWxzIGZyb20gdGhlIFA0IG1vZGVsIGFuZCBtYWtlIGEgcmVzaWR1YWwgcGxvdC4qKgoKYGBge3IgcDV9CnNjaG9vbGRhdGEkcmVzaWRfbTIgPC0gcmVzaWR1YWxzKG0yKQoKZ2dwbG90KHNjaG9vbGRhdGEsIGFlcyh4ID0gYXR0Xzk1X3BjdCwgeSA9IHJlc2lkX20yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKQpgYGAKCj4gKkludGVycHJldGF0aW9uLiogVGhlIHJlc2lkdWFscyBmb3JtIGEgc3Ryb25nIGRpYWdvbmFsIGxpbmUg4oCUIGFsbW9zdCBsaWtlCj4gYW5vdGhlciByZWdyZXNzaW9uIGxpbmUuIFRoaXMgbWVhbnMgdGhlIG1vZGVsIGlzIG5vdCBjYXB0dXJpbmcgdGhlCj4gcmVsYXRpb25zaGlwIGNvcnJlY3RseSBhbmQgc3VnZ2VzdHMgbW9kZWwgYXNzdW1wdGlvbnMgKGxpbmVhcml0eSBvcgo+IGluZGVwZW5kZW5jZSkgYXJlIHZpb2xhdGVkLgoKKipQNikgTWFrZSBhIGhpc3RvZ3JhbSBvZiByZXNpZHVhbHMuKioKCmBgYHtyIHA2fQpnZ3Bsb3Qoc2Nob29sZGF0YSwgYWVzKHggPSByZXNpZF9tMikpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMzAsIGNvbG9yID0gIndoaXRlIiwgZmlsbCA9ICJzdGVlbGJsdWUiKSArCiAgbGFicyh0aXRsZSA9ICJSZXNpZHVhbHMgSGlzdG9ncmFtIiwgeCA9ICJSZXNpZHVhbHMiLCB5ID0gIkNvdW50IikKYGBgCg==