A Decade of Interventions Targeting the Social Determinants of Mental Health

Analyzing Individual vs. Systems, First vs. Second-Order Change

Authors

Edie Gobel

Connor Sayle

Kaila Pelton-Flavin

Alejandro Baez

Shane McCarty, PhD

Lisa Cosgrove, PhD

Published

June 18, 2025

Abstract

In 2014, the mental health field began a shift from treatment-focused, individual-level mental health interventions toward promotion/prevention approaches to address social determinants of mental health (SDoMH: see WHO, 2014) that facilitate community mental health (Nelson, Kloos & Ornelas, 2014) and population mental health (Dodge et al., 2024). Recent reviews of mental health interventions targeting social factors demonstrate the effectiveness of housing, cash transfer programs, and psychosocial interventions (e.g., Oswald et al., 2023). However, these reviews do not critically assess the extent to which neoliberal influences may be undermining efforts to realize SDMH goals of structural change. Specifically, Chater and Loewenstein (2022) argue that a historical over-emphasis on ‘i-frame’ (individual-level) interventions, often driven by corporate interests, diverts from ‘s-frame’ (systems-level) interventions which could more effectively address population mental health. Numerous leaders in the field of community psychology have distinguished between first-order change and second-order change (Bond et al., 2017) along with ameliorative and transformational change (Prilleltensky, 2008). Despite this important dynamic, it is unknown whether the new paradigm brought about by the social determinants of mental health movement has resulted in interventions that have shifted away from the dominant individual-level to a systems-level. In a scoping review, we identify interventions targeting the social determinants of mental health/ill health and categorize them using two independent reviewers into i-frame or s-frame interventions. Additionally, coders consider the commercialization of interventions, such as conflicts of interest with authors and industry as well as copyrighted intervention approaches to account for the commercial drivers of health (Maani, Petticrew & Galea, 2022).

Keywords

social determinants of mental health, scoping review, individual-level interventions, systems-level interventions, i-frame, s-frame, community psychology, population mental health

1 Introduction

A 2014 report by the World Health Organization on the Social Determinants of Mental Health called for the examinination of unfavorable social, economic, and environmental circumstances on population mental health. The mainstream adoption of the social determinants of health (SDOH) framework and its application to mental health (SDOMH: Compton & Shim, 2015) differs markedly from its origin story with a power-centric, social determination theory that emphasizes sociopolitical and structural factors as the primary cause of health and health inequity (Breilh, 2019). Oswald and colleagues (2024) categorized 101 systematic review articles on SDOMH interventions as: demographic, economic, environmental events, neighborhood, or sociocultural. However, this analysis fails to account for other categorical frameworks related to critical mental health and community mental health studies. 

In critical community psychology and across the behavioral sciences, different analytical frameworks have been developed to categorize interventions based on: the target focus as individuals (i-frame) vs. systems (s-frame: Chater and Loewenstein 2022), the type of change as ameliorative/first-order change or transformative/second-order change (Prilleltensky, 2008), and the strategy of the intervention to reform the existing paradigm, offer an alternative, or build toward a new paradigm (Wright, 2015). In this study, we aim to determine the prevalence of SDOMH intervention types and the associations between target, change type, and strategy.

2 Method

Review articles (n = 101) from a systematic review by Oswald et al., 2024 were uploaded into a screening tool, Rayyan. Three study team members (E.G., C.S., A.B.) generated the codebook and were assigned two-thirds of the 100 included articles to code. Inter-rater reliability shows fair to moderate agreement (κ = .342 to .517). Coders reviewed their codes with their assigned partner to identify and discuss coding discrepancies, arriving at a single choice (e.g., ALT) or a hybrid option when needed (e.g., REFORM-ALT).

3 Results

3.1 Definitions

3.1.1 Agreement

Dataset dimensions: 100 rows, 29 columns
Sample size for analysis: 100 observations
=== INTER-RATER RELIABILITY ANALYSIS ===
FIRST (Change) (FIRST_rate1 vs FIRST_rate2):
  Cohen's κ = 0.463
  Interpretation: Moderate
  Observed Agreement = 87.0% (87/100)
  p-value = 0.0000
  Sample Size = 100

SECOND (Change) (SECOND_rate1 vs SECOND_rate2):
  Cohen's κ = 0.466
  Interpretation: Moderate
  Observed Agreement = 86.0% (86/100)
  p-value = 0.0000
  Sample Size = 100

I (Individual Frame) (I_rate1 vs I_rate2):
  Cohen's κ = 0.342
  Interpretation: Fair
  Observed Agreement = 93.0% (93/100)
  p-value = 0.0000
  Sample Size = 100

S (Structural Frame) (S_rate1 vs S_rate2):
  Cohen's κ = 0.504
  Interpretation: Moderate
  Observed Agreement = 93.0% (93/100)
  p-value = 0.0000
  Sample Size = 100

REFORM (Strategy) (REFORM_rate1 vs REFORM_rate2):
  Cohen's κ = 0.517
  Interpretation: Moderate
  Observed Agreement = 76.0% (76/100)
  p-value = 0.0000
  Sample Size = 100

ALT (Strategy) (ALT_rate1 vs ALT_rate2):
  Cohen's κ = 0.496
  Interpretation: Moderate
  Observed Agreement = 75.0% (75/100)
  p-value = 0.0000
  Sample Size = 100
BUILD (Strategy) (BUILD_rate1 vs BUILD_rate2):
  Cohen's κ = NaN
  Interpretation: Cannot calculate
  Observed Agreement = 100.0% (99/99)
  Sample Size = 99
=== SUMMARY TABLE ===


|Variable             | Cohen's κ |Interpretation   | Observed Agreement | Agreements |  N  | p-value |
|:--------------------|:---------:|:----------------|:------------------:|:----------:|:---:|:-------:|
|FIRST (Change)       |   0.463   |Moderate         |        87%         |   87/100   | 100 | 0.0000  |
|SECOND (Change)      |   0.466   |Moderate         |        86%         |   86/100   | 100 | 0.0000  |
|I (Individual Frame) |   0.342   |Fair             |        93%         |   93/100   | 100 | 0.0000  |
|S (Structural Frame) |   0.504   |Moderate         |        93%         |   93/100   | 100 | 0.0000  |
|REFORM (Strategy)    |   0.517   |Moderate         |        76%         |   76/100   | 100 | 0.0000  |
|ALT (Strategy)       |   0.496   |Moderate         |        75%         |   75/100   | 100 | 0.0000  |
|BUILD (Strategy)     |    NaN    |Cannot calculate |        100%        |   99/99    | 99  |    —    |

=== OVERALL STATISTICS ===
Average Cohen's κ: 0.465 (Moderate)
Average Observed Agreement: 87.1%
Number of variable pairs analyzed: 7
Total observations per variable: 100

Results saved to 'kappa_results.csv'
Source: Article Notebook

3.1.2 Plots

3.1.2.1 Setup

3.1.2.2 Load Required Libraries

Show the code
library(readxl)
library(dplyr)
library(ggplot2)
Show the code
library(ggmosaic)
library(tidyr)
library(vcd)
Show the code
library(RColorBrewer)
library(kableExtra)
Show the code
library(irr)
library(psych)

3.1.2.3 Import Data

Show the code
# Import the Excel file
raw_data <- read_excel("SDOMHCode.xlsx")
Show the code
# Check if data imported correctly
cat("Dataset dimensions:", dim(raw_data), "\n")
Dataset dimensions: 100 29 
Show the code
cat("Column names:\n")
Column names:
Show the code
print(names(raw_data))
 [1] "...1"             "article_short"    "...3"             "Rater 1"         
 [5] "Rater 2"          "FIRST_rate1"      "FIRST_rate2"      "FIRST"           
 [9] "SECOND_rate1"     "SECOND_rate2"     "SECOND"           "ORDER_NEITHER"   
[13] "I_rate1"          "I_rate2"          "IFRAME"           "S_rate1"         
[17] "S_rate2"          "SFRAME"           "FRAME_NEITHER"    "REFORM_rate1"    
[21] "REFORM_rate2"     "REFORM"           "ALT_rate1"        "ALT_rate2"       
[25] "ALT"              "BUILD_rate1"      "BUILD_rate2"      "BUILD"           
[29] "STRATEGY_NEITHER"
Show the code
# Check data types
cat("\nData structure:\n")

Data structure:
Show the code
#str(raw_data)

# Display first few rows
cat("\nFirst few rows:\n")

First few rows:
Show the code
#head(raw_data)

# Check for any issues with the data object
cat("\nClass of data object:", class(raw_data), "\n")

Class of data object: tbl_df tbl data.frame 

3.1.2.4 Data Transformation

Show the code
# Create the transformed dataset with new categorical variables
data_transformed <- raw_data %>%
  mutate(
    # Create CHANGE variable based on FIRST and SECOND
    CHANGE = case_when(
      FIRST == 1 & SECOND == 1 ~ "BOTH",
      FIRST == 1 & SECOND == 0 ~ "FIRST",
      FIRST == 0 & SECOND == 1 ~ "SECOND",
      TRUE ~ "NEITHER"
    ),
    
    # Create FRAME variable based on IFRAME and SFRAME
    FRAME = case_when(
      IFRAME == 1 & SFRAME == 1 ~ "BOTH",
      IFRAME == 1 & SFRAME == 0 ~ "IFRAME",
      IFRAME == 0 & SFRAME == 1 ~ "SFRAME",
      TRUE ~ "NEITHER"
    ),
    
    # Create STRATEGY variable based on REFORM, ALT, and BUILD
    STRATEGY = case_when(
      REFORM == 1 & ALT == 1 & BUILD == 1 ~ "REFORM-ALT-BUILD",
      REFORM == 1 & ALT == 1 & BUILD == 0 ~ "REFORM-ALT",
      REFORM == 1 & ALT == 0 & BUILD == 1 ~ "REFORM-BUILD",
      REFORM == 0 & ALT == 1 & BUILD == 1 ~ "ALT-BUILD",
      REFORM == 1 & ALT == 0 & BUILD == 0 ~ "REFORM",
      REFORM == 0 & ALT == 1 & BUILD == 0 ~ "ALT",
      REFORM == 0 & ALT == 0 & BUILD == 1 ~ "BUILD",
      TRUE ~ "NEITHER"
    )
  )

# Display summary of new variables
cat("Summary of transformed variables:\n")
Summary of transformed variables:
Show the code
cat("\nCHANGE variable distribution:\n")

CHANGE variable distribution:
Show the code
table(data_transformed$CHANGE)

   BOTH   FIRST NEITHER  SECOND 
      1      87       1      11 
Show the code
cat("\nFRAME variable distribution:\n")

FRAME variable distribution:
Show the code
table(data_transformed$FRAME)

  BOTH IFRAME SFRAME 
     3     94      3 
Show the code
cat("\nSTRATEGY variable distribution:\n")

STRATEGY variable distribution:
Show the code
table(data_transformed$STRATEGY)

       ALT    NEITHER     REFORM REFORM-ALT 
        54          1         38          7 
Show the code
# Display first few rows with new variables
head(data_transformed %>% select(FIRST, SECOND, CHANGE, IFRAME, SFRAME, FRAME, REFORM, ALT, BUILD, STRATEGY))
# A tibble: 6 × 10
  FIRST SECOND CHANGE IFRAME SFRAME FRAME  REFORM   ALT BUILD STRATEGY
  <dbl>  <dbl> <chr>   <dbl>  <dbl> <chr>   <dbl> <dbl> <dbl> <chr>   
1     1      0 FIRST       1      0 IFRAME      0     1     0 ALT     
2     1      0 FIRST       1      0 IFRAME      0     1     0 ALT     
3     1      0 FIRST       1      0 IFRAME      0     1     0 ALT     
4     1      0 FIRST       1      0 IFRAME      0     1     0 ALT     
5     1      0 FIRST       1      0 IFRAME      0     1     0 ALT     
6     1      0 FIRST       1      0 IFRAME      0     1     0 ALT     

3.1.2.5 Mosaic Plots

3.1.2.6 Data Cleaning and Preparation

Show the code
# First, let's clean and prepare the data properly
data_clean <- data_transformed %>%
  filter(!is.na(CHANGE) & !is.na(FRAME) & !is.na(STRATEGY)) %>%
  filter(CHANGE != "" & FRAME != "" & STRATEGY != "")

# Check data distribution
cat("Sample size after cleaning:", nrow(data_clean), "\n\n")
Sample size after cleaning: 100 
Show the code
cat("CHANGE distribution:\n")
CHANGE distribution:
Show the code
change_dist <- table(data_clean$CHANGE)
print(change_dist)

   BOTH   FIRST NEITHER  SECOND 
      1      87       1      11 
Show the code
cat("Percentages:", round(prop.table(change_dist) * 100, 1), "\n\n")
Percentages: 1 87 1 11 
Show the code
cat("FRAME distribution:\n") 
FRAME distribution:
Show the code
frame_dist <- table(data_clean$FRAME)
print(frame_dist)

  BOTH IFRAME SFRAME 
     3     94      3 
Show the code
cat("Percentages:", round(prop.table(frame_dist) * 100, 1), "\n\n")
Percentages: 3 94 3 
Show the code
cat("STRATEGY distribution:\n")
STRATEGY distribution:
Show the code
strategy_dist <- table(data_clean$STRATEGY)
print(strategy_dist)

       ALT    NEITHER     REFORM REFORM-ALT 
        54          1         38          7 
Show the code
cat("Percentages:", round(prop.table(strategy_dist) * 100, 1), "\n\n")
Percentages: 54 1 38 7 
Show the code
## Cross-Tabulations
### Basic Cross-Tabulations

# Two-way cross-tabulations
cat("CHANGE × FRAME Cross-Tabulation:\n")
CHANGE × FRAME Cross-Tabulation:
Show the code
change_frame_table <- table(data_transformed$CHANGE, data_transformed$FRAME)
print(change_frame_table)
         
          BOTH IFRAME SFRAME
  BOTH       0      1      0
  FIRST      2     85      0
  NEITHER    0      1      0
  SECOND     1      7      3
Show the code
cat("\nCHANGE × STRATEGY Cross-Tabulation:\n")

CHANGE × STRATEGY Cross-Tabulation:
Show the code
change_strategy_table <- table(data_transformed$CHANGE, data_transformed$STRATEGY)
print(change_strategy_table)
         
          ALT NEITHER REFORM REFORM-ALT
  BOTH      0       0      1          0
  FIRST    44       1     35          7
  NEITHER   1       0      0          0
  SECOND    9       0      2          0
Show the code
cat("\nFRAME × STRATEGY Cross-Tabulation:\n")

FRAME × STRATEGY Cross-Tabulation:
Show the code
frame_strategy_table <- table(data_transformed$FRAME, data_transformed$STRATEGY)
print(frame_strategy_table)
        
         ALT NEITHER REFORM REFORM-ALT
  BOTH     1       0      2          0
  IFRAME  50       1     36          7
  SFRAME   3       0      0          0
Show the code
# Option 2: Flattened Table with All Combinations
all_combinations <- data_transformed %>%
  count(CHANGE, FRAME, STRATEGY) %>%
  arrange(desc(n)) %>%
  mutate(
    Combination = paste(CHANGE, FRAME, STRATEGY, sep = " × "),
    Percentage = round(n / sum(n) * 100, 1),
    Cumulative_Pct = round(cumsum(n) / sum(n) * 100, 1)
  ) %>%
  select(Combination, Count = n, Percentage, Cumulative_Pct)

kable(all_combinations,
      caption = "All CHANGE × FRAME × STRATEGY Combinations (Sorted by Frequency)",
      booktabs = TRUE) %>%
  kable_styling(bootstrap_options = c("striped", "hover"))
All CHANGE × FRAME × STRATEGY Combinations (Sorted by Frequency)
Combination Count Percentage Cumulative_Pct
FIRST × IFRAME × ALT 44 44 44
FIRST × IFRAME × REFORM 33 33 77
FIRST × IFRAME × REFORM-ALT 7 7 84
SECOND × IFRAME × ALT 5 5 89
SECOND × SFRAME × ALT 3 3 92
FIRST × BOTH × REFORM 2 2 94
SECOND × IFRAME × REFORM 2 2 96
BOTH × IFRAME × REFORM 1 1 97
FIRST × IFRAME × NEITHER 1 1 98
NEITHER × IFRAME × ALT 1 1 99
SECOND × BOTH × ALT 1 1 100
Show the code
print(all_combinations)
# A tibble: 11 × 4
   Combination                 Count Percentage Cumulative_Pct
   <chr>                       <int>      <dbl>          <dbl>
 1 FIRST × IFRAME × ALT           44         44             44
 2 FIRST × IFRAME × REFORM        33         33             77
 3 FIRST × IFRAME × REFORM-ALT     7          7             84
 4 SECOND × IFRAME × ALT           5          5             89
 5 SECOND × SFRAME × ALT           3          3             92
 6 FIRST × BOTH × REFORM           2          2             94
 7 SECOND × IFRAME × REFORM        2          2             96
 8 BOTH × IFRAME × REFORM          1          1             97
 9 FIRST × IFRAME × NEITHER        1          1             98
10 NEITHER × IFRAME × ALT          1          1             99
11 SECOND × BOTH × ALT             1          1            100
Show the code
# Three-way cross-tabulation
cat("Three-Way Cross-Tabulation: CHANGE × FRAME × STRATEGY\n")
Three-Way Cross-Tabulation: CHANGE × FRAME × STRATEGY
Show the code
three_way_table <- table(data_transformed$CHANGE, 
                        data_transformed$FRAME, 
                        data_transformed$STRATEGY)
print(three_way_table)
, ,  = ALT

         
          BOTH IFRAME SFRAME
  BOTH       0      0      0
  FIRST      0     44      0
  NEITHER    0      1      0
  SECOND     1      5      3

, ,  = NEITHER

         
          BOTH IFRAME SFRAME
  BOTH       0      0      0
  FIRST      0      1      0
  NEITHER    0      0      0
  SECOND     0      0      0

, ,  = REFORM

         
          BOTH IFRAME SFRAME
  BOTH       0      1      0
  FIRST      2     33      0
  NEITHER    0      0      0
  SECOND     0      2      0

, ,  = REFORM-ALT

         
          BOTH IFRAME SFRAME
  BOTH       0      0      0
  FIRST      0      7      0
  NEITHER    0      0      0
  SECOND     0      0      0

3.1.2.7 Treemap

Show the code
# Alternative Visualizations for CHANGE × FRAME × STRATEGY
library(ggplot2)
library(dplyr)
library(tidyr)
library(RColorBrewer)

# Prepare data
plot_data <- data_transformed %>%
  count(CHANGE, FRAME, STRATEGY) %>%
  mutate(
    CHANGE = factor(CHANGE, levels = c("FIRST", "BOTH", "SECOND", "NEITHER")),
    FRAME = factor(FRAME, levels = c("IFRAME", "BOTH", "SFRAME"))
  )

# Reorder the CHANGE variable to your desired order
data_transformed$CHANGE <- factor(data_transformed$CHANGE, 
                                 levels = c("FIRST", "BOTH", "SECOND", "NEITHER"))


# Improved ggplot2 script with custom layout requirements
# 1. STACKED BAR CHARTS with improved layout
p1.1 <- ggplot(plot_data, aes(x = STRATEGY, y = n, fill = CHANGE)) +
  geom_bar(stat = "identity", width = 0.8) +
  
  # Custom faceting with specified order: IFRAME, SFRAME, BOTH
  facet_wrap(~ factor(FRAME, levels = c("IFRAME", "SFRAME", "BOTH")), 
             labeller = labeller(FRAME = c("IFRAME" = "IFRAME", 
                                         "SFRAME" = "SFRAME", 
                                         "BOTH" = "BOTH")),
             strip.position = "bottom") +
  
  # Custom x-axis ordering: ALT, REFORM, REFORM-ALT, NEITHER
  scale_x_discrete(limits = c("ALT", "REFORM", "REFORM-ALT", "NEITHER"),
                   drop = FALSE) +
  
  # Remove grid and set transparent background
  theme_minimal() +
  theme(
    # Remove grid lines
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    
    # Transparent background
    plot.background = element_rect(fill = "transparent", color = NA),
    panel.background = element_rect(fill = "transparent", color = NA),
    
    axis.text.y = element_text(size = 12),
    
    # Rotate x-axis labels for better readability
    axis.text.x = element_text(angle = 45, hjust = 1),
    
    # Center title and subtitle
    plot.title = element_text(hjust = 0.5, size = 14),
    plot.subtitle = element_text(hjust = 0.5, size = 10),
    
    # Optional: make strip background transparent too
    strip.background = element_rect(fill = "transparent", color = NA),
    
    # Move legend position (x, y coordinates where 0,0 is bottom-left and 1,1 is top-right)
    legend.position = c(0.88, 0.75),  # Move right about 1 inch and up 0.5 inch
    legend.background = element_rect(fill = "transparent", color = NA)
  ) +
  
  # Labels and styling
  labs(
    title = "SDOMH INTERVENTION TYPES",
    subtitle = "Associations by Frame, Change, Strategy",
    x = "", 
    y = "Frequency of Systematic Reviews", 
    fill = "CHANGE"
  ) +
  
  # Color palette
  scale_fill_brewer(palette = "Set3")

# Print the plot
print(p1.1)

Show the code
# Optional: Save with transparent background and dimensions matching screenshot
ggsave("plot.png", p1.1, bg = "transparent", width = 14, height = 8, dpi = 300)
Show the code
# 3. HEAT MAP (Great for seeing all combinations)
p3 <- plot_data %>%
  unite("FRAME_STRATEGY", FRAME, STRATEGY, sep = " × ") %>%
  ggplot(aes(x = FRAME_STRATEGY, y = CHANGE, fill = n)) +
  geom_tile(color = "white", size = 0.5) +
  geom_text(aes(label = n), color = "white", fontweight = "bold", size = 3) +
  scale_fill_gradient(low = "lightblue", high = "darkblue", name = "Count") +
  labs(
    title = "Heat Map: All Three-Way Combinations",
    subtitle = "Numbers show frequency counts",
    x = "FRAME × STRATEGY", y = "CHANGE"
  ) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1),
    panel.grid = element_blank()
  )
Show the code
print(p3)

Show the code
# 6. TREEMAP (Shows proportional areas)
# Install treemap if needed: install.packages("treemap")
library(treemap)

treemap_data <- plot_data %>%
  filter(n > 0) %>%
  mutate(label = paste(CHANGE, FRAME, STRATEGY, sep = "\n"))

treemap(treemap_data,
        index = c("CHANGE", "FRAME", "STRATEGY"),
        vSize = "n",
        type = "index",
        title = "Treemap: CHANGE × FRAME × STRATEGY",
        fontsize.labels = c(12, 10, 8),
        fontcolor.labels = "white",
        border.col = "white",
        palette = "Set3")

3.1.2.8

Show the code
# Now create the plot with the new ordering
plot.mosiac.three <- ggplot(data = data_transformed) +
  geom_mosaic(aes(x = product(STRATEGY, FRAME, CHANGE), 
                  fill = CHANGE)) +
  labs(
    title = "Three-Way Mosaic Plot: CHANGE × FRAME × STRATEGY",
    subtitle = "Showing associations between all three categorical variables",
    x = "STRATEGY and FRAME",
    y = "CHANGE"
  ) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(size = 4, angle = 90, hjust = 1, vjust = 0.5),
    plot.margin = margin(10, 10, 50, 10)
  ) +
  scale_fill_brewer(palette = "Set3")

ggsave(
  filename = "plot.mosiac.three.png",
  plot = plot.mosiac.three,
  device = "png",
  width = 35,
  height = 15,
  units = "cm",
  dpi = 300
)
Show the code
plot.mosiac.three

3.1.2.9 Alt Plots

Show the code
# Improved ggplot2 script with custom layout requirements

# 1. STACKED BAR CHARTS with improved layout
p1.1 <- ggplot(plot_data, aes(x = STRATEGY, y = n, fill = CHANGE)) +
  geom_bar(stat = "identity", width = 0.8) +
  
  # Custom faceting with specified order: IFRAME, SFRAME, BOTH
  facet_wrap(~ factor(FRAME, levels = c("IFRAME", "SFRAME", "BOTH")), 
             labeller = labeller(FRAME = c("IFRAME" = "IFRAME", 
                                         "SFRAME" = "SFRAME", 
                                         "BOTH" = "BOTH")),
             strip.position = "bottom") +
  
  # Custom x-axis ordering: ALT, REFORM, REFORM-ALT, NEITHER
  scale_x_discrete(limits = c("ALT", "REFORM", "REFORM-ALT", "NEITHER"),
                   drop = FALSE) +
  
  # Remove grid and set transparent background
  theme_minimal() +
  theme(
    # Remove grid lines
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    
    # Transparent background
    plot.background = element_rect(fill = "transparent", color = NA),
    panel.background = element_rect(fill = "transparent", color = NA),
    
    # Rotate x-axis labels for better readability
    axis.text.x = element_text(angle = 45, hjust = 1),
    
    # Optional: make strip background transparent too
    strip.background = element_rect(fill = "transparent", color = NA)
  ) +
  
  # Labels and styling
  labs(
    title = "SDOMH Intervention Categorizations",
    subtitle = "Associations by Frame, Change, Strategy",
    x = "", 
    y = "Frequency of Systematic Reviews", 
    fill = "CHANGE"
  ) +
  
  # Color palette
  scale_fill_brewer(palette = "Set3")

# Print the plot
print(p1.1)

Show the code
# Optional: Save with transparent background
ggsave("plot.png", p1.1, bg = "transparent", width = 12, height = 6, dpi = 300)
Show the code
# 4. BALLOON PLOT (Size = frequency)
p4 <- ggplot(plot_data, aes(x = STRATEGY, y = interaction(CHANGE, FRAME))) +
  geom_point(aes(size = n, color = CHANGE), alpha = 0.7) +
  scale_size_continuous(range = c(2, 15), name = "Count") +
  scale_color_brewer(palette = "Set3", name = "CHANGE") +
  labs(
    title = "Balloon Plot: Three-Way Associations",
    subtitle = "Point size = frequency, Color = CHANGE, Y-axis = CHANGE × FRAME",
    x = "STRATEGY", y = "CHANGE × FRAME"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

print(p4)

Show the code
# 5. FACETED DOT PLOT
p5 <- ggplot(plot_data, aes(x = n, y = STRATEGY, color = CHANGE)) +
  geom_point(size = 4) +
  facet_wrap(~ FRAME, scales = "free") +
  labs(
    title = "Dot Plot: Frequency by STRATEGY and FRAME",
    subtitle = "Faceted by FRAME, colored by CHANGE",
    x = "Count", y = "STRATEGY", color = "CHANGE"
  ) +
  theme_minimal() +
  scale_color_brewer(palette = "Set3")

print(p5)

Show the code
# 8. GROUPED BAR CHART (Alternative grouping)
p8 <- ggplot(plot_data, aes(x = FRAME, y = n, fill = STRATEGY)) +
  geom_bar(stat = "identity", position = "dodge") +
  facet_wrap(~ CHANGE, scales = "free") +
  labs(
    title = "Grouped Bar Chart: Alternative View",
    subtitle = "Faceted by CHANGE, grouped by STRATEGY",
    x = "FRAME", y = "Count", fill = "STRATEGY"
  ) +
  theme_minimal() +
  scale_fill_brewer(palette = "Set2")

print(p8)

Show the code
# 9. SIMPLE TABLE VISUALIZATION (Clean and clear)
library(gt)

table_viz <- plot_data %>%
  pivot_wider(names_from = STRATEGY, values_from = n, values_fill = 0) %>%
  arrange(CHANGE, FRAME) %>%
  gt() %>%
  tab_header(
    title = "Three-Way Cross-Tabulation",
    subtitle = "CHANGE × FRAME × STRATEGY"
  ) %>%
  data_color(
    columns = c(ALT, NEITHER, REFORM, `REFORM-ALT`),
    colors = scales::col_numeric(
      palette = c("white", "darkblue"),
      domain = NULL
    )
  )
Show the code
# 10. SUMMARY: Which visualization to choose?

cat("VISUALIZATION RECOMMENDATIONS:\n\n")
VISUALIZATION RECOMMENDATIONS:
Show the code
cat("For UNBALANCED data (like yours):\n")
For UNBALANCED data (like yours):
Show the code
cat("✓ Stacked bar charts (p1, p2) - Best overall\n")
✓ Stacked bar charts (p1, p2) - Best overall
Show the code
cat("✓ Heat map (p3) - Shows all combinations clearly\n")
✓ Heat map (p3) - Shows all combinations clearly
Show the code
cat("✓ Table visualization - Most accurate\n\n")
✓ Table visualization - Most accurate
Show the code
cat("For BALANCED data:\n")
For BALANCED data:
Show the code
cat("✓ Mosaic plots\n")
✓ Mosaic plots
Show the code
cat("✓ Alluvial diagrams\n")
✓ Alluvial diagrams
Show the code
cat("✓ Treemaps\n\n")
✓ Treemaps
Show the code
cat("For PRESENTATION:\n")
For PRESENTATION:
Show the code
cat("✓ Clean heat map (p3)\n")
✓ Clean heat map (p3)
Show the code
cat("✓ Proportional stacked bars (p2)\n")
✓ Proportional stacked bars (p2)
Show the code
cat("✓ Simple table with color coding\n")
✓ Simple table with color coding

4 Discussion

The resulting categorizations of SDOMH interventions using critical community and behavioral science frameworks demonstrate the ubiquity of ameliorative, individual-level interventions rather than the more radical, socio-structural analysis associated with social determination theory (Breilh, 2019).  Iframe, first-order change interventions offering an alternative to or as a reform for the biomedical paradigm were the most common intervention type (85%, n = 85). Interestingly, all sframe interventions were coded as second-order change interventions (n=3). The absence of interventions building toward a new paradigm beyond the biomedical mental health model is notable.

Future research should continue to examine prior SDOMH interventions and call for more alternative and/or building-type strategies to advance an SDOMH paradigm for mental health beyond the biomedical paradigm. 

4.1 Future Directions

While numerous studies examine the social conditions of mental health (Compton & Shim, 2014), recent reviews highlight a critical gap in policy-level interventions within the SDoMH literature (Alegría et al., 2023; Kirkbride et al., 2024). This gap may reflect what Chater and Loewenstein (2022) describe as corporate interests driving researchers to prioritize ‘i-frame’ over ‘s-frame’ interventions. Notably, the growth of the $32.7 billion private, for-profit social determinants of health industry (Goldberg et al., 2024) suggests corporate actors are capitalizing on social interventions (see Maani, Petticrew & Galea, 2022).

Using PRISMA guidelines (Tricco, 2018), we plan to conduct a scoping review of 3484 peer-reviewed articles published between 2014-2024 in academic databases (PubMed, PsycINFO, Web of Science). Our search combines “mental health,” “mental illness,” “mental ill health,” and “social determinants of health” to answer: 1) what is the relative prevalence of i-frame versus s-frame social interventions for mental health, and 2) how frequently are these interventions commercialized?

5 References

  • Breilh, J. (2019). Critical Epidemiology in Latin America: Roots, Philosophical and Methodological Ruptures. In J. Vallverdú, A. Puyol, & A. Estany (Eds.), Philosophical and Methodological Debates in Public Health (pp. 21–45). Springer International Publishing. https://doi.org/10.1007/978-3-030-28626-2_3

  • Chater, N., & Loewenstein, G. (2023). The i-frame and the s-frame: How focusing on individual-level solutions has led behavioral public policy astray. Behavioral and Brain Sciences, 46, e147. https://doi.org/10.1017/S0140525X22002023

  • Compton, M. T., & Shim, R. S. (2015). The Social Determinants of Mental Health. FOCUS, 13(4), 419–425. https://doi.org/10.1176/appi.focus.20150017

  • Oswald, T. K., Nguyen, M. T., Mirza, L., Lund, C., Jones, H. G., Crowley, G., Aslanyan, D., Dean, K., Schofield, P., Hotopf, M., & Das-Munshi, J. (2024). Interventions targeting social determinants of mental disorders and the Sustainable Development Goals: A systematic review of reviews. Psychological Medicine, 1–25. https://doi.org/10.1017/S0033291724000333

  • Prilleltensky, I. (2008). The role of power in wellness, oppression, and liberation: The promise of psychopolitical validity. Journal of Community Psychology, 36(2), 116–136. https://doi.org/10.1002/jcop.20225

  • Wright, E. O. (2015, December 2). How to Be an Anticapitalist Today. Jacobin. https://jacobin.com/2015/12/erik-olin-wright-real-utopias-anticapitalism-democray