Module 5
Exercise Session
24-hour Recall Data
The dataset (“24h_recall”) is derived from weighed 24-hour food intake data recording of students. The data is available in different formats (eg. xls, csv) that allow you to import it in different statistical software packages such as R, Stata, SPSS or SPlus. For this exercise, and the solutions, R will be used?
Datasets are often described using ‘dictionaries’, which give an easy to excess and understandable description of all variables present in the dataset.
The goal of this exercise is to provide hands-on experience in analyzing dietary data and applying energy adjustment methods.
When analyzing dietary data, individual differences in energy intake can bias relationships between nutrients and health outcomes. For example, people who consume more energy overall may also eat more fruit and fiber. To accurately assess the association between fruit intake and fiber intake, it’s crucial to adjust for total energy intake.
Before starting your analysis in R, you need to load the necessary packages. If you haven’t installed these packages before, run install.packages() first.
Tip: Install each package only once. After installation, use library() to load them whenever needed.
# Run this only once to install the required packages
# install.packages("tidyverse") # For data science workflows
# install.packages("pastecs") # For descriptive statistics
# install.packages("modelr") # For regression models
# install.packages("broom") # For tidying model outputs
# install.packages("ggplot2") # For data visualization
# install.packages("readr") # For reading data files
# install.packages("purrr") # For functional programming
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# Tidyverse: A suite of data science packages including ggplot2, dplyr, tidyr, readr, purrr, tibble, and more.
# It simplifies tasks like data cleaning, transformation, and visualization.
library(pastecs)
##
## Attaching package: 'pastecs'
##
## The following objects are masked from 'package:dplyr':
##
## first, last
##
## The following object is masked from 'package:tidyr':
##
## extract
# Pastecs: Provides tools for descriptive statistics, e.g., 'stat.desc()' for means, medians, SDs, and skewness.
library(modelr)
# Modelr: Works seamlessly with tidyverse to streamline regression modeling workflows.
# Functions like 'add_predictions()' and 'add_residuals()' help integrate model outputs with data.
library(broom)
##
## Attaching package: 'broom'
##
## The following object is masked from 'package:modelr':
##
## bootstrap
# Broom: Converts statistical model outputs into tidy data frames, making them easier to analyze and visualize.
# Example: 'tidy()' summarizes model estimates, 'augment()' adds predictions, 'glance()' gives overall model stats.
library(ggplot2)
# GGplot2: A data visualization package to create high-quality, customizable graphs using a layered grammar of graphics.
library(readr)
# Readr: Provides fast and friendly functions for importing and reading data, e.g., 'read_csv()' for CSV files.
library(purrr)
# Purrr: Simplifies functional programming tasks, like applying functions over lists and vectors with 'map()'.
# It works well with tidyverse pipelines for iterative operations.
First, download the file “24h_recall.csv” from Ufora, and save it to a location on your computer where you can easily find it.
Option 1: Set Your Working Directory
To load the file into R, you need to set the folder where you saved it as your working directory. For example, if you saved the file in:
You can set the working directory with the following command:
setwd("/Users/jeroenberden/Library/CloudStorage/OneDrive-UGent/PhD/module5_files")
Option 2: Import Manually
Alternatively, you can import the file directly into RStudio without setting the working directory:
Go to the Environment tab.
Click Import Dataset → From Text (readr).
Browse to the file location and follow the prompts.
We will use the first option described above. First, set your working directory to the folder where you saved the file. Then, use read_csv() to load the data into R.
setwd("/Users/jeroenberden/Library/CloudStorage/OneDrive-UGent/PhD/module5_files")
energy <- read_csv("24h_recall.csv")
## Rows: 68 Columns: 30
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): gender
## dbl (29): id, quantity, fruit_weight, vegetable_weight, protein_gram, fat_gr...
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
energy <- energy %>%
mutate_all(~replace(., is.na(.), 0))
# 'read_csv()' is a function from the 'readr' package, part of the 'tidyverse'.
# It reads CSV (comma-separated values) files into R and creates a clean "tibble" data frame.
# The dataset "database energy adjustment.csv" is now stored in the 'energy' object
# for further analysis and manipulation.
Before calculating the mean and SD, it’s good practice to explore the data visually for potential outliers. Outliers can be removed for a cleaner analysis. Here, we visualize the distribution using histograms and boxplots and then filter out extreme outliers.
Visualise current dataset for energy intake to visually get an idea of potential outliers.
# Histogram: Energy intake distribution
ggplot(energy, aes(x = energy_kcal)) +
geom_histogram(binwidth = 100, fill = "skyblue", color = "black", alpha = 0.7) +
labs(title = "Distribution of Total Energy Intake (kcal/day)",
x = "Energy Intake (kcal/day)",
y = "Frequency") +
theme_minimal()
# Boxplot: Detect outliers in energy intake
ggplot(energy, aes(y = energy_kcal)) +
geom_boxplot(fill = "lightgreen", color = "black", alpha = 0.7) +
labs(title = "Boxplot of Total Energy Intake (kcal/day)",
y = "Energy Intake (kcal/day)") +
theme_minimal()
Remove extreme outliers and save as a new dataset. There are different ways to do this, but this exercise opts for a energy intake cut-off value of 4000 kcalories per day.
energy_filtered <- filter(energy, energy_kcal < 4000)
Visualise the filtered data.
# Histogram:
ggplot(energy_filtered, aes(x = energy_kcal)) +
geom_histogram(binwidth = 100, fill = "skyblue", color = "black", alpha = 0.7) +
labs(title = "Distribution of Total Energy Intake (kcal/day) - After Outlier Removal",
x = "Energy Intake (kcal/day)",
y = "Frequency") +
theme_minimal()
# Boxplot
ggplot(energy_filtered, aes(y = energy_kcal)) +
geom_boxplot(fill = "lightgreen", color = "black", alpha = 0.7) +
labs(title = "Boxplot of Total Energy Intake (kcal/day) - After Outlier Removal",
y = "Energy Intake (kcal/day)") +
theme_minimal()
Calculate mean and SD individually.
mean(energy_filtered$energy_kcal)
## [1] 2030.047
sd(energy_filtered$energy_kcal)
## [1] 686.8057
Here, we calculate all relevant summary statistics (mean, SD, min, max, and count) and save them in a single object called sum_energy.
sum_energy <- energy_filtered %>%
summarise(
count = n(), # Number of observations
mean_energy = mean(energy_kcal, na.rm = TRUE), # Mean energy intake
sd_energy = sd(energy_kcal, na.rm = TRUE), # Standard deviation
min_energy = min(energy_kcal, na.rm = TRUE), # Minimum value
max_energy = max(energy_kcal, na.rm = TRUE) # Maximum value
)
sum_energy
## # A tibble: 1 × 5
## count mean_energy sd_energy min_energy max_energy
## <int> <dbl> <dbl> <dbl> <dbl>
## 1 62 2030. 687. 739. 3982.
After the removal of 6 extreme values (using an arbitrary cut-off of > 4000 kcal for this exercise), the dataset now contains 62 observations.
Descriptive Statistics:
Mean energy intake: 2030 kcal
Standard deviation (SD): 687
Minimum value: 739 kcal
Maximum value: 3982 kcal
Sample size (n): 62
Before we begin analyzing, it’s important to check the basic summary statistics of our data. This helps us understand the spread and distribution of the values. We’ll calculate the minimum, maximum, mean, and other key measures to get a feel for our data. Checking for any unusual values or outliers is always a good practice.
First, we create a smaller dataset that contains the energy intake for each time period: morning meals, lunch, dinner, and snacks. Then, we’ll calculate the descriptive statistics for these variables.
Create a new dataset with energy intake by time of day.
energy_stat_time <- cbind(
energy_filtered$energy_morning,
energy_filtered$energy_lunch,
energy_filtered$energy_dinner,
energy_filtered$energy_snack
)
energy_stat_time <- as.data.frame(energy_stat_time)
stat.desc(energy_stat_time)
## V1 V2 V3 V4
## nbr.val 62.000000 62.000000 62.000000 62.000000
## nbr.null 43.000000 42.000000 42.000000 43.000000
## nbr.na 0.000000 0.000000 0.000000 0.000000
## min 0.000000 0.000000 0.000000 0.000000
## max 1222.132476 1129.831360 1188.460577 1325.499718
## range 1222.132476 1129.831360 1188.460577 1325.499718
## sum 10104.561574 11753.289660 10980.760507 7813.429185
## median 0.000000 0.000000 0.000000 0.000000
## mean 162.976800 189.569188 177.109040 126.023051
## SE.mean 38.369326 39.527638 37.903395 34.007897
## CI.mean.0.95 76.724210 79.040397 75.792522 68.002993
## var 91276.722273 96870.918151 89073.375051 71705.297258
## std.dev 302.120377 311.240933 298.451629 267.778448
## coef.var 1.853763 1.641833 1.685129 2.124837
Once we have the percentages, we can calculate the average (mean) percentage of total energy intake for each time period (morning meals, lunch, dinner, snacks). This helps us understand how energy is distributed across the day.
means <- energy_share %>%
summarise(
mean_morning = mean(share_morning, na.rm = TRUE)*100,
mean_lunch = mean(share_lunch, na.rm = TRUE)*100,
mean_dinner = mean(share_dinner, na.rm = TRUE)*100,
mean_snack = mean(share_snack, na.rm = TRUE)*100
)
print(means)
## # A tibble: 1 × 4
## mean_morning mean_lunch mean_dinner mean_snack
## <dbl> <dbl> <dbl> <dbl>
## 1 23.3 30.1 28.2 18.4
To get a better understanding of how energy intake is distributed across the day, we’ll visualize the data using a boxplot. This will show the spread of the data for each time period, and we’ll add points for the average (mean) percentage.
energy_long <- energy_share %>%
select(share_morning, share_lunch, share_dinner, share_snack) %>%
pivot_longer(
cols = everything(),
names_to = "time_period",
values_to = "share"
)
# Create a boxplot
ggplot(energy_long, aes(x = time_period, y = share, fill = time_period)) +
geom_boxplot(outlier.colour = "red", outlier.size = 1.5) +
stat_summary(fun = mean, geom = "point", shape = 20, size = 3, color = "blue") + # Add mean points
scale_y_continuous(labels = scales::percent) +
labs(
title = "Distribution of Energy Intake by Time Period",
x = "Time Period",
y = "Percentage of Total Energy Intake",
fill = "Time Period"
) +
theme_minimal()
Finally, after calculating the percentages and visualizing the data, you will see the distribution of energy intake across the time periods. Based on the analysis, the typical distribution of energy intake might look something like this:
Morning meals: 23% of total energy intake
Lunch: 30% of total energy intake
Dinner: 28% of total energy intake
Snacks: 18% of total energy intake
We will compare the percentage of energy intake from food prepared at home versus food eaten out of home. By combining all out-of-home energy sources into one variable, we can easily analyze the data.
We first create a new variable called energy_allout that sums up energy intake from UGent, other places, and unspecified sources. This will represent all food consumed out of home.
energy_out_of_home <- energy_filtered %>%
mutate(
energy_allout = energy_ugent + energy_out + energy_no
)
To understand the distribution of energy intake for both home-prepared food (energy_home) and out-of-home food (energy_allout), we calculate descriptive statistics using the stat.desc() function.
stat.desc(energy_out_of_home[c("energy_home", "energy_allout")])
## energy_home energy_allout
## nbr.val 6.200000e+01 6.200000e+01
## nbr.null 0.000000e+00 1.700000e+01
## nbr.na 0.000000e+00 0.000000e+00
## min 1.142985e+02 0.000000e+00
## max 3.982048e+03 2.392214e+03
## range 3.867750e+03 2.392214e+03
## sum 9.392621e+04 3.193671e+04
## median 1.300452e+03 2.884729e+02
## mean 1.514939e+03 5.151083e+02
## SE.mean 1.116469e+02 6.836276e+01
## CI.mean.0.95 2.232517e+02 1.366998e+02
## var 7.728316e+05 2.897550e+05
## std.dev 8.791084e+02 5.382889e+02
## coef.var 5.802930e-01 1.045002e+00
Next, we reshape the data into a long format for visualization and create a boxplot to compare energy intake between the two locations.
energy_long <- energy_out_of_home %>%
select(energy_home, energy_allout) %>%
pivot_longer(cols = everything(), names_to = "location", values_to = "energy_intake")
ggplot(energy_long, aes(x = location, y = energy_intake, fill = location)) +
geom_boxplot() +
stat_summary(fun = mean, geom = "point", shape = 20, size = 3, color = "blue") + # Add mean points
labs(
title = "Comparison of Energy Intake: Home vs. Out of Home",
x = "Location",
y = "Energy Intake (kcal)",
fill = "Location"
) +
theme_minimal()
We now calculate the percentage of total energy intake (energy_kcal) coming from:
Home-prepared food (share_home)
Out-of-home food (share_allout)
energy_out_of_home <- energy_out_of_home %>%
mutate(
share_home = energy_home / energy_kcal, # Percentage of energy intake from home
share_allout = energy_allout / energy_kcal # Percentage of energy intake from out-of-home sources
)
To summarize the results, we calculate the mean percentage of energy intake for home-prepared and out-of-home food.
energy_out_of_home_shares <- energy_out_of_home %>%
summarise(
mean_home = mean(share_home, na.rm = TRUE) * 100, # Convert to percentage
mean_allout = mean(share_allout, na.rm = TRUE) * 100 # Convert to percentage
)
print(energy_out_of_home_shares)
## # A tibble: 1 × 2
## mean_home mean_allout
## <dbl> <dbl>
## 1 72.5 27.5
To make the results more intuitive, we create a bar plot to show the average percentage of energy intake from home and out-of-home sources.
energy_shares_long <- pivot_longer(energy_out_of_home_shares, cols = everything(), names_to = "source", values_to = "percentage")
# Bar plot
ggplot(energy_shares_long, aes(x = source, y = percentage, fill = source)) +
geom_bar(stat = "identity", width = 0.6) +
labs(
title = "Percentage of Energy Intake: Home vs. Out of Home",
x = "Source of Energy Intake",
y = "Mean Percentage (%)",
fill = "Source"
) +
theme_minimal() +
scale_fill_manual(values = c("mean_home" = "skyblue", "mean_allout" = "salmon")) +
geom_text(aes(label = round(percentage, 1)), vjust = -0.5)
The Shapiro-Wilk test is used to statistically test if the data is normally distributed:
Null hypothesis: The data is normally distributed.
Alternative hypothesis: The data is not normally distributed.
If the p-value < 0.05, we reject the null hypothesis, indicating the data is not normally distributed.
shapiro.test(energy_out_of_home$energy_kcal)
##
## Shapiro-Wilk normality test
##
## data: energy_out_of_home$energy_kcal
## W = 0.94405, p-value = 0.006966
shapiro.test(energy_out_of_home$share_home)
##
## Shapiro-Wilk normality test
##
## data: energy_out_of_home$share_home
## W = 0.88293, p-value = 2.526e-05
shapiro.test(energy_out_of_home$share_allout)
##
## Shapiro-Wilk normality test
##
## data: energy_out_of_home$share_allout
## W = 0.88293, p-value = 2.526e-05
The distribution of energy_kcal, share_home and share_allout are normally distributed.
t.test(energy_out_of_home$share_home, energy_out_of_home$share_allout, paired= TRUE)
##
## Paired t-test
##
## data: energy_out_of_home$share_home and energy_out_of_home$share_allout
## t = 6.6247, df = 61, p-value = 1.019e-08
## alternative hypothesis: true mean difference is not equal to 0
## 95 percent confidence interval:
## 0.3137455 0.5850360
## sample estimates:
## mean difference
## 0.4493908
• Home-prepared food accounts for about 72.5% of total energy intake.
• Out-of-home food accounts for about 27.5% of total energy intake.
Make boxplots.
ggplot(energy_filtered, aes(x = gender, y = energy_kcal, fill = gender)) +
geom_boxplot(outlier.shape = NA, alpha = 0.7) +
labs(
title = "Total Energy Intake by Gender",
x = "Gender",
y = "Total Energy Intake (kcal)"
) +
theme_minimal()
shapiro_results <- energy_filtered %>%
group_by(gender) %>%
summarize(shapiro_p_value = shapiro.test(energy_kcal)$p.value)
print(shapiro_results)
## # A tibble: 2 × 2
## gender shapiro_p_value
## <chr> <dbl>
## 1 female 0.490
## 2 male 0.00512
As energy intake is normally distributed for women, but not for men, we will both perform a t-test and a non-parametric test to compare results.
t.test(energy_kcal ~ gender, data = energy_filtered)
##
## Welch Two Sample t-test
##
## data: energy_kcal by gender
## t = -0.42899, df = 49.989, p-value = 0.6698
## alternative hypothesis: true difference in means between group female and group male is not equal to 0
## 95 percent confidence interval:
## -428.1023 277.4176
## sample estimates:
## mean in group female mean in group male
## 1992.376 2067.718
wilcox.test(energy_kcal ~ gender, data = energy_filtered)
##
## Wilcoxon rank sum exact test
##
## data: energy_kcal by gender
## W = 499, p-value = 0.8014
## alternative hypothesis: true location shift is not equal to 0
There is no signficant difference between men and women for energy intake, both the t-test and the non-paramteric alternative lead to the same conclusions.
Visualise first.
# Histogram
ggplot(energy_filtered, aes(x = fibre_gram)) +
geom_histogram(binwidth = 100, fill = "skyblue", color = "black", alpha = 0.7) +
labs(title = "Distribution of Total Fibre Intake (g/day)", # Update title to reflect fibre intake in grams
x = "Fibre Intake (g/day)", # Update x-axis label
y = "Frequency") +
theme_minimal()
# Boxplot
ggplot(energy_filtered, aes(y = fibre_gram)) +
geom_boxplot(fill = "lightgreen", color = "black", alpha = 0.7) +
labs(title = "Boxplot of Total Fibre Intake (g/day)", # Update title to reflect fibre intake in grams
y = "Fibre Intake (g/day)") + # Update y-axis label
theme_minimal()
mean(energy_filtered$fibre_gram, na.rm = TRUE)
## [1] 20.74819
sd(energy_filtered$fibre_gram, na.rm = TRUE)
## [1] 13.02773
Summary of fibre intake in grams per day:
mean = 21
SD = 13
# Histogram
ggplot(energy_filtered, aes(x = health_marker)) +
geom_histogram(binwidth = 100, fill = "skyblue", color = "black", alpha = 0.7) +
labs(title = "Distribution of Health Marker", # Updated title to reflect health marker
x = "Health Marker", # Updated x-axis label
y = "Frequency") +
theme_minimal()
# Boxplot
ggplot(energy_filtered, aes(y = health_marker)) +
geom_boxplot(fill = "lightgreen", color = "black", alpha = 0.7) +
labs(title = "Boxplot of Health Marker", # Updated title to reflect health marker
y = "Health Marker") + # Updated y-axis label
theme_minimal()
mean(energy_filtered$health_marker, na.rm = TRUE)
## [1] 2262.216
sd(energy_filtered$health_marker, na.rm = TRUE)
## [1] 915.6574
Summary of the health marker:
mean = 2262
SD = 916
Scatter plot for the relationship fiber intake and health marker
ggplot(data = energy_filtered, mapping = aes(y = health_marker, x = fibre_gram)) +
geom_point() + # Scatter plot
geom_smooth(se = TRUE) + # Add smoothing line with confidence interval
labs(title = "Health Marker vs. Fibre Intake",
x = "Fibre Intake (grams)",
y = "Health Marker") +
theme_minimal()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
The scatter plot seems to indicate an association between fibre intake and health.
Use the slides to help you interpret results!
After each model, don’t forget to check:
• Independence of errors: Residuals vs fitted plot.
• Homoscedasticity: Residuals vs fitted plot.
• Normality of errors: Histogram, Q-Q plot, or Shapiro-Wilk test.
model0 <- lm(health_marker ~ fibre_gram, data = energy_filtered)
summary(model0)
##
## Call:
## lm(formula = health_marker ~ fibre_gram, data = energy_filtered)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2095.06 -466.10 -45.23 736.62 1553.79
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1970.869 217.321 9.069 7.56e-13 ***
## fibre_gram 14.042 8.891 1.579 0.12
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 904.6 on 60 degrees of freedom
## Multiple R-squared: 0.03991, Adjusted R-squared: 0.02391
## F-statistic: 2.494 on 1 and 60 DF, p-value: 0.1195
confint(model0)
## 2.5 % 97.5 %
## (Intercept) 1536.161704 2405.57622
## fibre_gram -3.742308 31.82641
ggplot(data = data.frame(fitted = fitted(model0), residuals = residuals(model0)), aes(x = fitted, y = residuals)) +
geom_point() +
geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
labs(title = "Residuals vs Fitted (Homoscedasticity)",
x = "Fitted values",
y = "Residuals") +
theme_minimal()
hist(residuals(model0))
energy_filtered$Remaining_Energy <- energy_filtered$energy_kcal - energy_filtered$fibre_gram * 2
model1 <- lm(health_marker ~ fibre_gram + Remaining_Energy, data = energy_filtered)
summary(model1)
##
## Call:
## lm(formula = health_marker ~ fibre_gram + Remaining_Energy, data = energy_filtered)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1757.0 -448.7 -107.2 559.0 2435.1
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3188.2479 329.1250 9.687 8.35e-14 ***
## fibre_gram 52.3671 11.4769 4.563 2.61e-05 ***
## Remaining_Energy -1.0121 0.2241 -4.517 3.06e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 786.4 on 59 degrees of freedom
## Multiple R-squared: 0.2866, Adjusted R-squared: 0.2624
## F-statistic: 11.85 on 2 and 59 DF, p-value: 4.71e-05
ggplot(data = data.frame(fitted = fitted(model1), residuals = residuals(model1)), aes(x = fitted, y = residuals)) +
geom_point() +
geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
labs(title = "Residuals vs Fitted (Homoscedasticity)",
x = "Fitted values",
y = "Residuals") +
theme_minimal()
hist(residuals(model1))
model2 <- lm(health_marker ~ fibre_gram + energy_kcal, data = energy_filtered)
summary(model2)
##
## Call:
## lm(formula = health_marker ~ fibre_gram + energy_kcal, data = energy_filtered)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1757.0 -448.7 -107.2 559.0 2435.1
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3188.2479 329.1250 9.687 8.35e-14 ***
## fibre_gram 54.3912 11.8120 4.605 2.25e-05 ***
## energy_kcal -1.0121 0.2241 -4.517 3.06e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 786.4 on 59 degrees of freedom
## Multiple R-squared: 0.2866, Adjusted R-squared: 0.2624
## F-statistic: 11.85 on 2 and 59 DF, p-value: 4.71e-05
ggplot(data = data.frame(fitted = fitted(model2), residuals = residuals(model2)), aes(x = fitted, y = residuals)) +
geom_point() +
geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
labs(title = "Residuals vs Fitted (Homoscedasticity)",
x = "Fitted values",
y = "Residuals") +
theme_minimal()
hist(residuals(model2))
energy_filtered$fiber_per_kcal <- energy_filtered$fibre_gram/energy_filtered$energy_kcal
model3 <- lm(health_marker ~ fiber_per_kcal, data = energy_filtered)
summary(model3)
##
## Call:
## lm(formula = health_marker ~ fiber_per_kcal, data = energy_filtered)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1764.72 -481.11 -58.62 606.17 1678.37
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1153.8 316.4 3.647 0.000557 ***
## fiber_per_kcal 111538.6 30005.6 3.717 0.000445 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 832.4 on 60 degrees of freedom
## Multiple R-squared: 0.1872, Adjusted R-squared: 0.1736
## F-statistic: 13.82 on 1 and 60 DF, p-value: 0.0004448
ggplot(data = data.frame(fitted = fitted(model3), residuals = residuals(model3)), aes(x = fitted, y = residuals)) +
geom_point() +
geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
labs(title = "Residuals vs Fitted (Homoscedasticity)",
x = "Fitted values",
y = "Residuals") +
theme_minimal()
hist(residuals(model3))
model_res <- lm(fibre_gram ~ energy_kcal, data = energy_filtered)
summary(model_res)
##
## Call:
## lm(formula = fibre_gram ~ energy_kcal, data = energy_filtered)
##
## Residuals:
## Min 1Q Median 3Q Max
## -17.3796 -4.9480 0.6486 4.5226 25.6133
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -8.372366 3.430933 -2.440 0.0176 *
## energy_kcal 0.014345 0.001602 8.953 1.19e-12 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 8.595 on 60 degrees of freedom
## Multiple R-squared: 0.5719, Adjusted R-squared: 0.5648
## F-statistic: 80.15 on 1 and 60 DF, p-value: 1.185e-12
energy_filtered$residual_fiber <- residuals(model_res)
model4 <- lm(health_marker ~ residual_fiber, data = energy_filtered)
summary(model4)
##
## Call:
## lm(formula = health_marker ~ residual_fiber, data = energy_filtered)
##
## Residuals:
## Min 1Q Median 3Q Max
## -2150.2 -428.9 -123.3 693.1 2092.3
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 2262.22 101.11 22.373 < 2e-16 ***
## residual_fiber 54.39 11.96 4.548 2.68e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 796.2 on 60 degrees of freedom
## Multiple R-squared: 0.2564, Adjusted R-squared: 0.244
## F-statistic: 20.69 on 1 and 60 DF, p-value: 2.684e-05
ggplot(data = data.frame(fitted = fitted(model4), residuals = residuals(model4)), aes(x = fitted, y = residuals)) +
geom_point() +
geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
labs(title = "Residuals vs Fitted (Homoscedasticity)",
x = "Fitted values",
y = "Residuals") +
theme_minimal()
hist(residuals(model4))
model5 <- lm(health_marker ~ fibre_gram + vitc_mg + protein_gram + fat_gram + carbohydrates_gram + na_mg, data = energy_filtered)
summary(model5)
##
## Call:
## lm(formula = health_marker ~ fibre_gram + vitc_mg + protein_gram +
## fat_gram + carbohydrates_gram + na_mg, data = energy_filtered)
##
## Residuals:
## Min 1Q Median 3Q Max
## -8.8354 -3.4568 -0.1075 3.1763 12.3069
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 4.278e+03 2.318e+00 1845.53 <2e-16 ***
## fibre_gram 3.496e+01 7.847e-02 445.49 <2e-16 ***
## vitc_mg 8.914e-01 5.183e-03 172.00 <2e-16 ***
## protein_gram 8.378e-01 3.040e-02 27.56 <2e-16 ***
## fat_gram -1.444e+00 3.367e-02 -42.88 <2e-16 ***
## carbohydrates_gram -1.487e+00 8.046e-03 -184.83 <2e-16 ***
## na_mg -8.997e-01 8.051e-04 -1117.58 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 4.972 on 55 degrees of freedom
## Multiple R-squared: 1, Adjusted R-squared: 1
## F-statistic: 3.447e+05 on 6 and 55 DF, p-value: < 2.2e-16
ggplot(data = data.frame(fitted = fitted(model5), residuals = residuals(model5)), aes(x = fitted, y = residuals)) +
geom_point() +
geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
labs(title = "Residuals vs Fitted (Homoscedasticity)",
x = "Fitted values",
y = "Residuals") +
theme_minimal()
hist(residuals(model5))
Estimate of relationship between fiber intake in grams per day and the health marker for each model:
Model 0: 14.04
Model 1: 52.37
Model 2: 54.39
Model 3: 11539
Model 4: 54.40
Model 5: 34.96