Introduction University is often described as an investment in the future. However, graduate outcomes differ substantially across fields of study. These five interactive visualisations explore how Australian undergraduate degrees compare across salary, employment and medium-term earnings growth. The data is sourced from the Quality Indicators for Learning and Teaching Graduate Outcomes Survey and Graduate Outcomes Survey – Longitudinal datasets.

salary_raw <- read_excel(
  "data/GOS_2024_National_Report_Tables.xlsx",
  sheet = "SAL_UG_ALL_2Y_AREA_E315",
  skip = 9,
  col_names = FALSE
)
## New names:
## • `` -> `...1`
## • `` -> `...2`
## • `` -> `...3`
## • `` -> `...4`
## • `` -> `...5`
## • `` -> `...6`
## • `` -> `...7`
## • `` -> `...8`
salary_clean <- salary_raw %>%
  select(1:8) %>%
  set_names(c(
    "blank", "study_area", "male_2023", "male_2024",
    "female_2023", "female_2024", "total_2023", "total_2024"
  )) %>%
  filter(!is.na(study_area)) %>%
  filter(!study_area %in% c("Back to INDEX", "Total", "Standard deviation")) %>%
  mutate(
    total_2024 = as.numeric(gsub(",", "", total_2024))
  ) %>%
  filter(!is.na(total_2024))

employment_raw <- read_excel(
  "data/GOS_2024_National_Report_Tables.xlsx",
  sheet = "EMP_UG_ALL_2Y_AREA45",
  skip = 8,
  col_names = FALSE
)
## New names:
## • `` -> `...1`
## • `` -> `...2`
## • `` -> `...3`
## • `` -> `...4`
## • `` -> `...5`
## • `` -> `...6`
## • `` -> `...7`
## • `` -> `...8`
employment_clean <- employment_raw %>%
  select(1:8) %>%
  set_names(c(
    "blank", "study_area", "fte_2023", "fte_2024",
    "overall_2023", "overall_2024", "lfp_2023", "lfp_2024"
  )) %>%
  filter(!is.na(study_area)) %>%
  filter(!study_area %in% c("Back to INDEX", "Total", "Standard deviation")) %>%
  mutate(
    fte_2024 = as.numeric(fte_2024),
    overall_2024 = as.numeric(overall_2024),
    lfp_2024 = as.numeric(lfp_2024)
  ) %>%
  filter(!is.na(fte_2024))

employment_grouped <- employment_clean %>%
  mutate(
    study_group = case_when(
      str_detect(study_area, "Engineering") ~ "Engineering",
      str_detect(study_area, "Teacher education") ~ "Teacher education",
      str_detect(study_area, "Law") ~ "Law and paralegal studies",
      str_detect(study_area, "Psychology") ~ "Psychology",
      str_detect(study_area, "Social work") ~ "Social work",
      str_detect(study_area, "Nursing") ~ "Nursing",
      str_detect(study_area, "Medicine") ~ "Medicine",
      str_detect(study_area, "Pharmacy") ~ "Pharmacy",
      str_detect(study_area, "Dentistry") ~ "Dentistry",
      str_detect(study_area, "Veterinary science") ~ "Veterinary science",
      str_detect(study_area, "Humanities|Political science|Language") ~ "Humanities & Social Sciences",
      str_detect(study_area, "Computing") ~ "Computing & IT",
      str_detect(study_area, "Accounting|Banking|Economics|Management|Sales") ~ "Business & Management",
      str_detect(study_area, "Art|Music") ~ "Creative arts",
      TRUE ~ NA_character_
    )
  ) %>%
  filter(!is.na(study_group))

employment_summary <- employment_grouped %>%
  group_by(study_group) %>%
  summarise(
    fte_2024 = mean(fte_2024, na.rm = TRUE),
    overall_2024 = mean(overall_2024, na.rm = TRUE),
    lfp_2024 = mean(lfp_2024, na.rm = TRUE),
    .groups = "drop"
  )

salary_summary <- salary_clean %>%
  mutate(
    study_group = case_when(
      study_area == "Business and management" ~ "Business & Management",
      study_area == "Computing and information systems" ~ "Computing & IT",
      study_area == "Humanities, culture and social sciences" ~ "Humanities & Social Sciences",
      TRUE ~ study_area
    )
  ) %>%
  select(study_group, total_2024)

merged_data <- left_join(
  salary_summary,
  employment_summary,
  by = "study_group"
)

gosl_raw <- read_excel(
  "data/GOSL_2025_National_Tables_Public.xlsx",
  sheet = "SAL_UG_ALL_AREA_E315",
  skip = 10,
  col_names = FALSE
)
## New names:
## • `` -> `...1`
## • `` -> `...2`
## • `` -> `...3`
## • `` -> `...4`
## • `` -> `...5`
## • `` -> `...6`
## • `` -> `...7`
## • `` -> `...8`
salary_growth <- gosl_raw %>%
  select(1:8) %>%
  set_names(c(
    "blank", "study_area", "short_male", "short_female",
    "short_total", "medium_male", "medium_female", "medium_total"
  )) %>%
  filter(!is.na(study_area)) %>%
  filter(!study_area %in% c("Total", "Standard deviation")) %>%
  mutate(
    short_total = gsub(",", "", short_total),
    medium_total = gsub(",", "", medium_total),
    short_total = ifelse(short_total == "n/a", NA, short_total),
    medium_total = ifelse(medium_total == "n/a", NA, medium_total),
    short_total = as.numeric(short_total),
    medium_total = as.numeric(medium_total),
    salary_growth = medium_total - short_total
  ) %>%
  filter(!is.na(short_total), !is.na(medium_total))
  1. GRADUATE SALARIES VERY DRAMATICALLY ACROSS DISCIPLINES
chart1_data <- salary_clean %>%
  slice_max(total_2024, n = 15) %>%
  arrange(total_2024)

plot_ly(
  chart1_data,
  x = ~total_2024,
  y = ~reorder(study_area, total_2024),
  type = "bar",
  orientation = "h",
  text = ~paste(
    "<b>", study_area, "</b>",
    "<br>Median salary:", dollar(total_2024)
  ),
  hoverinfo = "text"
) %>%
  layout(
    title = "Graduate salaries vary dramatically across disciplines",
    xaxis = list(title = "Median full-time salary, 2024 ($)"),
    yaxis = list(title = ""),
    margin = list(l = 230)
  )
  1. EMPLOYEMENT OUTCOMES NOT EQUAL ACROSS DEGREES
chart2_data <- employment_clean %>%
  slice_max(fte_2024, n = 15) %>%
  arrange(fte_2024)

plot_ly(
  chart2_data,
  x = ~fte_2024,
  y = ~reorder(study_area, fte_2024),
  type = "bar",
  orientation = "h",
  text = ~paste(
    "<b>", study_area, "</b>",
    "<br>Full-time employment:", round(fte_2024, 1), "%"
  ),
  hoverinfo = "text"
) %>%
  layout(
    title = "Employment outcomes are not equal across degrees",
    xaxis = list(title = "Full-time employment rate, 2024 (%)"),
    yaxis = list(title = ""),
    margin = list(l = 230)
  )

3.HIGH SALARIES DO NOT ALWAYS MEAN BETTER EMPLYEMENT OPORTUNITIES

chart3_data <- merged_data %>%
  filter(!is.na(total_2024), !is.na(fte_2024))

plot_ly(
  chart3_data,
  x = ~total_2024,
  y = ~fte_2024,
  type = "scatter",
  mode = "markers+text",
  text = ~study_group,
  textposition = "top center",
  marker = list(size = 14, opacity = 0.75),
  hovertemplate = paste(
    "<b>%{text}</b><br>",
    "Salary: $%{x:,.0f}<br>",
    "Full-time employment: %{y:.1f}%<extra></extra>"
  )
) %>%
  layout(
    title = "High salaries do not always mean better employment prospects",
    xaxis = list(title = "Median full-time salary, 2024 ($)"),
    yaxis = list(title = "Full-time employment rate, 2024 (%)")
  )
  1. PAYOFF OF DEGRESS EMERGES YEARS LATER
growth_chart <- salary_growth %>%
  arrange(salary_growth) %>%
  mutate(study_area = factor(study_area, levels = study_area))

plot_ly(
  growth_chart,
  x = ~salary_growth,
  y = ~study_area,
  type = "bar",
  orientation = "h",
  text = ~paste0("$", comma(salary_growth)),
  hovertemplate = paste(
    "<b>%{y}</b><br>",
    "Short-term salary: $%{customdata[0]:,.0f}<br>",
    "Medium-term salary: $%{customdata[1]:,.0f}<br>",
    "Growth: $%{x:,.0f}<extra></extra>"
  ),
  customdata = ~cbind(short_total, medium_total)
) %>%
  layout(
    title = "The payoff from some degrees emerges years later",
    xaxis = list(title = "Increase in median salary ($)"),
    yaxis = list(title = "")
  )

5.NOT ALL DEGREES PAYOFF EQUALLY

chart5_data <- merged_data %>%
  left_join(
    salary_growth %>%
      mutate(
        study_group = case_when(
          study_area == "Business and management" ~ "Business & Management",
          study_area == "Computing and information systems" ~ "Computing & IT",
          study_area == "Humanities, culture and social sciences" ~ "Humanities & Social Sciences",
          TRUE ~ study_area
        )
      ) %>%
      select(study_group, salary_growth),
    by = "study_group"
  ) %>%
  filter(!is.na(total_2024), !is.na(fte_2024), !is.na(salary_growth)) %>%
  mutate(
    salary_score = rescale(total_2024, to = c(0, 100)),
    employment_score = rescale(fte_2024, to = c(0, 100)),
    growth_score = rescale(salary_growth, to = c(0, 100)),
    return_score = round((salary_score + employment_score + growth_score) / 3, 1)
  ) %>%
  arrange(return_score)

plot_ly(
  chart5_data,
  x = ~return_score,
  y = ~reorder(study_group, return_score),
  type = "bar",
  orientation = "h",
  text = ~paste0(return_score, "/100"),
  hovertemplate = paste(
    "<b>%{y}</b><br>",
    "Overall return score: %{x}/100<br>",
    "Median salary: $%{customdata[0]:,.0f}<br>",
    "Full-time employment: %{customdata[1]:.1f}%<br>",
    "Salary growth: $%{customdata[2]:,.0f}<extra></extra>"
  ),
  customdata = ~cbind(total_2024, fte_2024, salary_growth)
) %>%
  layout(
    title = "Not all degrees pay off equally",
    xaxis = list(title = "Overall return score"),
    yaxis = list(title = ""),
    margin = list(l = 230)
  )