Step 3: H2, H3, H4: Core Hypothesis Tests

This notebook covers Sections 5.3, 5.4, and 5.5 of the thesis — the three central hypothesis tests.

3.1 Load Packages & Data

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

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.3: H2 The Adaptation Discount

H2: Climate adaptation portfolios achieve lower private capital mobilisation than mitigation and non-climate portfolios, after controlling for country characteristics and provider type.

DV: ihs_mob_total (IHS-transformed total mobilised USD M)
Key IV: adaptation_tag
Expected: β₁ < 0 (negative, significant)

3.2 H2 Progressive OLS — Four Models

The progressive approach adds controls step by step so you can see whether the adaptation coefficient changes this tells what is driving the discount.

# Model 1: Baseline — climate tags + year trend only
h2_m1 <- lm(ihs_mob_total ~ adaptation_tag + mitigation_tag + year_c,
             data = df)

# Model 2: + Macroeconomic controls
h2_m2 <- lm(ihs_mob_total ~ adaptation_tag + mitigation_tag + year_c +
              log_gdp_pc + gdp_growth + fdi_net,
              data = df)

# Model 3: + Governance (key test: does adaptation coefficient change?)
h2_m3 <- lm(ihs_mob_total ~ adaptation_tag + mitigation_tag + year_c +
              log_gdp_pc + gdp_growth + fdi_net + gov_quality_index,
              data = df)

# Model 4: + Structural proxies (robustness: first-loss + concessional dominance)
h2_m4 <- lm(ihs_mob_total ~ adaptation_tag + mitigation_tag + year_c +
              log_gdp_pc + gdp_growth + fdi_net + gov_quality_index +
              first_loss_proxy + concessional_dominance,
              data = df)

3.3 H2 Clustered Standard Errors

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

cl_m1 <- vcovCL(h2_m1, cluster = df$country_code)
cl_m2 <- vcovCL(h2_m2, cluster = df$country_code)
cl_m3 <- vcovCL(h2_m3, cluster = df$country_code)
cl_m4 <- vcovCL(h2_m4, cluster = df$country_code)

cat("=== H2 Model 1: Baseline ===\n")
## === H2 Model 1: Baseline ===
print(coeftest(h2_m1, vcov = cl_m1))
## 
## t test of coefficients:
## 
##                 Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)     3.864622   0.100941 38.2859 < 2.2e-16 ***
## adaptation_tag -0.238145   0.153328 -1.5532  0.120431    
## mitigation_tag  0.313942   0.118836  2.6418  0.008266 ** 
## year_c          0.013409   0.016218  0.8268  0.408377    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cat("\n=== H2 Model 2: + Macro ===\n")
## 
## === H2 Model 2: + Macro ===
print(coeftest(h2_m2, vcov = cl_m2))
## 
## t test of coefficients:
## 
##                  Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)    -0.2571804  0.6546698 -0.3928   0.69445    
## adaptation_tag -0.3015347  0.1510175 -1.9967   0.04590 *  
## mitigation_tag  0.2503697  0.1179264  2.1231   0.03378 *  
## year_c          0.0016309  0.0156178  0.1044   0.91683    
## log_gdp_pc      0.5171035  0.0819013  6.3137 2.902e-10 ***
## gdp_growth      0.0124010  0.0135774  0.9134   0.36109    
## fdi_net        -0.0082209  0.0106091 -0.7749   0.43843    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cat("\n=== H2 Model 3: + Governance ===\n")
## 
## === H2 Model 3: + Governance ===
print(coeftest(h2_m3, vcov = cl_m3))
## 
## t test of coefficients:
## 
##                     Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)       -0.1720753  0.9626496 -0.1788   0.85814    
## adaptation_tag    -0.3026631  0.1503558 -2.0130   0.04416 *  
## mitigation_tag     0.2494505  0.1151830  2.1657   0.03037 *  
## year_c             0.0018973  0.0164678  0.1152   0.90828    
## log_gdp_pc         0.5084753  0.1108976  4.5851 4.622e-06 ***
## gdp_growth         0.0122092  0.0133581  0.9140   0.36076    
## fdi_net           -0.0085590  0.0111434 -0.7681   0.44247    
## gov_quality_index  0.0323448  0.2245791  0.1440   0.88549    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
cat("\n=== H2 Model 4: + Structural Proxies ===\n")
## 
## === H2 Model 4: + Structural Proxies ===
print(coeftest(h2_m4, vcov = cl_m4))
## 
## t test of coefficients:
## 
##                          Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)            -1.7182376  0.9228837 -1.8618   0.06267 .  
## adaptation_tag         -0.0331570  0.1472039 -0.2252   0.82180    
## mitigation_tag          0.4061160  0.1028366  3.9491 7.926e-05 ***
## year_c                  0.0044127  0.0155607  0.2836   0.77674    
## log_gdp_pc              0.6432305  0.1055508  6.0940 1.164e-09 ***
## gdp_growth              0.0128184  0.0124543  1.0292   0.30341    
## fdi_net                -0.0123971  0.0115436 -1.0739   0.28289    
## gov_quality_index       0.0338747  0.2160878  0.1568   0.87544    
## first_loss_proxy        1.6273416  0.1419779 11.4619 < 2.2e-16 ***
## concessional_dominance -0.5598390  0.1415683 -3.9546 7.749e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

3.4 H2 Regression Table 5 (Publication-Ready)

stargazer(h2_m1, h2_m2, h2_m3, h2_m4,
          type = "html",
          title = "H2: Adaptation Discount: Progressive OLS Regressions",
          dep.var.labels = "IHS Total Mobilised (USD M)",
          column.labels = c("Baseline", "+ Macro", "+ Governance", "+ Structural"),
          covariate.labels = c("Adaptation Tag", "Mitigation Tag",
                                "Year (centered)", "Log GDP per capita",
                                "GDP Growth", "FDI Net Inflows",
                                "Governance Quality Index",
                                "First-Loss Proxy", "Concessional Dominance"),
          se = list(sqrt(diag(cl_m1)), sqrt(diag(cl_m2)),
                    sqrt(diag(cl_m3)), sqrt(diag(cl_m4))),
          omit.stat = c("f"),
          notes = "Standard errors clustered at the country level.",
          notes.append = TRUE,
          star.cutoffs = c(0.1, 0.05, 0.01))
H2: Adaptation Discount: Progressive OLS Regressions
Dependent variable:
IHS Total Mobilised (USD M)
Baseline
  • Macro
  • Governance
  • Structural
(1) (2) (3) (4)
Adaptation Tag -0.238 -0.302** -0.303** -0.033
(0.153) (0.151) (0.150) (0.147)
Mitigation Tag 0.314*** 0.250** 0.249** 0.406***
(0.119) (0.118) (0.115) (0.103)
Year (centered) 0.013 0.002 0.002 0.004
(0.016) (0.016) (0.016) (0.016)
Log GDP per capita 0.517*** 0.508*** 0.643***
(0.082) (0.111) (0.106)
GDP Growth 0.012 0.012 0.013
(0.014) (0.013) (0.012)
FDI Net Inflows -0.008 -0.009 -0.012
(0.011) (0.011) (0.012)
Governance Quality Index 0.032 0.034
(0.225) (0.216)
First-Loss Proxy 1.627***
(0.142)
Concessional Dominance -0.560***
(0.142)
Constant 3.865*** -0.257 -0.172 -1.718*
(0.101) (0.655) (0.963) (0.923)
Observations 6,557 6,549 6,549 6,549
R2 0.004 0.062 0.062 0.167
Adjusted R2 0.004 0.061 0.061 0.166
Residual Std. Error 1.983 (df = 6553) 1.924 (df = 6542) 1.924 (df = 6541) 1.813 (df = 6539)
Note: p<0.1; p<0.05; p<0.01
Standard errors clustered at the country level.

3.5 H2 Coefficient Tracking

This is the key diagnostic: how does the adaptation coefficient move across models?

# Extract adaptation coefficient from each model
adapt_coefs <- data.frame(
  Model = c("M1: Baseline", "M2: + Macro", "M3: + Governance", "M4: + Structural"),
  Coefficient = c(
    coeftest(h2_m1, vcov = cl_m1)["adaptation_tag", 1],
    coeftest(h2_m2, vcov = cl_m2)["adaptation_tag", 1],
    coeftest(h2_m3, vcov = cl_m3)["adaptation_tag", 1],
    coeftest(h2_m4, vcov = cl_m4)["adaptation_tag", 1]
  ),
  SE = c(
    coeftest(h2_m1, vcov = cl_m1)["adaptation_tag", 2],
    coeftest(h2_m2, vcov = cl_m2)["adaptation_tag", 2],
    coeftest(h2_m3, vcov = cl_m3)["adaptation_tag", 2],
    coeftest(h2_m4, vcov = cl_m4)["adaptation_tag", 2]
  ),
  P_value = c(
    coeftest(h2_m1, vcov = cl_m1)["adaptation_tag", 4],
    coeftest(h2_m2, vcov = cl_m2)["adaptation_tag", 4],
    coeftest(h2_m3, vcov = cl_m3)["adaptation_tag", 4],
    coeftest(h2_m4, vcov = cl_m4)["adaptation_tag", 4]
  )
)

adapt_coefs$Significant <- ifelse(adapt_coefs$P_value < 0.01, "***",
                            ifelse(adapt_coefs$P_value < 0.05, "**",
                            ifelse(adapt_coefs$P_value < 0.1, "*", "")))

cat("=== ADAPTATION COEFFICIENT TRACKING ACROSS MODELS ===\n\n")
## === ADAPTATION COEFFICIENT TRACKING ACROSS MODELS ===
print(adapt_coefs, row.names = FALSE)
##             Model Coefficient        SE    P_value Significant
##      M1: Baseline  -0.2381452 0.1533285 0.12043083            
##       M2: + Macro  -0.3015347 0.1510175 0.04590058          **
##  M3: + Governance  -0.3026631 0.1503558 0.04415771          **
##  M4: + Structural  -0.0331570 0.1472039 0.82179543
# Attenuation from M1 to M3
atten_m1_m3 <- (adapt_coefs$Coefficient[1] - adapt_coefs$Coefficient[3]) /
                adapt_coefs$Coefficient[1] * 100

# Attenuation from M3 to M4
atten_m3_m4 <- (adapt_coefs$Coefficient[3] - adapt_coefs$Coefficient[4]) /
                adapt_coefs$Coefficient[3] * 100

cat("\nAttenuation from M1 to M3 (adding macro + governance):",
    round(atten_m1_m3, 1), "%\n")
## 
## Attenuation from M1 to M3 (adding macro + governance): -27.1 %
cat("Attenuation from M3 to M4 (adding structural proxies):",
    round(atten_m3_m4, 1), "%\n")
## Attenuation from M3 to M4 (adding structural proxies): 89 %
cat("\nInterpretation:\n")
## 
## Interpretation:
cat("  < 10% attenuation = discount is robust to controls\n")
##   < 10% attenuation = discount is robust to controls
cat("  10-30% attenuation = partial mediation\n")
##   10-30% attenuation = partial mediation
cat("  > 30% attenuation = substantial mediation through that channel\n")
##   > 30% attenuation = substantial mediation through that channel

3.6 H2 Visualization: Coefficient Plot

library(kableExtra)

adapt_coefs$CI_lower <- adapt_coefs$Coefficient - 1.96 * adapt_coefs$SE
adapt_coefs$CI_upper <- adapt_coefs$Coefficient + 1.96 * adapt_coefs$SE
adapt_coefs$Sig <- ifelse(adapt_coefs$CI_upper < 0, "Yes", "No")

adapt_coefs %>%
  mutate(
    Coefficient = sprintf("%.3f", Coefficient),
    SE = sprintf("(%.3f)", SE),
    `95% CI` = paste0("[", sprintf("%.3f", CI_lower), ", ", sprintf("%.3f", CI_upper), "]")
  ) %>%
  select(Model, Coefficient, SE, `95% CI`, Sig) %>%
  kbl(caption = "Table 5.5: Adaptation Tag Coefficient Across Progressive Models (H2)",
      col.names = c("Model", "Coefficient", "SE", "95\\% CI", "Sig. at 5\\%"),
      align = "lcccc",
      booktabs = TRUE) %>%
  kable_styling(latex_options = c("hold_position"),
                full_width = FALSE) %>%
  footnote(general = "Clustered standard errors at the country level. Significance based on 95% CI excluding zero.")
Table 5.5: Adaptation Tag Coefficient Across Progressive Models (H2)
Model Coefficient SE 95% CI Sig. at 5%
M1: Baseline -0.238 (0.153) [-0.539, 0.062] No
M2: + Macro -0.302 (0.151) [-0.598, -0.006] Yes
M3: + Governance -0.303 (0.150) [-0.597, -0.008] Yes
M4: + Structural -0.033 (0.147) [-0.322, 0.255] No
Note:
Clustered standard errors at the country level. Significance based on 95% CI excluding zero.

3.7 H2 Model Fit & Diagnostics

cat("=== MODEL FIT ===\n")
## === MODEL FIT ===
cat("M1 R²:", round(summary(h2_m1)$r.squared, 4),
    "| Adj R²:", round(summary(h2_m1)$adj.r.squared, 4), "\n")
## M1 R²: 0.0043 | Adj R²: 0.0038
cat("M2 R²:", round(summary(h2_m2)$r.squared, 4),
    "| Adj R²:", round(summary(h2_m2)$adj.r.squared, 4), "\n")
## M2 R²: 0.0622 | Adj R²: 0.0614
cat("M3 R²:", round(summary(h2_m3)$r.squared, 4),
    "| Adj R²:", round(summary(h2_m3)$adj.r.squared, 4), "\n")
## M3 R²: 0.0622 | Adj R²: 0.0612
cat("M4 R²:", round(summary(h2_m4)$r.squared, 4),
    "| Adj R²:", round(summary(h2_m4)$adj.r.squared, 4), "\n")
## M4 R²: 0.1674 | Adj R²: 0.1663
cat("\n=== VIF for M4 (fullest model) ===\n")
## 
## === VIF for M4 (fullest model) ===
print(vif(h2_m4))
##         adaptation_tag         mitigation_tag                 year_c 
##               1.021817               1.024933               1.024026 
##             log_gdp_pc             gdp_growth                fdi_net 
##               1.502874               1.035529               1.039089 
##      gov_quality_index       first_loss_proxy concessional_dominance 
##               1.462362               1.728037               1.702939

3.8 H2 Interpretation

coef_h2 <- coeftest(h2_m3, vcov = cl_m3)["adaptation_tag", ]
coef_h2_mit <- coeftest(h2_m3, vcov = cl_m3)["mitigation_tag", ]

cat("=== H2 KEY FINDINGS ===\n\n")
## === H2 KEY FINDINGS ===
cat("Adaptation coefficient (M3):", round(coef_h2[1], 4), "\n")
## Adaptation coefficient (M3): -0.3027
cat("  p-value:", format(coef_h2[4], scientific = TRUE), "\n")
##   p-value: 4.415771e-02
cat("Mitigation coefficient (M3):", round(coef_h2_mit[1], 4), "\n")
## Mitigation coefficient (M3): 0.2495
cat("  p-value:", format(coef_h2_mit[4], scientific = TRUE), "\n\n")
##   p-value: 3.03711e-02
if (coef_h2[1] < 0 & coef_h2[4] < 0.05) {
  cat(" H2 SUPPORTED: Adaptation portfolios mobilise significantly LESS\n")
  cat("   private capital than non-climate portfolios.\n")
  cat("   The IHS coefficient of", round(coef_h2[1], 3),
      "implies adaptation portfolios mobilise\n")
  cat("   approximately", round((exp(coef_h2[1]) - 1) * 100, 1),
      "% less private capital.\n")
} else {
  cat("️ H2 NOT SUPPORTED at 5% level.\n")
}
##  H2 SUPPORTED: Adaptation portfolios mobilise significantly LESS
##    private capital than non-climate portfolios.
##    The IHS coefficient of -0.303 implies adaptation portfolios mobilise
##    approximately -26.1 % less private capital.
if (abs(coef_h2[1]) > abs(coef_h2_mit[1])) {
  cat("\n   Adaptation penalty (", round(abs(coef_h2[1]), 3),
      ") > Mitigation penalty (", round(abs(coef_h2_mit[1]), 3),
      ")\n   → Adaptation is more severely penalised.\n")
}
## 
##    Adaptation penalty ( 0.303 ) > Mitigation penalty ( 0.249 )
##    → Adaptation is more severely penalised.
cat("\n   COMBINED WITH H1: Adaptation portfolios use FEWER concessional\n")
## 
##    COMBINED WITH H1: Adaptation portfolios use FEWER concessional
cat("   instruments (H1 finding) AND mobilise LESS private capital (H2).\n")
##    instruments (H1 finding) AND mobilise LESS private capital (H2).
cat("   This establishes the ADAPTATION PARADOX.\n")
##    This establishes the ADAPTATION PARADOX.

SECTION 5.4: H3 Governance and Country Risk

H3: Higher country governance quality increases private capital mobilisation; the adaptation discount persists after controlling for governance heterogeneity.

3.9 H3 Specification A Composite Governance Index

This is already captured in H2 Model 3

# Already estimated as h2_m3 — reuse
cat("=== H3 Spec A: Composite Governance Index ===\n")
## === H3 Spec A: Composite Governance Index ===
cat("(Same as H2 Model 3)\n\n")
## (Same as H2 Model 3)
print(coeftest(h2_m3, vcov = cl_m3))
## 
## t test of coefficients:
## 
##                     Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)       -0.1720753  0.9626496 -0.1788   0.85814    
## adaptation_tag    -0.3026631  0.1503558 -2.0130   0.04416 *  
## mitigation_tag     0.2494505  0.1151830  2.1657   0.03037 *  
## year_c             0.0018973  0.0164678  0.1152   0.90828    
## log_gdp_pc         0.5084753  0.1108976  4.5851 4.622e-06 ***
## gdp_growth         0.0122092  0.0133581  0.9140   0.36076    
## fdi_net           -0.0085590  0.0111434 -0.7681   0.44247    
## gov_quality_index  0.0323448  0.2245791  0.1440   0.88549    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

3.10 H3 Specification B Full WGI Decomposition

interesting specification = which governance dimensions matter?

h3_decomp <- lm(ihs_mob_total ~ adaptation_tag + mitigation_tag + year_c +
                  log_gdp_pc + gdp_growth + fdi_net +
                  va_est + pv_est + ge_est + rq_est + rl_est + cc_est,
                  data = df)

# Clustered SEs
cl_h3 <- vcovCL(h3_decomp, cluster = df$country_code)

cat("=== H3 Spec B: WGI Decomposition ===\n")
## === H3 Spec B: WGI Decomposition ===
print(coeftest(h3_decomp, vcov = cl_h3))
## 
## t test of coefficients:
## 
##                  Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)    -0.1676389  0.7779066 -0.2155  0.829384    
## adaptation_tag -0.4033724  0.1549046 -2.6040  0.009235 ** 
## mitigation_tag  0.2049666  0.1125683  1.8208  0.068680 .  
## year_c          0.0047453  0.0151156  0.3139  0.753581    
## log_gdp_pc      0.4922566  0.0958828  5.1339 2.919e-07 ***
## gdp_growth      0.0085281  0.0121527  0.7018  0.482860    
## fdi_net         0.0038960  0.0075008  0.5194  0.603496    
## va_est          0.0698647  0.1587264  0.4402  0.659837    
## pv_est         -0.7355613  0.1158698 -6.3482 2.325e-10 ***
## ge_est          0.9285000  0.2138548  4.3417 1.435e-05 ***
## rq_est         -0.5285051  0.2360624 -2.2388  0.025200 *  
## rl_est          0.7761803  0.2947197  2.6336  0.008468 ** 
## cc_est         -0.3903539  0.1933624 -2.0188  0.043552 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

3.11 H3 Regression Table 6

coeftest(h3_decomp, vcov = cl_h3)["va_est", ]
##   Estimate Std. Error    t value   Pr(>|t|) 
## 0.06986472 0.15872637 0.44015824 0.65983706
# Clustered coefficient tests
ct_m3 <- coeftest(h2_m3, vcov = cl_m3)
ct_h3 <- coeftest(h3_decomp, vcov = cl_h3)

# Extract clustered standard errors
se_m3 <- ct_m3[, "Std. Error"]
se_h3 <- ct_h3[, "Std. Error"]

# Extract clustered p-values
p_m3 <- ct_m3[, "Pr(>|t|)"]
p_h3 <- ct_h3[, "Pr(>|t|)"]

stargazer(
  h2_m3, h3_decomp,
  type = "html",
  title = "H3: Governance Decomposition",
  dep.var.labels = "IHS Total Mobilised (USD M)",
  column.labels = c("Composite Index", "WGI Decomposition"),

  # Force exact variable order
  order = c(
    "adaptation_tag",
    "mitigation_tag",
    "year_c",
    "log_gdp_pc",
    "gdp_growth",
    "fdi_net",
    "gov_quality_index",
    "va_est",
    "pv_est",
    "ge_est",
    "rq_est",
    "rl_est",
    "cc_est"
  ),

  # Use safe labels: no ampersand
  covariate.labels = c(
    "Adaptation Tag",
    "Mitigation Tag",
    "Year (centered)",
    "Log GDP per capita",
    "GDP Growth",
    "FDI Net Inflows",
    "Governance Quality (composite)",
    "Voice and Accountability",
    "Political Stability",
    "Government Effectiveness",
    "Regulatory Quality",
    "Rule of Law",
    "Control of Corruption"
  ),

  se = list(se_m3, se_h3),
  p = list(p_m3, p_h3),

  omit.stat = c("f"),
  notes = c(
    "*p < 0.1; **p < 0.05; ***p < 0.01",
    "Standard errors clustered at the country level."
  ),
  notes.append = FALSE,
  star.cutoffs = c(0.1, 0.05, 0.01)
)
H3: Governance Decomposition
Dependent variable:
IHS Total Mobilised (USD M)
Composite Index WGI Decomposition
(1) (2)
Adaptation Tag -0.303** -0.403***
(0.150) (0.155)
Mitigation Tag 0.249** 0.205*
(0.115) (0.113)
Year (centered) 0.002 0.005
(0.016) (0.015)
Log GDP per capita 0.508*** 0.492***
(0.111) (0.096)
GDP Growth 0.012 0.009
(0.013) (0.012)
FDI Net Inflows -0.009 0.004
(0.011) (0.008)
Governance Quality (composite) 0.032
(0.225)
Voice and Accountability 0.070
(0.159)
Political Stability -0.736***
(0.116)
Government Effectiveness 0.928***
(0.214)
Regulatory Quality -0.529**
(0.236)
Rule of Law 0.776***
(0.295)
Control of Corruption -0.390**
(0.193)
Constant -0.172 -0.168
(0.963) (0.778)
Observations 6,549 6,549
R2 0.062 0.112
Adjusted R2 0.061 0.111
Residual Std. Error 1.924 (df = 6541) 1.873 (df = 6536)
Note: p < 0.1; p < 0.05; p < 0.01
Standard errors clustered at the country level.

3.12 H3 VIF Check Critical for Decomposition

WGI dimensions are highly correlated. VIFs > 5 are expected; > 10 is problematic.

cat("=== VIF for H3 Decomposition ===\n")
## === VIF for H3 Decomposition ===
vif_h3 <- vif(h3_decomp)
print(round(vif_h3, 2))
## adaptation_tag mitigation_tag         year_c     log_gdp_pc     gdp_growth 
##           1.02           1.01           1.03           2.10           1.08 
##        fdi_net         va_est         pv_est         ge_est         rq_est 
##           1.07           2.43           1.81           4.76           4.84 
##         rl_est         cc_est 
##           8.08           4.09
cat("\nMax VIF:", round(max(vif_h3), 2), "\n")
## 
## Max VIF: 8.08
if (max(vif_h3) > 10) {
  cat("️ WARNING: VIF > 10 detected. Multicollinearity is a concern.\n")
  cat("   Report BOTH Spec A and Spec B. Interpret individual WGI\n")
  cat("   coefficients with caution.\n")
} else if (max(vif_h3) > 5) {
  cat("️ CAUTION: VIF > 5 detected. Moderate multicollinearity.\n")
  cat("   Report both specifications. Note in limitations.\n")
} else {
  cat(" All VIFs < 5. No multicollinearity concerns.\n")
}
## ️ CAUTION: VIF > 5 detected. Moderate multicollinearity.
##    Report both specifications. Note in limitations.

3.13 H3 Interpretation

coef_h3 <- coeftest(h3_decomp, vcov = cl_h3)
adapt_h3 <- coef_h3["adaptation_tag", ]

cat("=== H3 KEY FINDINGS ===\n\n")
## === H3 KEY FINDINGS ===
cat("Adaptation coefficient (decomposition):", round(adapt_h3[1], 4),
    "  p:", format(adapt_h3[4], scientific = TRUE), "\n\n")
## Adaptation coefficient (decomposition): -0.4034   p: 9.23497e-03
# Which WGI dimensions are significant?
wgi_names <- c("va_est", "pv_est", "ge_est", "rq_est", "rl_est", "cc_est")
wgi_labels <- c("Voice & Accountability", "Political Stability",
                 "Government Effectiveness", "Regulatory Quality",
                 "Rule of Law", "Control of Corruption")

cat("Governance dimensions:\n")
## Governance dimensions:
for (i in 1:length(wgi_names)) {
  coef_val <- coef_h3[wgi_names[i], 1]
  p_val <- coef_h3[wgi_names[i], 4]
  sig <- ifelse(p_val < 0.01, "***", ifelse(p_val < 0.05, "**",
          ifelse(p_val < 0.1, "*", "   ")))
  cat("  ", wgi_labels[i], ":", round(coef_val, 4), sig, "\n")
}
##    Voice & Accountability : 0.0699     
##    Political Stability : -0.7356 *** 
##    Government Effectiveness : 0.9285 *** 
##    Regulatory Quality : -0.5285 ** 
##    Rule of Law : 0.7762 *** 
##    Control of Corruption : -0.3904 **
cat("\nDoes adaptation discount persist after governance?\n")
## 
## Does adaptation discount persist after governance?
if (adapt_h3[4] < 0.05) {
  cat("   YES — adaptation coefficient remains significant.\n")
  cat("  → The discount is NOT just a governance/country-risk story.\n")
  cat("  → Problem is in INSTRUMENT DESIGN, not country conditions.\n")
} else {
  cat(" ️ Adaptation coefficient is no longer significant.\n")
  cat("  → Governance may explain the discount.\n")
}
##    YES — adaptation coefficient remains significant.
##   → The discount is NOT just a governance/country-risk story.
##   → Problem is in INSTRUMENT DESIGN, not country conditions.

SECTION 5.5: H4 Non-Linear Concessionality

H4: Higher concessionality (guarantee share) increases mobilisation, with diminishing marginal returns at high levels.

3.14 H4 Quadratic Guarantee Model

# H4 main specification
h4 <- lm(ihs_mob_total ~ guarantee_share + guarantee_share_sq +
           adaptation_tag + mitigation_tag + year_c +
           log_gdp_pc + gdp_growth + fdi_net + gov_quality_index,
           data = df)

# Clustered SEs
cl_h4 <- vcovCL(h4, cluster = df$country_code)

cat("=== H4: Non-Linear Concessionality ===\n")
## === H4: Non-Linear Concessionality ===
print(coeftest(h4, vcov = cl_h4))
## 
## t test of coefficients:
## 
##                      Estimate Std. Error  t value  Pr(>|t|)    
## (Intercept)        -0.9857484  0.9095269  -1.0838  0.278492    
## guarantee_share    10.0125368  0.3906669  25.6293 < 2.2e-16 ***
## guarantee_share_sq -9.8871965  0.3991751 -24.7691 < 2.2e-16 ***
## adaptation_tag     -0.1228729  0.1446289  -0.8496  0.395593    
## mitigation_tag      0.3169613  0.1025484   3.0908  0.002004 ** 
## year_c              0.0027547  0.0164498   0.1675  0.867011    
## log_gdp_pc          0.5612384  0.1042168   5.3853 7.484e-08 ***
## gdp_growth          0.0111308  0.0122204   0.9108  0.362413    
## fdi_net            -0.0086249  0.0099408  -0.8676  0.385629    
## gov_quality_index  -0.0178579  0.2008202  -0.0889  0.929144    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

3.15 H4 Regression Table 7

stargazer(
  h2_m3, h4,
  type = "html",
  title = "H4: Non-Linear Guarantee Effects",
  dep.var.labels = "IHS Total Mobilised (USD M)",
  column.labels = c("H2 Model 3 (ref)", "H4 Quadratic"),
  order = c(
    "adaptation_tag",
    "mitigation_tag",
    "year_c",
    "log_gdp_pc",
    "gdp_growth",
    "fdi_net",
    "gov_quality_index",
    "guarantee_share",
    "guarantee_share_sq"
  ),
  covariate.labels = c(
    "Adaptation Tag",
    "Mitigation Tag",
    "Year (centered)",
    "Log GDP per capita",
    "GDP Growth",
    "FDI Net Inflows",
    "Governance Quality Index",
    "Guarantee Share",
    "Guarantee Share²"
  ),

  se = list(
    sqrt(diag(cl_m3)),
    sqrt(diag(cl_h4))
  ),
  
  omit.stat = c("f"),
  star.cutoffs = c(0.1, 0.05, 0.01),
  
  notes = c(
    "*p < 0.1; **p < 0.05; ***p < 0.01",
    "Standard errors clustered at the country level."
  ),
  notes.append = FALSE
)
H4: Non-Linear Guarantee Effects
Dependent variable:
IHS Total Mobilised (USD M)
H2 Model 3 (ref) H4 Quadratic
(1) (2)
Adaptation Tag -0.303** -0.123
(0.150) (0.145)
Mitigation Tag 0.249** 0.317***
(0.115) (0.103)
Year (centered) 0.002 0.003
(0.016) (0.016)
Log GDP per capita 0.508*** 0.561***
(0.111) (0.104)
GDP Growth 0.012 0.011
(0.013) (0.012)
FDI Net Inflows -0.009 -0.009
(0.011) (0.010)
Governance Quality Index 0.032 -0.018
(0.225) (0.201)
Guarantee Share 10.013***
(0.391)
Guarantee Share² -9.887***
(0.399)
Constant -0.172 -0.986
(0.963) (0.910)
Observations 6,549 6,549
R2 0.062 0.191
Adjusted R2 0.061 0.190
Residual Std. Error 1.924 (df = 6541) 1.788 (df = 6539)
Note: p < 0.1; p < 0.05; p < 0.01
Standard errors clustered at the country level.

3.16 H4 Turning Point Calculation

If β₁ > 0 and β₂ < 0, the optimal guarantee share is: share* = −β₁ / (2·β₂)

b1 <- coef(h4)["guarantee_share"]
b2 <- coef(h4)["guarantee_share_sq"]

cat("=== H4 TURNING POINT ===\n\n")
## === H4 TURNING POINT ===
cat("β₁ (guarantee_share):", round(b1, 4), "\n")
## β₁ (guarantee_share): 10.0125
cat("β₂ (guarantee_share²):", round(b2, 4), "\n\n")
## β₂ (guarantee_share²): -9.8872
if (b1 > 0 & b2 < 0) {
  turning_point <- -b1 / (2 * b2)
  cat(" Inverted-U CONFIRMED\n")
  cat("   Optimal guarantee share:", round(turning_point, 3),
      "(", round(turning_point * 100, 1), "%)\n")
  cat("   Below this: more guarantees help mobilisation\n")
  cat("   Above this: diminishing/negative returns\n")
} else if (b1 > 0 & b2 >= 0) {
  cat("️ Positive linear, no diminishing returns detected.\n")
  cat("   Guarantees help but no inverted-U.\n")
} else if (b1 <= 0) {
  cat(" Linear term is not positive. Relationship is different than expected.\n")
  cat("   Check: is guarantee_share collinear with other variables?\n")
}
##  Inverted-U CONFIRMED
##    Optimal guarantee share: 0.506 ( 50.6 %)
##    Below this: more guarantees help mobilisation
##    Above this: diminishing/negative returns

3.17 H4 Predicted Curve Visualization

library(ggplot2)

# Create prediction data
pred_data <- data.frame(
  guarantee_share = seq(0, 1, by = 0.01)
)

pred_data$guarantee_share_sq <- pred_data$guarantee_share^2

# Set remaining model variables to baseline / sample means
pred_data$adaptation_tag <- 0
pred_data$mitigation_tag <- 0
pred_data$year_c <- 0
pred_data$log_gdp_pc <- mean(df$log_gdp_pc, na.rm = TRUE)
pred_data$gdp_growth <- mean(df$gdp_growth, na.rm = TRUE)
pred_data$fdi_net <- mean(df$fdi_net, na.rm = TRUE)
pred_data$gov_quality_index <- mean(df$gov_quality_index, na.rm = TRUE)

# Predictions with confidence intervals
preds <- predict(h4, newdata = pred_data, se.fit = TRUE)

pred_data$predicted <- preds$fit
pred_data$se <- preds$se.fit
pred_data$ci_lower <- pred_data$predicted - 1.96 * pred_data$se
pred_data$ci_upper <- pred_data$predicted + 1.96 * pred_data$se

# Optimal point
b1 <- coef(h4)["guarantee_share"]
b2 <- coef(h4)["guarantee_share_sq"]

optimal_x <- -b1 / (2 * b2)
optimal_y <- pred_data$predicted[which.min(abs(pred_data$guarantee_share - optimal_x))]

# Observed data range
q_low <- quantile(df$guarantee_share, 0.01, na.rm = TRUE)
q_high <- quantile(df$guarantee_share, 0.99, na.rm = TRUE)

# Plot
ggplot(pred_data, aes(x = guarantee_share, y = predicted)) +
  annotate("rect", xmin = 0, xmax = q_low, ymin = -Inf, ymax = Inf,
           fill = "grey90", alpha = 0.5) +
  annotate("rect", xmin = q_high, xmax = 1, ymin = -Inf, ymax = Inf,
           fill = "grey90", alpha = 0.5) +
  geom_ribbon(aes(ymin = ci_lower, ymax = ci_upper),
              fill = "#2A9D8F", alpha = 0.2) +
  geom_line(linewidth = 1.2, color = "#E74C3C") +
  geom_vline(xintercept = optimal_x,
             linetype = "dashed", color = "#2C3E50") +
  annotate("text",
           x = optimal_x + 0.05,
           y = optimal_y - 0.1,
           label = paste0("Optimal: ", round(optimal_x * 100, 1), "%"),
           hjust = 1,
           size = 3) +
  labs(
    title = "H4: Predicted Mobilisation by Guarantee Share",
    subtitle = "Non-linear relationship; climate dummies set to non-climate baseline and controls held at sample means",
    x = "Guarantee Share",
    y = "Predicted IHS Mobilised Private Finance"
  ) +
  theme_minimal(base_size = 12)

3.18 H4 Diagnostics

cat("=== H4 MODEL FIT ===\n")
## === H4 MODEL FIT ===
cat("R²:", round(summary(h4)$r.squared, 4), "\n")
## R²: 0.191
cat("Adj R²:", round(summary(h4)$adj.r.squared, 4), "\n\n")
## Adj R²: 0.1899
cat("=== VIF ===\n")
## === VIF ===
print(round(vif(h4), 2))
##    guarantee_share guarantee_share_sq     adaptation_tag     mitigation_tag 
##              34.93              34.87               1.02               1.02 
##             year_c         log_gdp_pc         gdp_growth            fdi_net 
##               1.02               1.51               1.03               1.04 
##  gov_quality_index 
##               1.46
# Check adaptation coefficient in H4
adapt_h4 <- coeftest(h4, vcov = cl_h4)["adaptation_tag", ]
cat("\nAdaptation coefficient in H4:", round(adapt_h4[1], 4),
    " p:", format(adapt_h4[4], scientific = TRUE), "\n")
## 
## Adaptation coefficient in H4: -0.1229  p: 3.95593e-01
cat("(Still significant? → discount persists beyond guarantee design)\n")
## (Still significant? → discount persists beyond guarantee design)

Summary of All Findings

cat("╔══════════════════════════════════════════════════════════════════╗\n")
## ╔══════════════════════════════════════════════════════════════════╗
cat("║              HYPOTHESIS TEST RESULTS SUMMARY                   ║\n")
## ║              HYPOTHESIS TEST RESULTS SUMMARY                   ║
cat("╠══════════════════════════════════════════════════════════════════╣\n\n")
## ╠══════════════════════════════════════════════════════════════════╣
# H1 (from Step 2 — restate)
cat("H1 (Concessionality Comparison):\n")
## H1 (Concessionality Comparison):
cat("   FINDING: Adaptation uses FEWER concessional instruments\n")
##    FINDING: Adaptation uses FEWER concessional instruments
cat("   (negative coefficient — opposite direction from expected)\n")
##    (negative coefficient — opposite direction from expected)
cat("   → Instrument mismatch confirmed\n\n")
##    → Instrument mismatch confirmed
# H2
coef_h2_final <- coeftest(h2_m3, vcov = cl_m3)["adaptation_tag", ]
cat("H2 (Adaptation Discount):\n")
## H2 (Adaptation Discount):
cat("   Coefficient:", round(coef_h2_final[1], 4), "\n")
##    Coefficient: -0.3027
cat("   p-value:", format(coef_h2_final[4], scientific = TRUE), "\n")
##    p-value: 4.415771e-02
cat("   FINDING:", ifelse(coef_h2_final[1] < 0 & coef_h2_final[4] < 0.05,
    "SUPPORTED — adaptation mobilises less ",
    "NOT SUPPORTED ️"), "\n\n")
##    FINDING: SUPPORTED — adaptation mobilises less
# H3
adapt_h3_final <- coeftest(h3_decomp, vcov = cl_h3)["adaptation_tag", ]
cat("H3 (Governance):\n")
## H3 (Governance):
cat("   Adaptation persists after WGI decomposition:", 
    round(adapt_h3_final[1], 4),
    "p:", format(adapt_h3_final[4], scientific = TRUE), "\n")
##    Adaptation persists after WGI decomposition: -0.4034 p: 9.23497e-03
cat("   FINDING:", ifelse(adapt_h3_final[4] < 0.05,
    "Discount persists — NOT just governance ",
    "Governance explains the discount ️"), "\n\n")
##    FINDING: Discount persists — NOT just governance
# H4
cat("H4 (Non-Linear Concessionality):\n")
## H4 (Non-Linear Concessionality):
cat("   β₁ (linear):", round(b1, 4), "\n")
##    β₁ (linear): 10.0125
cat("   β₂ (quadratic):", round(b2, 4), "\n")
##    β₂ (quadratic): -9.8872
if (b1 > 0 & b2 < 0) {
  cat("   FINDING: Inverted-U confirmed \n")
  cat("   Optimal guarantee share:", round(-b1 / (2 * b2) * 100, 1), "%\n")
} else {
  cat("   FINDING: Inverted-U NOT confirmed — see diagnostics\n")
}
##    FINDING: Inverted-U confirmed 
##    Optimal guarantee share: 50.6 %
cat("\n╠══════════════════════════════════════════════════════════════════╣\n")
## 
## ╠══════════════════════════════════════════════════════════════════╣
cat("║  ADAPTATION PARADOX:                                           ║\n")
## ║  ADAPTATION PARADOX:                                           ║
cat("║  H1: Adaptation uses FEWER loss-absorbing instruments          ║\n")
## ║  H1: Adaptation uses FEWER loss-absorbing instruments          ║
cat("║  H2: Adaptation mobilises LESS private capital                 ║\n")
## ║  H2: Adaptation mobilises LESS private capital                 ║
cat("║  → The instrument mix FAILS to convert public input into       ║\n")
## ║  → The instrument mix FAILS to convert public input into       ║
cat("║    private capital output for adaptation.                      ║\n")
## ║    private capital output for adaptation.                      ║
cat("╚══════════════════════════════════════════════════════════════════╝\n")
## ╚══════════════════════════════════════════════════════════════════╝

Checklist: Before Moving to Step 4 (Chapter 6)