DATA 698-Research Project

County-Level Geographic Differences in Adult Obesity Prevalence After Controlling for Known Risk Factors

Bikash Bhowmik —- 18 Oct 2026

Introduction
Importance of the Study
Literature Review
Research Question
Hypotheses
Research Methods, Models, Variables, and Data Sets
Measurement and Evaluation
Methodology
          Missing Value Handling
          Analysis and Visualization
                  Vaccination Coverage Countries
                  Vaccination Progress Over Time
          Interpretation and Key Findings
Dataset 2: Renewable Power Plants
          Data Source
          Data Loading and Initial Inspection
          Data Structure Before Tidying
          Tidy Data Transformation
          Missing Value Handling
          Analysis and Visualization
                  Total Installed Renewable Energy Capacity
                  Total Renewable Energy Capacity
          Interpretation and Key Findings
Dataset 3: World GDP by Country (1960–2022)
          Data Source
          Data Loading and Initial Inspection
          Data Structure Before Tidying
          Tidy Data Transformation
          Validation of the Tidy Dataset
          Missing Value Handling
          Analysis and Visualization
                  GDP Trends for Selected Countries
                  Average GDP for Selected Countries
          Interpretation and Key Findings
Conclusion

Introduction

Adult obesity is an important public health issue in the United States and varies considerably across geographic areas. While obesity is frequently examined at the national or state level, substantial differences can also occur among individual counties. Counties differ in their socioeconomic conditions, physical activity patterns, food security, behavioral characteristics, and environmental conditions.
Many of the relationships between adult obesity and factors such as physical inactivity, socioeconomic conditions, and food-related conditions are already well established. Therefore, the primary purpose of this project is not simply to determine whether these known factors are associated with obesity. Instead, this study will examine whether geographic differences among U.S. counties remain after accounting for these known county-level characteristics. The project will investigate the extent to which county-level geographic variation in adult obesity prevalence can be explained by behavioral, socioeconomic, food-related, and environmental characteristics. The analysis will first estimate the variation in obesity prevalence across counties and then examine whether substantial geographic differences remain after the known factors are included in a multivariable model.
Because the study uses observational county-level data, the purpose is to identify statistical associations rather than causal relationships. The analysis will not determine whether living in a particular county causes obesity. Instead, it will examine whether differences between counties remain statistically meaningful after controlling for measured county-level characteristics.

Importance of the Study

Understanding geographic differences in obesity is important because health outcomes can vary substantially across communities. Two counties may have similar levels of physical activity, socioeconomic resources, food insecurity, and environmental characteristics but still have different adult obesity prevalence. Examining this remaining geographic variation may provide additional information about health inequality at the county level.
The study may also demonstrate how quantitative regression methods can be used to investigate geographic variation in a public-health outcome using data from multiple sources. Rather than focusing only on whether individual predictors are statistically significant, the analysis will compare models, evaluate explained variation, examine effect sizes and uncertainty, and assess regression assumptions.
The findings will be interpreted at the county level and will not be used to make individual-level conclusions. Geographic differences that remain after adjustment may indicate that other county-level characteristics, spatial patterns, or unmeasured factors warrant further investigation.

Literature Review

Obesity and Geographic Differences

Previous public-health research has established adult obesity as an important population-health issue and has examined relationships between obesity and behavioral, socioeconomic, and environmental characteristics. Much of the available discussion focuses on national or state-level patterns. County-level analysis provides a more detailed geographic perspective because communities within the same state can have substantially different health and socioeconomic characteristics.
The county-level perspective is particularly useful for this project because the research question is concerned with whether differences between geographic areas remain after accounting for measured risk factors. Rather than treating county location only as a source of descriptive variation, the analysis will investigate whether geographic differences persist in adjusted models.
Known Factors Associated with Adult Obesity
Physical activity is an important behavioral characteristic when examining obesity, and differences in physical activity across communities may be associated with differences in obesity prevalence. The Institute for Health Metrics and Evaluation (IHME) provides the United States Physical Activity and Obesity Prevalence, County dataset, which contains county-level measures of physical activity and obesity prevalence.
Socioeconomic characteristics such as education, employment, and economic resources are also relevant county-level characteristics. Counties with different socioeconomic conditions may have different access to health resources, food, and opportunities for physical activity. Food insecurity may also be related to socioeconomic conditions and therefore needs to be considered together with other socioeconomic variables.
These factors will be treated primarily as known explanatory or control variables in the proposed analysis. The purpose is not to establish that these relationships exist, but to determine whether county-level geographic differences in obesity remain after accounting for them.
Geographic and Spatial Variation
The central research issue is whether the location of a county is associated with differences in adult obesity prevalence beyond the measured characteristics of that county. If geographic differences remain after adjustment, the remaining variation could reflect other county-level conditions, spatially patterned factors, or variables that are not included in the available datasets. The project will therefore compare an adjusted model containing the known risk factors with models that incorporate geographic or spatial information, when the available data and project scope support such an analysis. This approach shifts the emphasis from simply identifying known obesity correlates to measuring the amount of geographic variation that remains after adjustment.
Originality of the Research
The originality of this project does not depend on claiming that the relationship between obesity and physical activity, socioeconomic conditions, food insecurity, or environmental characteristics has never been studied. Instead, the project will focus on a more specific question: whether meaningful county-level geographic differences in adult obesity prevalence remain after these known factors are controlled for. The analysis will combine county-level measures of adult obesity, physical activity, education, unemployment, wealth, food insecurity, and other available characteristics within a common regression framework. The project will compare models and evaluate whether geographic or spatial information provides additional explanatory value after adjustment for the known factors. This approach provides a quantitative way to examine geographic health inequality while recognizing that the study is observational and that unmeasured factors may contribute to the remaining county-level variation.

Research Question

To what extent do county-level geographic differences in adult obesity prevalence remain after controlling for known behavioral, socioeconomic, food-related, and environmental factors?

Supporting Research Questions
1. How much variation in adult obesity prevalence exists across U.S. counties before accounting for other county-level characteristics?
2. To what extent do known behavioral, socioeconomic, food-related, and environmental characteristics explain differences in adult obesity prevalence across counties?
3. After controlling for known county-level factors, do substantial differences in adult obesity prevalence remain across U.S. counties?
4. Does geographic or spatial information provide additional explanatory information about county-level adult obesity prevalence beyond the known risk factors?
5. How does model performance change when geographic or spatial information is considered in addition to the known county-level risk factors?

Hypotheses

H1: Adult obesity prevalence varies significantly across U.S. counties.
H2: County-level behavioral, socioeconomic, food-related, and environmental characteristics explain a significant portion of the variation in adult obesity prevalence across counties.
H3: After controlling for known behavioral, socioeconomic, food-related, and environmental characteristics, significant geographic differences in adult obesity prevalence remain across U.S. counties.
H4: A model that incorporates geographic or spatial information provides additional explanatory information about county-level adult obesity prevalence beyond a model containing only the known county-level risk factors.

Research Methods, Models, Variables, and Data Sets

Research Design
The study will use a quantitative, observational research design. The primary unit of analysis will be the U.S. county. Data from multiple publicly available datasets will be combined to create a county-level analytical dataset. County identifiers, including Federal Information Processing Standards (FIPS) codes where available, will be used to merge the datasets. The analysis will use a sequential modeling strategy. First, the amount of geographic variation in adult obesity prevalence will be described. Second, a multivariable model will be estimated using known behavioral, socioeconomic, food-related, and environmental characteristics. Third, geographic or spatial information will be considered to determine whether meaningful geographic variation remains after adjustment.
Because the data are observational and aggregated at the county level, the results will not be interpreted as individual-level causal relationships. The analysis will describe county-level associations and geographic variation.
Modeling Approach
The initial analysis will describe the distribution of adult obesity prevalence across counties and measure the amount of between-county variation.
The first regression model will include the known county-level factors. This model will provide a baseline for determining how much of the observed variation in adult obesity prevalence is associated with measured behavioral, socioeconomic, food-related, and environmental characteristics.
A second model will incorporate geographic or spatial information, if the final data structure and project scope support an appropriate spatial specification. The two models will be compared using measures such as explained variation, model fit, prediction error, and statistical uncertainty.
The main analytical question will be whether meaningful geographic variation remains after the known risk factors are controlled for. Multicollinearity, residual behavior, influential observations, and other relevant regression assumptions will be assessed before interpreting the final model.
Dependent Variable
The primary dependent variable will be:
Adult obesity prevalence
The primary obesity measure will come from the IHME United States Physical Activity and Obesity Prevalence, County dataset. The final study year will be selected after examining the years available in the obesity dataset and the other predictor datasets. Preference will be given to a year or period for which the major variables are sufficiently comparable. Independent and Control Variables The potential explanatory and control variables will include the following categories: Behavioral factors • Physical activity or physical inactivity • Alcohol consumption • Socioeconomic factors • Education • Unemployment • Wealth Food-related factors • Food insecurity Environmental factors • Sunlight or other relevant environmental measures available in the data Population/contextual information • County population Population will primarily be used as contextual information and will also be considered when evaluating whether weighted regression is appropriate. Population will not automatically be used as a regression weight simply because counties differ in size. Data Sets IHME United States Physical Activity and Obesity Prevalence, County This dataset will provide the primary adult obesity prevalence measure and physical activity measure. Education.csv This dataset will provide a county-level education measure. Unemployment.csv This dataset will provide a county-level unemployment measure. Wealth.csv This dataset will provide a county-level measure of wealth or economic resources. FoodInsecurity.csv This dataset will provide a county-level measure of food insecurity. AlcoholConsumption.csv This dataset will provide a county-level behavioral measure related to alcohol consumption. Sunlight.csv This dataset will provide a county-level environmental measure related to sunlight exposure. population.csv This dataset will provide county population information. Additional datasets, including IHME life expectancy and cardiovascular disease measures, may be examined for exploratory analysis. These variables will not automatically be included in the primary model because their relevance and time periods need to be evaluated. Data Preparation The datasets will first be examined individually before they are merged. Variable definitions, units, years, missing values, and county identifiers will be reviewed for comparability. The datasets will then be merged using county identifiers, preferably FIPS codes. Counties with missing values in variables required for the primary analysis will be evaluated and handled using an approach appropriate for the amount and pattern of missingness. Descriptive statistics and visualizations will be used to examine the distribution of obesity prevalence and the potential explanatory variables. Correlations and multicollinearity diagnostics will be evaluated before fitting the final multivariable models. The final analytical dataset and model specification will depend on the availability and comparability of the source datasets. Any changes to the planned variables will be documented in the final report.

Measurement and Evaluation
The primary outcome will be county-level adult obesity prevalence. The primary explanatory framework will distinguish between known county-level risk factors and remaining geographic variation. The analysis will evaluate descriptive differences in obesity prevalence across counties, coefficient estimates and uncertainty for the known factors, overall model fit, explained variation, and prediction error. Residual diagnostics and other regression assumptions will also be assessed.
The key comparison will be between a model containing known behavioral, socioeconomic, food-related, and environmental characteristics and a model that additionally accounts for geographic or spatial information when appropriate. Evidence that geographic variation remains after adjustment will be evaluated from the statistical significance and magnitude of the geographic component, changes in model fit, and the amount of variation explained. Because this is an observational county-level study, results will be described as associations. The analysis will not claim that geographic location itself causes adult obesity, and county-level findings will not be interpreted as individual-level effects.

Methodology

Research Questions

Our research focuses on examining geographic differences in adult obesity prevalence across counties in the United States and determining whether these differences persist after accounting for known socioeconomic, behavioral, and environmental risk factors.

The study aims to answer the following research questions:

  1. How does adult obesity prevalence vary across counties in the United States?
  2. To what extent are county-level differences in adult obesity prevalence associated with socioeconomic, behavioral, and environmental factors, such as poverty, physical inactivity, and access to healthy food?
  3. After controlling for these known risk factors, do significant geographic differences in adult obesity prevalence remain across counties or regions of the United States?
  4. Which statistical regression model best explains county-level variations in adult obesity prevalence while accounting for geographic and other relevant factors?

Research Methodology

To address these research questions, the study will follow a three-stage analytical process: Data Collection and Preprocessing, Exploratory Data Analysis and Preparation, and Multivariate Regression Analysis.

1. Data Collection and Preprocessing

The first stage will involve collecting county-level data from publicly available sources, including the Institute for Health Metrics and Evaluation (IHME), the United States Department of Agriculture (USDA), and other relevant government databases.

Adult obesity prevalence will serve as the dependent variable, while socioeconomic, behavioral, and environmental characteristics will be considered independent variables.

The collected datasets will be cleaned and standardized using common geographic identifiers, such as county Federal Information Processing Standards (FIPS) codes, to create a consolidated dataset suitable for statistical analysis.

2. Exploratory Data Analysis and Preparation

The second stage will focus on understanding the characteristics and distributions of the collected variables.

Exploratory data analysis will include descriptive statistics, visualizations, correlation analysis, and examination of geographic patterns in obesity prevalence.

Missing values, potential outliers, and inconsistencies will be identified and addressed using appropriate statistical techniques.

Relationships between adult obesity prevalence and explanatory variables, including physical inactivity, poverty, and healthy food accessibility, will be examined.

Data transformations and additional geographic variables may be introduced where appropriate to improve model assumptions and analytical performance.

3. Multivariate Regression Analysis

The final stage will involve developing and evaluating multivariate regression models to examine associations between adult obesity prevalence and county-level characteristics.

Multiple linear regression will be used as an initial modeling approach to estimate relationships between obesity prevalence and selected explanatory variables.

Additional regression approaches, including weighted least squares (WLS) and generalized least squares (GLS), may be considered to address potential heteroscedasticity and correlation structures in the data.

Geographic indicators, such as state or regional variables, will be incorporated to determine whether geographic differences remain statistically significant after controlling for known risk factors.

Model performance will be evaluated using appropriate statistical measures, including adjusted R-squared, root mean squared error (RMSE), and examination of model residuals.

Where predictive comparisons are appropriate, training and testing datasets may be used to evaluate model performance on observations not used for model estimation.

The results will help identify the extent to which geographic differences in adult obesity prevalence can be explained by known risk factors and whether additional geographic variation remains after statistical adjustment.

Missing Value Handling

Analysis and Visualization

Vaccination Coverage Countries

Vaccination Progress Over Time

Dataset 2 — Renewable Energy Capacity Time Series

Data Source

Data Loading and Initial Inspection

Data Structure Before Tidying

Tidy Data Transformation

Missing Value Handling

Analysis and Visualization

Total Installed Renewable Energy Capacity

Total Renewable Energy Capacity

Interpretation and Key Findings

Dataset 3 — GDP by Country (1960–2022)

Data Source

Data Loading and Initial Inspection

Data Structure Before Tidying

Tidy Data Transformation

Validation of the Tidy Dataset

Missing Value Handling

Analysis and Visualization

GDP Trends for Selected Countries

Average GDP for Selected Countries

Interpretation and Key Findings

Conclusion

#---User-defined function(s)---#

#Adapted correlation matrix used in EDA section:
plot_corr_matrix <- function(dataframe, significance_threshold){
  title <- paste0('Correlation Matrix for significance > ',
                  significance_threshold)
  
  df_cor <- dataframe %>% mutate_if(is.character, as.factor)
  
  df_cor <- df_cor %>% mutate_if(is.factor, as.numeric)
  #run a correlation and drop the insignificant ones
  corr <- cor(df_cor, use = 'na.or.complete')
  #prepare to drop duplicates and correlations of 1     
  corr[lower.tri(corr,diag=TRUE)] <- NA 
  #drop perfect correlations
  corr[corr == 1] <- NA 
  #turn into a 3-column table
  corr <- as.data.frame(as.table(corr))
  #remove the NA values from above 
  corr <- na.omit(corr) 
  #select significant values  
  corr <- subset(corr, abs(Freq) > significance_threshold) 
  #sort by highest correlation
  corr <- corr[order(-abs(corr$Freq)),] 
  #print table
  # print(corr)
  #turn corr back into matrix in order to plot with corrplot
  mtx_corr <- reshape2::acast(corr, Var1~Var2, value.var="Freq")
  
  #plot correlations visually
  corrplot(mtx_corr,
           title=title,
           mar=c(0,0,1,0),
           method='color', 
           tl.col="black", 
           na.label= " ",
           addCoef.col = 'black',
           number.cex = .9)
}

#Evaluation metrics used in Model Building / Selection section:
eval_metrics = function(model, df, predictions, target){
  resids = df[,target] - predictions
  resids2 = resids**2
  N = length(predictions)
  r2 = as.character(round(summary(model)$r.squared, 4))
  adj_r2 = as.character(round(summary(model)$adj.r.squared, 4))
  print(adj_r2) #Adjusted R-squared
  print(as.character(round(sqrt(sum(resids2)/N), 4))) #RMSE
}

## --- Model Selection --- ##

#Create Kable table to succinctly summarize variable research
Source <- c('Bhardwaj, Amiri, Buchwald, and Amram', 'Kaminsky, Lessler, Sharfstein, and Wallace', 'Azevedo, Hallal, Wells, and Victoria', 'An, Li, and Jiang', 'Holick and Wacker', 'Bremberg', 'Ahern, Brown, and Dukas', 'Ferreira, Jardim, Jardim, Sousa, and Rosa', 'Islam, Leer, Teo, et al.', 'Hammarstrom, Janlert, and Winefield')
Type <- c('Dependent', 'Dependent', 'Dependent', 'Independent', 'Independent', 'Independent', 'Independent', 'Independent', 'Independent', 'Independent')
Variable <- c('Longevity', 'Obesity', 'Physical Activity', 'EQI (Environmental Quality)', 'Sunlight', 'Population Density', 'Nutrition', 'Alcohol Consumption', 'Income', 'Unemployment')

LRVS <- cbind(Source, Type, Variable)

LRVS %>%
  kbl(caption = "Literature Review: Variable Summary") %>%
  kable_minimal() %>%
  kable_styling(latex_options = "hold_position")
Literature Review: Variable Summary
Source Type Variable
Bhardwaj, Amiri, Buchwald, and Amram Dependent Longevity
Kaminsky, Lessler, Sharfstein, and Wallace Dependent Obesity
Azevedo, Hallal, Wells, and Victoria Dependent Physical Activity
An, Li, and Jiang Independent EQI (Environmental Quality)
Holick and Wacker Independent Sunlight
Bremberg Independent Population Density
Ahern, Brown, and Dukas Independent Nutrition
Ferreira, Jardim, Jardim, Sousa, and Rosa Independent Alcohol Consumption
Islam, Leer, Teo, et al.  Independent Income
Hammarstrom, Janlert, and Winefield Independent Unemployment
## --- Dependent Variable Creation --- ##

#Read in life expectancy data, convert to tibble, and select pertinent columns:
longevity <- read_csv("https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/IHME_LifeExpectancy.csv")
life_table <- as_tibble(longevity)
life_table <- life_table %>% dplyr::select(1:2,13:16) %>% na.omit()

#Read in obesity data, convert to tibble, and select pertinent columns:
obesity <- read_csv("https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/IHME_Obesity.csv")
obesity_table <- as_tibble(obesity)
obesity_table <- obesity_table %>% dplyr::select(1:2,5:6,9:10) %>% na.omit()

#Read in physical activity data, convert to tibble, and select pertinent columns:
activity <- read_csv("https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/IHME_PhysicalActivity.csv")
act_table <- as_tibble(activity)
act_table <- act_table %>% dplyr::select(1:2,5:6,9:10) %>% na.omit()

###LIFE EXPECTANCY DATA: exploration, normalization, and compilation

#Explore life expectancy data at a county level:
glimpse(life_table)
Rows: 3,142
Columns: 6
$ State                                                     <chr> "Alabama", "…
$ County                                                    <chr> "Autauga", "…
$ `Male life expectancy, 2010 (years)`                      <dbl> 73.3, 75.0, …
$ `Female life expectancy, 2010 (years)`                    <dbl> 78.8, 80.3, …
$ `Difference in male life expectancy, 1985-2010 (years)`   <dbl> 5.2, 3.8, 5.…
$ `Difference in female life expectancy, 1985-2010 (years)` <dbl> 1.8, 1.5, 1.…
summary(life_table)
    State              County          Male life expectancy, 2010 (years)
 Length:3142        Length:3142        Min.   :63.90                     
 Class :character   Class :character   1st Qu.:73.00                     
 Mode  :character   Mode  :character   Median :75.00                     
                                       Mean   :74.71                     
                                       3rd Qu.:76.50                     
                                       Max.   :81.70                     
 Female life expectancy, 2010 (years)
 Min.   :72.7                        
 1st Qu.:78.4                        
 Median :79.8                        
 Mean   :79.7                        
 3rd Qu.:81.1                        
 Max.   :85.0                        
 Difference in male life expectancy, 1985-2010 (years)
 Min.   :-1.500                                       
 1st Qu.: 2.900                                       
 Median : 3.900                                       
 Mean   : 3.919                                       
 3rd Qu.: 4.900                                       
 Max.   :13.000                                       
 Difference in female life expectancy, 1985-2010 (years)
 Min.   :-3.500                                         
 1st Qu.: 0.600                                         
 Median : 1.500                                         
 Mean   : 1.528                                         
 3rd Qu.: 2.400                                         
 Max.   : 8.400                                         
#Extract variables of interest
m1 <- life_table$`Male life expectancy, 2010 (years)`
f1 <- life_table$`Female life expectancy, 2010 (years)`
dm1 <- life_table$`Difference in male life expectancy, 1985-2010 (years)`
df1 <- life_table$`Difference in female life expectancy, 1985-2010 (years)`

#Normalize data scale to be from 0 to 1
n_m1 = (m1-min(m1))/(max(m1)-min(m1))
n_f1 = (f1-min(f1))/(max(f1)-min(f1))
n_dm1 = (dm1-min(dm1))/(max(dm1)-min(dm1))
n_df1 = (df1-min(df1))/(max(df1)-min(df1))

#Histogram of original vs. normalized data
##Life expectancy histograms
par(mfrow=c(2,2))
hist(m1, breaks=10, xlab="Age (years)", col="lightblue", main="Male life expectancy, 2010")
hist(n_m1, breaks=10, xlab="Normalized Age (years)", col="lightblue", main="Male life expectancy, 2010")
hist(f1, breaks=10, xlab="Age (years)", col="lightblue", main="Female life expectancy, 2010")
hist(n_f1, breaks=10, xlab="Normalized Age (years)", col="lightblue", main="Female life expectancy, 2010")

##Longevity improvement histograms
par(mfrow=c(2,2))
hist(dm1, breaks=10, xlab="Age (years)", col="lightblue", main="Male longevity improvement, 1985-2010")
hist(n_dm1, breaks=10, xlab="Normalized Age (years)", col="lightblue", main="Male longevity improvement, 1985-2010")
hist(df1, breaks=10, xlab="Age (years)", col="lightblue", main="Female longevity improvement, 1985-2010")
hist(n_df1, breaks=10, xlab="Normalized Age (years)", col="lightblue", main="Female longevity improvement, 1985-2010")

#Add normalized variables together
life <- n_m1 + n_dm1 + n_f1 + n_df1

#Normalize activity to 0-1 range
n_life = (life-min(life))/(max(life)-min(life))
#head(n_life)

#Histogram of original vs. normalized data
#par(mfrow=c(1,2))
#hist(life, breaks=10, xlab="Score", col="lightblue", main="Longevity metric")
hist(n_life, breaks=10, xlab="Normalized Score", col="lightblue", main="Longevity metric")
summary(n_life) #slight left skew
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.0000  0.4106  0.5039  0.4970  0.5804  1.0000 
###OBESITY DATA: exploration, normalization, and compilation

#Explore obesity data at a county level:
glimpse(obesity_table)
Rows: 3,142
Columns: 6
$ State                                                                    <chr> …
$ County                                                                   <chr> …
$ `Male obesity prevalence, 2009 (%)`                                      <dbl> …
$ `Female obesity  prevalence, 2009 (%)`                                   <dbl> …
$ `Difference in male obesity prevalence, 2001-2009 (percentage points)`   <dbl> …
$ `Difference in female obesity prevalence, 2001-2009 (percentage points)` <dbl> …
summary(obesity_table)
    State              County          Male obesity prevalence, 2009 (%)
 Length:3142        Length:3142        Min.   :17.8                     
 Class :character   Class :character   1st Qu.:34.3                     
 Mode  :character   Mode  :character   Median :36.2                     
                                       Mean   :35.9                     
                                       3rd Qu.:38.1                     
                                       Max.   :45.6                     
 Female obesity  prevalence, 2009 (%)
 Min.   :17.20                       
 1st Qu.:34.80                       
 Median :37.60                       
 Mean   :38.08                       
 3rd Qu.:41.00                       
 Max.   :59.30                       
 Difference in male obesity prevalence, 2001-2009 (percentage points)
 Min.   :-2.900                                                      
 1st Qu.: 5.900                                                      
 Median : 7.200                                                      
 Mean   : 7.171                                                      
 3rd Qu.: 8.475                                                      
 Max.   :15.800                                                      
 Difference in female obesity prevalence, 2001-2009 (percentage points)
 Min.   :-1.400                                                        
 1st Qu.: 5.300                                                        
 Median : 6.700                                                        
 Mean   : 6.754                                                        
 3rd Qu.: 8.100                                                        
 Max.   :16.400                                                        
#Extract variables of interest
m2 <- obesity_table$`Male obesity prevalence, 2009 (%)` 
f2 <- obesity_table$`Female obesity  prevalence, 2009 (%)` 
dm2 <- obesity_table$`Difference in male obesity prevalence, 2001-2009 (percentage points)` 
df2 <- obesity_table$`Difference in female obesity prevalence, 2001-2009 (percentage points)`

#Normalize
n_m2 = (m2-min(m2))/(max(m2)-min(m2))
n_f2 = (f2-min(f2))/(max(f2)-min(f2))
n_dm2 = (dm2-min(dm2))/(max(dm2)-min(dm2))
n_df2 = (df2-min(df2))/(max(df2)-min(df2))

#Histogram of original vs. normalized data
par(mfrow=c(2,2))

hist(m2, breaks=10, xlab="Obesity rate (%)", col="lightblue", main="Male obesity prevalence, 2009")
hist(n_m2, breaks=10, xlab="Normalized obesity rate (%)", col="lightblue", main="Male obesity prevalence, 2009")
hist(f2, breaks=10, xlab="Obesity rate (%)", col="lightblue", main="Female obesity prevalence, 2009")
hist(n_f2, breaks=10, xlab="Normalized obesity rate (%)", col="lightblue", main="Female obesity prevalence, 2009")

par(mfrow=c(2,2))
hist(dm2, breaks=10, xlab="Obesity rate (%)", col="lightblue", main="Male obesity increase, 2001-2009")
hist(n_dm2, breaks=10, xlab="Normalized obesity rate (%)", col="lightblue", main="Male obesity increase, 2001-2009")
hist(df2, breaks=10, xlab="Obesity rate (%)", col="lightblue", main="Female obesity increase, 2001-2009")
hist(n_df2, breaks=10, xlab="Normalized obesity rate (%)", col="lightblue", main="Female obesity increase, 2001-2009")

#Add normalized variables together
fat <- n_m2 + n_dm2 + n_f2 + n_df2

#Normalize activity to 0-1 range
n_fat = (fat-min(fat))/(max(fat)-min(fat))

#head(n_fat)

# Histogram of original vs. normalized data
#par(mfrow=c(1,2))
#hist(fat, breaks=10, xlab="Score", col="lightblue", main="Obesity metric")
hist(n_fat, breaks=10, xlab="Normalized Score", col="lightblue", main="Obesity metric")
summary(n_fat) #right skewed
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.0000  0.5089  0.5819  0.5783  0.6565  1.0000 
###PHYSICAL ACTIVITY DATA: exploration, normalization, and compilation

glimpse(act_table)
Rows: 3,142
Columns: 6
$ State                                                                                         <chr> …
$ County                                                                                        <chr> …
$ `Male sufficient physical activity  prevalence, 2009 (%)`                                     <dbl> …
$ `Female sufficient physical activity  prevalence, 2009 (%)`                                   <dbl> …
$ `Difference in male sufficient physical activity prevalence, 2001-2009 (percentage points)`   <dbl> …
$ `Difference in female sufficient physical activity prevalence, 2001-2009 (percentage points)` <dbl> …
summary(act_table)
    State              County         
 Length:3142        Length:3142       
 Class :character   Class :character  
 Mode  :character   Mode  :character  
                                      
                                      
                                      
 Male sufficient physical activity  prevalence, 2009 (%)
 Min.   :34.4                                           
 1st Qu.:51.0                                           
 Median :55.5                                           
 Mean   :55.0                                           
 3rd Qu.:58.9                                           
 Max.   :75.7                                           
 Female sufficient physical activity  prevalence, 2009 (%)
 Min.   :27.40                                            
 1st Qu.:43.80                                            
 Median :49.20                                            
 Mean   :48.68                                            
 3rd Qu.:53.70                                            
 Max.   :73.10                                            
 Difference in male sufficient physical activity prevalence, 2001-2009 (percentage points)
 Min.   :-11.400                                                                          
 1st Qu.: -0.500                                                                          
 Median :  1.900                                                                          
 Mean   :  1.954                                                                          
 3rd Qu.:  4.200                                                                          
 Max.   : 16.700                                                                          
 Difference in female sufficient physical activity prevalence, 2001-2009 (percentage points)
 Min.   :-6.200                                                                             
 1st Qu.: 2.300                                                                             
 Median : 4.500                                                                             
 Mean   : 4.661                                                                             
 3rd Qu.: 6.800                                                                             
 Max.   :18.300                                                                             
#Explore and normalize male physical activity data
m3 <- act_table$`Male sufficient physical activity  prevalence, 2009 (%)`
f3 <- act_table$`Female sufficient physical activity  prevalence, 2009 (%)`
dm3 <- act_table$`Difference in male sufficient physical activity prevalence, 2001-2009 (percentage points)`  
df3 <- act_table$`Difference in female sufficient physical activity prevalence, 2001-2009 (percentage points)` 

#Normalized Data
n_m3 = (m3-min(m3))/(max(m3)-min(m3))
n_f3 = (f3-min(f3))/(max(f3)-min(f3))
n_dm3 = (dm3-min(dm3))/(max(dm3)-min(dm3))
n_df3 = (df3-min(df3))/(max(df3)-min(df3))

#Histogram of original vs. normalized data
par(mfrow=c(2,2))

hist(m3, breaks=10, xlab="Physical activity rate (%)", col="lightblue", main="Male activity prevalence, 2009")
hist(n_m3, breaks=10, xlab="Normalized physical activity rate (%)", col="lightblue", main="Male activity prevalence, 2009")
hist(f3, breaks=10, xlab="Physical activity rate (%)", col="lightblue", main="Female activity prevalence, 2009")
hist(n_f3, breaks=10, xlab="Normalized obesity rate (%)", col="lightblue", main="Female activity prevalence, 2009")

par(mfrow=c(2,2))
hist(dm3, breaks=10, xlab="Physical activity rate (%)", col="lightblue", main="Male activity difference, 2001-2009")
hist(n_dm3, breaks=10, xlab="Normalized physical activity rate (%)", col="lightblue", main="Male activity difference, 2001-2009")
hist(df3, breaks=10, xlab="Physical activity rate (%)", col="lightblue", main="Female activity difference, 2009")
hist(n_df3, breaks=10, xlab="Normalized obesity rate (%)", col="lightblue", main="Female activity difference, 2009")

#Add normalized variables together
active <- n_m3 + n_dm3 + n_f3 + n_df3

#Normalize activity to 0-1 range
n_active = (active-min(active))/(max(active)-min(active))

#head(n_active)

# Histogram of original vs. normalized data
#par(mfrow=c(1,2))
#hist(active, breaks=10, xlab="Score", col="lightblue", main="Physical activity metric")
hist(n_active, breaks=10, xlab="Normalized Score", col="lightblue", main="Physical activity metric")

summary(n_active) #slight right skew
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.0000  0.4152  0.5150  0.5086  0.6051  1.0000 
###DEPENDENT VARIABLE CREATION: sum, normalize, and plot

#Add normalized variables together
lifestyle <- n_life - n_fat + n_active

#Normalize health to 0-1 range
normalized_lifestyle = (lifestyle-min(lifestyle))/(max(lifestyle)-min(lifestyle))

#Histogram of original vs. normalized data
#par(mfrow=c(1,2))
#hist(lifestyle, breaks=10, xlab="Score", col="lightblue", main="Health metric")
hist(normalized_lifestyle, breaks=10, xlab="Normalized Score", col="lightblue", main="Health metric")

summary(normalized_lifestyle)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.0000  0.3708  0.4642  0.4639  0.5546  1.0000 
#head(normalized_lifestyle)

#create new df with state | county | health score
starter_df <- life_table %>% 
  dplyr::select(1:2)

starter_df$health_score <- normalized_lifestyle
healthiest_counties <- filter(starter_df, `health_score` > 0.895) #top 10
healthiest_counties <- healthiest_counties[order(-healthiest_counties$`health_score`),] #descending order

#head(starter_df)
#nrow(healthiest_counties) #10
healthiest_counties %>%
  kbl() %>%
  kable_minimal() %>%
  kable_styling(latex_options = "hold_position")
State County health_score
Colorado Routt 1.0000000
California Marin 0.9889641
Colorado Pitkin 0.9833580
Wyoming Teton 0.9778477
Colorado Eagle 0.9745991
California San Francisco 0.9139634
Colorado Summit 0.9072650
Colorado Douglas 0.9054401
Utah Summit 0.9014540
Colorado Gunnison 0.8978052
## --- Independent Variable Preprocessing --- ##

#Read in alcohol data, convert to tibble, and drop impertinent observation:
alcohol <- read_csv("https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/AlcoholConsumption.csv")
alcohol <- as_tibble(alcohol)
alcohol <- alcohol[-1,] #drop State = National
#dim(alcohol) #3178 x 6

alcohol <- alcohol[alcohol$Location != alcohol$State,] #drop state observations from Location column
#dim(alcohol) #3178 - 3127 = 51 observations dropped

#rename columns
alcohol <- alcohol %>% rename( 
  County = Location,
  Hvy = Hvy_2012,
  Bng = Binge_2012,
  HvyPctChg = `HvyPctChg_2005-2012`,
  BngPctChg = `BingePctChg_2005-2012`)

#drop excess verbage from County column
stopwords <- c("and", "Area", "Borough", "Census", "City", "County", "Division", "Municipality", "Parish")
alcohol$County <- removeWords(alcohol$County, stopwords)
#head(alcohol)

#Read in heart data, convert to tibble, and drop impertinent observation:
heart <- read_csv("https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/CardiovascularDisease.csv")
heart <- as_tibble(heart)
heart <- subset(heart, select = -c(`Mortality Rate, 2010*`)) #drop 2010
heart <- heart[-1,] #drop State = National
#dim(heart) #3193 x 4

# remove State == Location
heart <- heart[heart$Location != heart$State,]
dim(heart) #3193 - 3143 = 50 observations removed
[1] 3143    4
# retitle columns
heart <- heart %>% rename( 
  County = Location,
  Mortality_2005 = `Mortality Rate, 2005*`,
  Mortality_2014 = `Mortality Rate, 2014*`)

# retain value EXCLUSIVELY for Mortality Rate columns
heart$Mortality_2005 <- gsub("\\s*\\([^\\)]+\\)","",as.character(heart$Mortality_2005))
heart$Mortality_2014 <- gsub("\\s*\\([^\\)]+\\)","",as.character(heart$Mortality_2014))

#convert columns to proper type
heart$Mortality_2005 <- as.double(heart$Mortality_2005)
heart$Mortality_2014 <- as.double(heart$Mortality_2014)

#drop excess verbage from County column
heart$County <- removeWords(heart$County, stopwords)
heart$County <- gsub("(.*),.*", "\\1", heart$County) #remove everything after comma

# add Chg column
heart$MortalityChg <- heart$Mortality_2014 - heart$Mortality_2005

#finalize format of df
heart <- subset(heart, select = -c(`Mortality_2005`)) #drop 2005
heart <- heart %>% rename( Mortality = Mortality_2014)

#head(heart)

#Read in education data, convert to tibble, and drop impertinent observation:
education <- read_csv("https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/Education.csv")
education <- as_tibble(education)
education <- education[-1,] #drop State = National

education$State <- state.name[match(education$State, state.abb)] #convert state abbreviation to name

education <- education[education$`Area name` != education$State,] #drop state observations from Area name column
#dim(education) #3281 - 3233 = 48 observations dropped

#rename columns
education <- education %>% rename(
  County = `Area name`,
  LTHighSchool = `Percent of adults with less than a high school diploma, 2015-19`,
  HighSchool = `Percent of adults with a high school diploma only, 2015-19`,
  SomeCollege = `Percent of adults completing some college or associate's degree, 2015-19`,
  College = `Percent of adults with a bachelor's degree or higher, 2015-19`)

#drop excess verbage from County column
education$County <- removeWords(education$County, stopwords)

#head(education) #verify

#Read in eqi data and convert to tibble:
eqi <- read_csv("https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/EnvironmentalQualityIndex.csv")
eqi <- as_tibble(eqi)
eqi <- subset(eqi, select = -c(3:7)) #drop indices that makeup EQI score

eqi$State <- state.name[match(eqi$State, state.abb)] #convert state abbreviation to name

#rename columns
eqi <- eqi %>% rename(
  County = County_Name,
  EQI = environmental_quality_index)

#drop excess verbage from County column
eqi$County <- removeWords(eqi$County, stopwords)

#head(eqi) #verify
#dim(eqi) #3281 x 6

#Read in food insecurity data and convert to tibble:
food <- read_csv("https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/FoodInsecurity.csv")
food <- as_tibble(food)
#head(food)

#drop FIPS
food <- subset(food, select=-c(FIPS))
#dim(food) #3142 x 3: no need to drop observations

#convert State to full name
food$State <- state.name[match(food$State, state.abb)] #convert state abbreviation to name

#rename columns
food <- food %>% rename(
  County = `County, State`,
  FoodInsecurity = `2018 Food Insecurity Rate`)

#remove excess verbage from County
food$County <- removeWords(food$County, stopwords)
food$County <- gsub("(.*),.*", "\\1", food$County) #remove everything after comma

#drop % from Food Insecurity and convert to double
food$FoodInsecurity = as.double(gsub("[\\%,]", "", food$FoodInsecurity))

#head(food) #verify

#Read in sun data and convert to tibble:
#sun <- read_csv("https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/Sunlight.csv")
#sun <- as_tibble(sun)
#dim(sun) #3161 x 3



# Read in sun data and convert to tibble
sun <- readr::read_csv(
  "https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/Sunlight.csv",
  show_col_types = FALSE,
  locale = readr::locale(encoding = "UTF-8"),
  name_repair = "minimal"
)

# Remove any hidden spaces from column names
names(sun) <- trimws(names(sun))

# Check column names
print(names(sun))
[1] "State"  "County" "Sun"   
# Check dimensions
dim(sun)
[1] 3161    3
#drop excess verbage from County column
sun$County <- removeWords(sun$County, stopwords)
sun$County <- gsub("(.*),.*", "\\1", sun$County) #remove everything after comma

#head(sun) #verify

#Read in une ployment data and convert to tibble:
unemp <- read_csv("https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/Unemployment.csv")
unemp <- as_tibble(unemp)
unemp <- unemp[-1,] #drop State = National
unemp <- subset(unemp, select=-c(3)) #drop 2016

unemp$State <- state.name[match(unemp$Stabr, state.abb)] #convert state abbreviation to name
unemp <- unemp[unemp$area_name != unemp$State,] #drop state observations from Area name column

unemp <- unemp %>% rename(
  County = area_name,
  Unemployment = Unemployment_rate_2019,
  UnemploymentChg = `Unemployment_chg_2016-2019`) #rename columns

unemp$County <- removeWords(unemp$County, stopwords) #drop excess verbage from County column
unemp$County <- gsub("(.*),.*", "\\1", unemp$County) #remove everything after comma

#dim(unemp) #3274 - 3224 = 50 dropped
#head(unemp) #verify


#Read in wealth data and convert to tibble:
# Read wealth data
wealth <- readr::read_csv(
  "https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/Wealth.csv",
  show_col_types = FALSE
)

wealth <- as_tibble(wealth)

# Remove national observation
wealth <- wealth[-1, ]

# Convert state abbreviations to full names
wealth$State <- state.name[match(wealth$State, state.abb)]

# Remove state-level observations
wealth <- wealth[wealth$County != wealth$State, ]

# Convert columns to numeric
wealth$PovertyRate <- as.numeric(wealth$PovertyRate)
wealth$Income <- as.numeric(gsub(",", "", wealth$Income))

# Rename PovertyRate
wealth <- wealth %>%
  dplyr::rename(Poverty = PovertyRate)

# Clean county names
wealth$County <- tm::removeWords(wealth$County, stopwords)
wealth$County <- trimws(wealth$County)

# Verify
names(wealth)
[1] "State"   "County"  "Poverty" "Income" 
class(wealth$Income)
[1] "numeric"
#Read in population data and convert to tibble:
pop <- read_csv("https://raw.githubusercontent.com/BIKASHBHOWMIK15/Data-698/main/population.csv")
pop <- as_tibble(pop)

# rename columns
pop <- pop %>% rename(
  State = STNAME,
  County = CTYNAME,
  Pop_2010 = CENSUS2010POP,
  Population = POPESTIMATE2019,
  Births = BIRTHS2019,
  Deaths = DEATHS2019,
  NetMig = NETMIG2019) #rename columns

#add population change variable
pop$PopChg <- pop$Population - pop$Pop_2010

pop <- subset(pop, select=-c(3)) #drop 2010

#dim(pop) #3193 x 7
pop <- pop[pop$County != pop$State,] #drop state observations from Area name column
#dim(pop) #3193 - 3141 = 52 observations dropped

pop$County[1802] <- "Dona Ana County" #invalid UTF-8
pop$County <- removeWords(pop$County, stopwords) #drop excess verbage from County column

#head(pop)

## --- Merge df's --- ##

##1. merge health score and alcohol df's
#Trim white space
alcohol$County <- trimws(alcohol$County)
starter_df$County <- trimws(starter_df$County)
#SQL join
df <- sqldf("SELECT *
             FROM starter_df
             LEFT JOIN alcohol ON starter_df.State = alcohol.State AND starter_df.County = alcohol.County")
#Remove extra State, County columns
df <- subset(df, select=-c(4,5))

##2. merge heart to df
#Trim white space
heart$County <- trimws(heart$County)
#SQL join
df <- sqldf("SELECT *
             FROM df
             LEFT JOIN heart ON df.State = heart.State AND df.County = heart.County")
#remove extra State, County columns
df <- subset(df, select=-c(8,9))

##3. merge education to df
#Trim white space
education$County <- trimws(education$County)
#SQL join
df <- sqldf("SELECT *
             FROM df
             LEFT JOIN education ON df.State = education.State AND df.County = education.County")
#remove extra State, County columns
df <- subset(df, select=-c(10,11))

##4. merge eqi to df
#Trim white space
eqi$County <- trimws(eqi$County)
#SQL join
df <- sqldf("SELECT *
             FROM df
             LEFT JOIN eqi ON df.State = eqi.State AND df.County = eqi.County")
#remove extra State, County columns
df <- subset(df, select=-c(14,15))

##5. merge food to df
#Trim white space
food$County <- trimws(food$County)
#SQL join
df <- sqldf("SELECT *
             FROM df
             LEFT JOIN food ON df.State = food.State AND df.County = food.County")
#remove extra State, County columns
df <- subset(df, select=-c(15,16))

##6. merge sun to df
#Trim white space
sun$County <- trimws(sun$County)
#SQL join
df <- sqldf("SELECT *
             FROM df
             LEFT JOIN sun ON df.State = sun.State AND df.County = sun.County")
#remove extra State, County columns
df <- subset(df, select=-c(16,17))

##7. merge unemp to df
#Trim white space
unemp$County <- trimws(unemp$County)
#SQL join
df <- sqldf("SELECT *
             FROM df
             LEFT JOIN unemp ON df.State = unemp.State AND df.County = unemp.County")
#remove extra State, County columns
df <- subset(df, select=-c(17,18))



##8. merge wealth to df
#Trim white space
wealth$County <- trimws(wealth$County)
#SQL join
df <- sqldf("SELECT *
             FROM df
             LEFT JOIN wealth ON df.State = wealth.State AND df.County = wealth.County")
#remove extra State, County columns
df <- subset(df, select=-c(19,20))

##9. merge pop to df
#Trim white space
pop$County <- trimws(pop$County)
#SQL join
df <- sqldf("SELECT *
             FROM df
             LEFT JOIN pop ON df.State = pop.State AND df.County = pop.County")
#remove extra State, County columns
df <- subset(df, select=-c(21,22))

##verify variables and dimensions
head(df) 
    State  County health_score  Hvy HvyPctChg  Bng BngPctChg Mortality
1 Alabama Autauga    0.4597290 14.7      -2.7 31.1      -1.1    316.36
2 Alabama Baldwin    0.5185262 16.6       1.2 30.4      -1.8    272.04
3 Alabama Barbour    0.2292573 16.9      17.5 32.9       3.4    255.09
4 Alabama    Bibb    0.2954267 15.4       1.4 31.9      -2.3    378.09
5 Alabama  Blount    0.2025867 14.3       3.1 29.4      -3.4    307.90
6 Alabama Bullock    0.2151089 18.8      21.4 38.1      15.9    322.56
  MortalityChg LTHighSchool HighSchool SomeCollege College          EQI
1       -52.47         11.5       33.6        28.4    26.6  0.284681678
2       -36.95          9.2       27.7        31.3    31.9  0.754969418
3       -45.95         26.8       35.6        26.0    11.6 -0.005637936
4       -31.16         20.9       44.9        23.8    10.4  0.304039747
5       -39.89         19.5       33.4        34.0    13.1  1.141660571
6       -68.95         25.3       40.3        22.3    12.1  0.104423791
  FoodInsecurity      Sun Unemployment UnemploymentChg County.1 Poverty
1           15.6 18557.98          2.7            -2.4  Autauga    12.1
2           12.9 19101.34          2.7            -2.6  Baldwin    10.1
3           21.9 18642.40          3.8            -4.5  Barbour    27.1
4           15.1 18282.04          3.1            -3.3     Bibb    20.3
5           13.6 17606.36          2.7            -2.7   Blount    16.3
6           20.5 18662.86          3.6            -3.2  Bullock    30.0
  County.2 Population Births Deaths NetMig PopChg
1  Autauga      55869    624    541    254   1298
2  Baldwin     223234   2304   2326   5377  40969
3  Barbour      24686    256    312   -128  -2771
4     Bibb      22394    240    252     41   -521
5   Blount      57826    651    657     65    504
6  Bullock      10101    109    109    -73   -813
dim(df) #3154 x 25
[1] 3154   26
# ==========================================
# FIX MISSING INCOME VARIABLE
# ==========================================

# Check Income in wealth
print(names(wealth))
[1] "State"   "County"  "Poverty" "Income" 
# Ensure Income is numeric
wealth$Income <- readr::parse_number(as.character(wealth$Income))

# Clean county names
wealth$County <- trimws(wealth$County)

# Check for duplicate county keys
# Check duplicate counties
duplicate_counties <- wealth %>%
  dplyr::count(State, County) %>%
  dplyr::filter(n > 1)

print(duplicate_counties, n = Inf)
# A tibble: 1 × 3
  State County     n
  <chr> <chr>  <int>
1 <NA>  <NA>       2
# Display the actual duplicate records
wealth %>%
  dplyr::semi_join(
    duplicate_counties,
    by = c("State", "County")
  ) %>%
  dplyr::arrange(State, County) %>%
  print(n = Inf)
# A tibble: 2 × 4
  State County Poverty Income
  <chr> <chr>    <dbl>  <dbl>
1 <NA>  <NA>        NA     NA
2 <NA>  <NA>        NA     NA
# Add Income to merged dataframe
df <- df %>%
  dplyr::select(-dplyr::any_of("Income")) %>%
  dplyr::left_join(
    wealth %>%
      dplyr::select(State, County, Income),
    by = c("State", "County")
  )

# Verify
print("Income" %in% names(df))
[1] TRUE
print(class(df$Income))
[1] "numeric"
# ==========================================
# END INCOME FIX
# ==========================================

## --- EDA --- ##

#baseline EDA: glimpse() and summary()
glimpse(df)
Rows: 3,154
Columns: 27
$ State           <chr> "Alabama", "Alabama", "Alabama", "Alabama", "Alabama",…
$ County          <chr> "Autauga", "Baldwin", "Barbour", "Bibb", "Blount", "Bu…
$ health_score    <dbl> 0.4597290, 0.5185262, 0.2292573, 0.2954267, 0.2025867,…
$ Hvy             <dbl> 14.7, 16.6, 16.9, 15.4, 14.3, 18.8, 16.8, 13.9, 17.7, …
$ HvyPctChg       <dbl> -2.7, 1.2, 17.5, 1.4, 3.1, 21.4, 8.7, 12.4, 10.9, 12.1…
$ Bng             <dbl> 31.1, 30.4, 32.9, 31.9, 29.4, 38.1, 33.6, 31.0, 33.9, …
$ BngPctChg       <dbl> -1.1, -1.8, 3.4, -2.3, -3.4, 15.9, 8.1, 3.9, 0.0, -5.0…
$ Mortality       <dbl> 316.36, 272.04, 255.09, 378.09, 307.90, 322.56, 382.55…
$ MortalityChg    <dbl> -52.47, -36.95, -45.95, -31.16, -39.89, -68.95, -39.47…
$ LTHighSchool    <dbl> 11.5, 9.2, 26.8, 20.9, 19.5, 25.3, 15.0, 15.6, 18.4, 1…
$ HighSchool      <dbl> 33.6, 27.7, 35.6, 44.9, 33.4, 40.3, 45.2, 32.8, 36.7, …
$ SomeCollege     <dbl> 28.4, 31.3, 26.0, 23.8, 34.0, 22.3, 23.7, 33.2, 31.6, …
$ College         <dbl> 26.6, 31.9, 11.6, 10.4, 13.1, 12.1, 16.1, 18.5, 13.3, …
$ EQI             <dbl> 0.284681678, 0.754969418, -0.005637936, 0.304039747, 1…
$ FoodInsecurity  <dbl> 15.6, 12.9, 21.9, 15.1, 13.6, 20.5, 19.1, 17.4, 16.4, …
$ Sun             <dbl> 18557.98, 19101.34, 18642.40, 18282.04, 17606.36, 1866…
$ Unemployment    <dbl> 2.7, 2.7, 3.8, 3.1, 2.7, 3.6, 3.6, 3.5, 2.9, 2.9, 2.7,…
$ UnemploymentChg <dbl> -2.4, -2.6, -4.5, -3.3, -2.7, -3.2, -3.3, -3.0, -2.5, …
$ County.1        <chr> "Autauga", "Baldwin", "Barbour", "Bibb", "Blount", "Bu…
$ Poverty         <dbl> 12.1, 10.1, 27.1, 20.3, 16.3, 30.0, 21.6, 17.2, 19.6, …
$ County.2        <chr> "Autauga", "Baldwin", "Barbour", "Bibb", "Blount", "Bu…
$ Population      <dbl> 55869, 223234, 24686, 22394, 57826, 10101, 19448, 1136…
$ Births          <dbl> 624, 2304, 256, 240, 651, 109, 213, 1269, 354, 222, 55…
$ Deaths          <dbl> 541, 2326, 312, 252, 657, 109, 272, 1532, 441, 343, 50…
$ NetMig          <dbl> 254, 5377, -128, 41, 65, -73, -123, -461, -259, 303, 2…
$ PopChg          <dbl> 1298, 40969, -2771, -521, 504, -813, -1499, -4967, -96…
$ Income          <dbl> 58233, 59871, 35972, 47918, 52902, 31906, 39944, 47747…
#summary(df)


#drop NAs from consideration
df <- drop_na(df)
#dim(df) #3154 - 3004 = 150 dropped observations


#Select numeric variables

# Drop rows with missing values
df <- tidyr::drop_na(df)

# Select numeric variables, including health_score
df_num <- df %>%
  dplyr::select(where(is.numeric)) %>%
  as.data.frame()

# Check that health_score is present and numeric
print(names(df_num))
 [1] "health_score"    "Hvy"             "HvyPctChg"       "Bng"            
 [5] "BngPctChg"       "Mortality"       "MortalityChg"    "LTHighSchool"   
 [9] "HighSchool"      "SomeCollege"     "College"         "EQI"            
[13] "FoodInsecurity"  "Sun"             "Unemployment"    "UnemploymentChg"
[17] "Poverty"         "Population"      "Births"          "Deaths"         
[21] "NetMig"          "PopChg"          "Income"         
print(class(df_num$health_score))
[1] "numeric"
# Correlation matrix
plot_corr_matrix(df_num, 0.2)

# Compute proportion of missing data per variable
Missing_Data <- sapply(
  df_num,
  function(x) sum(is.na(x)) / nrow(df_num)
)

# Compute correlation with health_score
predictors <- setdiff(names(df_num), "health_score")

HealthScore_Corr <- sapply(
  predictors,
  function(x) cor(
    df_num$health_score,
    df_num[[x]],
    use = "complete.obs"
  )
)

# Combine results
MDHSC <- data.frame(
  Variable = predictors,
  Missing_Data = Missing_Data[predictors],
  HealthScore_Corr = HealthScore_Corr
)

MDHSC %>%
  kbl(caption = "Proportion of Missing Data vs. Correlation with Health Score") %>%
  kable_minimal() %>%
  kable_styling(latex_options = "hold_position")
Proportion of Missing Data vs. Correlation with Health Score
Variable Missing_Data HealthScore_Corr
Hvy Hvy 0 0.0257216
HvyPctChg HvyPctChg 0 0.1965403
Bng Bng 0 -0.0994431
BngPctChg BngPctChg 0 0.2149214
Mortality Mortality 0 -0.7106678
MortalityChg MortalityChg 0 -0.0242877
LTHighSchool LTHighSchool 0 -0.5698870
HighSchool HighSchool 0 -0.5193321
SomeCollege SomeCollege 0 0.2569020
College College 0 0.6469479
EQI EQI 0 -0.1844920
FoodInsecurity FoodInsecurity 0 -0.6425830
Sun Sun 0 -0.1956494
Unemployment Unemployment 0 -0.3526851
UnemploymentChg UnemploymentChg 0 0.3470860
Poverty Poverty 0 -0.5908770
Population Population 0 0.2098390
Births Births 0 0.1912476
Deaths Deaths 0 0.1944959
NetMig NetMig 0 0.0396260
PopChg PopChg 0 0.2077735
Income Income 0 0.5984743
#Utilize Boruta for feature ranking and selection
library(Boruta)

# Perform Boruta search
boruta_output <- Boruta(health_score ~ ., data=na.omit(df_num), doTrace=0, maxRuns = 1000)

#Get significant variables including tentatives
boruta_signif <- getSelectedAttributes(boruta_output, withTentative = TRUE)
#print(boruta_signif)

# Plot variable importance
plot(boruta_output, cex.axis=.7, las=2, xlab="", main="Variable Importance")

#Feature selection (address weak target correlation and multicollinearity)
#select_df <- df_num[, c(1,4,6,8,11:14,16,18,23)]
#head(select_df) #verify

# Select variables for regression modeling
selected_vars <- c(
  "health_score",
  "Mortality",
  "College",
  "FoodInsecurity",
  "LTHighSchool",
  "Sun",
  "EQI",
  "Income",
  "PopChg",
  "Bng",
  "UnemploymentChg"
)

# Check whether any variables are missing
setdiff(selected_vars, names(df_num))
character(0)
# Select variables
select_df <- df_num %>%
  dplyr::select(dplyr::all_of(selected_vars))

# Verify
dim(select_df)
[1] 3004   11
names(select_df)
 [1] "health_score"    "Mortality"       "College"         "FoodInsecurity" 
 [5] "LTHighSchool"    "Sun"             "EQI"             "Income"         
 [9] "PopChg"          "Bng"             "UnemploymentChg"
#Histograms for all variables
select_df %>%
  gather() %>%
  ggplot(aes(value)) +
  facet_wrap(~ key, scales = "free", ncol=4) +
  geom_histogram(bins=20,color="darkblue", fill="lightblue")

#Ensure reproducibility
set.seed(123)

#Train-test split data
dt = sort(sample(nrow(select_df), nrow(select_df)*.75))
train <- select_df[dt,]
test <- select_df[-dt,] 
#dim(train) #2253 x 11
#dim(test) # 751 x 11

###LINEAR REGRESSION (baseline model): prior to outlier handling, normalization, feature engineering

model_1 <- lm(health_score ~., data = train)
summary(model_1) #R^2 = 0.6603, vars = 10

Call:
lm(formula = health_score ~ ., data = train)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.25433 -0.05409  0.00107  0.05231  0.35087 

Coefficients:
                      Estimate     Std. Error t value    Pr(>|t|)    
(Intercept)      0.58513063780  0.03142037499  18.623     < 2e-16 ***
Mortality       -0.00077673014  0.00004088638 -18.997     < 2e-16 ***
College          0.00360090177  0.00030264187  11.898     < 2e-16 ***
FoodInsecurity  -0.00705562201  0.00081642636  -8.642     < 2e-16 ***
LTHighSchool    -0.00223163467  0.00041880781  -5.329 0.000000109 ***
Sun              0.00000478363  0.00000116234   4.115 0.000040031 ***
EQI             -0.01780408783  0.00210737989  -8.448     < 2e-16 ***
Income           0.00000075346  0.00000023440   3.214     0.00133 ** 
PopChg           0.00000012659  0.00000005557   2.278     0.02283 *  
Bng              0.00063339875  0.00036477979   1.736     0.08263 .  
UnemploymentChg  0.00540024624  0.00198078248   2.726     0.00645 ** 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.07808 on 2242 degrees of freedom
Multiple R-squared:  0.6603,    Adjusted R-squared:  0.6588 
F-statistic: 435.8 on 10 and 2242 DF,  p-value: < 2.2e-16
#Imputation / removal (address NAs): 0 missing values

#Normalization: 0 impact on R^2
norm_minmax <- function(x){(x- min(x, na.rm = TRUE)) /(max(x, na.rm = TRUE)-min(x, na.rm = TRUE))}
norm_df <- as.data.frame(lapply(train, norm_minmax))
#head(norm_df) #verify

#Outlier handling: reduced R^2
#cooksD <- cooks.distance(model_1)
#influential <- as.numeric(names(cooksD)[(cooksD > (10 * mean(cooksD, na.rm = TRUE)))])
#train[influential,] #verify outliers - 20 rows
#out_df <- train[-influential,]

#Feature engineering

#combine datasets so we don't have to make features twice
train$dataset <- 'train'
test$dataset <- 'test'
final_df <- rbind(train, test)

#Creating new features (started with 1st, 3rd quartile values then adjusted)
##strong EQI and lots of sun
final_df$env_and_sun <- as.factor(ifelse(final_df$EQI > 0.90017 & final_df$Sun > 16350, 1, 0))
##high proportion college graduates and high income (3rd quartile)
final_df$college_and_income <- as.factor(ifelse(final_df$College > 20.0 & final_df$Income > 65000, 1, 0))
##low binge drinking and high income (older, more responsible?)
final_df$lowDrink_and_HiIncome <- as.factor(ifelse(final_df$Bng < 32.5 & final_df$Income > 61629, 1, 0))
##high binge drinking and high income (expendable income, socially active)
final_df$HiDrink_and_HiIncome <- as.factor(ifelse(final_df$Bng > 35.0 & final_df$Income > 61629, 1, 0))
##high food insecurity and high mortality and less than HS
final_df$LowFood_HiDeath_LowEd <- as.factor(ifelse(final_df$FoodInsecurity > 15.7 & final_df$Mortality > 500.00 & final_df$LTHighSchool > 20.0, 1, 0))
##low income and less than HS and growth in unemployment
final_df$LoIncome_LowEd_HiUnemp <- as.factor(ifelse(final_df$Income < 30000 & final_df$UnemploymentChg < -1.7 & final_df$LTHighSchool > 16.8, 1, 0))
##big PopChg and low unemployment
final_df$HiPop_LoUnemp <- as.factor(ifelse(final_df$PopChg > 1595.5 & final_df$UnemploymentChg > -1, 1, 0))

#Transformed (model 2): after handling outliers, normalization, feature engineering
train2 <- final_df %>% filter(dataset == 'train') %>% dplyr::select(-dataset)
test2 <- final_df %>% filter(dataset == 'test') %>% dplyr::select(-dataset)
dat <- final_df %>% dplyr::select(-dataset)
model_2 <- lm(health_score ~., data = train2)
#summary(model_2) #R^2 = 0.6735, vars = 17

###LINEAR REGRESSION (transformed model): features created, AIC optimized
#stepAIC(model_2)

model_3 <- lm(formula = health_score ~ Mortality + College + FoodInsecurity + 
                LTHighSchool + Sun + EQI + Income + PopChg + Bng + UnemploymentChg + 
                env_and_sun + lowDrink_and_HiIncome + HiDrink_and_HiIncome + 
                LowFood_HiDeath_LowEd + LoIncome_LowEd_HiUnemp + HiPop_LoUnemp, 
              data = train2)

#summary(model_3) #R^2 = 0.6734, vars = 16

# Predict and evaluate raw_lm model on training data
predictions = predict(model_3, newdata = train2)
eval_metrics(model_3, train2, predictions, target = 'health_score') #0.671, 0.0764
[1] "0.671"
[1] "0.0764"
# Predict and evaluate raw_lm model on testing data
predictions = predict(model_3, newdata = test2)
eval_metrics(model_3, test2, predictions, target = 'health_score') #0.671, 0.0815
[1] "0.671"
[1] "0.0815"
## --- Model Building --- ##

###LINEAR REGRESSION (raw data)

#Train-test split data
dt2 = sort(sample(nrow(df_num), nrow(df_num)*.75))
train_raw <- df_num[dt2,]
test_raw <- df_num[-dt2,] 
#dim(train_raw) #2253 x 23
#dim(test_raw) # 751 x 23

#Train raw_lm model:
raw_lm <- lm(health_score ~., data = train_raw)
#summary(raw_lm) #R^2 = 0.6808, vars = 22

# Predict and evaluate raw_lm model on training data
predictions = predict(raw_lm, newdata = train_raw)
eval_metrics(raw_lm, train_raw, predictions, target = 'health_score') #0.6678, 0.0759
[1] "0.6736"
[1] "0.0757"
# Predict and evaluate raw_lm model on testing data
predictions = predict(raw_lm, newdata = test_raw)
eval_metrics(raw_lm, test_raw, predictions, target = 'health_score') #0.6678, 0.0827
[1] "0.6736"
[1] "0.0848"
###LINEAR REGRESSION (raw data, AIC optimized)
#stepAIC(raw_lm)

#Train aic_raw_lm model:
aic_raw_lm <- lm(formula = health_score ~ Hvy + HvyPctChg + Bng + Mortality + 
                   HighSchool + SomeCollege + College + EQI + FoodInsecurity + 
                   Sun + Unemployment + UnemploymentChg + Poverty + Population + 
                   Births + Deaths + NetMig, data = train_raw)

#summary(aic_raw_lm) #R^2 = 0.6802, vars = 17

# Predict and evaluate aic_raw_lm model on training data
predictions = predict(aic_raw_lm, newdata = train_raw)
eval_metrics(aic_raw_lm, train_raw, predictions, target = 'health_score') #0.6671, 0.0761
[1] "0.673"
[1] "0.0759"
# Predict and evaluate aic_raw_lm model on testing data
predictions = predict(aic_raw_lm, newdata = test_raw)
eval_metrics(aic_raw_lm, test_raw, predictions, target = 'health_score') #0.6671, 0.0828
[1] "0.673"
[1] "0.0842"
###RIDGE REGRESSION (raw data)

#Specify column names
cr_raw = c('Hvy', 'HvyPctChg', 'BngPctChg', 'MortalityChg', 'HighSchool', 'SomeCollege', 'College', 'Unemployment', 'Poverty', 'Population', 'Births', 'Deaths', 'NetMig', 'Mortality', 'FoodInsecurity', 'LTHighSchool', 'Sun', 'EQI', 'Income', 'PopChg', 'Bng', 'UnemploymentChg', 'health_score')

#Generate dummy variables from data (if applicable)
dummies <- dummyVars(health_score ~ ., data = df_num[,cr_raw])
train_dummies = predict(dummies, newdata = train_raw[,cr_raw]) #2253 x 22
test_dummies = predict(dummies, newdata = test_raw[,cr_raw]) #751 x 22
print(dim(train_dummies)); print(dim(test_dummies))
[1] 2253   22
[1] 751  22
#Create numeric model matrices
x = as.matrix(train_dummies)
y_train = train_raw$health_score
x_test = as.matrix(test_dummies)
y_test = test_raw$health_score

lambdas <- 10^seq(2, -3, by = -.1) #specify lambda sequence

#Train model
raw_ridge_reg = glmnet(x, y_train, nlambda = 25, alpha = 0, family = 'gaussian', lambda = lambdas)
summary(raw_ridge_reg)
          Length Class     Mode   
a0          51   -none-    numeric
beta      1122   dgCMatrix S4     
df          51   -none-    numeric
dim          2   -none-    numeric
lambda      51   -none-    numeric
dev.ratio   51   -none-    numeric
nulldev      1   -none-    numeric
npasses      1   -none-    numeric
jerr         1   -none-    numeric
offset       1   -none-    logical
call         7   -none-    call   
nobs         1   -none-    numeric
#Compute optimal lambda
raw_cv_ridge <- cv.glmnet(x, y_train, alpha = 0, lambda = lambdas)
ol <- raw_cv_ridge$lambda.min
ol #0.003162278
[1] 0.001
#Compute R^2 from true and predicted values
eval_results <- function(true, predicted, df) {
  SSE <- sum((predicted - true)^2)
  SST <- sum((true - mean(true))^2)
  R_square <- round((1 - SSE / SST),4)
  RMSE = round(sqrt(SSE/nrow(df)),4)
  
  # Model performance metrics
  data.frame(RMSE = RMSE, Rsquare = R_square)
}

#Predict and evaluate raw_ridge_reg model on training data
predictions_train <- predict(raw_ridge_reg, s = ol, newx = x)
eval_results(y_train, predictions_train, train_raw) #RMSE: 0.0761, RSquare: 0.6734
    RMSE Rsquare
1 0.0761  0.6734
#Predict and evaluate raw_ridge_reg model on testing data
predictions_test <- predict(raw_ridge_reg, s = ol, newx = x_test)
eval_results(y_test, predictions_test, test_raw) #RMSE: 0.0789, RSquare: 0.659
    RMSE Rsquare
1 0.0789   0.659
#Ridge regression lambda plot
plot(raw_ridge_reg)

###RIDGE REGRESSION (transformed data)

#Specify column names
cols_reg = c('Mortality', 'College', 'FoodInsecurity', 'LTHighSchool', 'Sun', 'EQI', 'Income', 'PopChg','Bng','UnemploymentChg','env_and_sun','lowDrink_and_HiIncome','HiDrink_and_HiIncome','LowFood_HiDeath_LowEd','LoIncome_LowEd_HiUnemp','HiPop_LoUnemp','health_score')

#Generate dummy variables from data (if applicable)
dummies <- dummyVars(health_score ~ ., data = dat[,cols_reg])
train_dummies2 = predict(dummies, newdata = train2[,cols_reg]) #2253 x 22
test_dummies2 = predict(dummies, newdata = test2[,cols_reg]) #751 x 22
print(dim(train_dummies2)); print(dim(test_dummies2))
[1] 2253   22
[1] 751  22
#Create numeric model matrices
x2 = as.matrix(train_dummies2)
y_train2 = train2$health_score
x_test2 = as.matrix(test_dummies2)
y_test2 = test2$health_score

#Train model
ridge_reg = glmnet(x2, y_train2, nlambda = 25, alpha = 0, family = 'gaussian', lambda = lambdas)
summary(ridge_reg)
          Length Class     Mode   
a0          51   -none-    numeric
beta      1122   dgCMatrix S4     
df          51   -none-    numeric
dim          2   -none-    numeric
lambda      51   -none-    numeric
dev.ratio   51   -none-    numeric
nulldev      1   -none-    numeric
npasses      1   -none-    numeric
jerr         1   -none-    numeric
offset       1   -none-    logical
call         7   -none-    call   
nobs         1   -none-    numeric
#Compute optimal lambda
cv_ridge2 <- cv.glmnet(x2, y_train2, alpha = 0, lambda = lambdas)
ol2 <- cv_ridge2$lambda.min
ol2 #0.003162278
[1] 0.003981072
#Predict and evaluate ridge_reg model on training data
predictions_train2 <- predict(ridge_reg, s = ol2, newx = x2)
eval_results(y_train2, predictions_train2, train2) #RMSE: 0.0764, RSquare: 0.6732
    RMSE Rsquare
1 0.0764  0.6732
#Predict and evaluate ridge_reg model on training data
predictions_test2 <- predict(ridge_reg, s = ol2, newx = x_test2)
eval_results(y_test2, predictions_test2, test2) #RMSE: 0.0814, RSquare: 0.632
    RMSE Rsquare
1 0.0814  0.6322
###LASSO REGRESSION (raw data)

# Setting alpha = 1 implements lasso regression
lasso_reg <- cv.glmnet(x, y_train, alpha = 1, lambda = lambdas, standardize = TRUE, nfolds = 5)

#Compute optimal lambda
lambda_best <- lasso_reg$lambda.min 
lambda_best #0.001
[1] 0.001
raw_lasso <- glmnet(x, y_train, alpha = 1, lambda = lambda_best, standardize = TRUE)

predictions_train <- predict(raw_lasso, s = lambda_best, newx = x)
eval_results(y_train, predictions_train, train_raw) #RMSE: 0.0767, Rsquare: 0.6681
    RMSE Rsquare
1 0.0767  0.6681
predictions_test <- predict(raw_lasso, s = lambda_best, newx = x_test)
eval_results(y_test, predictions_test, test_raw) #RMSE: 0.0783, Rsquare: 0.6642
    RMSE Rsquare
1 0.0783  0.6642
#Lasso regression lambda plots
op <- par(mfrow=c(1, 2))

plot(lasso_reg$glmnet.fit, "norm",   label=TRUE)
plot(lasso_reg$glmnet.fit, "lambda", label=TRUE)

par(op)

###LASSO REGRESSION (transformed data)

# Setting alpha = 1 implements lasso regression
lasso_reg2 <- cv.glmnet(x2, y_train2, alpha = 1, lambda = lambdas, standardize = TRUE, nfolds = 5)

#Compute optimal lambda
lambda_best2 <- lasso_reg2$lambda.min 
lambda_best2 #0.001
[1] 0.001
lasso_model <- glmnet(x2, y_train2, alpha = 1, lambda = lambda_best2, standardize = TRUE)

predictions_train2 <- predict(lasso_model, s = lambda_best2, newx = x2)
eval_results(y_train2, predictions_train2, train2) #RMSE: 0.0765, Rsquare: 0.6724
    RMSE Rsquare
1 0.0765  0.6724
predictions_test2 <- predict(lasso_model, s = lambda_best2, newx = x_test2)
eval_results(y_test2, predictions_test2, test2) #RMSE: 0.0811, Rsquare: 0.6354
    RMSE Rsquare
1 0.0811  0.6354
## --- Model Selection --- ##

#Create Kable table to succinctly summarize model optimization results
Model <- c('1', '2', '3', '4', '5', '6', '7')
Method <- c('Linear', 'Linear', 'Linear', 'Ridge', 'Ridge', 'Lasso', 'Lasso')
Data <- c('raw', 'raw(AIC)', 'transformed', 'raw', 'transformed', 'raw', 'transformed')
Var_Num <- c(22, 17, 16, 22, 16, 22, 16)
R2_train <- c(0.6678, 0.673, 0.671, 0.6734, 0.6732, 0.6681, 0.6724)
RMSE_train <- c(0.0759, 0.0759, 0.0764, 0.0761, 0.0764, 0.0767, 0.0765)
R2_test <- c(0.6678, 0.673, 0.71, 0.659, 0.632, 0.6642, 0.6354)
RMSE_test <- c(0.0827, 0.0842, 0.0815, 0.0789, 0.0814, 0.0783, 0.0811)

output <- cbind(Model, Method, Data, Var_Num, R2_train, RMSE_train, R2_test, RMSE_test)

output %>%
  kbl(caption = "Regression Model Comparison") %>%
  kable_minimal() %>%
  kable_styling(latex_options = "hold_position")
Regression Model Comparison
Model Method Data Var_Num R2_train RMSE_train R2_test RMSE_test
1 Linear raw 22 0.6678 0.0759 0.6678 0.0827
2 Linear raw(AIC) 17 0.673 0.0759 0.673 0.0842
3 Linear transformed 16 0.671 0.0764 0.71 0.0815
4 Ridge raw 22 0.6734 0.0761 0.659 0.0789
5 Ridge transformed 16 0.6732 0.0764 0.632 0.0814
6 Lasso raw 22 0.6681 0.0767 0.6642 0.0783
7 Lasso transformed 16 0.6724 0.0765 0.6354 0.0811