Step 2: Descriptive Statistics + H1 Analysis

This notebook produces the descriptive statistics tables for Section 5.1 and the H1 concessionality regression for Section 5.2 of the thesis.

2.1 Load Packages & Data

library(sandwich)
library(lmtest)
library(car)
library(stargazer)
library(ggplot2)
library(dplyr)
library(tidyr)
library(kableExtra)

df <- read.csv("~/Master Thesis/Analysis/r_dataset_3_regression_complete.csv",
               stringsAsFactors = FALSE)

cat("Dataset loaded:", nrow(df), "observations\n")
## Dataset loaded: 6557 observations

SECTION 5.1: Descriptive Statistics

2.2 Table 1: Full Sample Descriptive Statistics

This is the main descriptive table for Section 5.1. Reports N, Mean, SD, Min, Median, Max for all key variables.

desc_vars <- df[, c("total_mob", "ihs_mob_total", "guarantee_share",
                     "direct_inv_share", "synd_loan_share", "credit_line_share",
                     "concessional_mechanism_share", "first_loss_proxy",
                     "concessional_dominance",
                     "gdp_pc", "log_gdp_pc", "gdp_growth", "fdi_net",
                     "gov_quality_index", "va_est", "pv_est", "ge_est",
                     "rq_est", "rl_est", "cc_est")]

# Build descriptive table
desc_table <- data.frame(
  Variable = names(desc_vars),
  N = sapply(desc_vars, function(x) sum(!is.na(x))),
  Mean = sapply(desc_vars, function(x) round(mean(x, na.rm = TRUE), 3)),
  SD = sapply(desc_vars, function(x) round(sd(x, na.rm = TRUE), 3)),
  Min = sapply(desc_vars, function(x) round(min(x, na.rm = TRUE), 3)),
  Median = sapply(desc_vars, function(x) round(median(x, na.rm = TRUE), 3)),
  Max = sapply(desc_vars, function(x) round(max(x, na.rm = TRUE), 3))
)
rownames(desc_table) <- NULL

kable(desc_table, caption = "Table 1: Full Sample Descriptive Statistics (N = 6,557)") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>%
  pack_rows("Outcome & Instrument Variables", 1, 9) %>%
  pack_rows("Macroeconomic Controls (WDI)", 10, 13) %>%
  pack_rows("Governance Controls (WGI)", 14, 20)
Table 1: Full Sample Descriptive Statistics (N = 6,557)
Variable N Mean SD Min Median Max
Outcome & Instrument Variables
total_mob 6557 138.173 390.172 0.000 26.580 9700.000
ihs_mob_total 6557 3.912 1.987 0.000 3.974 9.873
guarantee_share 6557 0.294 0.419 0.000 0.000 1.000
direct_inv_share 6557 0.356 0.434 0.000 0.026 1.000
synd_loan_share 6557 0.167 0.329 0.000 0.000 1.000
credit_line_share 6557 0.116 0.288 0.000 0.000 1.000
concessional_mechanism_share 6557 0.410 0.454 0.000 0.073 1.000
first_loss_proxy 6557 0.413 0.492 0.000 0.000 1.000
concessional_dominance 6557 0.412 0.492 0.000 0.000 1.000
Macroeconomic Controls (WDI)
gdp_pc 6557 4406.870 3769.609 254.403 3378.435 33034.958
log_gdp_pc 6557 8.017 0.908 5.539 8.125 10.405
gdp_growth 6557 2.092 4.772 -34.831 2.569 62.111
fdi_net 6549 3.215 4.470 -37.173 2.299 34.990
Governance Controls (WGI)
gov_quality_index 6557 -0.447 0.444 -1.971 -0.413 1.038
va_est 6557 -0.443 0.619 -2.029 -0.409 1.241
pv_est 6557 -0.569 0.675 -2.614 -0.522 1.230
ge_est 6557 -0.283 0.510 -2.060 -0.235 1.059
rq_est 6557 -0.318 0.465 -1.978 -0.267 1.276
rl_est 6557 -0.535 0.465 -2.305 -0.535 1.159
cc_est 6557 -0.534 0.491 -1.806 -0.524 1.385

2.3 Table 2: Descriptive Statistics by Climate Category

This is the key comparison table. Look for: adaptation has lower mobilisation and higher concessionality.

table2 <- df %>%
  group_by(climate_detail) %>%
  summarise(
    N = n(),
    `% of Sample` = round(n() / nrow(df) * 100, 1),
    `Mean Mob (USD M)` = round(mean(total_mob), 1),
    `Median Mob (USD M)` = round(median(total_mob), 1),
    `Mean IHS Mob` = round(mean(ihs_mob_total), 2),
    `Guarantee Share` = round(mean(guarantee_share), 3),
    `Direct Inv Share` = round(mean(direct_inv_share), 3),
    `Synd Loan Share` = round(mean(synd_loan_share), 3),
    `Credit Line Share` = round(mean(credit_line_share), 3),
    `Concessional Mech Share` = round(mean(concessional_mechanism_share), 3),
    `First-Loss Proxy` = round(mean(first_loss_proxy), 3),
    `Conc Dominance` = round(mean(concessional_dominance), 3),
    `Gov Quality Index` = round(mean(gov_quality_index), 3),
    `GDP per capita` = round(mean(gdp_pc), 0),
    .groups = "drop"
  ) %>%
  arrange(match(climate_detail, c("adaptation", "mitigation", "climate-other", "non-climate")))

kable(table2, caption = "Table 2: Descriptive Statistics by Climate Category") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
Table 2: Descriptive Statistics by Climate Category
climate_detail N % of Sample Mean Mob (USD M) Median Mob (USD M) Mean IHS Mob Guarantee Share Direct Inv Share Synd Loan Share Credit Line Share Concessional Mech Share First-Loss Proxy Conc Dominance Gov Quality Index GDP per capita
adaptation 254 3.9 78.0 24.5 3.65 0.139 0.443 0.214 0.112 0.250 0.205 0.264 -0.394 4982
mitigation 980 14.9 144.4 38.1 4.19 0.192 0.428 0.234 0.087 0.279 0.287 0.287 -0.399 4842
climate-other 2270 34.6 247.4 66.1 4.73 0.258 0.336 0.178 0.159 0.417 0.473 0.416 -0.404 4829
non-climate 3053 46.6 60.0 12.0 3.23 0.366 0.339 0.134 0.094 0.459 0.426 0.462 -0.499 3906

Interpretation checklist:

  • Is Mean Mob lower for adaptation than mitigation? → Supports H2
  • Is Concessional Mech Share higher for adaptation? → Supports H1
  • Is GDP per capita lower for adaptation? → Country-risk confound to control for
  • Is Gov Quality Index lower for adaptation? → Governance confound to control for

2.4 Table 3: First-Loss & Concessional Dominance by Climate Category

This table directly addresses Fix #2 from the thesis alignment.

table3 <- df %>%
  group_by(climate_detail) %>%
  summarise(
    N = n(),
    `First-Loss = 1 (%)` = round(mean(first_loss_proxy) * 100, 1),
    `Conc Dominance = 1 (%)` = round(mean(concessional_dominance) * 100, 1),
    `Mean Guarantee Share` = round(mean(guarantee_share), 3),
    `Mean Concessional Share` = round(mean(concessional_mechanism_share), 3),
    `Mean Credit Line Share` = round(mean(credit_line_share), 3),
    .groups = "drop"
  ) %>%
  arrange(match(climate_detail, c("adaptation", "mitigation", "climate-other", "non-climate")))

kable(table3, caption = "Table 3: Structural Proxy Variables by Climate Category") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE)
Table 3: Structural Proxy Variables by Climate Category
climate_detail N First-Loss = 1 (%) Conc Dominance = 1 (%) Mean Guarantee Share Mean Concessional Share Mean Credit Line Share
adaptation 254 20.5 26.4 0.139 0.250 0.112
mitigation 980 28.7 28.7 0.192 0.279 0.087
climate-other 2270 47.3 41.6 0.258 0.417 0.159
non-climate 3053 42.6 46.2 0.366 0.459 0.094

Key finding to highlight: If adaptation shows a LOWER first-loss proxy despite higher overall concessionality, this is the instrument mismatch; adaptation deploys concessional capital through lower-leverage channels (credit lines, direct investment) rather than guarantees.

2.5 Bar Chart: Instrument Composition by Climate Category

Visual for the instrument mismatch argument.

instrument_data <- df %>%
  group_by(climate_detail) %>%
  summarise(
    Guarantees = mean(guarantee_share),
    `Direct Investment` = mean(direct_inv_share),
    `Syndicated Loans` = mean(synd_loan_share),
    `Shares in CIVs` = mean(civ_share_share),
    `Credit Lines` = mean(credit_line_share),
    .groups = "drop"
  ) %>%
  pivot_longer(cols = -climate_detail, names_to = "Instrument", values_to = "Share") %>%
  mutate(climate_detail = factor(climate_detail,
         levels = c("adaptation", "mitigation", "climate-other", "non-climate")))

ggplot(instrument_data, aes(x = climate_detail, y = Instrument, fill = Share)) +
  geom_tile(color = "white", linewidth = 1.2) +
  geom_text(aes(label = scales::percent(Share, accuracy = 0.1)), size = 3.5) +
  scale_fill_gradient(low = "#f7fbff", high = "#2171B5", labels = scales::percent) +
  labs(title = "Instrument Composition by Climate Category",
       subtitle = "Mean share of total mobilisation by leveraging mechanism",
       x = "", y = "", fill = "Share") +
  theme_minimal(base_size = 12) +
  theme(panel.grid = element_blank(),
        axis.text.x = element_text(angle = 0, hjust = 0.5))


SECTION 5.2: H1 Concessionality Comparison

2.6 H1 Regressions

H1: Climate adaptation portfolios exhibit a higher share of concessional mechanisms than mitigation and non-climate portfolios.

DV: concessional_mechanism_share Key IV: adaptation_tag Expected: β₁ > 0 (positive, significant)

Model H1a: Baseline (climate tags + year trend)

h1a <- lm(concessional_mechanism_share ~ adaptation_tag + mitigation_tag + year_c,
           data = df)

Model H1b: + Macroeconomic controls

h1b <- lm(concessional_mechanism_share ~ adaptation_tag + mitigation_tag + year_c +
           log_gdp_pc + gdp_growth + fdi_net,
           data = df)

Model H1c: + Governance control

h1c <- lm(concessional_mechanism_share ~ adaptation_tag + mitigation_tag + year_c +
           log_gdp_pc + gdp_growth + fdi_net + gov_quality_index,
           data = df)

2.7 H1 Results with Clustered Standard Errors

All standard errors are clustered at the country level to account for within-country correlation.

# Clustered standard errors at country level
cl_h1a <- vcovCL(h1a, cluster = df$country_code)
cl_h1b <- vcovCL(h1b, cluster = df$country_code)
cl_h1c <- vcovCL(h1c, cluster = df$country_code)

# Results with clustered SEs
cat("=== H1a: Baseline ===\n")
## === H1a: Baseline ===
print(coeftest(h1a, vcov = cl_h1a))
## 
## t test of coefficients:
## 
##                  Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)     0.4497492  0.0227481 19.7708 < 2.2e-16 ***
## adaptation_tag -0.1758196  0.0288763 -6.0887 1.203e-09 ***
## mitigation_tag -0.1600531  0.0278405 -5.7489 9.385e-09 ***
## year_c         -0.0136026  0.0036798 -3.6966 0.0002203 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cat("\n=== H1b: + Macro Controls ===\n")
## 
## === H1b: + Macro Controls ===
print(coeftest(h1b, vcov = cl_h1b))
## 
## t test of coefficients:
## 
##                  Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)     1.1267019  0.1399874  8.0486 9.870e-16 ***
## adaptation_tag -0.1679671  0.0285480 -5.8837 4.211e-09 ***
## mitigation_tag -0.1503090  0.0277659 -5.4134 6.402e-08 ***
## year_c         -0.0116674  0.0036543 -3.1928  0.001416 ** 
## log_gdp_pc     -0.0857018  0.0170536 -5.0254 5.156e-07 ***
## gdp_growth     -0.0032008  0.0021754 -1.4713  0.141247    
## fdi_net         0.0043092  0.0031579  1.3646  0.172428    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cat("\n=== H1c: + Governance ===\n")
## 
## === H1c: + Governance ===
print(coeftest(h1c, vcov = cl_h1c))
## 
## t test of coefficients:
## 
##                     Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)        1.2252351  0.1978443  6.1929 6.264e-10 ***
## adaptation_tag    -0.1692735  0.0286073 -5.9171 3.441e-09 ***
## mitigation_tag    -0.1513733  0.0280145 -5.4034 6.770e-08 ***
## year_c            -0.0113589  0.0037018 -3.0685   0.00216 ** 
## log_gdp_pc        -0.0956913  0.0221958 -4.3112 1.647e-05 ***
## gdp_growth        -0.0034229  0.0021121 -1.6206   0.10515    
## fdi_net            0.0039178  0.0031579  1.2406   0.21479    
## gov_quality_index  0.0374483  0.0458747  0.8163   0.41435    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

2.8 H1 Regression Table (Publication-Ready)

stargazer(h1a, h1b, h1c,
          type = "html",
          title = "Table 4: H1: Concessionality Mechanism Share Regressions",
          dep.var.labels = "Concessional Mechanism Share",
          column.labels = c("Baseline", "+ Macro", "+ Governance"),
          covariate.labels = c("Adaptation Tag", "Mitigation Tag", "Year (centered)",
                               "Log GDP per capita", "GDP Growth", "FDI Net Inflows",
                               "Governance Quality Index"),
          se = list(sqrt(diag(cl_h1a)), sqrt(diag(cl_h1b)), sqrt(diag(cl_h1c))),
          omit.stat = c("f"),
          notes = "Standard errors clustered at the country level.",
          notes.append = TRUE,
          star.cutoffs = c(0.1, 0.05, 0.01),
          header = FALSE)
Table 4: H1: Concessionality Mechanism Share Regressions
Dependent variable:
Concessional Mechanism Share
Baseline
  • Macro
  • Governance
(1) (2) (3)
Adaptation Tag -0.176*** -0.168*** -0.169***
(0.029) (0.029) (0.029)
Mitigation Tag -0.160*** -0.150*** -0.151***
(0.028) (0.028) (0.028)
Year (centered) -0.014*** -0.012*** -0.011***
(0.004) (0.004) (0.004)
Log GDP per capita -0.086*** -0.096***
(0.017) (0.022)
GDP Growth -0.003 -0.003
(0.002) (0.002)
FDI Net Inflows 0.004 0.004
(0.003) (0.003)
Governance Quality Index 0.037
(0.046)
Constant 0.450*** 1.127*** 1.225***
(0.023) (0.140) (0.198)
Observations 6,557 6,549 6,549
R2 0.028 0.062 0.063
Adjusted R2 0.028 0.061 0.062
Residual Std. Error 0.447 (df = 6553) 0.439 (df = 6542) 0.439 (df = 6541)
Note: p<0.1; p<0.05; p<0.01
Standard errors clustered at the country level.

2.9 H1 Diagnostics

# VIF check for the fullest model
cat("=== VIF for H1c ===\n")
## === VIF for H1c ===
print(vif(h1c))
##    adaptation_tag    mitigation_tag            year_c        log_gdp_pc 
##          1.013855          1.010363          1.017446          1.449632 
##        gdp_growth           fdi_net gov_quality_index 
##          1.034195          1.037501          1.460827
cat("\nInterpretation: VIF > 10 = problematic. VIF > 5 = watch carefully.\n")
## 
## Interpretation: VIF > 10 = problematic. VIF > 5 = watch carefully.
# R-squared comparison
cat("\n=== Model Fit ===\n")
## 
## === Model Fit ===
cat("H1a R²:", round(summary(h1a)$r.squared, 4), "\n")
## H1a R²: 0.0284
cat("H1b R²:", round(summary(h1b)$r.squared, 4), "\n")
## H1b R²: 0.0622
cat("H1c R²:", round(summary(h1c)$r.squared, 4), "\n")
## H1c R²: 0.0631

2.10 H1 Interpretation Guide

# Extract key coefficient
coef_adapt_h1c <- coeftest(h1c, vcov = cl_h1c)["adaptation_tag", ]
coef_mitig_h1c <- coeftest(h1c, vcov = cl_h1c)["mitigation_tag", ]

cat("=== H1 KEY FINDINGS ===\n\n")
## === H1 KEY FINDINGS ===
cat("Adaptation Tag coefficient:", round(coef_adapt_h1c[1], 4),
    "\n  SE:", round(coef_adapt_h1c[2], 4),
    "\n  p-value:", round(coef_adapt_h1c[4], 4),
    "\n  Significant:", ifelse(coef_adapt_h1c[4] < 0.1, "YES", "NO"), "\n\n")
## Adaptation Tag coefficient: -0.1693 
##   SE: 0.0286 
##   p-value: 0 
##   Significant: YES
cat("Mitigation Tag coefficient:", round(coef_mitig_h1c[1], 4),
    "\n  SE:", round(coef_mitig_h1c[2], 4),
    "\n  p-value:", round(coef_mitig_h1c[4], 4),
    "\n  Significant:", ifelse(coef_mitig_h1c[4] < 0.1, "YES", "NO"), "\n\n")
## Mitigation Tag coefficient: -0.1514 
##   SE: 0.028 
##   p-value: 0 
##   Significant: YES
cat("Interpretation:\n")
## Interpretation:
if (coef_adapt_h1c[1] > 0 & coef_adapt_h1c[4] < 0.1) {
  cat("  H1 SUPPORTED: Adaptation portfolios use significantly MORE\n")
  cat("     concessional mechanisms than non-climate portfolios.\n")
  cat("     Adaptation portfolios have a", round(coef_adapt_h1c[1] * 100, 1),
      "percentage point higher\n     concessional mechanism share.\n")
} else if (coef_adapt_h1c[4] >= 0.1) {
  cat(" ️ H1 NOT SUPPORTED: Coefficient is not statistically significant.\n")
  cat("     The difference in concessionality is not distinguishable from zero.\n")
} else {
  cat("  H1 REVERSED: Adaptation portfolios use LESS concessional mechanisms.\n")
}
##   H1 REVERSED: Adaptation portfolios use LESS concessional mechanisms.
if (coef_adapt_h1c[1] > coef_mitig_h1c[1]) {
  cat("\n  Adaptation requires MORE concessionality than mitigation\n")
  cat("  (adaptation β =", round(coef_adapt_h1c[1], 4),
      "> mitigation β =", round(coef_mitig_h1c[1], 4), ")\n")
}

Summary Checklist — Before Moving to Step 3