Packages selected for inspecting and cleaning Varsity Tutors, district data from Kent School District.
library(tidyverse)
── 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.6/29/26 Note: Using latest roster, Kent SD_Varsity Tutors request 5-12-2026.xlxs.
roster <-read_csv("roster_vt_kent.csv")
Rows: 5634 Columns: 19
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (13): Grade, School, MOY i-Ready Math Composite Scaled Score*, MOY i-Rea...
dbl (5): Student Number, Special Ed, MLE, Low Income, LAP
lgl (1): Treatment
ℹ 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.
Inspect data variables. Data file appears to contain all requested elements. MLE, or multilingual education status, presents in place of ELL status. Low Income presents in place of Free/Reduced Price Lunch status. Sped, or Special Education, presents in place of IEP status.
colnames(roster)
[1] "Student Number"
[2] "Grade"
[3] "School"
[4] "Treatment"
[5] "MOY i-Ready Math Composite Scaled Score*"
[6] "MOY i-Ready Math Math Test Date*"
[7] "EOY i-Ready Math Composite Scaled Score*"
[8] "EOY i-Ready Math Math Test Date*"
[9] "MOY i-Ready ELA Composite Scaled Score*"
[10] "MOY i-Ready ELA Test Date*"
[11] "EOY i-Ready ELA Composite Scaled Score*"
[12] "EOY i-Ready ELA Test Date*"
[13] "Gender Code"
[14] "Special Ed"
[15] "MLE"
[16] "Low Income"
[17] "Race/Ethnicity"
[18] "LAP"
[19] "MLE Level"
Change column headers for roster.
roster = roster %>%rename(student_id ="Student Number",grade ="Grade", school ="School", treat ="Treatment", moy_math_comp ="MOY i-Ready Math Composite Scaled Score*", moy_math_date ="MOY i-Ready Math Math Test Date*", moy_ela_comp ="MOY i-Ready ELA Composite Scaled Score*", moy_ela_date ="MOY i-Ready ELA Test Date*", eoy_math_comp ="EOY i-Ready Math Composite Scaled Score*", eoy_math_date ="EOY i-Ready Math Math Test Date*", eoy_ela_comp ="EOY i-Ready ELA Composite Scaled Score*", eoy_ela_date ="EOY i-Ready ELA Test Date*", gender ="Gender Code", sped ="Special Ed", mll ="MLE", #multilingual education/multilingual learnersmll_lvl ="MLE Level",low_inc ="Low Income", race_eth ="Race/Ethnicity", low_ap ="LAP"#low academic performance )colnames(roster)
Import roster from VT that details which students have accounts in their system (cross walk file).
#import ID crosswalk from VTvt_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.
#import kent usage data from VTvt_usage_kent =read_csv("vt_usage_kent.csv")
Rows: 1792 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.
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.
# A tibble: 1,792 × 10
school_vt school_BOY_date school_EOY_date vt_acct_id num_days_active
<chr> <chr> <chr> <dbl> <dbl>
1 Meridian Middle 2/2/26 4/14/26 13151928 18
2 Meridian Middle 2/2/26 4/14/26 13151799 27
3 Meridian Middle 2/2/26 4/14/26 13151928 18
4 Meridian Middle 2/2/26 4/14/26 13151849 21
5 Meridian Middle 2/2/26 4/14/26 13151928 18
6 Meridian Middle 2/2/26 4/14/26 13152086 24
7 Meridian Middle 2/2/26 4/14/26 13151694 26
8 Meridian Middle 2/2/26 4/14/26 13152119 23
9 Meridian Middle 2/2/26 4/14/26 13151685 28
10 Meridian Middle 2/2/26 4/14/26 13152119 23
# ℹ 1,782 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, 13151681. Inspect sum of Num_sessions_AI before collapsing.
Append start date and end date following district schedule.
merge_vt_kent_usage = merge_vt_kent_usage %>%mutate(#start of implementation, per usage fileSOI_date =as.Date("2026-02-02"),#end of implementation Fri 4/10/2026 (last school day before EOY testing week)EOI_date =as.Date("2026-04-10") )#transform other dates as.datesmerge_vt_kent_usage = merge_vt_kent_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_kent_usage = merge_vt_kent_usage %>%mutate(num_weeks =as.numeric(difftime(EOI_date, SOI_date, units ="weeks")))#round to the nearest whole weekmerge_vt_kent_usage$num_weeks =round(merge_vt_kent_usage$num_weeks)#subtract 2 weeks for spring break merge_vt_kent_usage$num_weeks = merge_vt_kent_usage$num_weeks -2#inspect avgmean(merge_vt_kent_usage$num_weeks, na.rm =TRUE)
[1] 8
Step 2. Create num_days_active_week for number of days active per week per student.
# A tibble: 7 × 4
school p25 p50 p75
<chr> <dbl> <dbl> <dbl>
1 Canyon Ridge Middle NA NA NA
2 Cedar Heights Middle NA NA NA
3 Mattson Middle NA NA NA
4 Meeker Middle NA NA NA
5 Meridian Middle 2.12 2.62 3
6 Mill Creek Middle NA NA NA
7 Northwood Middle NA NA NA
Step 3. Filter out students who have less than 3 sessions (N = 5634 to 5573). 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 very few students meet the threshold for usage, this will impact the data summaries that follow.
merge_vt_kent_usage_final = merge_vt_kent_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_kent_usage_final)
# A tibble: 5,573 × 35
student_id grade school treat moy_math_comp moy_math_date eoy_math_comp
<dbl> <chr> <chr> <lgl> <chr> <date> <chr>
1 391033 07 Meridian Mi… NA 484 2026-01-06 490
2 412115 08 Meridian Mi… NA 384 2026-01-07 409
3 425285 06 Meridian Mi… NA 468 2026-01-06 <NA>
4 373185 07 Meridian Mi… NA 533 2026-01-07 549
5 394412 06 Meridian Mi… NA 571 2026-01-07 568
6 389860 07 Meridian Mi… NA 530 2026-01-07 549
7 389442 07 Meridian Mi… NA 467 2026-01-06 454
8 384402 07 Meridian Mi… NA 506 2026-01-07 496
9 382167 07 Meridian Mi… NA 481 2026-01-07 477
10 417702 06 Meridian Mi… NA 505 2026-01-06 499
# ℹ 5,563 more rows
# ℹ 28 more variables: eoy_math_date <date>, moy_ela_comp <chr>,
# moy_ela_date <date>, eoy_ela_comp <chr>, eoy_ela_date <date>, gender <chr>,
# sped <dbl>, mll <dbl>, low_inc <dbl>, race_eth <chr>, low_ap <dbl>,
# mll_lvl <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>, …
Step 4. Create treat: Equals 1 if students had more than 2 sessions. All else equals 0.
all_tests: dummy for students with both math and ELA test info complete across MOY and EOY.
table(merge_vt_kent_test_info$all_tests)
FALSE TRUE
1097 4476
#complete math and ELA: MOY and EOY presentmerge_vt_kent_test_info$all_tests =ifelse(merge_vt_kent_test_info$all_tests, 1, 0)table(merge_vt_kent_test_info$all_tests) #check counts
0 1
1097 4476
math_moy_missing_eoy_present: dummy for students who have EOY math test info but MOY test info is missing.
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.
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.
# A tibble: 7 × 26
school num_sessions_AI_Q1 num_sessions_AI_median num_sessions_AI_mean
<chr> <dbl> <dbl> <dbl>
1 Canyon Ridge M… NA NA NaN
2 Cedar Heights … NA NA NaN
3 Mattson Middle NA NA NaN
4 Meeker Middle NA NA NaN
5 Meridian Middle 28.5 32 33.8
6 Mill Creek Mid… NA NA NaN
7 Northwood Midd… 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>, …
American Indian or Alaska Native
21
Asian
1355
Black or African American
745
Hispanic/Latino
1290
Native Hawaiian or Other Pacific Islander
185
Two or More Races
506
White
1471
merge_vt_kent_test_info = merge_vt_kent_test_info %>%mutate(race_A =ifelse(race_eth =="Asian", 1, ifelse(is.na(race_eth), NA, 0)),race_B =ifelse(race_eth =="Black or African American", 1, ifelse(is.na(race_eth), NA, 0)),race_H =ifelse(race_eth =="Hispanic/Latino", 1, ifelse(is.na(race_eth), NA, 0)),race_Multi =ifelse(race_eth =="Two or More Races", 1, ifelse(is.na(race_eth), NA, 0)),race_P =ifelse(race_eth =="Native Hawaiian or Other Pacific Islander", 1, ifelse(is.na(race_eth), NA, 0)),race_W =ifelse(race_eth =="White", 1, ifelse(is.na(race_eth), NA, 0)) )table(merge_vt_kent_test_info$race_B) #quick check coding on race_B
0 1
4828 745
Export files for e2i Coach
Save e2i file with all matched students from the merge, regardless of missing test info.
#select columns ready for e2ivt_kent_e2i_usage_test_info = merge_vt_kent_test_info %>% dplyr::select(student_id, grade, school_num, 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, sped, mll, low_inc,low_ap, treat, race_A, race_B, race_H, race_Multi, race_P, 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_kent_e2i_usage_test_info, file ="vt_kent_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_kent_e2i_usage_test_info$both_math)
0 1
842 4731
#subset: keep only students with complete test info vt_kent_e2i_both_math = vt_kent_e2i_usage_test_info %>%filter(both_math ==1)print(vt_kent_e2i_both_math)
Save e2i file with only containing matched students from the merge with complete test info for ELA.
table(vt_kent_e2i_usage_test_info$both_ela)
0 1
759 4814
#subset: keep only students with complete test infovt_kent_e2i_both_ela = vt_kent_e2i_usage_test_info %>%filter(both_ela ==1)print(vt_kent_e2i_both_ela)