Project 2

Image: ©2026 TPR Education IP Holdings, LLC. All Rights Reserved. https://www.princetonreview.com/college-advice/sat-practice-questions

Introduction

This project analyzes the dataset “School Scores CSV File”. The SAT is a standardized test meant to test college applicants on Mathematics and Reading/Writing. It is used throughout universities in the United States. The dataset looks at the SAT scores among all American states from the years 2005 to 2015. It offers numerous variables to consider. This data was collected by Austin Cory Bart at Virginia Tech.

I chose this dataset due to my position as a university applicant for Fall 2027. Since the SAT is regarded as a significant aspect of college applications nationwide, I was curious to see what the indicators were for a high SAT score.

Variables

Total.Math: The average Math score of students in this state during this year. Quantitative.

Mathematics.Average.GPA: The average GPA of all students in this state during this year in Math. Note that this is just the GPA within the subject, not across all academic subjects. Quantitative.

English.Average.GPA: The average GPA of all students in this state during this year in English. Note that this is just the GPA within the subject, not across all academic subjects. Quantitative.

Foreign.Languages.Average.GPA: The average GPA of all students in this state during this year in Foreign Languages. Note that this is just the GPA within the subject, not across all academic subjects. Quantitative.

Natural.Sciences.Average.GPA: The average GPA of all students in this state during this year in the Natural Sciences. Note that this is just the GPA within the subject, not across all academic subjects.Quantitative.

Social.Sciences.History.Average.GPA: The average GPA of all students in this state during this year in Social Sciences and History. Note that this is just the GPA within the subject, not across all academic subjects. Quantitative.

Arts.Music.Average.GPA: The average GPA of all students in this state during this year in Arts/Music. Note that this is just the GPA within the subject, not across all academic subjects. Quantitative.

Year: The year of the report. Categorical.

State.Code: The two letter abbreviation of the state for the report. Categorical.

Since there were 99 variables, I had a lot of trouble narrowing down the variables I needed. I utilized a correlation plot to examine a couple variables. Many of the variables shared high correlation; I ended up focusing on the numerous school subjects and the GPA of each.

I cleaned up the dataset by substituting spaces for periods, substituting slashes for periods, and removing extraneous variable wording, all using gsub.

Research Question

What is the relationship between GPA and math SAT scores? Can GPA predict math SAT scores? Which subject GPA is the best predictor for math SAT score?

Loading libraries and dataset

#load libraries
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
library(dplyr)
library(readr)
library(ggplot2)
#load dataset
sat_data <- read_csv("/Users/chloelin/Documents/Montgomery College/FALL 2026/DATA 110/Project 2/school_scores.csv")
Rows: 577 Columns: 99
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (2): State.Code, State.Name
dbl (97): Year, Total.Math, Total.Test-takers, Total.Verbal, Academic Subjec...

ℹ 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.
#look at data
head(sat_data)
# A tibble: 6 × 99
   Year State.Code State.Name Total.Math `Total.Test-takers` Total.Verbal
  <dbl> <chr>      <chr>           <dbl>               <dbl>        <dbl>
1  2005 AL         Alabama           559                3985          567
2  2005 AK         Alaska            519                3996          523
3  2005 AZ         Arizona           530               18184          526
4  2005 AR         Arkansas          552                1600          563
5  2005 CA         California        522              186552          504
6  2005 CO         Colorado          560               11990          560
# ℹ 93 more variables: `Academic Subjects.Arts/Music.Average GPA` <dbl>,
#   `Academic Subjects.Arts/Music.Average Years` <dbl>,
#   `Academic Subjects.English.Average GPA` <dbl>,
#   `Academic Subjects.English.Average Years` <dbl>,
#   `Academic Subjects.Foreign Languages.Average GPA` <dbl>,
#   `Academic Subjects.Foreign Languages.Average Years` <dbl>,
#   `Academic Subjects.Mathematics.Average GPA` <dbl>, …

Cleaning Dataset

#substitute spaces for periods
names(sat_data) <- gsub(" ",".",names(sat_data))

#substitute / for periods
names(sat_data) <- gsub("/", ".", names(sat_data))

#get rid of "Academic.Subjects"
names(sat_data) <- gsub("Academic.Subjects.", "", names(sat_data))

head(sat_data)
# A tibble: 6 × 99
   Year State.Code State.Name Total.Math `Total.Test-takers` Total.Verbal
  <dbl> <chr>      <chr>           <dbl>               <dbl>        <dbl>
1  2005 AL         Alabama           559                3985          567
2  2005 AK         Alaska            519                3996          523
3  2005 AZ         Arizona           530               18184          526
4  2005 AR         Arkansas          552                1600          563
5  2005 CA         California        522              186552          504
6  2005 CO         Colorado          560               11990          560
# ℹ 93 more variables: Arts.Music.Average.GPA <dbl>,
#   Arts.Music.Average.Years <dbl>, English.Average.GPA <dbl>,
#   English.Average.Years <dbl>, Foreign.Languages.Average.GPA <dbl>,
#   Foreign.Languages.Average.Years <dbl>, Mathematics.Average.GPA <dbl>,
#   Mathematics.Average.Years <dbl>, Natural.Sciences.Average.GPA <dbl>,
#   Natural.Sciences.Average.Years <dbl>,
#   Social.Sciences.History.Average.GPA <dbl>, …
#keep data that would be further explored
some_sat_data <- sat_data |>
  select(Total.Math, Total.Verbal, Mathematics.Average.GPA, English.Average.GPA, Foreign.Languages.Average.GPA, Natural.Sciences.Average.GPA, Arts.Music.Average.GPA, Natural.Sciences.Average.GPA, Social.Sciences.History.Average.GPA, Year, State.Code, Gender.Female.Math, Gender.Male.Math)
head(some_sat_data)
# A tibble: 6 × 12
  Total.Math Total.Verbal Mathematics.Average.GPA English.Average.GPA
       <dbl>        <dbl>                   <dbl>               <dbl>
1        559          567                    3.41                3.53
2        519          523                    3.06                3.35
3        530          526                    3.25                3.45
4        552          563                    3.46                3.61
5        522          504                    3.05                3.32
6        560          560                    3.33                3.49
# ℹ 8 more variables: Foreign.Languages.Average.GPA <dbl>,
#   Natural.Sciences.Average.GPA <dbl>, Arts.Music.Average.GPA <dbl>,
#   Social.Sciences.History.Average.GPA <dbl>, Year <dbl>, State.Code <chr>,
#   Gender.Female.Math <dbl>, Gender.Male.Math <dbl>
str(some_sat_data)
tibble [577 × 12] (S3: tbl_df/tbl/data.frame)
 $ Total.Math                         : num [1:577] 559 519 530 552 522 560 517 502 478 498 ...
 $ Total.Verbal                       : num [1:577] 567 523 526 563 504 560 517 503 490 498 ...
 $ Mathematics.Average.GPA            : num [1:577] 3.41 3.06 3.25 3.46 3.05 3.33 3 3.07 2.91 3.07 ...
 $ English.Average.GPA                : num [1:577] 3.53 3.35 3.45 3.61 3.32 3.49 3.13 3.21 3.03 3.29 ...
 $ Foreign.Languages.Average.GPA      : num [1:577] 3.54 3.34 3.41 3.64 3.29 3.41 3.03 3.18 3.04 3.3 ...
 $ Natural.Sciences.Average.GPA       : num [1:577] 3.52 3.25 3.43 3.55 3.2 3.43 3.07 3.19 2.99 3.27 ...
 $ Arts.Music.Average.GPA             : num [1:577] 3.92 3.76 3.85 3.9 3.76 3.88 3.66 3.71 3.54 3.77 ...
 $ Social.Sciences.History.Average.GPA: num [1:577] 3.59 3.39 3.55 3.67 3.38 3.56 3.18 3.3 3.11 3.39 ...
 $ Year                               : num [1:577] 2005 2005 2005 2005 2005 ...
 $ State.Code                         : chr [1:577] "AL" "AK" "AZ" "AR" ...
 $ Gender.Female.Math                 : num [1:577] 538 505 513 536 504 546 502 486 451 484 ...
 $ Gender.Male.Math                   : num [1:577] 582 535 549 570 543 577 534 521 509 516 ...

Explore Dataset

#use correlation plot to explore correlation among the variables
library(DataExplorer)
plot_correlation(some_sat_data)
Warning in dummify(data, maxcat = maxcat): Ignored all discrete features since
`maxcat` set to 20 categories!

#select the variables that should be used
sat_data2 <- sat_data |>
  select(Total.Math, Mathematics.Average.GPA, English.Average.GPA, Foreign.Languages.Average.GPA, Natural.Sciences.Average.GPA, Social.Sciences.History.Average.GPA, Arts.Music.Average.GPA, Year, State.Code)

Create ggplot

#create scatterplot for math GPA and math SAT
p1 <- ggplot(sat_data2, aes(x = Mathematics.Average.GPA, y = Total.Math)) +
  xlab("GPA in Math Subjects") +
  ylab("Math Score on Sat") +
  geom_point() +
  ggtitle("GPA in Math Subjects VS Math Score on SAT")
p1

#add linear regression with confidence interval
p2 <- p1 + geom_smooth(method='lm',formula=y~x)
p2

#create scatterplot for english GPA and math SAT
p3 <- ggplot(sat_data2, aes(x = English.Average.GPA, y = Mathematics.Average.GPA)) +
  xlab("GPA in English Subjects") +
  ylab("GPA in Math Subjects") +
  geom_point() +
  ggtitle("GPA in English Subjects VS GPA in Math Subjects") +
  geom_smooth(method='lm',formula=y~x)
p3

#create scatterplot for reading GPA and math GPA
p4 <- ggplot(sat_data2, aes(x = English.Average.GPA, y = Total.Math)) +
  xlab("GPA in English Subjects") +
  ylab("Math Score on Sat") +
  geom_point() +
  ggtitle("GPA in English Subjects VS Math Score on SAT") +
  geom_smooth(method='lm',formula=y~x)
p4

Linear Regression

#check linear regression for math GPA
linear_math_sat <- lm(Total.Math ~ Mathematics.Average.GPA, data = sat_data2)
summary(linear_math_sat)

Call:
lm(formula = Total.Math ~ Mathematics.Average.GPA, data = sat_data2)

Residuals:
     Min       1Q   Median       3Q      Max 
-122.167   -9.790    4.551   17.214   87.031 

Coefficients:
                        Estimate Std. Error t value Pr(>|t|)    
(Intercept)              -40.285     17.361   -2.32   0.0207 *  
Mathematics.Average.GPA  173.992      5.233   33.25   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 27.03 on 575 degrees of freedom
Multiple R-squared:  0.6578,    Adjusted R-squared:  0.6572 
F-statistic:  1105 on 1 and 575 DF,  p-value: < 2.2e-16

The adjusted r-squared is 0.6572 or 65.7%, which shows that the model explains 65.7% of the SAT scores.

The p-value on the right of math GPA has 3 asterisks, which shows that it is a highly significant variable to explain the linear increase in math SAT score.

The slope is 173.992. This means that with every one point increase in math GPA, the math SAT score rises by 174 points.

#create a multiple linear regression model
linear_sat <- lm(Total.Math ~ Mathematics.Average.GPA + English.Average.GPA + Foreign.Languages.Average.GPA + Natural.Sciences.Average.GPA + Arts.Music.Average.GPA + Social.Sciences.History.Average.GPA, data = sat_data2)
summary(linear_sat)

Call:
lm(formula = Total.Math ~ Mathematics.Average.GPA + English.Average.GPA + 
    Foreign.Languages.Average.GPA + Natural.Sciences.Average.GPA + 
    Arts.Music.Average.GPA + Social.Sciences.History.Average.GPA, 
    data = sat_data2)

Residuals:
     Min       1Q   Median       3Q      Max 
-112.404   -9.495    1.123   16.179   87.040 

Coefficients:
                                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)                          -280.84      77.54  -3.622 0.000319 ***
Mathematics.Average.GPA               115.43      24.58   4.696 3.32e-06 ***
English.Average.GPA                  -116.64      35.56  -3.280 0.001100 ** 
Foreign.Languages.Average.GPA         -69.67      23.05  -3.022 0.002620 ** 
Natural.Sciences.Average.GPA           33.04      46.67   0.708 0.479278    
Arts.Music.Average.GPA                 71.82      35.74   2.009 0.044987 *  
Social.Sciences.History.Average.GPA   197.58      45.51   4.341 1.68e-05 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 25.6 on 570 degrees of freedom
Multiple R-squared:  0.6959,    Adjusted R-squared:  0.6927 
F-statistic: 217.4 on 6 and 570 DF,  p-value: < 2.2e-16

The adjusted r-squared is 0.6927 or 69.3%, which shows that the model explains 69.3% of the SAT scores. This is a slight increase from only math GPA.

Math GPA: The p-value on the right of math GPA has 3 asterisks, which shows that it is a highly significant variable to explain the linear increase in math SAT score. The slope is 115.43. This means that with every one point increase in math GPA, the math SAT score rises by 115 points.

English GPA: The p-value on the right of english GPA has 2 asterisks, which shows that it is a significant variable to explain the linear increase in math SAT score. The slope is -116.64. This means that with every one point increase in english GPA, the math SAT score falls by 117 points.

Foreign Languages GPA: The p-value on the right of foreign languages GPA has 2 asterisks, which shows that it is a significant variable to explain the linear increase in math SAT score. The slope is -69.67. This means that with every one point increase in foreign languages GPA, the math SAT score falls by 70 points.

Natural Sciences GPA: The p-value on the right of natural sciences GPA has 0 asterisks, which shows that it is not a significant variable to explain the linear increase in math SAT score. The slope is 33.04. This means that with every one point increase in natural sciences GPA, the math SAT score rises by 33 points.

Arts and Music GPA: The p-value on the right of arts and music GPA has 1 asterisk, which shows that it is a less significant variable to explain the linear increase in math SAT score. The slope is 71.82. This means that with every one point increase in arts and music GPA, the math SAT score rises by 72 points.

Social Sciences GPA: The p-value on the right of social sciences GPA has 3 asterisks, which shows that it is a highly significant variable to explain the linear increase in math SAT score. The slope is 197.58. This means that with every one point increase in math GPA, the math SAT score rises by 198 points.

The math GPA is the most statistically significant relationship with math SAT scores.

Yes, GPA in many subjects is able to predict math SAT scoring. The math GPA, as expected, is the best predictor with Social Sciences GPA being the second-best. The least significant variable is Natural Sciences GPA.

Plotly Graph

#load required packages
library(plotly)

Attaching package: 'plotly'
The following object is masked from 'package:ggplot2':

    last_plot
The following object is masked from 'package:stats':

    filter
The following object is masked from 'package:graphics':

    layout
#add caption, theme and additional colors
p2 <- p2 + 
  labs(caption = "Source: CORGIS Dataset Project") +
  aes(color = State.Code) +
  theme_minimal(base_size = 9, base_family = "serif") +
  geom_point(size = 1, alpha = 0.5)
p2

#add interactivity
pp2 <- ggplotly(p2)
pp2

The visualization shows the correlation between GPA in math Subjects and math score on the SAT. The relationship can be seen for specific states, with some exhibiting interesting behavior. For example, the regression line for Puerto Rico and District of Columbia seems to be negative, while most other states positive. Additionally, District of Columbia stands out as as a state with lower GPA and math score.

I struggled with finding a custom color palette with enough colors for all states. I also struggled with making the regression lines thinner and less opaque without provoking a glitch in the graph. I would have wanted to also allow the year to show up when hovering over a data point.

ggplot Graph

#map all state codes to their region
region_map <- c(
  "AL" = "South",     "AK" = "West",      "AZ" = "West",      "AR" = "South",
  "CA" = "West",      "CO" = "West",      "CT" = "Northeast", "DE" = "South",
  "DC" = "South",     "FL" = "South",     "GA" = "South",     "HI" = "West",
  "ID" = "West",      "IL" = "Midwest",   "IN" = "Midwest",   "IA" = "Midwest",
  "KS" = "Midwest",   "KY" = "South",     "LA" = "South",     "ME" = "Northeast",
  "MD" = "South",     "MA" = "Northeast", "MI" = "Midwest",   "MN" = "Midwest",
  "MS" = "South",     "MO" = "Midwest",   "MT" = "West",      "NE" = "Midwest",
  "NV" = "West",      "NH" = "Northeast", "NJ" = "Northeast", "NM" = "West",
  "NY" = "Northeast", "NC" = "South",     "ND" = "Midwest",   "OH" = "Midwest",
  "OK" = "South",     "OR" = "West",      "PA" = "Northeast", "RI" = "Northeast",
  "SC" = "South",     "SD" = "Midwest",   "TN" = "South",     "TX" = "South",
  "UT" = "West",      "VT" = "Northeast", "VA" = "South",     "WA" = "West",
  "WV" = "South",     "WI" = "Midwest",   "WY" = "West"
)
#add the Region column to your existing sat_data
sat_data$Region <- region_map[sat_data$State.Code]
#generate the scatter plot colored by the manual Region column
ggplot(sat_data, aes(x = `Total.Test-takers`, y = Total.Math, color = Region)) +
  geom_point(size = 4) +
  labs(
    title = "Math Scores vs. Total Test-Takers by Region (2005)",
    x = "Total Test-Takers",
    y = "Total Math Score",
    color = "US Region"
  ) +
  theme_minimal()

Bibliography

CN, P., Kouchi, B., & Kurup, M. (2026, June 9). How to use sub() and GSUB() in R | DigitalOcean. Www.Digitalocean.Com. https://www.digitalocean.com/community/tutorials/sub-and-gsub-function-r

Keita, Z. (2022, December 6). Multiple linear regression in R: Tutorial with examples. DataCamp. Datacamp.Com. https://www.datacamp.com/tutorial/multiple-linear-regression-r-tutorial

Multiple linear regression in R: LM() & interpretation. (2026, June 23). Datanovia. https://www.datanovia.com/learn/biostatistics/regression/multiple-linear-regression-in-r