Clean Data
Import
Packages selected for inspecting and cleaning Varsity Tutors, district data from William Penn School District.
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.2.1 ✔ readr 2.2.0
✔ forcats 1.0.1 ✔ stringr 1.6.0
✔ ggplot2 4.0.3 ✔ tibble 3.3.1
✔ lubridate 1.9.5 ✔ tidyr 1.3.2
✔ purrr 1.2.2
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Import roster.
roster <- read_csv ("roster_vt_penn.csv" )
New names:
Rows: 1279 Columns: 41
── Column specification
──────────────────────────────────────────────────────── Delimiter: "," chr
(10): School Name*, MOY MAP Math Math Test Date*, EOY MAP Math Math Test... dbl
(7): Student ID*, Student Grade Level*, Treatment*, MOY MAP Math Compos... lgl
(24): ...18, ...19, ...20, ...21, ...22, ...23, ...24, ...25, ...26, ......
ℹ Use `spec()` to retrieve the full column specification for this data. ℹ
Specify the column types or set `show_col_types = FALSE` to quiet this message.
• `` -> `...18`
• `` -> `...19`
• `` -> `...20`
• `` -> `...21`
• `` -> `...22`
• `` -> `...23`
• `` -> `...24`
• `` -> `...25`
• `` -> `...26`
• `` -> `...27`
• `` -> `...28`
• `` -> `...29`
• `` -> `...30`
• `` -> `...31`
• `` -> `...32`
• `` -> `...33`
• `` -> `...34`
• `` -> `...35`
• `` -> `...36`
• `` -> `...37`
• `` -> `...38`
• `` -> `...39`
• `` -> `...40`
• `` -> `...41`
Inspect data variables. Data file appears to contain all requested elements - except for Academically at-risk.
[1] "Student ID*"
[2] "Student Grade Level*"
[3] "School Name*"
[4] "Treatment*"
[5] "MOY MAP Math Composite Scaled Score*"
[6] "MOY MAP Math Math Test Date*"
[7] "EOY MAP Math Composite Scaled Score*"
[8] "EOY MAP Math Math Test Date*"
[9] "MOY MAP ELA Composite Scaled Score*"
[10] "MOY MAP ELA Test Date*"
[11] "EOY MAP ELA Composite Scaled Score*"
[12] "EOY MAP ELA Test Date*"
[13] "Gender"
[14] "IEP"
[15] "ELL"
[16] "FRPL"
[17] "Race/Ethnicity"
[18] "...18"
[19] "...19"
[20] "...20"
[21] "...21"
[22] "...22"
[23] "...23"
[24] "...24"
[25] "...25"
[26] "...26"
[27] "...27"
[28] "...28"
[29] "...29"
[30] "...30"
[31] "...31"
[32] "...32"
[33] "...33"
[34] "...34"
[35] "...35"
[36] "...36"
[37] "...37"
[38] "...38"
[39] "...39"
[40] "...40"
[41] "...41"
Change column headers for roster.
roster = roster [, - c (18 : 41 )] #drop empty columns
roster = roster %>%
rename (
student_id = "Student ID*" ,
grade = "Student Grade Level*" ,
school = "School Name*" ,
treat = "Treatment*" ,
moy_math_comp = "MOY MAP Math Composite Scaled Score*" ,
moy_math_date = "MOY MAP Math Math Test Date*" ,
moy_ela_comp = "MOY MAP ELA Composite Scaled Score*" ,
moy_ela_date = "MOY MAP ELA Test Date*" ,
eoy_math_comp = "EOY MAP Math Composite Scaled Score*" ,
eoy_math_date = "EOY MAP Math Math Test Date*" ,
eoy_ela_comp = "EOY MAP ELA Composite Scaled Score*" ,
eoy_ela_date = "EOY MAP ELA Test Date*" ,
gender = "Gender" ,
ell = "ELL" ,
frpl = "FRPL" ,
race_eth = "Race/Ethnicity" ,
iep = "IEP"
)
colnames (roster)
[1] "student_id" "grade" "school" "treat"
[5] "moy_math_comp" "moy_math_date" "eoy_math_comp" "eoy_math_date"
[9] "moy_ela_comp" "moy_ela_date" "eoy_ela_comp" "eoy_ela_date"
[13] "gender" "iep" "ell" "frpl"
[17] "race_eth"
Inspect schools.
Aldan Elementary School Ardmore Avenue Elementary School
117 311
Bell Avenue Elementary School Colwyn Elementary School
128 72
East Lansdowne Elementary School Park Lane Elementary School
155 170
W.B. Evans Elementary School Walnut Street Elementary School
179 147
Create school_num for each school.
roster = roster %>%
mutate (school_num = recode (school,
"Aldan Elementary School" = 1 ,
"Ardmore Avenue Elementary School" = 2 ,
"Bell Avenue Elementary School" = 3 ,
"Colwyn Elementary School" = 4 ,
"East Lansdowne Elementary School" = 5 ,
"Park Lane Elementary School" = 6 ,
"W.B. Evans Elementary School" = 7 ,
"Walnut Street Elementary School" = 8 ,
))
table (roster$ school_num)
1 2 3 4 5 6 7 8
117 311 128 72 155 170 179 147
Inspect duplicates
Identify duplicate student records within roster file. No duplicates to report.
# frequency table of student_id occurrences
id_counts_roster = roster %>%
group_by (student_id) %>%
summarise (n = n ()) %>%
arrange (desc (n))
# filter only IDs that appear > 1
dupes_roster = id_counts_roster %>%
filter (n > 1 )
# view results
print (dupes_roster)
# A tibble: 0 × 2
# ℹ 2 variables: student_id <dbl>, n <int>
#export - NA: no duplicates to report
#write.csv(dupes_roster, file = "duplicate_ids_roster_vt_penn.csv", row.names = FALSE)
6/29/2026 Notes: Archive code. // Decision: keep first occurrence of student ID based on current row order.
roster_unique = roster
#roster_unique = roster %>%
#distinct(student_id, .keep_all = TRUE)
#print(roster_unique)
Merge Usage Data to District Data
Clean Penn Usage Data
Import roster from VT that details which students have accounts in their system.
#import ID crosswalk from VT
vt_roster = read_csv ("vt_roster_all_districts.csv" )
Rows: 1496 Columns: 4
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): District, School
dbl (2): District ID, Varsity Tutors ID
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# rename vars
vt_roster = vt_roster %>%
rename (
district_vt = "District" ,
school_vt = "School" ,
student_id = "District ID" ,
vt_acct_id = "Varsity Tutors ID"
)
Import usage data from VT.
#import penn usage data from VT
vt_usage_penn = read_csv ("vt_usage_penn.csv" )
Rows: 1648 Columns: 10
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (3): Sch_name*, Sch_BOY_date*, Sch_EOY_date
dbl (7): Stu_ID_VT*, Num_days_active*, Num_sessions_AI, Num_sessions_human, ...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
[1] "Sch_name*" "Sch_BOY_date*"
[3] "Sch_EOY_date" "Stu_ID_VT*"
[5] "Num_days_active*" "Num_sessions_AI"
[7] "Num_sessions_human" "Num_classesviewed"
[9] "Num_essaysreviewed" "Num_AI_practiceproblemsessions"
Rename columns in usage file. Prepare for merging.
# rename vars
vt_usage_penn = vt_usage_penn %>%
rename (
school_vt = "Sch_name*" ,
school_BOY_date = "Sch_BOY_date*" ,
school_EOY_date = "Sch_EOY_date" ,
vt_acct_id = "Stu_ID_VT*" ,
num_days_active = "Num_days_active*" ,
num_sessions_AI = "Num_sessions_AI" ,
num_sessions_human = "Num_sessions_human" ,
num_classesviewed = "Num_classesviewed" ,
num_essaysreviewed = "Num_essaysreviewed" ,
num_AI_practiceproblemsessions = "Num_AI_practiceproblemsessions"
)
print (vt_usage_penn)
# A tibble: 1,648 × 10
school_vt school_BOY_date school_EOY_date vt_acct_id num_days_active
<chr> <chr> <chr> <dbl> <dbl>
1 Bell Avenue Eleme… 1/5/26 5/24/26 13134752 3
2 Bell Avenue Eleme… 1/5/26 5/24/26 13183824 15
3 Bell Avenue Eleme… 1/5/26 5/24/26 13183824 15
4 Bell Avenue Eleme… 1/5/26 5/24/26 13155212 8
5 Bell Avenue Eleme… 1/5/26 5/24/26 13193600 16
6 Bell Avenue Eleme… 1/5/26 5/24/26 13154761 9
7 Bell Avenue Eleme… 1/5/26 5/24/26 13155203 NA
8 Bell Avenue Eleme… 1/5/26 5/24/26 13130626 3
9 Bell Avenue Eleme… 1/5/26 5/24/26 13155895 16
10 Bell Avenue Eleme… 1/5/26 5/24/26 13152761 12
# ℹ 1,638 more rows
# ℹ 5 more variables: num_sessions_AI <dbl>, num_sessions_human <dbl>,
# num_classesviewed <dbl>, num_essaysreviewed <dbl>,
# num_AI_practiceproblemsessions <dbl>
Identify duplicate student records within usage data file (N=280).
# frequency table of vt_acct_id occurrences
id_counts_vt_usage_penn = vt_usage_penn %>%
group_by (vt_acct_id) %>%
summarise (n = n ()) %>%
arrange (desc (n))
# filter only acct that appear > 1
dupes_vt_usage_penn = id_counts_vt_usage_penn %>%
filter (n > 1 )
# view results
print (dupes_vt_usage_penn)
# A tibble: 280 × 2
vt_acct_id n
<dbl> <int>
1 NA 200
2 13143440 15
3 13143446 14
4 13144034 14
5 13143434 13
6 13143436 13
7 13143370 11
8 13143438 11
9 13130599 10
10 13143445 10
# ℹ 270 more rows
#export
write.csv (dupes_vt_usage_penn, file = "duplicate_acct_ids_usage_vt_penn.csv" , row.names = FALSE )
Inspect duplicate student records in usage file (based on VT Account ID - not student ID). Observation : for a given duplicate student record, school name,num_days_active, BOY date and EOY date will match but usage data differs across the duplicate records.
dup_preview_usage_penn = vt_usage_penn %>%
group_by (vt_acct_id) %>%
filter (n () > 1 ) %>%
ungroup ()
print (dup_preview_usage_penn)
# A tibble: 1,572 × 10
school_vt school_BOY_date school_EOY_date vt_acct_id num_days_active
<chr> <chr> <chr> <dbl> <dbl>
1 Bell Avenue Eleme… 1/5/26 5/24/26 13134752 3
2 Bell Avenue Eleme… 1/5/26 5/24/26 13183824 15
3 Bell Avenue Eleme… 1/5/26 5/24/26 13183824 15
4 Bell Avenue Eleme… 1/5/26 5/24/26 13155212 8
5 Bell Avenue Eleme… 1/5/26 5/24/26 13193600 16
6 Bell Avenue Eleme… 1/5/26 5/24/26 13154761 9
7 Bell Avenue Eleme… 1/5/26 5/24/26 13130626 3
8 Bell Avenue Eleme… 1/5/26 5/24/26 13155895 16
9 Bell Avenue Eleme… 1/5/26 5/24/26 13152761 12
10 Bell Avenue Eleme… 1/5/26 5/24/26 13130597 4
# ℹ 1,562 more rows
# ℹ 5 more variables: num_sessions_AI <dbl>, num_sessions_human <dbl>,
# num_classesviewed <dbl>, num_essaysreviewed <dbl>,
# num_AI_practiceproblemsessions <dbl>
Decision : Collapse rows for each student ID. Per VT, these instances are rows that only capture one of the students multiple days active and need to be compressed.
Basic check of randomly selected student, 13130591. Inspect sum of Num_sessions_AI before collapsing.
sum (vt_usage_penn$ num_sessions_AI[vt_usage_penn$ vt_acct_id == 13130591 ], na.rm = TRUE )
Collapse rows.
vt_usage_penn_collapsed = vt_usage_penn %>%
group_by (vt_acct_id) %>%
summarise (
num_sessions_AI = sum (num_sessions_AI, na.rm = TRUE ),
num_sessions_human = sum (num_sessions_human, na.rm = TRUE ),
num_classesviewed = sum (num_classesviewed, na.rm = TRUE ),
num_essaysreviewed = sum (num_essaysreviewed, na.rm = TRUE ),
num_AI_practiceproblemsessions = sum (num_AI_practiceproblemsessions, na.rm = TRUE ),
# keep other columns (take first non-NA value)
across (- c (num_sessions_AI,
num_sessions_human,
num_classesviewed,
num_essaysreviewed,
num_AI_practiceproblemsessions),
~ first (.x[! is.na (.x)])),
.groups = "drop"
)
print (vt_usage_penn_collapsed)
# A tibble: 356 × 10
vt_acct_id num_sessions_AI num_sessions_human num_classesviewed
<dbl> <dbl> <dbl> <dbl>
1 13125602 4 0 4
2 13125757 0 2 2
3 13126268 0 6 6
4 13127796 0 7 7
5 13130564 0 0 0
6 13130590 0 0 0
7 13130591 6 3 9
8 13130592 0 7 7
9 13130594 0 3 3
10 13130595 3 3 6
# ℹ 346 more rows
# ℹ 6 more variables: num_essaysreviewed <dbl>,
# num_AI_practiceproblemsessions <dbl>, school_vt <chr>,
# school_BOY_date <chr>, school_EOY_date <chr>, num_days_active <dbl>
Check number of IDs that appear more than once.
sum (table (vt_usage_penn_collapsed$ vt_acct_id) > 1 )
Basic check of randomly selected student, 13130591. Inspect sum of Num_sessions_AI after collapsing.
sum (vt_usage_penn_collapsed$ num_sessions_AI[vt_usage_penn_collapsed$ vt_acct_id == 13130591 ], na.rm = TRUE )
Append student ID from crosswalk file to usage file.
vt_usage_penn_collapsed = vt_usage_penn_collapsed %>%
left_join (vt_roster %>% select (vt_acct_id, student_id),
by = "vt_acct_id" )
print (vt_usage_penn_collapsed)
# A tibble: 356 × 11
vt_acct_id num_sessions_AI num_sessions_human num_classesviewed
<dbl> <dbl> <dbl> <dbl>
1 13125602 4 0 4
2 13125757 0 2 2
3 13126268 0 6 6
4 13127796 0 7 7
5 13130564 0 0 0
6 13130590 0 0 0
7 13130591 6 3 9
8 13130592 0 7 7
9 13130594 0 3 3
10 13130595 3 3 6
# ℹ 346 more rows
# ℹ 7 more variables: num_essaysreviewed <dbl>,
# num_AI_practiceproblemsessions <dbl>, school_vt <chr>,
# school_BOY_date <chr>, school_EOY_date <chr>, num_days_active <dbl>,
# student_id <dbl>
##Merge usage data to Kent student data
merge_vt_penn_usage = roster_unique %>%
left_join (vt_usage_penn_collapsed, by = "student_id" )
print (merge_vt_penn_usage)
# A tibble: 1,279 × 28
student_id grade school treat moy_math_comp moy_math_date eoy_math_comp
<dbl> <dbl> <chr> <dbl> <dbl> <chr> <dbl>
1 203802 6 Colwyn Elem… 1 186 1/9/2026 190
2 203862 6 Colwyn Elem… 1 223 1/8/2026 242
3 204106 6 Colwyn Elem… 1 230 1/13/2026 226
4 204166 6 Colwyn Elem… 1 210 1/8/2026 214
5 204211 6 Colwyn Elem… 1 181 1/8/2026 195
6 204755 5 Colwyn Elem… 1 226 1/6/2026 234
7 204759 5 Colwyn Elem… 1 205 1/6/2026 211
8 204769 5 Colwyn Elem… 1 209 1/6/2026 214
9 204787 5 Colwyn Elem… 1 170 1/6/2026 173
10 204843 5 Colwyn Elem… 1 213 1/6/2026 221
# ℹ 1,269 more rows
# ℹ 21 more variables: eoy_math_date <chr>, moy_ela_comp <dbl>,
# moy_ela_date <chr>, eoy_ela_comp <dbl>, eoy_ela_date <chr>, gender <chr>,
# iep <chr>, ell <chr>, frpl <chr>, race_eth <chr>, school_num <dbl>,
# vt_acct_id <dbl>, num_sessions_AI <dbl>, num_sessions_human <dbl>,
# num_classesviewed <dbl>, num_essaysreviewed <dbl>,
# num_AI_practiceproblemsessions <dbl>, school_vt <chr>, …
Check for unmatched students.
merge_vt_penn_usage %>% filter (is.na (student_id)) # no NAs; no unmatched students.
# A tibble: 0 × 28
# ℹ 28 variables: student_id <dbl>, grade <dbl>, school <chr>, treat <dbl>,
# moy_math_comp <dbl>, moy_math_date <chr>, eoy_math_comp <dbl>,
# eoy_math_date <chr>, moy_ela_comp <dbl>, moy_ela_date <chr>,
# eoy_ela_comp <dbl>, eoy_ela_date <chr>, gender <chr>, iep <chr>, ell <chr>,
# frpl <chr>, race_eth <chr>, school_num <dbl>, vt_acct_id <dbl>,
# num_sessions_AI <dbl>, num_sessions_human <dbl>, num_classesviewed <dbl>,
# num_essaysreviewed <dbl>, num_AI_practiceproblemsessions <dbl>, …
Add VT Acct indicator. Equals 1 if student has VT account ID (N=353). All else equals 0.
merge_vt_penn_usage$ vt_present = ifelse (is.na (merge_vt_penn_usage$ vt_acct_id), 0 , 1 )
table (merge_vt_penn_usage$ vt_present)
Create treatment status
Step 1. Construct num_weeks.
Append start date and end date following district schedule.
merge_vt_penn_usage = merge_vt_penn_usage %>%
mutate (
#start of implementation, per usage file
SOI_date = as.Date ("2026-01-05" ),
#end of implementation Mon 5/25/2026 (last school day before EOY testing week)
EOI_date = as.Date ("2026-05-25" )
)
#transform other dates as.dates
merge_vt_penn_usage = merge_vt_penn_usage %>%
mutate (
SOI_date = as.Date (SOI_date,format = "%m/%d/%Y" ),
school_BOY_date = as.Date (school_BOY_date, format = "%m/%d/%Y" ),
school_EOY_date = as.Date (school_EOY_date, format = "%m/%d/%Y" ),
eoy_math_date = as.Date (eoy_math_date, format = "%m/%d/%Y" ),
moy_math_date = as.Date (moy_math_date, format = "%m/%d/%Y" ),
eoy_ela_date = as.Date (eoy_ela_date, format = "%m/%d/%Y" ),
moy_ela_date = as.Date (moy_ela_date, format = "%m/%d/%Y" )
)
Take time difference between start and end of implementation.
merge_vt_penn_usage = merge_vt_penn_usage %>%
mutate (
num_weeks = as.numeric (difftime (EOI_date, SOI_date, units = "weeks" )))
#round to the nearest whole week
merge_vt_penn_usage$ num_weeks = round (merge_vt_penn_usage$ num_weeks)
#subtract 1 week for spring break
merge_vt_penn_usage$ num_weeks = merge_vt_penn_usage$ num_weeks - 1
#inspect avg
mean (merge_vt_penn_usage$ num_weeks, na.rm = TRUE )
Step 2. Create num_days_active_week for number of days active per week per student.
Inspect num_days_active_week .
merge_vt_penn_usage = merge_vt_penn_usage %>%
mutate (num_days_active_week = num_days_active / num_weeks)
summary (merge_vt_penn_usage$ num_days_active_week, na.rm = TRUE )
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
0.0526 0.1053 0.2105 0.2709 0.3684 1.1053 946
sd (merge_vt_penn_usage$ num_days_active_week, na.rm = TRUE )
Number of students where num_days_active_week equal to or greater than 3.
sum (merge_vt_penn_usage$ num_days_active_week >= 3 , na.rm = TRUE )
REVISIT the above. Avg days active per week is low!
Statistical summary for num_days_active .
summary (merge_vt_penn_usage$ num_days_active, na.rm = TRUE )
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
1.000 2.000 4.000 5.147 7.000 21.000 946
sd (merge_vt_penn_usage$ num_days_active, na.rm = TRUE )
Num_days_active, by school.
school_percentiles = merge_vt_penn_usage %>%
group_by (school) %>%
summarise (
p25 = round (quantile (num_days_active_week, 0.25 , na.rm = TRUE ), 2 ),
p50 = round (quantile (num_days_active_week, 0.50 , na.rm = TRUE ), 2 ),
p75 = round (quantile (num_days_active_week, 0.75 , na.rm = TRUE ), 2 ),
.groups = "drop"
)
print (school_percentiles)
# A tibble: 8 × 4
school p25 p50 p75
<chr> <dbl> <dbl> <dbl>
1 Aldan Elementary School 0.05 0.16 0.32
2 Ardmore Avenue Elementary School NA NA NA
3 Bell Avenue Elementary School 0.16 0.32 0.47
4 Colwyn Elementary School 0.16 0.32 0.42
5 East Lansdowne Elementary School NA NA NA
6 Park Lane Elementary School NA NA NA
7 W.B. Evans Elementary School NA NA NA
8 Walnut Street Elementary School 0.05 0.11 0.21
write.csv (school_percentiles, file = "vt_penn_num_days_active_week_summary_by_school.csv" , row.names = FALSE )
Step 3. Filter out students who have less than 3 sessions (N = 1279 to 926). They are not part of the final sample group and should not be used as comparison students. Among students where vt_present = 1, exclude those where num_session is less than 3.
7/6/2026: Since no students meet the threshold for usage, this will impact the data summaries that follow.
merge_vt_penn_usage_final = merge_vt_penn_usage %>%
# keep those who don't appear in usage OR students that meet usage threshold.
filter (vt_present != 1 | num_days_active_week >= 3 )
print (merge_vt_penn_usage_final)
# A tibble: 926 × 33
student_id grade school treat moy_math_comp moy_math_date eoy_math_comp
<dbl> <dbl> <chr> <dbl> <dbl> <date> <dbl>
1 210383 3 Colwyn Elem… 0 184 2026-03-16 182
2 203634 6 Aldan Eleme… 0 202 2026-01-08 199
3 203637 6 Aldan Eleme… 0 186 2026-01-12 196
4 203638 6 Aldan Eleme… 0 206 2026-01-08 212
5 203867 6 Aldan Eleme… 0 NA NA NA
6 203925 6 Aldan Eleme… 0 NA NA NA
7 203945 6 Aldan Eleme… 0 164 2026-01-12 173
8 204653 4 Aldan Eleme… 0 NA NA NA
9 204685 5 Aldan Eleme… 0 NA NA NA
10 204739 5 Aldan Eleme… 0 205 2026-01-07 204
# ℹ 916 more rows
# ℹ 26 more variables: eoy_math_date <date>, moy_ela_comp <dbl>,
# moy_ela_date <date>, eoy_ela_comp <dbl>, eoy_ela_date <date>, gender <chr>,
# iep <chr>, ell <chr>, frpl <chr>, race_eth <chr>, school_num <dbl>,
# vt_acct_id <dbl>, num_sessions_AI <dbl>, num_sessions_human <dbl>,
# num_classesviewed <dbl>, num_essaysreviewed <dbl>,
# num_AI_practiceproblemsessions <dbl>, school_vt <chr>, …
Step 4. Create treat: Equals 1 if students had more than 2 sessions. All else equals 0.
merge_vt_penn_usage_final = merge_vt_penn_usage_final %>%
mutate (treat = ifelse (! is.na (num_days_active) & num_days_active_week >= 3 , 1 , 0 ))
table (merge_vt_penn_usage_final$ treat)
Inspect Merged Data
Missing Data Summary
Percentage missing for each variable. Observations: All other demographic info is present for all students. Some missing data for test info.
missing_summary = merge_vt_penn_usage_final %>%
summarise (across (
everything (),
~ mean (is.na (.)) * 100
)) %>%
pivot_longer (
cols = everything (),
names_to = "variable" ,
values_to = "percent_missing"
) %>%
arrange (desc (percent_missing))
print (missing_summary)
# A tibble: 33 × 2
variable percent_missing
<chr> <dbl>
1 vt_acct_id 100
2 num_sessions_AI 100
3 num_sessions_human 100
4 num_classesviewed 100
5 num_essaysreviewed 100
6 num_AI_practiceproblemsessions 100
7 school_vt 100
8 school_BOY_date 100
9 school_EOY_date 100
10 num_days_active 100
# ℹ 23 more rows
Percentage missing for each variable, grouped by treatment. Exported.
missing_summary_by_treat = merge_vt_penn_usage_final %>%
group_by (treat) %>%
summarise (across (
everything (),
~ mean (is.na (.)) * 100 ,
.names = "{.col}"
)) %>%
pivot_longer (
cols = - treat,
names_to = "variable" ,
values_to = "percent_missing"
) %>%
arrange (desc (percent_missing))
print (missing_summary_by_treat)
# A tibble: 32 × 3
treat variable percent_missing
<dbl> <chr> <dbl>
1 0 vt_acct_id 100
2 0 num_sessions_AI 100
3 0 num_sessions_human 100
4 0 num_classesviewed 100
5 0 num_essaysreviewed 100
6 0 num_AI_practiceproblemsessions 100
7 0 school_vt 100
8 0 school_BOY_date 100
9 0 school_EOY_date 100
10 0 num_days_active 100
# ℹ 22 more rows
#export
write.csv (missing_summary_by_treat, file = "vt_penn_pct_missing_vars_by_treat.csv" )
Percentage of student who have both MOY and EOY data overall
#indicator for having BOTH MOY and EOY scores
merge_vt_penn_test_info = merge_vt_penn_usage_final %>%
mutate (
both_math = ! is.na (moy_math_comp) & ! is.na (eoy_math_comp),
both_ela = ! is.na (moy_ela_comp) & ! is.na (eoy_ela_comp),
all_tests = both_math & both_ela
)
#pct across schools
overall_pct = merge_vt_penn_test_info %>%
summarise (
total_students = n (),
students_with_all_tests = sum (all_tests),
percent_with_all_tests = (students_with_all_tests / total_students) * 100
) %>%
mutate (percent_with_all_tests = round (percent_with_all_tests, 2 ))
print (overall_pct)
# A tibble: 1 × 3
total_students students_with_all_tests percent_with_all_tests
<int> <int> <dbl>
1 926 848 91.6
Percentage of student who have both MOY and EOY data by school
by_school_pct = merge_vt_penn_test_info %>%
group_by (school) %>%
summarise (
total_students = n (),
students_with_all_tests = sum (all_tests),
percent_with_all_tests = (students_with_all_tests / total_students) * 100
) %>%
mutate (percent_with_all_tests = round (percent_with_all_tests, 2 )) %>%
arrange (desc (percent_with_all_tests))
print (by_school_pct)
# A tibble: 8 × 4
school total_students students_with_all_te…¹ percent_with_all_tests
<chr> <int> <int> <dbl>
1 Colwyn Elementar… 1 1 100
2 East Lansdowne E… 155 148 95.5
3 Park Lane Elemen… 170 161 94.7
4 W.B. Evans Eleme… 179 166 92.7
5 Ardmore Avenue E… 311 284 91.3
6 Walnut Street El… 65 57 87.7
7 Bell Avenue Elem… 13 9 69.2
8 Aldan Elementary… 32 22 68.8
# ℹ abbreviated name: ¹students_with_all_tests
Percentage of student who have both MOY and EOY MATH data by treatment group, per school. Exported.
both_math_by_school_treat = merge_vt_penn_test_info %>%
group_by (school, treat) %>%
summarise (
n = n (),
n_both_math = sum (both_math == TRUE , na.rm = TRUE ),
pct_both_math = (n_both_math / n) * 100 ,
.groups = "drop"
)
print (both_math_by_school_treat)
# A tibble: 8 × 5
school treat n n_both_math pct_both_math
<chr> <dbl> <int> <int> <dbl>
1 Aldan Elementary School 0 32 22 68.8
2 Ardmore Avenue Elementary School 0 311 289 92.9
3 Bell Avenue Elementary School 0 13 9 69.2
4 Colwyn Elementary School 0 1 1 100
5 East Lansdowne Elementary School 0 155 148 95.5
6 Park Lane Elementary School 0 170 163 95.9
7 W.B. Evans Elementary School 0 179 166 92.7
8 Walnut Street Elementary School 0 65 57 87.7
#export
write.csv (both_math_by_school_treat, file = "vt_penn_pct_both_math_by_school_treat.csv" , row.names = FALSE )
Percentage of student who have both MOY and EOY ELA data by treatment group, per school. Exported.
both_ela_by_school_treat = merge_vt_penn_test_info %>%
group_by (school, treat) %>%
summarise (
n = n (),
n_both_ela = sum (both_ela == TRUE , na.rm = TRUE ),
pct_both_ela = (n_both_ela / n) * 100 ,
.groups = "drop"
)
print (both_ela_by_school_treat)
# A tibble: 8 × 5
school treat n n_both_ela pct_both_ela
<chr> <dbl> <int> <int> <dbl>
1 Aldan Elementary School 0 32 22 68.8
2 Ardmore Avenue Elementary School 0 311 284 91.3
3 Bell Avenue Elementary School 0 13 10 76.9
4 Colwyn Elementary School 0 1 1 100
5 East Lansdowne Elementary School 0 155 148 95.5
6 Park Lane Elementary School 0 170 161 94.7
7 W.B. Evans Elementary School 0 179 166 92.7
8 Walnut Street Elementary School 0 65 57 87.7
#export
write.csv (both_ela_by_school_treat, file = "vt_penn_pct_both_ela_by_school_treat.csv" , row.names = FALSE )
Dummies for test info
both_math : dummy for students with complete math test info across MOY and EOY.
table (merge_vt_penn_test_info$ both_math)
#complete math: MOY and EOY present
merge_vt_penn_test_info$ both_math = ifelse (merge_vt_penn_test_info$ both_math, 1 , 0 )
table (merge_vt_penn_test_info$ both_math) #check counts
both_ela : dummy for students with complete ELA test info across MOY and EOY..
table (merge_vt_penn_test_info$ both_ela)
#complete ELA: MOY and EOY present
merge_vt_penn_test_info$ both_ela = ifelse (merge_vt_penn_test_info$ both_ela, 1 , 0 )
table (merge_vt_penn_test_info$ both_ela) #check counts
all_tests : dummy for students with both math and ELA test info complete across MOY and EOY.
table (merge_vt_penn_test_info$ all_tests)
#complete math and ELA: MOY and EOY present
merge_vt_penn_test_info$ all_tests = ifelse (merge_vt_penn_test_info$ all_tests, 1 , 0 )
table (merge_vt_penn_test_info$ all_tests) #check counts
math_moy_missing_eoy_present : dummy for students who have EOY math test info but MOY test info is missing.
merge_vt_penn_test_info = merge_vt_penn_test_info %>%
mutate (
moy_math_present = ! is.na (moy_math_comp),
eoy_math_present = ! is.na (eoy_math_comp),
math_moy_missing_eoy_present = case_when (
! moy_math_present & eoy_math_present ~ 1 , # moy missing, eoy present
moy_math_present & eoy_math_present ~ 0 , # both present
! moy_math_present & ! eoy_math_present ~ NA_real_ # both missing
)
)
table (merge_vt_penn_test_info$ math_moy_missing_eoy_present)
ela_moy_missing_eoy_present : dummy for students who have EOY ELA test info but MOY test info is missing.
merge_vt_penn_test_info = merge_vt_penn_test_info %>%
mutate (
moy_ela_present = ! is.na (moy_ela_comp),
eoy_ela_present = ! is.na (eoy_ela_comp),
ela_moy_missing_eoy_present = case_when (
! moy_ela_present & eoy_ela_present ~ 1 , # moy missing, eoy present
moy_ela_present & eoy_ela_present ~ 0 , # both present
! moy_ela_present & ! eoy_ela_present ~ NA_real_ # both missing
)
)
table (merge_vt_penn_test_info$ ela_moy_missing_eoy_present)
Test info summary
Proportion of Math test info present (MOY present and EOY present, grouped by treatment status).
summary_math_tests_present = merge_vt_penn_test_info %>%
mutate (
moy_math_present = ! is.na (moy_math_comp),
eoy_math_present = ! is.na (eoy_math_comp)
) %>%
group_by (treat) %>%
summarise (
n_students = n (),
moy_count = sum (moy_math_present),
moy_prop = mean (moy_math_present),
eoy_count = sum (eoy_math_present),
eoy_prop = mean (eoy_math_present),
.groups = "drop"
)
# View result
print (summary_math_tests_present)
# A tibble: 1 × 6
treat n_students moy_count moy_prop eoy_count eoy_prop
<dbl> <int> <int> <dbl> <int> <dbl>
1 0 926 865 0.934 887 0.958
Proportion of ELA test info present (MOY present and EOY present, grouped by treatment status).
summary_ela_tests_present = merge_vt_penn_test_info %>%
mutate (
moy_ela_present = ! is.na (moy_ela_comp),
eoy_ela_present = ! is.na (eoy_ela_comp)
) %>%
group_by (treat) %>%
summarise (
n_students = n (),
moy_count = sum (moy_ela_present),
moy_prop = mean (moy_ela_present),
eoy_count = sum (eoy_ela_present),
eoy_prop = mean (eoy_ela_present),
.groups = "drop"
)
# View result
print (summary_ela_tests_present)
# A tibble: 1 × 6
treat n_students moy_count moy_prop eoy_count eoy_prop
<dbl> <int> <int> <dbl> <int> <dbl>
1 0 926 862 0.931 881 0.951
District Data Summary
Generate school-level means for MOY ELA and Math assessment data, number of students, plus distribution across demographics.
#create function to streamline calculations
calc_pct = function (x) {
prop.table (table (x)) * 100
}
#school-level means and counts for TEST data
merge_vt_penn_test_info = merge_vt_penn_test_info %>%
mutate (across (c (moy_math_comp, #transform all scores to numeric value
moy_ela_comp,
eoy_math_comp,
eoy_ela_comp), as.numeric))
school_means = merge_vt_penn_test_info %>%
group_by (school)%>%
summarise (
n_students = n (),
mean_moy_math = mean (moy_math_comp, na.rm = TRUE ),
mean_moy_ela = mean (moy_ela_comp, na.rm = TRUE ),
mean_eoy_math = mean (eoy_math_comp, na.rm = TRUE ),
mean_eoy_ela = mean (eoy_ela_comp, na.rm = TRUE ),
)
# DEMOGRAPHICS
# race/ethnicity
race_pct = merge_vt_penn_test_info %>%
group_by (school, race_eth) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = race_eth,
values_from = pct,
names_prefix = "race_"
)
#gender
gender_pct = merge_vt_penn_test_info %>%
group_by (school, gender) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = gender,
values_from = pct,
names_prefix = "gender_"
)
#frpl
frpl_pct = merge_vt_penn_test_info %>%
group_by (school, frpl) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = frpl,
values_from = pct,
names_prefix = "frpl_"
)
#ell
ell_pct = merge_vt_penn_test_info %>%
group_by (school, ell) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = ell,
values_from = pct,
names_prefix = "ell_"
)
#iep
iep_pct = merge_vt_penn_test_info %>%
group_by (school, iep) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = iep,
values_from = pct,
names_prefix = "iep_"
)
# COMBINE summaries
school_summary = school_means %>%
left_join (race_pct, by = "school" ) %>%
left_join (gender_pct, by = "school" ) %>%
left_join (frpl_pct, by = "school" ) %>%
left_join (ell_pct, by = "school" ) %>%
left_join (iep_pct, by = "school" )
school_summary = school_summary %>%
mutate (across (where (is.numeric), ~ round (.x, 2 )))
print (school_summary)
# A tibble: 8 × 21
school n_students mean_moy_math mean_moy_ela mean_eoy_math mean_eoy_ela
<chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Aldan Elemen… 32 196. 196. 194. 194.
2 Ardmore Aven… 311 202. 194. 208. 196.
3 Bell Avenue … 13 188. 177. 196. 179
4 Colwyn Eleme… 1 184 159 182 158
5 East Lansdow… 155 206. 202. 214. 205.
6 Park Lane El… 170 202. 195. 207. 193.
7 W.B. Evans E… 179 205. 199. 213. 201.
8 Walnut Stree… 65 196. 188. 201. 190.
# ℹ 15 more variables: `race_AMERICAN INDIAN/ALASKAN` <dbl>, race_ASIAN <dbl>,
# `race_BLACK/AFRICAN AMERICAN` <dbl>, `race_WHITE/CAUCASIAN` <dbl>,
# race_HISPANIC <dbl>, `race_NATIVE HAWAIIAN` <dbl>,
# `race_MULTI RACIAL - NON HISPANIC` <dbl>, gender_F <dbl>, gender_M <dbl>,
# frpl_0 <dbl>, frpl_Y <dbl>, ell_N <dbl>, ell_Y <dbl>, iep_N <dbl>,
# iep_Y <dbl>
write.csv (school_summary, "vt_penn_overall_district_data_summary_by_school.csv" , row.names = FALSE )
Treatment students only : Export school-level means for MOY ELA and Math assessment data, number of students, percentages of students by race/ethnicity, gender, iep, and FRPL-status.
#school-level means and counts for TEST data
school_means_treat = merge_vt_penn_test_info %>%
filter (treat == 1 ) %>%
group_by (school) %>%
summarise (
n_students = n (),
mean_moy_math = mean (moy_math_comp, na.rm = TRUE ),
mean_moy_ela = mean (moy_ela_comp, na.rm = TRUE )
)
# DEMOGRAPHICS
# race/ethnicity
race_pct_treat = merge_vt_penn_test_info %>%
filter (treat == 1 ) %>%
group_by (school, race_eth) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = race_eth,
values_from = pct,
names_prefix = "race_"
)
#gender
gender_pct_treat = merge_vt_penn_test_info %>%
filter (treat == 1 ) %>%
group_by (school, gender) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = gender,
values_from = pct,
names_prefix = "gender_"
)
#free reduced price lunch
frpl_pct_treat = merge_vt_penn_test_info %>%
filter (treat == 1 ) %>%
group_by (school, frpl) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = frpl,
values_from = pct,
names_prefix = "frpl_"
)
#english language learner
ell_pct_treat = merge_vt_penn_test_info %>%
filter (treat == 1 ) %>%
group_by (school, ell) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = ell,
values_from = pct,
names_prefix = "ell_"
)
#iep
iep_pct_treat = merge_vt_penn_test_info %>%
filter (treat == 1 ) %>%
group_by (school, iep) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = iep,
values_from = pct,
names_prefix = "iep_"
)
# COMBINE summaries
school_summary_treat = school_means_treat %>%
left_join (race_pct_treat, by = "school" ) %>%
left_join (gender_pct_treat, by = "school" ) %>%
left_join (frpl_pct_treat, by = "school" ) %>%
left_join (ell_pct_treat, by = "school" ) %>%
left_join (iep_pct_treat, by = "school" )
school_summary_treat = school_summary_treat %>%
mutate (across (where (is.numeric), ~ round (.x, 2 )))
print (school_summary_treat)
# A tibble: 0 × 4
# ℹ 4 variables: school <chr>, n_students <dbl>, mean_moy_math <dbl>,
# mean_moy_ela <dbl>
write.csv (school_summary_treat, "vt_penn_district_data_summary_by_school_treat_students.csv" , row.names = FALSE )
Comparison students only : Export school-level means for MOY ELA and Math assessment data, number of students, percentages of students by race/ethnicity, gender, iep, and FRPL-status.
#school-level means and counts for TEST data
school_means_comp = merge_vt_penn_test_info %>%
filter (treat == 0 ) %>%
group_by (school) %>%
summarise (
n_students = n (),
mean_moy_math = mean (moy_math_comp, na.rm = TRUE ),
mean_moy_ela = mean (moy_ela_comp, na.rm = TRUE )
)
# DEMOGRAPHICS
# race/ethnicity
race_pct_comp = merge_vt_penn_test_info %>%
filter (treat == 0 ) %>%
group_by (school, race_eth) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = race_eth,
values_from = pct,
names_prefix = "race_"
)
#gender
gender_pct_comp = merge_vt_penn_test_info %>%
filter (treat == 0 ) %>%
group_by (school, gender) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = gender,
values_from = pct,
names_prefix = "gender_"
)
#free reduced price lunch
frpl_pct_comp = merge_vt_penn_test_info %>%
filter (treat == 0 ) %>%
group_by (school, frpl) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = frpl,
values_from = pct,
names_prefix = "frpl_"
)
#english language learner
ell_pct_comp = merge_vt_penn_test_info %>%
filter (treat == 0 ) %>%
group_by (school, ell) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = ell,
values_from = pct,
names_prefix = "ell_"
)
#iep
iep_pct_comp = merge_vt_penn_test_info %>%
filter (treat == 0 ) %>%
group_by (school, iep) %>%
summarise (n = n (), .groups = "drop" ) %>%
group_by (school) %>%
mutate (pct = (n / sum (n)) * 100 ) %>%
select (- n) %>%
pivot_wider (
names_from = iep,
values_from = pct,
names_prefix = "iep_"
)
# COMBINE summaries
school_summary_comp = school_means_comp %>%
left_join (race_pct_comp, by = "school" ) %>%
left_join (gender_pct_comp, by = "school" ) %>%
left_join (frpl_pct_comp, by = "school" ) %>%
left_join (ell_pct_comp, by = "school" ) %>%
left_join (iep_pct_comp, by = "school" )
school_summary_comp = school_summary_comp %>%
mutate (across (where (is.numeric), ~ round (.x, 2 )))
print (school_summary_comp)
# A tibble: 8 × 19
school n_students mean_moy_math mean_moy_ela race_AMERICAN INDIAN…¹ race_ASIAN
<chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Aldan… 32 196. 196. 9.38 6.25
2 Ardmo… 311 202. 194. 1.61 3.86
3 Bell … 13 188. 177. NA 7.69
4 Colwy… 1 184 159 NA NA
5 East … 155 206. 202. 0.65 2.58
6 Park … 170 202. 195. 1.18 1.76
7 W.B. … 179 205. 199. 0.56 3.35
8 Walnu… 65 196. 188. NA NA
# ℹ abbreviated name: ¹`race_AMERICAN INDIAN/ALASKAN`
# ℹ 13 more variables: `race_BLACK/AFRICAN AMERICAN` <dbl>,
# `race_WHITE/CAUCASIAN` <dbl>, race_HISPANIC <dbl>,
# `race_NATIVE HAWAIIAN` <dbl>, `race_MULTI RACIAL - NON HISPANIC` <dbl>,
# gender_F <dbl>, gender_M <dbl>, frpl_0 <dbl>, frpl_Y <dbl>, ell_N <dbl>,
# ell_Y <dbl>, iep_N <dbl>, iep_Y <dbl>
write.csv (school_summary_comp, "vt_penn_district_data_summary_by_school_comp_students.csv" , row.names = FALSE )
Attrition
Percent of students who did not take the EOY assessment, per subject, within treatment group.
summary_attrition = merge_vt_penn_test_info %>%
filter (treat == 1 ) %>%
summarise (
n_total = n (),
# Math test missing
n_missing_eoy_math = sum (is.na (eoy_math_comp)),
pct_missing_eoy_math = (n_missing_eoy_math / n_total) * 100 ,
# ELA test missing
n_missing_eoy_ela = sum (is.na (eoy_ela_comp)),
pct_missing_eoy_ela = (n_missing_eoy_ela / n_total) * 100
)
print (summary_attrition)
# A tibble: 1 × 5
n_total n_missing_eoy_math pct_missing_eoy_math n_missing_eoy_ela
<int> <int> <dbl> <int>
1 0 0 NaN 0
# ℹ 1 more variable: pct_missing_eoy_ela <dbl>
Usage Data Summary
Count of students with any usage logged (defined as num_days_active => 1) by treatment.
merge_vt_penn_test_info$ num_days_active = as.numeric (merge_vt_penn_test_info$ num_days_active)
#create new any_usage for new tabulation
summary_usage_by_treat = merge_vt_penn_test_info %>%
mutate (any_usage = ifelse (num_days_active >= 1 , 1 , 0 ))
#for treat=0 cases, any_usage will be NA
#for many treat=0 cases, recode any_usage from NA to 0.
summary_usage_by_treat$ any_usage[is.na (summary_usage_by_treat$ any_usage)] <- 0
summary_usage_by_treat = summary_usage_by_treat %>%
group_by (treat) %>%
summarise (
students_with_usage = sum (any_usage == 1 , na.rm = TRUE ),
students_no_usage = sum (any_usage == 0 , na.rm = TRUE ),
.groups = "drop"
)
print (summary_usage_by_treat)
# A tibble: 1 × 3
treat students_with_usage students_no_usage
<dbl> <int> <int>
1 0 0 926
Count of students with any usage logged (defined as num_days_active => 1) by school . Exported.
any_usage_by_school = merge_vt_penn_test_info %>%
mutate (any_usage = ifelse (num_days_active >= 1 , 1 , 0 ))
any_usage_by_school$ any_usage[is.na (any_usage_by_school$ any_usage)] <- 0
any_usage_by_school = any_usage_by_school %>%
group_by (school) %>%
summarise (
students_with_usage = sum (any_usage == 1 , na.rm = TRUE ),
students_no_usage = sum (any_usage == 0 , na.rm = TRUE ),
.groups = "drop"
)
print (any_usage_by_school)
# A tibble: 8 × 3
school students_with_usage students_no_usage
<chr> <int> <int>
1 Aldan Elementary School 0 32
2 Ardmore Avenue Elementary School 0 311
3 Bell Avenue Elementary School 0 13
4 Colwyn Elementary School 0 1
5 East Lansdowne Elementary School 0 155
6 Park Lane Elementary School 0 170
7 W.B. Evans Elementary School 0 179
8 Walnut Street Elementary School 0 65
write.csv (any_usage_by_school, file = "vt_penn_any_usage_by_school.csv" )
School-level summary of usage variables . Including Q1, Median, Mean, Q3, and SD. Exported.
usage_vars = c (
"num_sessions_AI" ,
"num_sessions_human" ,
"num_classesviewed" ,
"num_essaysreviewed" ,
"num_AI_practiceproblemsessions"
)
summary_usage_by_school = merge_vt_penn_test_info %>%
group_by (school) %>%
summarise (across (
all_of (usage_vars),
list (
Q1 = ~ quantile (., 0.25 , na.rm = TRUE ),
median = ~ median (., na.rm = TRUE ),
mean = ~ mean (., na.rm = TRUE ),
Q3 = ~ quantile (., 0.75 , na.rm = TRUE ),
sd = ~ sd (., na.rm = TRUE )
),
.names = "{.col}_{.fn}"
),
.groups = "drop" )
print (summary_usage_by_school)
# A tibble: 8 × 26
school num_sessions_AI_Q1 num_sessions_AI_median num_sessions_AI_mean
<chr> <dbl> <dbl> <dbl>
1 Aldan Elementa… NA NA NaN
2 Ardmore Avenue… NA NA NaN
3 Bell Avenue El… NA NA NaN
4 Colwyn Element… NA NA NaN
5 East Lansdowne… NA NA NaN
6 Park Lane Elem… NA NA NaN
7 W.B. Evans Ele… NA NA NaN
8 Walnut Street … NA NA NaN
# ℹ 22 more variables: num_sessions_AI_Q3 <dbl>, num_sessions_AI_sd <dbl>,
# num_sessions_human_Q1 <dbl>, num_sessions_human_median <dbl>,
# num_sessions_human_mean <dbl>, num_sessions_human_Q3 <dbl>,
# num_sessions_human_sd <dbl>, num_classesviewed_Q1 <dbl>,
# num_classesviewed_median <dbl>, num_classesviewed_mean <dbl>,
# num_classesviewed_Q3 <dbl>, num_classesviewed_sd <dbl>,
# num_essaysreviewed_Q1 <dbl>, num_essaysreviewed_median <dbl>, …
write.csv (summary_usage_by_school, file = "vt_penn_summary_usage_by_school.csv" )
Export files for e2i Coach
Save e2i file with all matched students from the merge, regardless of missing test info.
#select columns ready for e2i
vt_penn_e2i_usage_test_info = merge_vt_penn_test_info %>%
dplyr:: select (school_num,
student_id,
grade,
moy_math_comp, moy_math_date,
moy_ela_comp,moy_ela_date,
eoy_math_comp, eoy_math_date,
eoy_ela_comp, eoy_ela_date,
male, iep, ell, frpl,
treat,
race_A, race_B, race_H, race_Multi, race_NH, race_AI, race_W,
both_math, both_ela, all_tests,
math_moy_missing_eoy_present, ela_moy_missing_eoy_present,
vt_acct_id,
num_days_active,num_sessions_AI, num_sessions_human,
num_classesviewed, num_essaysreviewed, num_AI_practiceproblemsessions,
school_BOY_date, school_EOY_date,
)
#export e2i with test indicators, all matched students from the merge, regardless of missing test info (all_tests == 1 or 0)
write.csv (vt_penn_e2i_usage_test_info, file = "vt_penn_e2i_all_students.csv" , row.names = FALSE )
Save e2i file with only containing matched students from the merge with complete test info for Math.
table (vt_penn_e2i_usage_test_info$ both_math)
#subset: keep only students with complete test info
vt_penn_e2i_both_math = vt_penn_e2i_usage_test_info %>% filter (both_math == 1 )
print (vt_penn_e2i_both_math)
# A tibble: 855 × 37
school_num student_id grade moy_math_comp moy_math_date moy_ela_comp
<dbl> <dbl> <dbl> <dbl> <date> <dbl>
1 4 210383 3 184 2026-03-16 159
2 1 203634 6 202 2026-01-08 196
3 1 203637 6 186 2026-01-12 171
4 1 203638 6 206 2026-01-08 210
5 1 203945 6 164 2026-01-12 168
6 1 204739 5 205 2026-01-07 216
7 1 204916 5 216 2026-01-07 207
8 1 205020 5 214 2026-01-07 208
9 1 205568 5 195 2026-01-07 176
10 1 205662 5 214 2026-01-07 223
# ℹ 845 more rows
# ℹ 31 more variables: moy_ela_date <date>, eoy_math_comp <dbl>,
# eoy_math_date <date>, eoy_ela_comp <dbl>, eoy_ela_date <date>, male <dbl>,
# iep <dbl>, ell <dbl>, frpl <dbl>, treat <dbl>, race_A <dbl>, race_B <dbl>,
# race_H <dbl>, race_Multi <dbl>, race_NH <dbl>, race_AI <dbl>, race_W <dbl>,
# both_math <dbl>, both_ela <dbl>, all_tests <dbl>,
# math_moy_missing_eoy_present <dbl>, ela_moy_missing_eoy_present <dbl>, …
write.csv (vt_penn_e2i_both_math, file = "vt_penn_e2i_complete_math_tests.csv" )
Save e2i file with only containing matched students from the merge with complete test info for ELA.
table (vt_penn_e2i_usage_test_info$ both_ela)
#subset: keep only students with complete test info
vt_penn_e2i_both_ela = vt_penn_e2i_usage_test_info %>% filter (both_ela == 1 )
print (vt_penn_e2i_both_ela)
# A tibble: 849 × 37
school_num student_id grade moy_math_comp moy_math_date moy_ela_comp
<dbl> <dbl> <dbl> <dbl> <date> <dbl>
1 4 210383 3 184 2026-03-16 159
2 1 203634 6 202 2026-01-08 196
3 1 203637 6 186 2026-01-12 171
4 1 203638 6 206 2026-01-08 210
5 1 203945 6 164 2026-01-12 168
6 1 204739 5 205 2026-01-07 216
7 1 204916 5 216 2026-01-07 207
8 1 205020 5 214 2026-01-07 208
9 1 205568 5 195 2026-01-07 176
10 1 205662 5 214 2026-01-07 223
# ℹ 839 more rows
# ℹ 31 more variables: moy_ela_date <date>, eoy_math_comp <dbl>,
# eoy_math_date <date>, eoy_ela_comp <dbl>, eoy_ela_date <date>, male <dbl>,
# iep <dbl>, ell <dbl>, frpl <dbl>, treat <dbl>, race_A <dbl>, race_B <dbl>,
# race_H <dbl>, race_Multi <dbl>, race_NH <dbl>, race_AI <dbl>, race_W <dbl>,
# both_math <dbl>, both_ela <dbl>, all_tests <dbl>,
# math_moy_missing_eoy_present <dbl>, ela_moy_missing_eoy_present <dbl>, …
write.csv (vt_penn_e2i_both_ela, file = "vt_penn_e2i_complete_ela_tests.csv" )