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.
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.
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.
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?
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.
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:
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.
Vaccination Coverage Countries
Vaccination Progress Over Time
Dataset 2 — Renewable Energy Capacity Time Series
Data Loading and Initial Inspection
Total Installed
Renewable Energy Capacity
Total Renewable Energy
Capacity
Interpretation and Key
Findings
Dataset 3 — GDP by Country (1960–2022)
Data Loading and Initial Inspection
Validation of the Tidy Dataset
GDP Trends for Selected Countries
Average GDP for Selected Countries
Interpretation and Key Findings
#---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")| 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.…
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> …
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
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> …
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"
[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"
[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
[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
[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"
[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")| 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
[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
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")| 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 |