TNS Urban Analysis

Author

Ismail Khadar

Published

January 30, 2026

Urban Programs at The New School

This collection of graphics illustrates our initial findings when coalescing the urban and urban related programs and classes in The New School. This data is not a complete look at The New School’s programs, nor is it a view into it’s classes writ-large. This is a working data set and the graphics provided are an analysis looking at the data under specific conditions. Namely

  1. The relationship between undergraduate-graduate-PhD programs
  2. The connection between different schools/colleges in regards to our thematic analysis
  3. How these themes relate to each other under the umbrella that is ‘urban’ at The New School

Our data pulls from the course catalogue and our initial analysis of emergent themes. These themes, which were developed through a group analysis, include: Sustainability, Policy, Public Engagement, Social Justice, Built Environment, Spatial Thinking, Housing, Global Urbanism, Migration, Economics, Design, Institutional Design, and Service Design.

These were the themes/topics emergent to our small team during our analysis, and we encourage you to add and or critique our analysis. These themes are especially relevant to the last two plots.

Urban Analysis Plots

When you click the Render button a document will be generated that includes both content and the output of embedded code. You can embed code like this:

Code
print
function (x, ...) 
UseMethod("print")
<bytecode: 0x0000029f82962578>
<environment: namespace:base>
Code
library(tidyverse)
library(ggalluvial)
library(janitor)
library(scales)

# -------------------------------
# 1. Load data
# -------------------------------
csv_path <- "C:/Users/ijkha/Desktop/work charts/data/newschool_city_programs + centers, labs & institutes.xlsx - programs.csv"

df <- readr::read_csv(csv_path, show_col_types = FALSE) %>%
  clean_names()

# -------------------------------
# 2. Create degree level
# -------------------------------
df2 <- df %>%
  mutate(
    degree_type_low = str_to_lower(str_squish(degree_type)),
    level = case_when(
      str_detect(degree_type_low, "phd|doctor") ~ "PhD",
      str_detect(degree_type_low, "ma|ms|mfa|mph|mpp|march") ~ "Graduate",
      str_detect(degree_type_low, "ba|bs|bfa|undergrad") ~ "Undergraduate",
      TRUE ~ "Other/Unknown"
    ),
    level = factor(level, levels = c("Undergraduate","Graduate","PhD","Other/Unknown")),
    program = str_squish(program),
    college = str_squish(college)
  ) %>%
  filter(!is.na(program), !is.na(college))

# -------------------------------
# 3. Flow weights (enrollment or 1)
# -------------------------------
df2 <- df2 %>%
  mutate(
    enrollment_num = suppressWarnings(as.numeric(enrollment_in_2024_or_last_enrolled)),
    weight = if_else(!is.na(enrollment_num) & enrollment_num > 0, enrollment_num, 1)
  )

# -------------------------------
# 4. Build alluvial table
# -------------------------------
alluvial_df <- df2 %>%
  count(college, level, program, wt = weight, name = "value") %>%
  filter(value > 0)

# -------------------------------
# 5. Simplify: Top N programs
# -------------------------------
top_n <- 15

keep_programs <- alluvial_df %>%
  group_by(program) %>%
  summarise(total = sum(value), .groups = "drop") %>%
  slice_max(total, n = top_n) %>%
  pull(program)

alluvial_df_big <- alluvial_df %>%
  mutate(program = if_else(program %in% keep_programs, program, "Other programs")) %>%
  group_by(college, level, program) %>%
  summarise(value = sum(value), .groups = "drop") %>%
  mutate(
    program = forcats::fct_relevel(program, "Other programs", after = Inf)
  )

# -------------------------------
# 6. Plot (with black flow borders)
# -------------------------------
wrap_width <- 20

p <- ggplot(
  alluvial_df_big,
  aes(axis1 = college, axis2 = level, axis3 = program, y = value)
) +
  geom_alluvium(
    aes(fill = level),
    alpha = 0.55,
    width = 1/18,
    color = "black",     # <-- border added
    size  = 0.15         # <-- border thickness
  ) +
  geom_stratum(
    width = 1/11,
    color = "grey25",
    fill = "grey95"
  ) +
  geom_text(
    stat = "stratum",
    aes(label = stringr::str_wrap(after_stat(stratum), width = wrap_width)),
    size = 3,
    lineheight = 0.9
  ) +
  scale_y_continuous(labels = comma) +
  labs(
    title = "New School Programs by Degree Level",
    subtitle = paste0("College → Level → Program (Top ", top_n, " programs; others grouped)"),
    x = NULL,
    y = "Flow weight",
    fill = "Level"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    panel.grid.major.x = element_blank(),
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank(),
    plot.margin = margin(10, 80, 10, 20)
  ) +
  coord_cartesian(clip = "off")

p

Code
# -------------------------------
# 7. Save
# -------------------------------
ggsave(
  "newschool_sankey_big_legible_borders.png",
  p,
  width = 20,
  height = 10,
  dpi = 300
)

This plot shows the relationship between enrollment weight (the amount of people enrolled) and the scholastic level (undergrad, graduate, and PhD)

This interactive plot shows the connections between the initial themes and their connectivity between the colleges.

Themes
Housing
Sustainablility
Social Justice
Migration
Institutional Design
Economics
Built Environment
Design
Policy
Public Engagement
Service Design
Global Urbanism
Spatial Thinking

Other tags (not themed)

This interactive plot shows the relationship between the initial themes and demonstrates how often they connect with one another within the dataset.