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

  1. Fresh start + packages
  2. Import & understand the School data
  3. Count unique schools
  4. Summaries: zero-suspension and attendance rates
  5. “Top” schools: highest suspension, graduation, most graduates, most students
  6. Correlations
  7. Regression refresher (lm) + residuals
  8. Quick practice

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

2 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
year_academic id_school name_school sector category group oss_denom oss_0_numer oss_0_pct oss_1_numer oss_1_pct oss_2_numer oss_2_pct oss_3_numer oss_3_pct oss_4_numer oss_4_pct att_denom att_95_numer att_95_pct grad_denom grad_4_numer grad_4_pct
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_numeross_4_numer = # of students with exactly 0, 1, 2, 3, or 4+ OSS.
  • Percentages by suspension frequency (relative to oss_denom)
    • oss_0_pctoss_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.

3 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

4 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))

5 “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

6 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"))

7 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")

8 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")

9 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==