library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.3 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
AllCountries <- read_csv("AllCountries.csv")
## Rows: 217 Columns: 26
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): Country, Code
## dbl (24): LandArea, Population, Density, GDP, Rural, CO2, PumpPrice, Militar...
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
str(AllCountries)
## spc_tbl_ [217 × 26] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ Country : chr [1:217] "Afghanistan" "Albania" "Algeria" "American Samoa" ...
## $ Code : chr [1:217] "AFG" "ALB" "DZA" "ASM" ...
## $ LandArea : num [1:217] 652.86 27.4 2381.74 0.2 0.47 ...
## $ Population : num [1:217] 37.172 2.866 42.228 0.055 0.077 ...
## $ Density : num [1:217] 56.9 104.6 17.7 277.3 163.8 ...
## $ GDP : num [1:217] 521 5254 4279 NA 42030 ...
## $ Rural : num [1:217] 74.5 39.7 27.4 12.8 11.9 34.5 75.4 8.1 36.9 56.6 ...
## $ CO2 : num [1:217] 0.29 1.98 3.74 NA 5.83 1.29 5.74 4.78 1.9 8.41 ...
## $ PumpPrice : num [1:217] 0.7 1.36 0.28 NA NA 0.97 NA 1.1 0.77 NA ...
## $ Military : num [1:217] 3.72 4.08 13.81 NA NA ...
## $ Health : num [1:217] 2.01 9.51 10.73 NA 14.02 ...
## $ ArmedForces : num [1:217] 323 9 317 NA NA 117 0 105 49 NA ...
## $ Internet : num [1:217] 11.4 71.8 47.7 NA 98.9 14.3 76 75.8 69.7 97.2 ...
## $ Cell : num [1:217] 67.4 123.7 111 NA 104.4 ...
## $ HIV : num [1:217] NA 0.1 0.1 NA NA 1.9 NA 0.4 0.2 NA ...
## $ Hunger : num [1:217] 30.3 5.5 4.7 NA NA 23.9 NA 3.8 4.3 NA ...
## $ Diabetes : num [1:217] 9.6 10.1 6.7 NA 8 3.9 13.2 5.5 7.1 11.6 ...
## $ BirthRate : num [1:217] 32.5 11.7 22.3 NA NA 41.3 16.1 17 13.1 11 ...
## $ DeathRate : num [1:217] 6.6 7.5 4.8 NA NA 8.4 5.8 7.6 9.7 8.9 ...
## $ ElderlyPop : num [1:217] 2.6 13.6 6.4 NA NA 2.5 7.2 11.3 11.4 13.6 ...
## $ LifeExpectancy: num [1:217] 64 78.5 76.3 NA NA 61.8 76.5 76.7 74.8 76 ...
## $ FemaleLabor : num [1:217] 50.3 55.9 16.4 NA NA 76.4 NA 57.1 55.8 NA ...
## $ Unemployment : num [1:217] 1.5 13.9 12.1 NA NA 7.3 NA 9.5 17.7 NA ...
## $ Energy : num [1:217] NA 808 1328 NA NA ...
## $ Electricity : num [1:217] NA 2309 1363 NA NA ...
## $ Developed : num [1:217] NA 1 1 NA NA 1 NA 2 1 NA ...
## - attr(*, "spec")=
## .. cols(
## .. Country = col_character(),
## .. Code = col_character(),
## .. LandArea = col_double(),
## .. Population = col_double(),
## .. Density = col_double(),
## .. GDP = col_double(),
## .. Rural = col_double(),
## .. CO2 = col_double(),
## .. PumpPrice = col_double(),
## .. Military = col_double(),
## .. Health = col_double(),
## .. ArmedForces = col_double(),
## .. Internet = col_double(),
## .. Cell = col_double(),
## .. HIV = col_double(),
## .. Hunger = col_double(),
## .. Diabetes = col_double(),
## .. BirthRate = col_double(),
## .. DeathRate = col_double(),
## .. ElderlyPop = col_double(),
## .. LifeExpectancy = col_double(),
## .. FemaleLabor = col_double(),
## .. Unemployment = col_double(),
## .. Energy = col_double(),
## .. Electricity = col_double(),
## .. Developed = col_double()
## .. )
## - attr(*, "problems")=<pointer: 0x5edfbd5f47e0>
summary(AllCountries)
## Country Code LandArea Population
## Length :217 Length :217 Min. : 0.01 Min. : 0.0120
## N.unique :217 N.unique :217 1st Qu.: 10.83 1st Qu.: 0.7728
## N.blank : 0 N.blank : 0 Median : 94.28 Median : 6.5725
## Min.nchar: 4 Min.nchar: 3 Mean : 608.38 Mean : 35.0335
## Max.nchar: 30 Max.nchar: 3 3rd Qu.: 446.30 3rd Qu.: 25.0113
## Max. :16376.87 Max. :1392.7300
## NAs :8 NAs :1
## Density GDP Rural CO2
## Min. : 0.1 Min. : 275 Min. : 0.00 Min. : 0.0400
## 1st Qu.: 37.5 1st Qu.: 2032 1st Qu.:19.62 1st Qu.: 0.8575
## Median : 92.1 Median : 5950 Median :38.15 Median : 2.7550
## Mean : 361.4 Mean : 14733 Mean :39.10 Mean : 4.9780
## 3rd Qu.: 219.8 3rd Qu.: 17298 3rd Qu.:57.83 3rd Qu.: 6.2525
## Max. :20777.5 Max. :114340 Max. :87.00 Max. :43.8600
## NAs :8 NAs :30 NAs :3 NAs :13
## PumpPrice Military Health ArmedForces
## Min. :0.1100 Min. : 0.000 Min. : 0.000 Min. : 0.0
## 1st Qu.:0.7450 1st Qu.: 3.015 1st Qu.: 6.157 1st Qu.: 12.0
## Median :0.9800 Median : 4.650 Median : 9.605 Median : 31.5
## Mean :0.9851 Mean : 6.178 Mean :10.597 Mean : 162.1
## 3rd Qu.:1.1800 3rd Qu.: 8.445 3rd Qu.:13.713 3rd Qu.: 146.5
## Max. :2.0000 Max. :31.900 Max. :39.460 Max. :3031.0
## NAs :50 NAs :67 NAs :29 NAs :49
## Internet Cell HIV Hunger
## Min. : 1.30 Min. : 13.70 Min. : 0.100 Min. : 2.50
## 1st Qu.:29.18 1st Qu.: 83.83 1st Qu.: 0.175 1st Qu.: 2.50
## Median :58.35 Median :110.00 Median : 0.400 Median : 6.50
## Mean :54.47 Mean :107.05 Mean : 1.941 Mean :11.25
## 3rd Qu.:78.92 3rd Qu.:127.50 3rd Qu.: 1.400 3rd Qu.:14.80
## Max. :98.90 Max. :328.80 Max. :27.400 Max. :61.80
## NAs :13 NAs :15 NAs :81 NAs :52
## Diabetes BirthRate DeathRate ElderlyPop
## Min. : 1.000 Min. : 7.00 Min. : 1.600 Min. : 1.200
## 1st Qu.: 5.350 1st Qu.:11.40 1st Qu.: 5.800 1st Qu.: 3.600
## Median : 7.200 Median :17.85 Median : 7.250 Median : 6.600
## Mean : 8.542 Mean :20.11 Mean : 7.683 Mean : 8.953
## 3rd Qu.:10.750 3rd Qu.:27.65 3rd Qu.: 9.350 3rd Qu.:14.500
## Max. :30.500 Max. :47.80 Max. :15.500 Max. :27.500
## NAs :10 NAs :15 NAs :15 NAs :24
## LifeExpectancy FemaleLabor Unemployment Energy
## Min. :52.20 Min. : 6.20 Min. : 0.100 Min. : 66
## 1st Qu.:66.90 1st Qu.:50.15 1st Qu.: 3.400 1st Qu.: 738
## Median :74.30 Median :60.60 Median : 5.600 Median : 1574
## Mean :72.46 Mean :57.95 Mean : 7.255 Mean : 2664
## 3rd Qu.:77.70 3rd Qu.:69.25 3rd Qu.: 9.400 3rd Qu.: 3060
## Max. :84.70 Max. :85.80 Max. :30.200 Max. :17923
## NAs :18 NAs :30 NAs :30 NAs :82
## Electricity Developed
## Min. : 39 Min. :1.00
## 1st Qu.: 904 1st Qu.:1.00
## Median : 2620 Median :2.00
## Mean : 4270 Mean :1.81
## 3rd Qu.: 5600 3rd Qu.:3.00
## Max. :53832 Max. :3.00
## NAs :76 NAs :75
colSums(is.na(AllCountries))
## Country Code LandArea Population Density
## 0 0 8 1 8
## GDP Rural CO2 PumpPrice Military
## 30 3 13 50 67
## Health ArmedForces Internet Cell HIV
## 29 49 13 15 81
## Hunger Diabetes BirthRate DeathRate ElderlyPop
## 52 10 15 15 24
## LifeExpectancy FemaleLabor Unemployment Energy Electricity
## 18 30 30 82 76
## Developed
## 75
#Simple Linear Regression Using the AllCountries dataset, fit a simple linear regression model to predict LifeExpectancy (average life expectancy in years) based on GDP (gross domestic product per capita in $US). Report the intercept and slope coefficients and interpret their meaning in the context of the dataset. What does the R² value tell you about how well GDP explains variation in life expectancy across countries?
# Fit simple linear regression model
modelcountries <- lm(LifeExpectancy ~ GDP, data = AllCountries)
summary(modelcountries)
##
## Call:
## lm(formula = LifeExpectancy ~ GDP, data = AllCountries)
##
## Residuals:
## Min 1Q Median 3Q Max
## -16.352 -3.882 1.550 4.458 9.330
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 6.842e+01 5.415e-01 126.36 <2e-16 ***
## GDP 2.476e-04 2.141e-05 11.56 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 5.901 on 177 degrees of freedom
## (38 observations deleted due to missingness)
## Multiple R-squared: 0.4304, Adjusted R-squared: 0.4272
## F-statistic: 133.7 on 1 and 177 DF, p-value: < 2.2e-16
coef_countries <- coef(modelcountries)
cat("\nIntercept:", round(coef_countries[1], 3), "\n")
##
## Intercept: 68.422
cat("GDP Slope:", round(coef_countries[2], 6), "\n\n")
## GDP Slope: 0.000248
r_squared <- summary(modelcountries)$r.squared
cat("R-squared:", round(r_squared, 3), "\n")
## R-squared: 0.43
If a country had zero income per person (which isn’t realistic) this is what the model predicts their life expectancy would be. It’s mostly just a mathematical starting point for the equation, not something we can really interpret. Then, for every $1,000 more money people earn on average, we need to look at how much longer they live. The number tells you the yearly increase in life expectancy. For our R-squared value, we use that to help us figure out the impact of different factors. What percentage of these variations in lifespacn, can we blame on differences in income? A value like 0.30 means 30% — so income matters, but so do the other factors.
#Multiple Linear Regression Fit a multiple linear regression model to predict LifeExpectancy using GDP, Health (percentage of government expenditures on healthcare), and Internet (percentage of population with internet access) as predictors. Interpret the coefficient for Health, explaining what it means in terms of life expectancy while controlling for GDP and Internet. How does the adjusted R² compare to the simple regression model from Question 1, and what does this suggest about the additional predictors?
# Fit multiple linear regression model
multi_model <- lm(LifeExpectancy ~ GDP + Health + Internet, data = AllCountries)
summary(multi_model)
##
## Call:
## lm(formula = LifeExpectancy ~ GDP + Health + Internet, data = AllCountries)
##
## Residuals:
## Min 1Q Median 3Q Max
## -14.5662 -1.8227 0.4108 2.5422 9.4161
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.908e+01 8.149e-01 72.499 < 2e-16 ***
## GDP 2.367e-05 2.287e-05 1.035 0.302025
## Health 2.479e-01 6.619e-02 3.745 0.000247 ***
## Internet 1.903e-01 1.656e-02 11.490 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 4.104 on 169 degrees of freedom
## (44 observations deleted due to missingness)
## Multiple R-squared: 0.7213, Adjusted R-squared: 0.7164
## F-statistic: 145.8 on 3 and 169 DF, p-value: < 2.2e-16
adj_r_squared_multi <- summary(multi_model)$adj.r.squared
cat("\nAdjusted R-squared (Multiple Model):", round(adj_r_squared_multi, 3), "\n")
##
## Adjusted R-squared (Multiple Model): 0.716
cat("R-squared (Simple Model):", round(summary(modelcountries)$r.squared, 3), "\n")
## R-squared (Simple Model): 0.43
cat("Difference:", round(adj_r_squared_multi - r_squared, 3), "\n")
## Difference: 0.286
If two countries have the same income and internet access, but one spends 1% more of its government budget on healthcare, the life expectancy difference between them should be about the Health coefficient number of years. When we added healthcare spending and internet access, we need to see if our predictions improved. If the adjusted R² went up, which it did, from 0.43 to 0.716, these new variables helped explain more of the differences in how long people live.
#Checking Assumptions For the simple linear regression model from Question 1 (LifeExpectancy ~ GDP), describe how you would check the assumptions of homoscedasticity and normality of residuals. For each assumption, explain what an ideal outcome would look like and what a violation might indicate about the model’s reliability for predicting life expectancy. Afterwords, code your answer and reflect if it matched the ideal outcome.
#Homoscedasticity
plot(modelcountries, which = 1, main = "Residuals vs Fitted")
#Normality
plot(modelcountries, which = 2, main = "Normal Q-Q")
#Scale-Location Plot
plot(modelcountries, which = 3, main = "Scale-Location")
#Histogram of Residuals
hist(modelcountries$residuals, breaks = 30,
main = "Histogram of Residuals",
xlab = "Residuals", col = "blue")
par(mfrow = c(1, 1)) # Reset plotting panel
I examined the Residuals vs Fitted plot from plot. Ideally, residuals should scatter randomly around zero with equal spread across all GDP values. In practice, I observed a fan shape where variance increased at higher GDP levels, indicating heteroscedasticity. This means the model predicts life expectancy better for some countries than others. I examined both the Q-Q plot and a histogram of residuals. Ideally, points should follow the diagonal line and the histogram should be bell-shaped. My data in the histogram showed deviations in the tails and slight left-skewness (median residual = 1.55, not 0), indicating non-normal residuals. The range from -16.35 to +9.33 was asymmetric. The QQ plot was mostly following the diagonal line, though. These findings mean we should examine the p-values and standard error and check the fit further to see if we can make this model more accurate. These are not very egregious violations, however. The model is mostly accurate.
#Diagnosing Model Fit (RMSE and Residuals) For the multiple regression model from Question 2 (LifeExpectancy ~ GDP + Health + Internet), calculate the RMSE and explain what it represents in the context of predicting life expectancy. How would large residuals for certain countries (e.g., those with unusually high or low life expectancy) affect your confidence in the model’s predictions, and what might you investigate further.
predictions_multi <- predict(multi_model, newdata = AllCountries)
residuals_multi <- AllCountries$LifeExpectancy - predictions_multi
RMSE <- sqrt(mean(residuals_multi^2, na.rm = TRUE))
cat("RMSE:", round(RMSE, 3), "years\n\n")
## RMSE: 4.056 years
# Create residual dataframe with country names
residual_df <- data.frame(
Country = AllCountries$Country,
Actual_LifeExpectancy = AllCountries$LifeExpectancy,
Predicted_LifeExpectancy = round(predictions_multi, 1),
Residual = round(residuals_multi, 1)
)
cat("--- Top 5 Highest Positive Residuals (Actual much higher than predicted) ---\n")
## --- Top 5 Highest Positive Residuals (Actual much higher than predicted) ---
print(head(residual_df[order(-residual_df$Residual), ], 5))
## Country Actual_LifeExpectancy Predicted_LifeExpectancy Residual
## 16 Bangladesh 72.8 63.4 9.4
## 114 Libya 72.1 63.4 8.7
## 97 Italy 83.2 74.9 8.3
## 181 Sri Lanka 75.5 67.8 7.7
## 176 Solomon Islands 71.0 63.4 7.6
cat("\n--- Top 5 Largest Negative Residuals (Actual much lower than predicted) ---\n")
##
## --- Top 5 Largest Negative Residuals (Actual much lower than predicted) ---
print(head(residual_df[order(residual_df$Residual), ], 5))
## Country Actual_LifeExpectancy Predicted_LifeExpectancy Residual
## 48 Cote d'Ivoire 54.1 68.7 -14.6
## 112 Lesotho 54.6 67.5 -12.9
## 145 Nigeria 53.9 65.6 -11.7
## 171 Sierra Leone 52.2 63.6 -11.4
## 64 Eswatini 58.3 68.7 -10.4
An RMSE of 4 years means if you pick any country, the prediction will usually be within about 4 years of the actual life expectancy. That’s pretty reasonable given life spans range from about 50 to 85 years worldwide. People live longer than the model expects in countries like Bangladesh and Libyam and people live less than the model expects in Lesotho and Nigeria. This indicates there are other factors at play in the countries — public health, diet, crime, war — that the model didn’t capture.
#Hypothetical Example (Multicollinearity in Multiple Regression) Suppose you are analyzing the AllCountries dataset and fit a multiple linear regression model to predict CO2 emissions (metric tons per capita) using Energy (kilotons of oil equivalent) and Electricity (kWh per capita) as predictors. You notice that Energy and Electricity are highly correlated. Explain how this multicollinearity might affect the interpretation of the regression coefficients and the reliability of the model.
cor_test <- cor(AllCountries$Energy, AllCountries$Electricity, use = "complete.obs")
cat("Correlation(Energy, Electricity):", round(cor_test, 3), "\n\n")
## Correlation(Energy, Electricity): 0.797
collinear_model <- lm(CO2 ~ Energy + Electricity, data = AllCountries)
summary(collinear_model)
##
## Call:
## lm(formula = CO2 ~ Energy + Electricity, data = AllCountries)
##
## Residuals:
## Min 1Q Median 3Q Max
## -12.7559 -1.1406 -0.2020 0.7143 7.3751
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 7.998e-01 2.655e-01 3.012 0.00311 **
## Energy 3.122e-03 1.066e-04 29.290 < 2e-16 ***
## Electricity -7.044e-04 5.526e-05 -12.747 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 2.331 on 131 degrees of freedom
## (83 observations deleted due to missingness)
## Multiple R-squared: 0.899, Adjusted R-squared: 0.8974
## F-statistic: 582.8 on 2 and 131 DF, p-value: < 2.2e-16
install.packages("car")
## Installing package into '/cloud/lib/x86_64-pc-linux-gnu-library/4.6'
## (as 'lib' is unspecified)
library(car)
## Loading required package: carData
##
## Attaching package: 'car'
## The following object is masked from 'package:dplyr':
##
## recode
## The following object is masked from 'package:purrr':
##
## some
vif_values <- vif(collinear_model)
cat("\nVariance Inflation Factors (VIF):\n")
##
## Variance Inflation Factors (VIF):
cat("\nVIF Rule of Thumb:\n")
##
## VIF Rule of Thumb:
cat("- VIF > 5: Moderate concern\n")
## - VIF > 5: Moderate concern
cat("- VIF > 10: Severe multicollinearity\n")
## - VIF > 10: Severe multicollinearity
The VIF numbers are 2.74, which is below the threshold of 5. This means Energy and Electricity are correlated, but the correlation isn’t strong enough to seriously distort our model. However, Energy and Electricity are still moderately correlated (approximately r ≈ √[(2.74-1)/2.74] ≈ 0.79), which explains why the model needed to take their effect into account. This model is reliable, since both predictors are highly statistically significant (p < 0.001) and The overall R-squared of 0.899 is very explanatory. The low VIF values suggest this whole model is stable.