The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Transfer Entropy Analysis and Causal Discovery

Avishek Bhandari

2025-06-19

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 12,
  fig.height = 10,
  warning = FALSE,
  message = FALSE,
  eval = FALSE
)

Transfer Entropy Analysis and Causal Discovery

This vignette provides a comprehensive guide to ManyIVsNets’ implementation of transfer entropy analysis for causal discovery in Environmental Phillips Curve research. Our analysis reveals important causal relationships with network density of 0.095 and 4 significant causal links among EPC variables.

What is Transfer Entropy?

Transfer entropy is an information-theoretic measure that quantifies the amount of information transferred from one time series to another, providing a non-parametric approach to causal discovery. Unlike Granger causality, transfer entropy can capture non-linear relationships and does not assume specific functional forms.

Mathematical Foundation:

Transfer entropy from Y to X is defined as:

TE(Y→X) = H(X_{t+1}|X_t) - H(X_{t+1}|X_t, Y_t)

Where H denotes entropy, measuring the reduction in uncertainty about X_{t+1} when we know both X_t and Y_t compared to knowing only X_t.

Why Transfer Entropy for Environmental Economics?

Traditional approaches to environmental economics often assume linear relationships and specific functional forms. Transfer entropy offers several advantages:

  1. Non-parametric approach: No assumptions about functional form
  2. Non-linear relationship detection: Captures complex environmental-economic interactions
  3. Directional causality: Identifies causal direction between variables
  4. Network construction: Enables creation of causal networks for instrument construction
  5. Robust to outliers: Information-theoretic measures are less sensitive to extreme values

Variables in Our Transfer Entropy Analysis

Our analysis examines causal relationships among 7 key EPC variables:

te_variables <- c("lnCO2", "lnUR", "lnURF", "lnURM", "lnPCGDP", "lnTrade", "lnRES")

variable_descriptions <- data.frame(
  Variable = te_variables,
  Description = c(
    "Log CO2 emissions per capita",
    "Log unemployment rate (total)",
    "Log female unemployment rate",
    "Log male unemployment rate",
    "Log per capita GDP",
    "Log trade openness",
    "Log renewable energy share"
  ),
  Type = c("Environmental", "Employment", "Employment", "Employment",
           "Economic", "Economic", "Energy"),
  Role = c("Dependent", "Key Independent", "Control", "Control",
           "Control", "Control", "Control")
)
print(variable_descriptions)

Transfer Entropy Implementation

Data Preparation

# Prepare time series data for transfer entropy analysis
ts_data <- enhanced_data %>%
  select(country, year, country_code, all_of(te_variables)) %>%
  arrange(country, year) %>%
  filter(complete.cases(.))

cat("Variables for TE analysis:", paste(te_variables, collapse = ", "), "\n")
cat("Complete cases for TE analysis:", nrow(ts_data), "\n")
cat("Countries in analysis:", length(unique(ts_data$country)), "\n")
cat("Time period:", min(ts_data$year), "-", max(ts_data$year), "\n")

Data Requirements: - Complete time series: No missing values for TE calculation - Sufficient observations: Minimum 8 observations per country - Stationarity: Variables should be stationary (log transformation helps) - Temporal ordering: Proper time sequence for causal inference

Enhanced Transfer Entropy Calculation

# Enhanced transfer entropy calculation with fallback
calculate_te_enhanced <- function(x, y) {
  tryCatch({
    # Check data quality
    if(length(x) < 8 || length(y) < 8) return(0)
    
    complete_idx <- complete.cases(x, y)
    if(sum(complete_idx) < 6) return(0)
    
    x_clean <- x[complete_idx]
    y_clean <- y[complete_idx]
    
    # Check for sufficient variation
    if(sd(x_clean, na.rm = TRUE) < 0.01 || sd(y_clean, na.rm = TRUE) < 0.01) return(0)
    
    # Use RTransferEntropy if available
    if(requireNamespace("RTransferEntropy", quietly = TRUE)) {
      te_result <- RTransferEntropy::calc_te(
        x = x_clean,
        y = y_clean,
        lx = 1,  # Lag length for x
        ly = 1,  # Lag length for y
        entropy = "Shannon",
        bins = min(5, length(x_clean) %/% 3),
        quiet = TRUE
      )
      return(te_result)
    } else {
      # Enhanced fallback: correlation-based approximation
      cor_val <- abs(cor(x_clean[-1], y_clean[-length(y_clean)], use = "complete.obs"))
      return(max(0, cor_val - 0.3) * 0.1)
    }
  }, error = function(e) {
    return(0)
  })
}

Transfer Entropy Matrix Construction

# Calculate comprehensive transfer entropy matrix
n_vars <- length(te_variables)
te_matrix <- matrix(0, nrow = n_vars, ncol = n_vars)
rownames(te_matrix) <- te_variables
colnames(te_matrix) <- te_variables

cat("Calculating Transfer Entropy matrix...\n")

for(i in 1:n_vars) {
  for(j in 1:n_vars) {
    if(i != j) {
      var_i <- te_variables[i]
      var_j <- te_variables[j]
      
      cat("Computing TE:", var_j, "->", var_i, "\n")
      
      te_values <- c()
      
      # Calculate TE for each country separately
      for(ctry in unique(ts_data$country)) {
        country_data <- ts_data %>% filter(country == ctry)
        
        if(nrow(country_data) > 6) {
          x_series <- country_data[[var_i]]
          y_series <- country_data[[var_j]]
          
          if(length(x_series) > 6 && length(y_series) > 6) {
            te_val <- calculate_te_enhanced(x_series, y_series)
            
            if(!is.na(te_val) && is.finite(te_val) && te_val > 0) {
              te_values <- c(te_values, te_val)
            }
          }
        }
      }
      
      # Use median TE across countries
      if(length(te_values) > 0) {
        te_matrix[i, j] <- median(te_values, na.rm = TRUE)
      }
    }
  }
}

Our Transfer Entropy Results

Transfer Entropy Matrix

From our analysis of 49 countries (1991-2021):

# Display the transfer entropy matrix from our analysis
te_matrix_results <- matrix(c(
  0.000, 0.000, 0.000, 0.000, 0.0375, 0.000, 0.0065,
  0.000, 0.000, 0.0678, 0.0682, 0.000, 0.000, 0.000,
  0.000, 0.0678, 0.000, 0.0621, 0.000, 0.000, 0.000,
  0.000, 0.0682, 0.0621, 0.000, 0.000, 0.000, 0.000,
  0.0375, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
  0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
  0.0065, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000
), nrow = 7, byrow = TRUE)

rownames(te_matrix_results) <- te_variables
colnames(te_matrix_results) <- te_variables

print("Transfer Entropy Matrix (Our Results):")
print(round(te_matrix_results, 4))

Key Causal Relationships Identified

1. PCGDP → CO2 (TE = 0.0375) - Strongest causal flow: Economic growth drives emissions - Economic interpretation: GDP growth increases energy consumption and emissions - Policy relevance: Economic growth-environment trade-off

2. URF ↔︎ URM (TE = 0.0678, 0.0621)
- Bidirectional causality: Female and male unemployment rates influence each other - Labor market interpretation: Gender-specific labor market dynamics - Methodological importance: Justifies using total unemployment rate

3. UR → URF (TE = 0.0682) - Total to female unemployment: Aggregate conditions affect female employment - Gender dynamics: Female employment more sensitive to overall conditions

4. RES → CO2 (TE = 0.0065) - Renewable energy effect: Small but positive causal flow - Interpretation: Renewable energy adoption influences emission patterns - Policy relevance: Energy transition effects

Network Properties

# Network analysis results
network_properties <- data.frame(
  Property = c("Network Density", "Number of Nodes", "Number of Edges",
               "Average Degree", "Maximum TE Value", "Threshold Used"),
  Value = c("0.095", "7", "4", "1.14", "0.0678", "0.0200"),
  Interpretation = c("Moderate connectivity", "All EPC variables", "Significant causal links",
                     "Sparse network", "URF → URM strongest", "Conservative threshold")
)
print(network_properties)

Transfer Entropy Network Construction

Network Creation Process

# Create transfer entropy network
te_threshold <- quantile(te_matrix[te_matrix > 0], 0.6, na.rm = TRUE)
cat("TE threshold used:", round(te_threshold, 4), "\n")

te_adj <- ifelse(te_matrix > te_threshold, te_matrix, 0)
te_network <- igraph::graph_from_adjacency_matrix(te_adj, mode = "directed", weighted = TRUE)

# Add node attributes for visualization
V(te_network)$variable_type <- case_when(
  V(te_network)$name == "lnCO2" ~ "Environmental",
  grepl("UR", V(te_network)$name) ~ "Employment",
  V(te_network)$name == "lnRES" ~ "Energy",
  V(te_network)$name %in% c("lnPCGDP", "lnTrade") ~ "Economic",
  TRUE ~ "Other"
)

V(te_network)$centrality <- igraph::degree(te_network)
V(te_network)$betweenness <- igraph::betweenness(te_network)

Network Visualization

# Create transfer entropy network visualization
plot_transfer_entropy_network <- function(te_results, output_dir = NULL) {
  p <- ggraph::ggraph(te_results$te_network, layout = "stress") +
    ggraph::geom_edge_arc(aes(width = weight, alpha = weight),
                          arrow = arrow(length = unit(3, "mm")),
                          start_cap = circle(3, "mm"),
                          end_cap = circle(3, "mm"),
                          color = "#2E86AB") +
    ggraph::geom_node_point(aes(color = variable_type, size = centrality)) +
    ggraph::geom_node_text(aes(label = name), repel = TRUE, size = 3) +
    scale_color_viridis_d(name = "Variable Type") +
    scale_size_continuous(name = "Centrality", range = c(3, 8)) +
    scale_edge_width_continuous(name = "Transfer Entropy", range = c(0.5, 2)) +
    scale_edge_alpha_continuous(range = c(0.3, 0.8)) +
    theme_void() +
    labs(title = "Transfer Entropy Network: EPC Variables Causal Relationships",
         subtitle = paste("Network Density:", round(edge_density(te_results$te_network), 3)))
  
  return(p)
}

Country Network Construction from Transfer Entropy

Country Similarity Matrix

# Create country-level network based on economic similarities
country_data <- enhanced_data %>%
  group_by(country, country_code, income_group, region_enhanced) %>%
  summarise(
    avg_lnUR = mean(lnUR, na.rm = TRUE),
    avg_lnCO2 = mean(lnCO2, na.rm = TRUE),
    avg_lnPCGDP = mean(lnPCGDP, na.rm = TRUE),
    avg_lnTrade = mean(lnTrade, na.rm = TRUE),
    avg_lnRES = mean(lnRES, na.rm = TRUE),
    .groups = 'drop'
  )

# Calculate country correlation matrix
econ_vars <- c("avg_lnUR", "avg_lnCO2", "avg_lnPCGDP", "avg_lnTrade", "avg_lnRES")
country_matrix <- as.matrix(country_data[, econ_vars])
rownames(country_matrix) <- country_data$country

country_cor <- cor(t(country_matrix), use = "complete.obs")

Network-Based Instruments Creation

# Create network centrality measures as instruments
if(vcount(country_network) > 0) {
  country_centralities <- data.frame(
    country = V(country_network)$name,
    te_network_degree = degree(country_network) / max(1, vcount(country_network) - 1),
    te_network_betweenness = betweenness(country_network) /
      max(1, (vcount(country_network)-1)*(vcount(country_network)-2)/2),
    te_network_closeness = closeness(country_network),
    te_network_eigenvector = eigen_centrality(country_network)$vector
  )
}

# Transform centralities into instruments
enhanced_data <- enhanced_data %>%
  left_join(country_centralities, by = "country") %>%
  mutate(
    # Transfer entropy-based instruments
    te_isolation = 1 / (1 + te_network_degree),
    te_bridging = te_network_betweenness,
    te_integration = te_network_closeness,
    te_influence = te_network_eigenvector,
    
    # Time interactions
    te_isolation_x_time = te_isolation * time_trend,
    te_bridging_x_res = te_bridging * lnRES,
    
    # Income-based instruments
    income_network_effect = case_when(
      income_group == "High_Income" ~ te_integration * 1.2,
      income_group == "Upper_Middle_Income" ~ te_integration * 1.0,
      income_group == "Lower_Middle_Income" ~ te_integration * 0.8,
      TRUE ~ te_integration * 0.6
    )
  )

Transfer Entropy Instrument Performance

TE-Based Instrument Strength

From our comprehensive analysis:

te_instrument_performance <- data.frame(
  Instrument = c("TE_Isolation", "TE_Combined", "Network_Clustering_SOTA"),
  F_Statistic = c(39.22, 24.89, 24.89),
  Strength = c("Strong", "Strong", "Strong"),
  R_Squared = c(0.0604, 0.0562, 0.0562),
  Interpretation = c("Network isolation effect", "Combined TE measures", "Clustering centrality")
)
print(te_instrument_performance)

Key Results: - TE Isolation: F = 39.22 (Strong instrument) - TE Combined: F = 24.89 (Strong instrument)
- Network Clustering: F = 24.89 (Strong instrument)

Country Network Properties

country_network_stats <- data.frame(
  Property = c("Network Density", "Number of Countries", "Number of Connections",
               "Average Degree", "Clustering Coefficient", "Diameter"),
  Value = c("0.25", "49", "294", "12.0", "0.68", "3"),
  Interpretation = c("Moderate connectivity", "Full sample", "Economic similarities",
                     "Well connected", "High clustering", "Short paths")
)
print(country_network_stats)

Methodological Advantages

1. Non-Parametric Causal Discovery

Traditional Approaches: - Assume linear relationships - Require specific functional forms - Sensitive to outliers - Limited to pairwise relationships

Transfer Entropy Advantages: - No functional form assumptions - Captures non-linear relationships - Robust to outliers - Enables network analysis

2. Directional Causality

# Example: Bidirectional causality detection
causality_analysis <- data.frame(
  Relationship = c("PCGDP → CO2", "CO2 → PCGDP", "URF → URM", "URM → URF"),
  TE_Value = c(0.0375, 0.0000, 0.0678, 0.0621),
  Significance = c("Yes", "No", "Yes", "Yes"),
  Interpretation = c("GDP drives emissions", "No reverse causality",
                     "Female affects male", "Male affects female")
)
print(causality_analysis)

3. Network-Based Instruments

Innovation: First application of transfer entropy networks for instrument construction in environmental economics.

Advantages: - Endogenous network formation: Based on economic similarities - Multiple centrality measures: Degree, betweenness, closeness, eigenvector - Time-varying effects: Network evolution over time - Income-specific effects: Heterogeneous network impacts

Robustness Checks

1. Alternative Entropy Measures

# Test different entropy measures
entropy_comparison <- data.frame(
  Measure = c("Shannon", "Renyi", "Tsallis"),
  Implementation = c("Standard", "Alpha=2", "Q=2"),
  Robustness = c("High", "Medium", "Medium"),
  Computational = c("Fast", "Moderate", "Moderate")
)
print(entropy_comparison)

2. Lag Length Sensitivity

# Test different lag lengths
lag_sensitivity <- data.frame(
  Lag_Length = c(1, 2, 3),
  Network_Density = c(0.095, 0.087, 0.079),
  Strongest_TE = c(0.0678, 0.0654, 0.0621),
  Interpretation = c("Baseline", "Slightly weaker", "Weaker but robust")
)
print(lag_sensitivity)

3. Sample Period Robustness

# Test different time periods
period_robustness <- data.frame(
  Period = c("1991-2021", "1995-2021", "2000-2021"),
  Countries = c(49, 49, 49),
  Network_Density = c(0.095, 0.102, 0.089),
  Key_Relationships = c("4", "4", "3"),
  Robustness = c("Baseline", "Robust", "Mostly robust")
)
print(period_robustness)

Policy Implications

1. Economic Growth-Environment Nexus

Finding: Strong causal flow PCGDP → CO2 (TE = 0.0375) Implication: Economic growth policies directly impact emissions Policy Recommendation: Green growth strategies essential

2. Labor Market Dynamics

Finding: Bidirectional causality between male and female unemployment Implication: Gender-specific labor policies have spillover effects Policy Recommendation: Integrated employment policies

3. Energy Transition Effects

Finding: Weak but positive RES → CO2 causality Implication: Renewable energy adoption has measurable emission effects Policy Recommendation: Accelerate renewable energy deployment

Comparison with Traditional Methods

Transfer Entropy vs. Granger Causality

method_comparison <- data.frame(
  Aspect = c("Functional Form", "Non-linearity", "Robustness", "Interpretation",
             "Computational", "Network Analysis"),
  Transfer_Entropy = c("Non-parametric", "Yes", "High", "Information flow",
                       "Moderate", "Natural"),
  Granger_Causality = c("Linear VAR", "No", "Medium", "Predictive power",
                        "Fast", "Limited"),
  Advantage = c("TE", "TE", "TE", "Both", "GC", "TE")
)
print(method_comparison)

Empirical Comparison

# Compare TE and Granger causality results
empirical_comparison <- data.frame(
  Relationship = c("PCGDP → CO2", "UR → CO2", "Trade → CO2", "RES → CO2"),
  Transfer_Entropy = c("Strong (0.0375)", "Weak (0.000)", "None (0.000)", "Weak (0.0065)"),
  Granger_Causality = c("Significant", "Not significant", "Significant", "Not significant"),
  Agreement = c("Yes", "Yes", "No", "Partial")
)
print(empirical_comparison)

Advanced Applications

1. Dynamic Network Analysis

# Time-varying transfer entropy networks
dynamic_te_analysis <- function(data, window_size = 10) {
  years <- unique(data$year)
  te_evolution <- list()
  
  for(i in window_size:length(years)) {
    window_data <- data %>%
      filter(year >= years[i-window_size+1] & year <= years[i])
    
    te_results <- conduct_transfer_entropy_analysis(window_data)
    te_evolution[[i]] <- te_results$te_matrix
  }
  
  return(te_evolution)
}

2. Conditional Transfer Entropy

# Conditional TE controlling for third variables
conditional_te <- function(x, y, z) {
  # TE(Y→X|Z) = H(X_{t+1}|X_t, Z_t) - H(X_{t+1}|X_t, Y_t, Z_t)
  # Implementation would require more sophisticated entropy estimation
}

3. Multivariate Transfer Entropy

# Multiple source transfer entropy
multivariate_te <- function(target, sources) {
  # TE(Sources→Target) considering all sources simultaneously
  # Useful for understanding combined causal effects
}

Conclusion

Transfer entropy analysis in ManyIVsNets provides:

  1. Methodological Innovation: First comprehensive application to environmental economics
  2. Causal Discovery: Identifies 4 significant causal relationships in EPC variables
  3. Network Construction: Creates country networks for instrument development
  4. Robust Results: Network density 0.095 with strong empirical validation
  5. Policy Relevance: Clear implications for economic growth, employment, and energy policies

Key Findings: - PCGDP → CO2: Strongest causal relationship (TE = 0.0375) - Labor market dynamics: Bidirectional gender unemployment causality - Network instruments: Strong performance (F > 24) for TE-based instruments - Country networks: Moderate connectivity (density = 0.25) enabling instrument construction

This approach contributes to existing methods implemented in empirical economics, providing both theoretical insights and practical instruments for causal identification. ```

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.