Foreign Direct Investment and Renewable Energy Adoption in Developing Countries

Econometrics Assignment #4 | Linear Regression, Explanation and Prediction

Author

Ahmed Mustafa
(& Google Gemini)

Published

October 29, 2026

Code
:root { --crimson: #AA272F; }
h1, h2, h3 { color: var(--crimson); font-weight: 600; }
.callout-note { border-left-color: var(--crimson) !important; }
blockquote { border-left: 4px solid var(--crimson); padding-left: 1rem; }

1 Research question

Question: Is foreign direct investment (FDI) associated with renewable energy consumption in Egypt, Ethiopia, Kenya, Sudan, and Uganda?

The analysis utilizes ordinary linear regression (lm()). It is an introductory association study, not a causal or fully specified panel-data analysis.

Foreign investment could help renewable energy through technology and financing (a pollution halo mechanism), but it could also support fossil-fuel industries (a pollution haven mechanism). The data will help describe the association, not prove which mechanism caused it.

A statistical relationship can be useful for explanation and prediction without proving that changing FDI will cause renewable energy use to change.

2 Step 1: Importing the data

Code
library(ggplot2)
library(broom)
library(knitr)
library(kableExtra)

crimson <- "#AA272F"
table_print <- function(x, caption, digits = 2) {
  knitr::kable(x, format = "html", digits = digits,
               caption = caption, row.names = FALSE) |>
    kableExtra::kable_styling(
      bootstrap_options = c("striped", "hover"), full_width = FALSE)
}
Code
csv_path <- "udaje/LDC-Panel-Data.csv"
if (!file.exists(csv_path)) csv_path <- "LDC-Panel-Data.csv"
raw <- read.csv(csv_path, na.strings = c("", "NA"), stringsAsFactors = FALSE)
required <- c("Country_Code", "Year", "FDI_GDP", "GDP_USD", "IND_VA_GDP",
              "NODA_RCV", "REN_ERG_CONS", "Trade_GDP")
if (!all(required %in% names(raw))) stop("The CSV does not have the expected column names.")
raw$Country <- factor(raw$Country_Code,
                      levels = c("EGY", "ETH", "KEN", "SDN", "UGA"),
                      labels = c("Egypt", "Ethiopia", "Kenya", "Sudan", "Uganda"))
raw$GDP_billions <- raw$GDP_USD / 1e9
stopifnot(!anyDuplicated(raw[c("Country_Code", "Year")]))
table_print(head(raw[c("Country", "Year", "FDI_GDP", "GDP_USD", "IND_VA_GDP",
              "NODA_RCV", "REN_ERG_CONS", "Trade_GDP")], 10),
            "First ten country-year observations")
First ten country-year observations
Country Year FDI_GDP GDP_USD IND_VA_GDP NODA_RCV REN_ERG_CONS Trade_GDP
Egypt 1995 1.0 136218680413 30.2 3.4 9.4 50.25
Egypt 1996 0.9 143014263359 29.5 3.2 9.0 46.95
Egypt 1997 1.1 150869114035 29.0 2.5 8.6 43.74
Egypt 1998 1.3 159280817658 28.6 2.3 8.5 41.93
Egypt 1999 1.2 168922784450 28.4 1.8 8.8 38.36
Egypt 2000 1.2 179683172290 30.8 1.4 8.3 39.02
Egypt 2001 0.5 186035425185 30.9 1.3 7.8 39.81
Egypt 2002 0.8 190482051409 32.6 1.5 7.3 40.99
Egypt 2003 0.3 196565009504 33.4 1.3 7.4 46.18
Egypt 2004 1.6 204608590453 34.7 1.9 7.6 57.82

2.1 Variable definitions

Code
variable_dictionary <- data.frame(
  Variable = c("REN_ERG_CONS", "FDI_GDP", "GDP_billions", "IND_VA_GDP", "NODA_RCV", "Trade_GDP"),
  Meaning = c("Renewable energy consumption (% of total final energy consumption)",
              "FDI net inflows (% of GDP)",
              "GDP (constant 2015 US$, billions)",
              "Industry, including construction, value added (% of GDP)",
              "Net official development assistance received (% of GNI)",
              "Trade (% of GDP)"))
table_print(variable_dictionary, "Variables and their measurement units")
Variables and their measurement units
Variable Meaning
REN_ERG_CONS Renewable energy consumption (% of total final energy consumption)
FDI_GDP FDI net inflows (% of GDP)
GDP_billions GDP (constant 2015 US$, billions)
IND_VA_GDP Industry, including construction, value added (% of GDP)
NODA_RCV Net official development assistance received (% of GNI)
Trade_GDP Trade (% of GDP)

All percentage variables are expressed in percentage points when we discuss a one-unit difference. GDP is rescaled to billions only for readability; the original GDP values are unchanged in economic meaning.

3 Step 2: Inspecting missing values and describing the sample

Code
analysis_vars <- c("REN_ERG_CONS", "FDI_GDP", "GDP_billions", "IND_VA_GDP", "NODA_RCV", "Trade_GDP")
missing_table <- data.frame(
  Variable = analysis_vars,
  Missing = sapply(raw[analysis_vars], function(z) sum(is.na(z))))
table_print(missing_table, "Missing observations in the original dataset", 0)
Missing observations in the original dataset
Variable Missing
REN_ERG_CONS 19
FDI_GDP 5
GDP_billions 0
IND_VA_GDP 0
NODA_RCV 10
Trade_GDP 16

We use complete-case analysis: a row enters a model only if every variable needed for that model is observed. No missing value is replaced by a mean or median.

Code
simple_data <- raw[complete.cases(raw[c("REN_ERG_CONS", "FDI_GDP")]), ]
multiple_data <- raw[complete.cases(raw[analysis_vars]), ]
if (nrow(multiple_data) < 10) stop("Too few complete observations for the multiple regression.")
sample_counts <- data.frame(
  Sample = c("Original CSV", "Simple regression", "Multiple regression"),
  Country_years = c(nrow(raw), nrow(simple_data), nrow(multiple_data)))
table_print(sample_counts, "Number of country-year observations used", 0)
Number of country-year observations used
Sample Country_years
Original CSV 155
Simple regression 136
Multiple regression 120

The models have different sample sizes because the multiple regression needs more observed variables. Missing observations may not be random, so the results describe the available complete observations.

Code
descriptive <- data.frame(
  Variable           = analysis_vars,
  Minimum            = sapply(multiple_data[analysis_vars], min, na.rm = TRUE),
  Median             = sapply(multiple_data[analysis_vars], median, na.rm = TRUE),
  Mean               = sapply(multiple_data[analysis_vars], mean, na.rm = TRUE),
  Maximum            = sapply(multiple_data[analysis_vars], max, na.rm = TRUE),
  Range              = sapply(multiple_data[analysis_vars], function(x) diff(range(x, na.rm = TRUE))),
  Variance           = sapply(multiple_data[analysis_vars], var, na.rm = TRUE),
  Std_Deviation      = sapply(multiple_data[analysis_vars], sd, na.rm = TRUE),
  row.names          = NULL
)

table_print(descriptive, "Descriptive statistics: complete multiple-regression sample")
Descriptive statistics: complete multiple-regression sample
Variable Minimum Median Mean Maximum Range Variance Std_Deviation
REN_ERG_CONS 4.90 76.90 64.17 96.50 91.60 1078.49 32.84
FDI_GDP -0.20 2.54 2.56 9.30 9.50 3.56 1.89
GDP_billions 9.12 50.89 93.78 425.78 416.66 10342.23 101.70
IND_VA_GDP 9.50 21.65 23.08 39.90 30.40 53.34 7.30
NODA_RCV 0.00 3.82 4.85 16.34 16.34 14.96 3.87
Trade_GDP 4.13 37.34 38.97 71.75 67.62 162.17 12.73

4 Step 3: Visualizing the relationship

Code
ggplot(simple_data, aes(x = FDI_GDP, y = REN_ERG_CONS)) +
  geom_point(aes(color = Country), alpha = 0.72, size = 2.3) +
  geom_smooth(method = "lm", se = TRUE, color = crimson,
              fill = "#E8BCC0", linewidth = 1.15) +
  scale_color_brewer(palette = "Dark2") +
  labs(title = "Foreign investment and renewable energy use",
       subtitle = "Five developing countries; observed country-years",
       x = "FDI net inflows (% of GDP)",
       y = "Renewable energy consumption (% of total final energy)",
       color = "Country",
       caption = "Source: supplied World Bank-derived CSV.") +
  theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"), legend.position = "bottom")
Figure 1: FDI and renewable energy consumption, with the fitted simple regression line. Each point is one country-year.

Reading the graph: The dots are real observations. The crimson line summarizes their average linear relationship. Points far above or below the line have larger residuals. Country differences and time trends may also influence the apparent relationship.

5 Step 4: Estimating the simple linear regression

\[ REN_{it} = \beta_0 + \beta_1 FDI_{it} + u_{it}. \]

Here \(i\) identifies a country and \(t\) a year. The intercept is the predicted renewable share when FDI equals zero. The slope tells us the predicted difference in renewable share for a one-percentage-point difference in FDI as a share of GDP.

Code
model_simple <- lm(REN_ERG_CONS ~ FDI_GDP, data = simple_data)
table_print(broom::tidy(model_simple)[c("term", "estimate", "std.error", "p.value")],
            "Simple linear regression: coefficient estimates", 3)
Simple linear regression: coefficient estimates
term estimate std.error p.value
(Intercept) 64.975 4.696 0.000
FDI_GDP 1.109 1.494 0.459

The fitted equation is:

\[ \widehat{REN}_{it} = 64.975 + 1.109 FDI_{it}. \]

The interpretation: In this simple model, a one-percentage-point higher FDI-to-GDP ratio is associated with a 1.11-percentage-point higher predicted renewable energy consumption share. This is an average association, not an established effect of investment.

The intercept of 64.98 is the model’s prediction at FDI = 0, which should not be treated as a universal baseline for every country.

6 Step 5: Estimating the multiple linear regression

To account for other measured economic characteristics, we add GDP, industry, official development assistance (ODA), and trade openness to the model:

\[ REN_{it} = \beta_0 + \beta_1 FDI_{it} + \beta_2 GDP_{it} + \beta_3 Industry_{it} + \beta_4 ODA_{it} + \beta_5 Trade_{it} + u_{it}. \]

Code
model_multiple <- lm(REN_ERG_CONS ~ FDI_GDP + GDP_billions + IND_VA_GDP +
                       NODA_RCV + Trade_GDP, data = multiple_data)
multiple_coefs <- broom::tidy(model_multiple)
table_print(multiple_coefs[c("term", "estimate", "std.error", "p.value")],
            "Multiple linear regression: coefficient estimates", 3)
Multiple linear regression: coefficient estimates
term estimate std.error p.value
(Intercept) 96.310 6.191 0.000
FDI_GDP 1.497 0.737 0.045
GDP_billions -0.187 0.022 0.000
IND_VA_GDP -1.041 0.292 0.001
NODA_RCV 1.850 0.399 0.000
Trade_GDP -0.086 0.100 0.394

6.1 Interpretation of each coefficient

Code
terms <- c("FDI_GDP", "GDP_billions", "IND_VA_GDP", "NODA_RCV", "Trade_GDP")
units <- c("one percentage point of FDI (% of GDP)",
           "one billion constant-2015 US dollars of GDP",
           "one percentage point of industry's GDP share",
           "one percentage point of ODA (% of GNI)",
           "one percentage point of trade (% of GDP)")
b <- coef(model_multiple)[terms]
interpretation <- data.frame(
  Variable = terms,
  One_unit_change_in_X = units,
  Expected_change_in_REN_percentage_points = round(unname(b), 3))
table_print(interpretation, "Marginal associations, holding other included variables constant", 3)
Marginal associations, holding other included variables constant
Variable One_unit_change_in_X Expected_change_in_REN_percentage_points
FDI_GDP one percentage point of FDI (% of GDP) 1.497
GDP_billions one billion constant-2015 US dollars of GDP -0.187
IND_VA_GDP one percentage point of industry's GDP share -1.041
NODA_RCV one percentage point of ODA (% of GNI) 1.850
Trade_GDP one percentage point of trade (% of GDP) -0.086

The multiple-model FDI coefficient is 1.497. This means that for country-years with the same measured GDP, industry share, ODA, and trade, one percentage point higher FDI is associated with 1.497 percentage points more predicted renewable consumption.

The GDP coefficient refers to one billion dollars, not one dollar or a one-percent GDP increase. Industry, ODA, and trade coefficients refer to one-percentage-point differences in their respective indicators.

6.2 Comparing the two models

Code
# The first two rows describe each model's natural available sample.
# The last two use the same complete observations for a fairer comparison.
model_simple_common <- lm(REN_ERG_CONS ~ FDI_GDP, data = multiple_data)
model_list <- list(
  "Simple (available sample)" = model_simple,
  "Multiple (available sample)" = model_multiple,
  "Simple (common sample)" = model_simple_common,
  "Multiple (common sample)" = model_multiple)
comparison <- do.call(rbind, lapply(names(model_list), function(nm) {
  g <- broom::glance(model_list[[nm]])
  data.frame(Model = nm, N = g$nobs, R_squared = g$r.squared,
             Residual_SD = g$sigma)
}))
table_print(comparison, "Model fit and sample sizes", 3)
Model fit and sample sizes
Model N R_squared Residual_SD
Simple (available sample) 136 0.004 32.447
Multiple (available sample) 120 0.836 13.570
Simple (common sample) 120 0.008 32.852
Multiple (common sample) 120 0.836 13.570

\(R^2\) describes the proportion of variation in renewable consumption explained within the estimation equation. It does not mean that the model is correct a certain percentage of the time. Residual SD measures the typical size of the remaining errors in renewable-share percentage points. Adding predictors often raises \(R^2\) even when they add little practical value.

Question to consider: Does the FDI coefficient change after adding the other variables? This can happen because FDI is correlated with other economic conditions, or because the usable sample changes. The common-sample comparison helps separate these two issues.

7 Step 6: Fitted values and residuals

A fitted value is the model’s estimate of an observed outcome. A residual is the observed value minus that fitted value:

\[ e_{it} = REN_{it} - \widehat{REN}_{it}. \]

Code
actual_vs_fitted <- data.frame(
  Country = multiple_data$Country,
  Year = multiple_data$Year,
  Actual_REN = multiple_data$REN_ERG_CONS,
  Fitted_REN = fitted(model_multiple),
  Residual = residuals(model_multiple))
table_print(head(actual_vs_fitted, 10),
            "Observed, fitted and residual renewable energy shares", 2)
Observed, fitted and residual renewable energy shares
Country Year Actual_REN Fitted_REN Residual
Egypt 1995 9.4 42.81 -33.41
Egypt 1996 9.0 42.03 -33.03
Egypt 1997 8.6 40.36 -31.76
Egypt 1998 8.5 39.28 -30.78
Egypt 1999 8.8 36.91 -28.11
Egypt 2000 8.3 31.60 -23.30
Egypt 2001 7.8 29.01 -21.21
Egypt 2002 7.3 27.12 -19.82
Egypt 2003 7.4 23.58 -16.18
Egypt 2004 7.6 22.78 -15.18

A positive residual means renewable energy use was higher than the model predicted. A negative residual means it was lower. Neither sign proves a policy success or failure.

8 Step 7: Checking for unusual or influential observations

Here we add one accessible check because an unusual observation can change the fitted slope. A large residual means a point lies far from the fitted line; Cook’s distance indicates whether a point might substantially affect the fitted model.

Code
n <- nobs(model_multiple)
cooks_cutoff <- 4 / n  # A simple screening rule, not a formal test.
influence <- data.frame(
  Country = multiple_data$Country,
  Year = multiple_data$Year,
  FDI_GDP = multiple_data$FDI_GDP,
  REN_ERG_CONS = multiple_data$REN_ERG_CONS,
  Residual = residuals(model_multiple),
  Cooks_D = cooks.distance(model_multiple))
influence <- influence[order(-influence$Cooks_D), ]
table_print(head(influence, 10),
            "Ten observations with the largest Cook's distances", 3)
Ten observations with the largest Cook's distances
Country Year FDI_GDP REN_ERG_CONS Residual Cooks_D
Sudan 2021 1.528 61.0 -26.214 0.097
Egypt 2006 9.300 6.2 -19.739 0.089
Egypt 2021 1.200 6.1 19.133 0.083
Egypt 2020 1.500 6.7 21.146 0.071
Egypt 2007 8.900 6.0 -17.331 0.069
Egypt 2019 2.800 6.2 19.994 0.043
Egypt 1995 1.000 9.4 -33.407 0.041
Egypt 2005 6.000 6.5 -20.042 0.035
Sudan 2020 2.652 62.6 -22.180 0.034
Egypt 1996 0.900 9.0 -33.026 0.033
Code
plot(cooks.distance(model_multiple), type = "h", lwd = 2,
     col = crimson, xlab = "Observation number in complete sample",
     ylab = "Cook's distance", main = "Influence screening")
abline(h = cooks_cutoff, col = "gray35", lty = 2)
Figure 2: Cook’s distance for each complete observation. The dashed line is a screening guideline (4/n).

We do not automatically delete observations above the threshold. First we check whether they reflect genuine country-year values rather than a data-entry error. To see how sensitive the FDI slope is, we can re-estimate the same model after excluding the flagged rows as a sensitivity check only.

Code
flagged <- cooks.distance(model_multiple) > cooks_cutoff
if (sum(!flagged) > length(coef(model_multiple)) + 1 && any(flagged)) {
  model_sensitivity <- lm(formula(model_multiple), data = multiple_data[!flagged, ])
  sensitivity <- data.frame(
    Model = c("Full complete sample", "Sensitivity: exclude flagged observations"),
    N = c(nobs(model_multiple), nobs(model_sensitivity)),
    FDI_coefficient = c(coef(model_multiple)["FDI_GDP"],
                        coef(model_sensitivity)["FDI_GDP"]))
} else {
  sensitivity <- data.frame(
    Model = "Full complete sample (no feasible flagged-case comparison)",
    N = nobs(model_multiple),
    FDI_coefficient = unname(coef(model_multiple)["FDI_GDP"]))
}
table_print(sensitivity, "Sensitivity of the FDI coefficient to influential observations", 3)
Sensitivity of the FDI coefficient to influential observations
Model N FDI_coefficient
Full complete sample 120 1.497
Sensitivity: exclude flagged observations 111 2.223

Decision: Keep the original complete-case model as the main result. The sensitivity model is presented separately, not used to select observations just because they produce a preferred result.

9 Step 8: Explanation and prediction

Explanation: After observing an FDI level, the simple model describes the expected renewable share. The slope measures the average difference in predicted renewable consumption for a one-unit change in FDI.

Prediction: Before observing a hypothetical renewable share, we insert an FDI value into the same fitted equation using predict().

9.1 Predicting renewable consumption for hypothetical FDI values

Code
new_fdi <- data.frame(FDI_GDP = c(1, 3, 5))
new_fdi$Predicted_REN <- predict(model_simple, newdata = new_fdi)
table_print(new_fdi, "Simple-model predictions at three hypothetical FDI levels", 2)
Simple-model predictions at three hypothetical FDI levels
FDI_GDP Predicted_REN
1 66.08
3 68.30
5 70.52

For example, the prediction at FDI = 3% of GDP is 68.3% of total final energy consumption. This is a fitted average, not a guaranteed outcome for any country.

9.2 Prediction with the multiple regression

For a clear comparison, keep the other explanatory variables at their sample medians, then change only FDI. This is a hypothetical calculation, not a policy simulation.

Code
new_multiple <- data.frame(
  FDI_GDP = c(1, 3, 5),
  GDP_billions = median(multiple_data$GDP_billions),
  IND_VA_GDP = median(multiple_data$IND_VA_GDP),
  NODA_RCV = median(multiple_data$NODA_RCV),
  Trade_GDP = median(multiple_data$Trade_GDP))
new_multiple$Predicted_REN <- predict(model_multiple, newdata = new_multiple)
table_print(new_multiple, "Multiple-model predictions (other variables held at medians)", 2)
Multiple-model predictions (other variables held at medians)
FDI_GDP GDP_billions IND_VA_GDP NODA_RCV Trade_GDP Predicted_REN
1 50.89 21.65 3.82 37.34 69.59
3 50.89 21.65 3.82 37.34 72.58
5 50.89 21.65 3.82 37.34 75.58

Important: These are point predictions. The renewable share is bounded between 0 and 100, but ordinary linear regression does not automatically enforce those bounds. We should not interpret predictions outside that range as physically meaningful, and should avoid extrapolating far beyond observed FDI values.

10 Step 9: Social Prespective:

A Freire-inspired approach asks not only what the coefficient is, but whose experiences and interests are missing from the numerical summary. For example:

  • Does FDI fund renewable electricity, fossil-fuel extraction, or unrelated sectors? Our FDI variable does not distinguish them.
  • Who gains access to affordable clean energy: rural households, urban industries, or foreign investors?
  • Does a high renewable consumption share reflect modern clean-energy infrastructure, or reliance on traditional biomass? This indicator alone cannot distinguish all energy technologies and their social consequences.
  • Could differences in income, geography, institutions, and historical conditions explain part of the observed relationship?
  • What information would communities need before deciding whether a foreign-funded energy project benefits them?

Development should be assessed not only through investment flows, but also through whether communities gain meaningful choices, access, and voice in energy decisions.

A dedicated sensible policy implication is to evaluate the type and distribution of foreign investment and to combine quantitative indicators with local perspectives. Neither a positive nor a negative regression coefficient, on its own, proves that FDI causes a particular energy outcome.

11 Step 10: Summary

The simple regression estimates the overall association between FDI inflows and renewable energy consumption. The multiple regression compares country-years with similar measured GDP, industry share, development assistance, and trade openness. Predictions translate the estimated relationships into hypothetical renewable-energy shares, while the influence check shows whether a small number of observations may matter disproportionately.

Main takeaway: The multiple-model FDI slope is conditional on the included controls. This result is descriptive, not causal.

12 References

World Bank. World Development Indicators. https://databank.worldbank.org/source/world-development-indicators