A-level Maths grades

Is Minimum Target Grade (MTG) a suitable predictor for A2 grade and if so how should it be used?

Background and Terminology

The intention of this document is to analyze student data recorded between 2019 and 2025 to gain insight into whether there are any suitable predictors for overall outcome. Discerning the best predictor allows a department to set realistic, individualized target grades. Which in turn enables lecturers to monitor and assess students’ progress. Currently (and historically) the metric that is used is a “Minimum Target Grade”, in my personal opinion it should just be “Target Grade” as Minimum implies that this grade is the absolute baseline grade that a student should achieve which is not realistic.

Minimum Target Grade (MTG) and Value Added (VA)

The calculation of an MTG is based on a students general GCSE grade profile. The MTG is the national average A2 grade achieved of students with a comparable set of GCSE grades. The fact that it is the average grade achieved reinforces my objection to the use of Minimum. In statistical parlance the MTG could be referred to as an “Expected” grade. Why does the wording matter? The MTG is used throughout the 2 year course as a benchmark to track progress. Fair enough. However, after completion of the course a metric of Value Added is calculated for each course, and attempts to quantify how well a student cohort performed against their MTG’s. A student who achieves their MTG would score 0. For every student who achieves a grade higher or lower than their MTG they would score +1 or -1 per grade. For example: A student with an MTG of a B goes on to achieve an A*, this student would contribute a +2 to the VA score. The VA for the course is the average student VA score across the cohort. As a lecturer this is a troubling metric, for one, teaching a student for 2 years and helping them achieve their Minimum Target Grade yields a score of zero! From the point of view of teacher morale and self worth, the label of zero value added and Minimum grade is incredibly demoralizing and does not instill any sense of achievement. Secondly, due to each grade being worth +1 or -1 and the value added metric being an average it means that a single exceptional under-performance (or over-performance) can hugely affect the overall result. Should 1 student with poor attendance achieving “-3” negate 3 students achieving “+1” grade above their MTG? I think not. Is attendance a factor? Yes! See below. Value added is calculated per course and is combined with other metrics to yield a performance score for the year. The issue is that VA has 3x the weighting of the other metrics.

IMPORTANT NOTE!

The VA score is NOT actually calculated according to the MTG. The MTG is an institutions best internal attempt at the “expected” grade ( I believe). VA is calculated externally based on national data, post results. This is hugely problematic, since teacher and department performance is weighted heavily towards the VA score. To use an analogy, we’re throwing darts at a spinning board, only when the board stops spinning will the score be revealed. Example: Suppose in a class of 10 they all achieve exactly the MTG set by the college. Internally Value Added = 0 “Success!!”. But, the national data “expected” one of these students to achieve 1 grade higher. Therefore, external VA = -0.1. “Failure”. A fabricated example but one that highlights the very real problem of using averages and making judgments based on uncertain target grades.

The Data

The data used throughout is from A-level Maths students from the same college spanning 7 years but with only 5 years included. 2020 and 2021 results have been removed due to the impact of the Covid-19 outbreak. Results in these years were entirely or partially teacher assessed grades. This means that predictors such as GCSE grade or MTG would have been used to inform their overall grades resulting in a significant lack of independence. The full cohort has been used each year with the exception of a few students where data was missing.

Below is a glimpse at the data.

Code
library(readxl)
library(tidyverse)

grades<-read_excel("cw_maths_grades.xlsx")
# Remove columns 28-43

grades <- grades |>
  select(-c(28:43))
glimpse(grades)
Rows: 315
Columns: 27
$ Year                                   <chr> "24-25", "24-25", "24-25", "24-…
$ Person                                 <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, …
$ MTG                                    <chr> "B", "B", "A", "B", "C", "B", "…
$ `GCSE Grade`                           <chr> "8", "9", NA, "9", "7", "9", "8…
$ `Year 1\r\nAutumn\r\nEffort`           <chr> "a", "a", "b", "b", "b", "a", "…
$ `Year 1\r\nAutumn\r\nPerformance`      <chr> "A", "A", "B", "A", "B/C", "A",…
$ `Year 1\r\nSpring\r\nEffort`           <chr> "a", "a", "b", "b", "b", "a", "…
$ `Year 1\r\nSpring\r\nPerformance`      <chr> "A", "A", "B", "A", "B", "A", "…
$ `Year 2\r\nAutumn 1\r\nEffort`         <chr> "c", "a", "a", "c", "c", "b", "…
$ `Year 2\r\nAutumn 1\r\nPerformance`    <chr> "B", "A*/A", "A", "B", "B/C", "…
$ `Year 2\r\nAutumn 2\r\nEffort`         <chr> "b", "b", "b", "c", "b", "b", "…
$ `Year 2\r\nAutumn 2\r\nPerformance`    <chr> "A/B", "A*/A", "A/B", "A", "A/B…
$ `Year 2\r\nSpring 1\r\nEffort`         <chr> "b", "a", "a", "b", "a", "b", "…
$ `Year 2\r\nSpring 1\r\nPerformance`    <chr> "A/B", "A*", "A", "A", "A", "A/…
$ `Year 2\r\nSpring 2\r\nEffort`         <chr> "b", "a", "a", "b", "b", "b", "…
$ `Year 2\r\nSpring 2\r\nPerformance`    <chr> "A/B", "A*", "A", "A", "A", "A"…
$ `Overall\r\nAttendance`                <dbl> 0.8971963, 0.9099099, 0.7272727…
$ `Initial skills check`                 <dbl> NA, NA, NA, NA, NA, NA, NA, NA,…
$ `A1 Test average`                      <dbl> NA, NA, NA, NA, NA, NA, NA, NA,…
$ `A2 Test average`                      <lgl> NA, NA, NA, NA, NA, NA, NA, NA,…
$ `A1 Grade`                             <chr> "B", "A", NA, "A", "A", "A", "A…
$ `Predicted grade`                      <chr> NA, NA, NA, NA, NA, NA, NA, NA,…
$ `A2 Grade`                             <chr> "B", "A*", "B", "A", "A", "A", …
$ `Diff between MTG and A2`              <dbl> 0, 2, -1, 1, 2, 1, 0, 3, 1, -1,…
$ `Diff between A1 and A2`               <dbl> 0, 1, NA, 0, 0, 0, -1, 0, 0, 1,…
$ `Diff between predicted and A2`        <dbl> NA, NA, NA, NA, NA, NA, NA, NA,…
$ `Diff between last performance and A2` <dbl> -0.5, 0.0, -1.0, 0.0, 0.0, 0.0,…

Day 1 predictors

If we are concerned with outcomes and measurable impact it makes sense to consider predictors that are known prior to the course starting. Known metrics on day 1 are, GCSE Maths grade and the MTG generated as a result of their GCSE grade profile. I’ll begin by considering these individually as predictors for A2 achievement. The question we want to answer is: “How suitable is MTG as a predictor of A2 outcome compared to subject specific prior achievement?”.

GCSE grade

A summary of the A-level Maths cohort is shown in Table 1. For reference, the minimum entry requirement is a grade 6 with the maximum possible GCSE Maths grade being 9. We can see that there is some variation year on year but in the last two years there seems to be more stability in both the cohort size and their prior attainment.

Code
# Load required packages
library(gt)

# Create summary table with average GCSE grade by year
GCSE_summary <- grades|>
  # Convert GCSE Grade to numeric (removing any NA values)
  mutate(`GCSE Grade` = as.numeric(`GCSE Grade`)) |>
  # Remove rows where GCSE Grade is NA
  filter(!is.na(`GCSE Grade`)) |>
  # Group by Year and calculate average
  group_by(Year) |>
  summarise(
    n_students = n(),
    avg_GCSE_grade = mean(`GCSE Grade`, na.rm = TRUE),
    .groups = "drop"
  ) |>
  # Create gt table
  gt() |>
  tab_header(
    title = "Maths grade on entry",
  ) |>
  cols_label(
    Year = "Academic Year",
    n_students = "Number of Students",
    avg_GCSE_grade = "Average GCSE Grade"
  ) |>
  cols_align(
    align = "center"
  )  |>
  fmt_number(
    columns = avg_GCSE_grade,
    decimals = 2
  ) |>
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  )

GCSE_summary
Table 1: GCSE Maths
Maths grade on entry
Academic Year Number of Students Average GCSE Grade
18-19 22 7.45
21-22 57 7.95
22-23 51 8.18
23-24 90 7.79
24-25 83 7.70

To consider correlation between grades we need to align GCSE grades with MTG and A2. We have a mix of numeric and categoric. I’ll express A2 grade and MTG as numeric such that they match GCSE scales. A grade 9 will represent an A*, 8 an A and so on down to a minimum of 3 representing a U grade. This will allow all possible outcomes.

In Figure 1 we can see there is clearly a positive linear correlation between GCSE grade on entry and the A2 result they end up achieving. This is no surprise.

Code
# Create correlation plot between GCSE and A2 grades with matching scale


# Calculate correlation coefficient
correlation_coef_GCSE <- cor(grades$`GCSE Grade`, grades$A2N)

# Create the scatter plot
ggplot(grades, aes(x = `GCSE Grade`, y = A2N)) +
  geom_jitter(alpha = 0.6, width = 0.1, height = 0.1, color = "steelblue") +
  geom_smooth(method = "lm", se = TRUE, color = "red", linewidth = 1) +
  scale_x_continuous(
    name = "GCSE Grade", 
    breaks = 6:9,
    labels = c("6", "7", "8", "9")
  ) +
  scale_y_continuous(
    name = "A2 Grade",
    breaks = 3:9,
    labels = c("U", "E", "D", "C", "B", "A", "A*")
  ) +
  labs(
    subtitle = paste0("Correlation coefficient: r = ", round(correlation_coef_GCSE, 3)),
    caption = "A-level Maths students 2019-2025"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(size = 14, face = "bold"),
    axis.title = element_text(size = 12),
    panel.grid.minor = element_blank()
  )
Figure 1: Correlation Between GCSE Grade and A2 Grade

Lets now glimpse at how MTG relates to A2 grade. It should be much the same, however MTG is calculated based on average GCSE grade across all subjects.

Minimum Target Grade

We see a very similar story in Figure 2 “good” positive correlation between MTG and A2 grade. It is worth noting that the correlation coefficients are very similar. On the face of it this would suggest that MTG and GCSE grade are both good predictors of A2 grade in their own right and there is little to choose between them. It is worth investigating whether these predictors are highly correlated with each other to deduce whether they are picking up on the same features. If they both pick up on different important features then perhaps a mixed model would be appropriate.

Code
# Create correlation plot between MTG and A2 grades with matching scale
# Calculate correlation coefficient
correlation_coef_MTG <- cor(grades$MTGN, grades$A2N)

# Create the scatter plot
ggplot(grades, aes(x =MTGN, y = A2N)) +
  geom_jitter(alpha = 0.6, width = 0.1, height = 0.1, color = "steelblue") +
  geom_smooth(method = "lm", se = TRUE, color = "red", linewidth = 1) +
  scale_x_continuous(
    name = "Minimum Target Grade", 
    breaks = 4:9,
    labels = c("4","5","6", "7", "8", "9")
  ) +
  scale_y_continuous(
    name = "A2 Grade",
    breaks = 3:9,
    labels = c("U", "E", "D", "C", "B", "A", "A*")
  ) +
  labs(
    subtitle = paste0("Correlation coefficient: r = ", round(correlation_coef_MTG, 3)),
    caption = "A-level Maths students 2019-2025"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(size = 14, face = "bold"),
    axis.title = element_text(size = 12),
    panel.grid.minor = element_blank()
  )
Figure 2: Correlation Between MTG and A2 Grade
Code
# Create correlation plot between MTG and A2 grades with matching scale
# Calculate correlation coefficient
correlation_coef_MTG_GCSE <- cor(y=grades$MTGN, x=grades$`GCSE Grade`)

# Create the scatter plot
ggplot(grades, aes(x =`GCSE Grade`, y = MTGN)) +
  geom_jitter(alpha = 0.6, width = 0.1, height = 0.1, color = "steelblue") +
  geom_smooth(method = "lm", se = TRUE, color = "red", linewidth = 1) +
  scale_x_continuous(
    name = "GCSE Maths Grade", 
    breaks = 6:9,
    labels = c("6", "7", "8", "9")
  ) +
  scale_y_continuous(
    name = "Minimum Target Grade",
    breaks = 3:9,
    labels = c("U", "E", "D", "C", "B", "A", "A*")
  ) +
  labs(
    subtitle = paste0("Correlation coefficient: r = ", round(correlation_coef_MTG_GCSE, 3)),
    caption = "Maths students 2019-2025"
  ) +
  theme_minimal() +
  theme(
    plot.title = element_text(size = 14, face = "bold"),
    axis.title = element_text(size = 12),
    panel.grid.minor = element_blank()
  )
Figure 3: Correlation Between MTG and GCSE Grade

In Figure 3 we can see that they are highly correlated as we’d expect but this needs further investigation to deduce whether this is in fact because they explain the same features when predicting A2 grade. Suppose we fit linear models with GCSE grade and MTG as predictors. I’ll also include Attendance as this clearly has an impact on A2 grade and we don’t want this feature being masked by the others. Summary results are shown in Table 2. In terms of model performance there is clear improvement in the model where both MTG and GCSE grade are used to predict A2 grade. Highlighted by the increase in R-squared (proportion of variance explained)and the lower relative AIC value. This suggests that MTG adds valuable information beyond just the mathematics GCSE grade alone.

Code
# Model with GCSE only
model_gcse <- lm(A2N ~ `GCSE Grade` + `Overall\r\nAttendance`, data = grades)

# Model with MTG only  
model_mtg <- lm(A2N ~ MTGN + `Overall\r\nAttendance`, data = grades)

# Model with both
model_both <- lm(A2N ~ `GCSE Grade` + MTGN + `Overall\r\nAttendance`, data = grades)

# Compare R-squared and AIC
df_comp_model<-data.frame(
  Model = c("GCSE only", "MTG only", "Both"),
  R_squared = c(summary(model_gcse)$r.squared,
                summary(model_mtg)$r.squared,
                summary(model_both)$r.squared),
  AIC = c(AIC(model_gcse), AIC(model_mtg), AIC(model_both))
)

# Convert data frame to a gt table
table_comp_model <- df_comp_model %>%
  gt() %>%  # Create gt table object
  tab_header(
    title = "Linear model comparison",
    subtitle = "Attendance has been accounted for in each case"
  ) %>%
  fmt_number(
    columns = c(R_squared, AIC),
    decimals = 3,
  ) %>%
  cols_label(
    Model = "Model",
    R_squared = "R-squared",
    AIC = "AIC"
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels(everything())
  )

# Print the table
table_comp_model
Table 2: Predicting A2 grade
Linear model comparison
Attendance has been accounted for in each case
Model R-squared AIC
GCSE only 0.453 986.609
MTG only 0.449 988.764
Both 0.516 951.717

Alternative Achievement Classifier

The first, and simplest adjustment would be to quantify how a course has performed by judging against a fixed target. If using Value Added then calculate an internal VA score based on the the internally generated MTG’s.

In that case, why use VA at all?

Value Added as a “score” is sensitive to extreme values and gives very little information about the distribution of grades.

For example: A score of 0 in a class of 20 could be achieved in so many ways. In one class all 20 students could achieve exactly their MTG and hence VA = 0. In another, 10 could score -1 whilst the other 10 score +1, giving VA = 0. Which class/teacher has “performed” better? If we judge “achievement” as matching or exceeding MTG then Class 1 has a 100% achievement rate, whilst class 2 has only 50% achievement rate. I’d argue that this is much more meaningful statistic and one that is not susceptible to extreme values. Let’s investigate what this might look like.

Achievement rate

Now lets consider whether the students in the dataset achieved their MTG or not. We’ll classify “achievement” as getting an A2 grade greater than or equivalent to their MTG. It would be good firstly to compare these quantities between GCSE grade and MTG over the years. In Table 3 it seems that over the years MTG has been achieved more often than their GCSE equivalent grade. This supports the use of an MTG as a target.

Code
#Adjusting GCSE grades to align with MTG. MTG only up to A=8 so changing 9's to 8. 
grades<-grades|>
  mutate(
    GCSE_adj = case_when(
      `GCSE Grade` == 9 ~ 8,
      `GCSE Grade` == 8 ~ 8,
      `GCSE Grade` == 7 ~ 7,
      `GCSE Grade` == 6 ~ 6,
      TRUE ~ NA_real_
    )
  )


# Load required packages
library(gt)

# Create summary table with achievemnt proportions surpassing/equalling MTG or GCSE equivalent.
acheive_summary <- grades|>
  # Group by Year and calculate average
  group_by(Year) |>
  summarise(
    n_students = n(),
    acheive_GCSE = sum(A2N>=GCSE_adj)/n_students,
    acheive_MTG = sum(A2N>=MTGN)/n_students,
    .groups = "drop"
  ) |>
  # Create gt table
  gt() |>
  tab_header(
    title = "Acheivement proportions",
  ) |>
  cols_label(
    Year = "Academic Year",
    n_students = "Number of Students",
    acheive_GCSE = "Acheive GCSE equiv",
    acheive_MTG = "Acheive MTG",
  ) |>
  cols_align(
    align = "center"
  )  |>
  fmt_number(
    columns = c(acheive_GCSE,acheive_MTG),
    decimals = 2
  ) |>
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  )

acheive_summary
Table 3: Acheivement counted when matching or exceeding grade
Acheivement proportions
Academic Year Number of Students Acheive GCSE equiv Acheive MTG
18-19 22 0.55 0.59
21-22 57 0.56 0.70
22-23 51 0.47 0.61
23-24 90 0.37 0.57
24-25 82 0.45 0.66
Code
# Load required packages
library(gt)

# Create summary table with achievemnt proportions surpassing/equalling MTG or GCSE equivalent.
acheive_summary_GCSE <- grades|>
  # Group by Year and calculate average
  group_by(`GCSE Grade`) |>
  summarise(
    n_students = n(),
    acheive_MTG = sum(A2N>=MTGN)/n_students,
    .groups = "drop"
  ) |>
  # Create gt table
  gt() |>
  tab_header(
    title = "Acheivement proportions",
    subtitle = "True if greater than or equal to MTG"
  ) |>
  cols_label(
    `GCSE Grade` = "GCSE Maths on entry",
    n_students = "Number of Students",
    acheive_MTG = "Acheive MTG",
  ) |>
  cols_align(
    align = "center"
  )  |>
  fmt_number(
    columns = acheive_MTG,
    decimals = 2
  ) |>
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  )

acheive_summary_GCSE
Acheivement proportions
True if greater than or equal to MTG
GCSE Maths on entry Number of Students Acheive MTG
6 28 0.50
7 82 0.43
8 102 0.67
9 90 0.80

It is worth mentioning, value added was exactly zero in Year 24-25. This would suggest on average every student achieved exactly their MTG, however what the table shows is that in fact 66% of students achieved their MTG or above. Tracking achievement rate year on year gives a much better insight into performance and a definitive metric to quantify how many students have and haven’t achieved their expected grade. This is not something we can discern from VA.

There are many questions one could ask off the back of the achievement rate….”Where are those students who have underachieved?“,”High MTG students? or low MTG students?“,”Could it be due to retention/attendance?”

Code
mtg_diff_att<-ggplot(grades, aes(x = MTG, y = `Diff between MTG and A2`)) +
  geom_jitter(aes(color = `Overall\r\nAttendance`)) +
  stat_summary(fun = 'mean', geom = 'point', aes(fill = 'Mean'), 
               size = 3, shape = 21, color = 'black') +
  stat_summary(aes(group = 1, linetype = 'Mean'), fun = 'mean', geom = 'line', 
               color = 'red', linewidth = 1) +
  scale_color_viridis_c(name = "Overall\nAttendance") +
  scale_fill_manual(values = c('Mean' = 'red'), name = NULL) +
  scale_linetype_manual(values = c('Mean' = 'solid'), name = NULL) +
  guides(fill = guide_legend(override.aes = list(linetype = 0)),
         linetype = guide_legend(override.aes = list(fill = NA, color = 'red'))) +
  theme(legend.position = 'right')
mtg_diff_att
Figure 4: Does attendace affect acheivement?

In Figure 4 it is clear that those with very poor attendance do not achieve their MTG and therefore will impact value added. This is unlikely to be a fault of the teaching, therefore should 1 student with poor attendance achieving “-3” have the same weighting as 3 students achieving “+1” grade above their MTG. I think not. It is admirable and important to support every student in achieving qualifications even if unforeseen circumstances stop them from attending regularly. If a student with poor attendance does not achieve their MTG then this is actually evidence to support the positive impact a teacher has on students in their lessons. BUT, Value Added reflects this result as a disproportionate negative effect.

Logistic regression model

Let’s create a generalized linear model to predict chance of achievement (meet or exceed MTG) based on GCSE grade and attendance as predictors.

Code
grades_binary <- grades |>
  mutate(achieved_target = ifelse(A2N >= MTGN, 1, 0))

glm_achievement <- glm(achieved_target ~ `GCSE Grade` + `Overall\r\nAttendance`, 
                       data = grades_binary, family = binomial)
summary(glm_achievement)

Call:
glm(formula = achieved_target ~ `GCSE Grade` + `Overall\r\nAttendance`, 
    family = binomial, data = grades_binary)

Coefficients:
                          Estimate Std. Error z value Pr(>|z|)    
(Intercept)               -15.1576     2.2740  -6.666 2.63e-11 ***
`GCSE Grade`                0.5610     0.1446   3.880 0.000105 ***
`Overall\\r\\nAttendance`  12.4233     2.1798   5.699 1.20e-08 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 399.33  on 301  degrees of freedom
Residual deviance: 325.96  on 299  degrees of freedom
AIC: 331.96

Number of Fisher Scoring iterations: 5

Using the logistic regression model we can produce a “probability of achievement” curve for each predictor. See below.

Code
library(ggeffects)

# Probability curves for GCSE Grade
prob_gcse <- ggpredict(glm_achievement, terms = "GCSE Grade [6:9]")
plot(prob_gcse) + 
  labs(title = "Probability of Achieving Target by GCSE Grade",
       y = "Probability of Achievement")

Code
# Probability curves for Attendance  
prob_attendance <- ggpredict(glm_achievement, terms = "Overall\r\nAttendance [0.5:1 by=0.05]")
plot(prob_attendance) + 
  labs(title = "Probability of Achieving Target by Attendance",
       y = "Probability of Achievement")

In Figure 5 we can see how various combinations of grade on entry and attendance can affect achievement.

Code
# Create prediction grid
pred_data <- expand_grid(
  `GCSE Grade` = seq(6, 9, by = 0.5),
  `Overall\r\nAttendance` = seq(0.6, 1.0, by = 0.1)
)

# Add predictions
pred_data <- pred_data |>
  mutate(
    predicted_prob = predict(glm_achievement, newdata = pred_data, type = "response")
  )

# Heatmap of probabilities
ggplot(pred_data, aes(x = `GCSE Grade`, y = `Overall\r\nAttendance`, fill = predicted_prob)) +
  geom_tile() +
  scale_fill_viridis_c(name = "Probability\nof Achievement", labels = scales::percent) +
  labs(x = "GCSE Grade", y = "Overall Attendance") +
  theme_minimal()
Figure 5: Based on data from 2019-2025