---
title: "431 Project B Sample Study 2 Report"
author: "Thomas E. Love"
date: last-modified
format:
html:
toc: true
number-sections: true
code-fold: show
code-tools: true
code-overflow: wrap
embed-resources: true
date-format: iso
theme: materia
---
:::{.callout-important}
## Reminders from Dr. Love
1. Remember that each subsection should include at least one complete sentence explaining what you are doing, specifying the variables you are using and how you are using them, and then conclude with at least one complete sentence of discussion of the key conclusions you draw from the current step, and a discussion of any limitations you can describe that apply to the results.
2. If you want to download the Quarto code I used to create this document **to use as a template for your own work**, click on the Code button near the title of this Sample Study.
3. In general, DO NOT use my exact words (other than the section and subsection headings) included in this sample report in your project. Rewrite everything to make it relevant to your situation. Do not repeat my instructions back to me.
- One exception is that I have demonstrated the interpretation of at least one point estimate and at least one confidence interval in this Sample Report, using language that I would be happy to see you use.
:::
# Setup and Data Ingest
This document demonstrates analyses we are asking you to complete in Study 2 for Project B. The simulated data used in this example report are found in the `hbp_study.csv` data file available in the projectB section of [our 431-data website](https://github.com/THOMASELOVE/431-data).
These are simulated data from a study of high blood pressure in 999 African-American adult subjects who are not of Hispanic or Latino ethnicity. To be included, the subject had to be between 33 and 83 years of age at baseline, have a series of items available in their health record at baseline, including a baseline systolic blood pressure, and then return for a blood pressure check 18 months later. Our goal will be to build a prediction model for the subject's systolic blood pressure at the end of the 18-month period, with the key predictor being that same subject's systolic blood pressure at the start of the period, and adjusting (in our larger model) for several other characteristics of the subjects at baseline.
## Initial Setup and Package Loads in R
```{r}
#| message: false
library(broom)
library(car)
library(GGally)
library(janitor)
library(knitr)
library(mosaic)
library(mice)
library(naniar)
library(patchwork)
library(xfun)
library(easystats)
library(tidyverse)
## Global options
opts_chunk$set(comment=NA)
theme_set(theme_lucid())
options(dplyr.summarise.inform = FALSE)
```
## Loading the Raw Data into R
Here, we load the data using `read_csv` and then convert all `character` variables to `factors` in R, and then change our identifying code: `subj_id` back to a character variable.
```{r data_load}
hbp_study <- read_csv("data/hbp_study.csv", show_col_types = FALSE) |>
mutate(across(where(is.character), as.factor)) |>
mutate(subj_id = as.character(subj_id))
```
# Cleaning the Data
## Merging the Data
In my little demonstration here, I don't have to do any merging. See the Study 1 Example for an example merging data.
## The Raw Data
The `hbp_study` data set includes 12 variables and 999 adult subjects. For each subject, we have gathered
- baseline information on their `age`, and their `sex`,
- whether or not they have a `diabetes` diagnosis,
- the socio-economic status of their neighborhood of residence (`nses`),
- their body-mass index (`bmi1`) and systolic blood pressure (`sbp1`),
- their `insurance` type, `tobacco` use history, and
- whether or not they have a prescription for a `statin`, or for a `diuretic`.
- Eighteen months later, we gathered a new systolic blood pressure (`sbp2`) for each subject.
```{r hbp_study_data_in_the_raw}
glimpse(hbp_study)
```
**Note**: If you have more than 20 variables in your initial (raw) data set, prune it down to 20 as the first step before showing us the results of `glimpse` for your data.
This tibble describes twelve variables, including:
- a character variable called `subj_id` not to be used in our model except for identification of subjects,
- our outcome (`sbp2`) and our key predictor (`sbp1`) that describe systolic blood pressure at two different times.
- seven categorical candidate predictors, specifically `sex`, `diabetes`, `nses`, `insurance`, `tobacco`, `statin`, and `diuretic`, each specified here in R as either a factor or a 1/0 numeric variable (`statin` and `diuretic`),
- three quantitative candidate predictors, specifically `age`, `bmi1` and `sbp1`.
## Which variables should be included in the tidy data set?
In fitting my models, I actually plan only to use five predictors: `sbp1`, `age`, `bmi1`, `diabetes` and `tobacco` to model my outcome: `sbp2`. Even though I'm not planning to use all of these predictors in my models, I'm going to build a tidy data set including all of them anyway, so I can demonstrate solutions to some problems you might have.
When you build your tidy data set in the next section, restrict it to the variables (outcomes, predictors and `subj_id`) that you will actually use in your modeling.
In building our tidy version of these data, we must:
- deal with the ordering of levels in the multi-categorical variables `nses`, `insurance` and `tobacco`,
- change the name of `nses` to something more helpful - I'll use `nbhd_ses` as the new name^[Admittedly, that's not much better.].
## Checking our Outcome and Key Predictor
```{r}
df_stats(~ sbp2 + sbp1, data = hbp_study)
```
We have no missing values in our outcome or our key predictor, and each of the values look plausible, so we'll move on.
## Checking the Quantitative Predictors
Besides `sbp1` we have two other quantitative predictor candidates, `age` and `bmi1`.
```{r}
df_stats(~ age + bmi1, data = hbp_study)
```
We know that all subjects in these data had to be between 33 and 83 years of age in order to be included, so we're happy to see that they are. We have five missing values (appropriately specified with NA) and no implausible values in our BMI values (I would use 16-80 as a plausible range of BMI values for adults.) Things look OK for now, as we'll deal with the missing values last.
## Checking the Categorical Variables
For categorical variables, it's always worth it to check to see whether the existing orders of the factor levels match the inherent order of the information, as well as whether there are any levels which we might want to collapse due to insufficient data, and whether there are any missing values.
### `nses`: home neighborhood's socio-economic status
```{r levels_of_nses}
hbp_study |> tabyl(nses)
```
- The order of `nses`, instead of the alphabetical ("High", "Low", "Middle", "Very Low"), should go from "Very Low" to "Low" to "Middle" to "High", or perhaps its reverse.
- Let's fix that using the `fct_relevel` function from the `forcats` package, which is part of the `tidyverse`. While we're at it, we'll rename the variable `nbhd_ses` which is more helpful to me.
- Then we'll see how many subjects fall in each category.
```{r relevel_nses}
hbp_study <- hbp_study |>
rename(nbhd_ses = nses) |>
mutate(nbhd_ses = fct_relevel(nbhd_ses, "Very Low", "Low",
"Middle", "High"))
hbp_study |> tabyl(nbhd_ses)
```
We have 8 missing values of `nbhd_ses`. We'll deal with that later.
### `tobacco`: tobacco use history
```{r levels_of_tobacco}
hbp_study |> tabyl(tobacco)
```
- For `tobacco`, instead of ("current", "never", "quit"), we want a new order: ("never", "quit", "current").
```{r relevel_tobacco}
hbp_study <- hbp_study |>
mutate(tobacco = fct_relevel(tobacco, "never", "quit",
"current"))
hbp_study |> count(tobacco)
```
We have 23 missing values of `tobacco`. Again, we'll deal with that later.
### `insurance`: primary insurance type
```{r levels_insurance}
hbp_study |> tabyl(insurance)
```
- For `insurance`, we'll change the order to ("Medicare", "Private", "Medicaid", "Uninsured")
```{r relevel_insurance}
hbp_study <- hbp_study |>
mutate(insurance = fct_relevel(insurance, "Medicare",
"Private", "Medicaid",
"Uninsured"))
hbp_study |> tabyl(insurance)
```
Note that any levels left out of a `fct_relevel` statement get included in their current order, after whatever levels have been specified.
### What about the subjects?
It is important to make sure that we have a unique (distinct) code (here, `subj_id`) for each row in the raw data set.
```{r}
nrow(hbp_study)
n_distinct(hbp_study |> select(subj_id))
```
OK, that's fine.
## Dealing with Missingness
In Study 2, we will take the following steps once we have ensured that any *missing* values are appropriately specified using `NA`.
1. If there are any missing values in your outcome, drop those subjects.
2. If there are any missing values in your key predictor, drop those subjects.
3. Build your codebook using the original data you have (including missing values.)
4. Once you have built the codebook, perform single imputation with the **mice** package to obtain your analytic sample, which you will then partition into a training and testing sample, and then use for the remainder of the work (everything after the codebook.)
## Steps 1 and 2: Missing values in our outcome or key predictor?
```{r}
miss_var_summary(hbp_study)
miss_case_table(hbp_study)
```
We are missing data for 36 of our 999 subjects, but we don't have any missing values in either our outcome `sbp2` or our key predictor `sbp1`, so we'll move on to build our codebook.
# Codebook and Data Description
## The Codebook
:::{.callout-note}
Below, I've demonstrated the task of building a set of variable descriptions for a larger set of predictors than I actually intend to use, just to illustrate.
The 12 variables in the `hbp_study` tibble are as follows.
Variable | Type | Description / Levels
---------: | :-------------: | --------------------------------------------
`subj_id` | Character | subject code (A001-A999)
`sbp2` | Quantitative | **outcome** variable, SBP after 18 months, in mm Hg
`sbp1` | Quantitative | **key predictor** baseline SBP (systolic blood pressure), in mm Hg
`age` | Quantitative | age of subject at baseline, in years
`sex` | Binary | Male or Female
`diabetes` | Binary | Does subject have a diabetes diagnosis: No or Yes
`nbhd_ses` | 4 level Cat. | Socio-economic status of subject's home neighborhood: Very Low, Low, Middle and High
`bmi1` | Quantitative | subject's body-mass index at baseline
`insurance` | 4 level Cat. | subject's insurance status at baseline: Medicare, Private, Medicaid, Uninsured
`tobacco` | 3 level Cat. | subject's tobacco use at baseline: never, quit (former), current
`statin` | Binary | 1 = statin prescription at baseline, else 0
`diuretic` | Binary | 1 = diuretic prescription at baseline, else 0
:::
In fitting my models, I actually plan only to use five predictors: `sbp1`, `age`, `bmi1`, `diabetes` and `tobacco` to model my outcome: `sbp2`. So let's create that data set as a tibble, and provide its set of variable descriptions.
```{r}
hbp_a1 <- hbp_study |>
select(subj_id, sbp2, sbp1, age, bmi1, diabetes, tobacco)
```
The 7 variables in the `hbp_a1` tibble are as follows.
Variable | Type | Description / Levels
---------: | :-------------: | --------------------------------------------
`subj_id` | Character | subject code (A001-A999)
`sbp2` | Quantitative | **outcome** variable, SBP after 18 months, in mm Hg
`sbp1` | Quantitative | **key predictor** baseline SBP (systolic blood pressure), in mm Hg
`age` | Quantitative | age of subject at baseline, in years
`bmi1` | Quantitative | subject's body-mass index at baseline
`diabetes` | Binary | Does subject have a diabetes diagnosis: No or Yes
`tobacco` | 3 level Cat. | subject's tobacco use at baseline: never, quit (former), current
## Print the Tibble
First, we'll provide a printout of the tibble, which confirms that we have one.
```{r}
hbp_a1
```
OK. All set. Now we show the `data_codebook()` results.
## `data_codebook()` results
```{r}
data_codebook(hbp_a1 |> select(-subj_id))
```
We should (and do) see no implausible values here, and our categorical variables are treated as factors with a rational ordering for the levels.
## Our Single Imputation
We will now assuming MISSING AT RANDOM and singly impute the missing values in `hbp_a1`, creating a new analytic tibble called `hbp_a2`, which we will use for the rest of our work.
```{r}
set.seed(4311)
hbp_imp <- mice(hbp_a1, m = 1, printFlag = FALSE)
hbp_a2 <- complete(hbp_imp)
n_miss(hbp_a2)
```
## Partition the Data
First, we should check that our subject identifying codes are unique to each row of our analytic data.
```{r}
c(nrow(hbp_a2), n_distinct(hbp_a2$subj_id))
```
OK. Since those two values match, we should be ready to partition. We'll put 70% of the data in the training sample (`hbp_train`) leaving the other 30% for the test sample (`hbp_test`).
```{r}
set.seed(4312)
hbp_train <- hbp_a2 |> slice_sample(prop = 0.7, replace = FALSE)
hbp_test <- anti_join(hbp_a2, hbp_train, by = "subj_id")
```
# My Research Question
Here you should provide background information on the study, and the subjects, so that we understand what you're talking about in your research question. I'll skip that in the demo, because I've done it already in introducing the data set, but you'll need that here.
A natural research question here would be something like:
> How effectively can we predict systolic BP 18 months after baseline using baseline systolic BP, and is the quality of prediction meaningfully improved when I adjust for four other predictors (baseline age, body-mass index, diabetes diagnosis and tobacco use) in the `hbp_study` data?
Please don't feel obliged to follow this format precisely in stating your question, and note that your "smaller" model needs to include your key predictor **and** at least one other predictor, something I didn't require of myself in posing this question.
# Transforming the Outcome
## Visualizing the Outcome Distribution
I see at least three potential graphs to use to describe the distribution of our outcome variable, `sbp2`. Again, remember we're using only the **training** sample here.
- A boxplot, probably accompanied by a violin plot to show the shape of the distribution more honestly.
- A histogram, which could perhaps be presented as a density plot with a Normal distribution superimposed.
- A Normal Q-Q plot to directly assess Normality.
I expect you to show at least two of these three, but I will display all three here. Should we see substantial skew in the outcome data, we will want to consider an appropriate transformation, and then display the results of that transformation, as well.
**WARNING**: Please note that I am deliberately showing you plots that are less finished than I hope you will provide.
- The coloring is dull or non-existent.
- The theme is the default gray and white grid that lots of people dislike.
- There are no meaningful titles or subtitles.
- The axis labels select the default settings, and use incomprehensible variable names.
- The coordinates aren't flipped when that might be appropriate.
- I expect a much nicer presentation in your final work. Use the class slides and course text for good ideas.
```{r}
viz1 <- ggplot(hbp_train, aes(x = "", y = sbp2)) +
geom_violin() +
geom_boxplot(width = 0.25)
viz2 <- ggplot(hbp_train, aes(x = sbp2)) +
geom_histogram(bins = 30, col = "white")
viz3 <- ggplot(hbp_train, aes(sample = sbp2)) +
geom_qq() + geom_qq_line()
viz1 + viz2 + viz3 +
plot_annotation(title = "Less-Than-Great Plots of My Outcome's Distribution",
subtitle = "complete with a rotten title, default axis labels and bad captions")
```
Later, we'll augment this initial look at the outcome data with a Box-Cox plot to suggest a potential transformation. Should you decide to make such a transformation, remember to return here to plot the results for your new and transformed outcome.
## Numerical Summary of the Outcome
Assuming you plan no transformation of the outcome (and in our case, I am happy that the outcome data appear reasonably well-modeled by the Normal distribution) then you should just summarize the training data, with your favorite tool for that task. That might be:
- `lovedist()` from our `Love-431.R` script, or
- `favstats` from the `mosaic` package, as shown below, or
- something else, I guess.
But show **ONE** of these choices, and not all of them. Make a decision and go with it!
```{r}
favstats(~ sbp2, data = hbp_train)
```
## Numerical Summaries of the Predictors
We also need an appropriate set of numerical summaries of each predictor variable, in the training data. The `inspect` function provides a way to get results like `favstats`, but for an entire data frame.
```{r}
hbp_train |> select(-subj_id, -sbp2) |>
inspect()
```
Next, we will build and interpret a scatterplot matrix to describe the associations (both numerically and graphically) between the outcome and all predictors.
- We'll also use a Box-Cox plot to investigate whether a transformation of our outcome is suggested, and
- describe what a correlation matrix suggests about collinearity between candidate predictors.
## Scatterplot Matrix
Here, we will build a scatterplot matrix (or two) to show the relationship between our outcome and the predictors. I'll demonstrate the use of `ggpairs` from the `GGally` package.
- If you have more than five predictors (as we do in our case) you should build two scatterplot matrices, each ending with the outcome. Anything more than one outcome and five predictors becomes unreadable in Professor Love's view.
- If you have a multi-categorical predictor with more than four categories, that predictor will be very difficult to see and explore in the scatterplot matrix produced.
```{r}
#| message: false
temp <- hbp_train |>
select(sbp1, age, bmi1, diabetes, tobacco, sbp2)
ggpairs(temp, title = "Scatterplot Matrix",
lower = list(combo = wrap("facethist", bins = 20)))
```
At the end of this section, you should provide some discussion of the distribution of any key predictors, and their relationship to the outcome (all of that is provided in the bottom row if you place the outcome last, as you should, in selecting variables for the plot.)
**HINT**: For categorical variables, your efforts in this regard to summarize the relationships you see may be challenging. Your comments would be aided by the judicious use of numerical summaries. For example, suppose you want to study the relationship between tobacco use and `sbp2`, then you probably want to run and discuss the following results, in addition to the scatterplot matrix above.
```{r}
favstats(sbp2 ~ tobacco, data = hbp_train)
```
## Collinearity Checking
Next, we'll take a brief look at potential collinearity. Remember that we want to see strong correlations between our **outcome** and the predictors, but relatively modest correlations between the predictors.
None of the numeric candidate predictors show any substantial correlation with each other. The largest Pearson correlation (in absolute value) between predictors is (-0.239) for `age` and `bmi1`, and that's not strong. If we did see signs of meaningful collinearity, we might rethink our selected set of predictors.
I'll recommend later that you run a generalized VIF (variance inflation factor) calculation^[As we'll see in that setting, none of the generalized variance inflation factors will approach the 5 or so that would cause us to be seriously concerned about collinearity.] after fitting your kitchen sink model just to see if anything pops up (in my case, it won't.)
## `boxCox` function to assess need for transformation of our outcome
To use the `boxCox` approach here, we need to ensure that the distribution of our outcome, `sbp2`, includes strictly positive values. We can see from our numerical summary earlier that the minimum `sbp2` in our `hbp_train` sample is 90, so we're OK.
- Note that I am restricting myself here to the five predictors I actually intend to use in building models.
- Although we're generally using a 90% confidence interval in this project, we won't worry about that issue in the `boxCox` plot, and instead just look at the point estimate from `powerTransform`.
- These commands (`boxCox` and `powerTransform`) come from the `car` package.
```{r boxCox_plot}
model_temp <- lm(sbp2 ~ sbp1 + age + bmi1 + diabetes + tobacco,
data = hbp_train)
boxCox(model_temp)
```
The estimated power transformation is about 0.5, which looks like a square root transformation of `sbp2` is useful. Given that I'm using another measure of `sbp`, specifically, `sbp1` to predict `sbp2`, perhaps I want to transform that, too?
```{r}
p1 <- ggplot(hbp_train, aes(x = sbp1, y = sqrt(sbp2))) +
geom_point() +
geom_smooth(method = "loess", formula = y ~ x, se = FALSE) +
geom_smooth(method = "lm", col = "red", formula = y ~ x, se = FALSE) +
labs(title = "SQRT(sbp2) vs. SBP1")
p2 <- ggplot(hbp_train, aes(x = sqrt(sbp1), y = sqrt(sbp2))) +
geom_point() +
geom_smooth(method = "loess", formula = y ~ x, se = FALSE) +
geom_smooth(method = "lm", col = "red", formula = y ~ x, se = FALSE) +
labs(title = "SQRT(sbp2) vs. SQRT(sbp1)")
p1 + p2
```
I don't see an especially large difference between these two plots. It is up to you to decide whether a transformation suggested by `boxCox` should be applied to your data.
- For the purposes of this project, you should stick to transformations of strictly positive outcomes, and to the square root (power = 0.5), square (power = 2), logarithm (power = 0) and inverse (power = -1) transformations. Don't make the transformation without being able to interpret the result well.
- Feel encouraged to scale your transformations (by multiplying or dividing by a constant) so that most of the transformed values wind up between 0 and 100 or 0 and 1000, if you like.
- If you do decide to include a transformation of your outcome in fitting models, be sure to back-transform any predictions you make at the end of the study so that we can understand the prediction error results.
- If your outcome data are substantially multimodal, I wouldn't treat the `boxCox` results as meaningful.
I'm going to use the square root transformation for both my outcome and for the key predictor, but I don't think it makes a big difference. I'm doing it mostly so that I can show you how to back-transform later.
# The Big Model
We will specify a "kitchen sink" linear regression model to describe the relationship between our outcome (potentially after transformation) and the main effects of each of our predictors. We'll need to:
- We'll assess the overall effectiveness, within your training sample, of your model, by considering performance in the training sample using a wide range of summaries.
- We'll need to specify the size, magnitude and meaning of all coefficients, and identify appropriate conclusions regarding effect sizes with 90% confidence intervals.
- Finally, we'll assess whether collinearity in the kitchen sink model has a meaningful impact, and describe how we know that.
## Fitting/Summarizing the Kitchen Sink model
Our "kitchen sink" or "big" model predicts the square root of `sbp2` using the predictors (square root of `sbp1`), `age`, `bmi1`, `diabetes` and `tobacco`.
First, we'll use `mutate` to create our two new transformed variables.
```{r kitchen_sink}
hbp_train <- hbp_train |>
mutate(sbp2_tr = sqrt(sbp2), sbp1_tr = sqrt(sbp1))
```
Next, we'll fit our "big" (kitchen sink) model.
```{r}
model_big <- lm(sbp2_tr ~ sbp1_tr + age + bmi1 + diabetes + tobacco,
data = hbp_train)
```
```{r}
model_performance(model_big)
```
## Effect Sizes: Coefficient Estimates
Specify the size and magnitude of all coefficients, providing estimated effect sizes with 90% confidence intervals.
```{r}
model_parameters(model_big, ci = 0.90)
```
## Describing the Equation
This model implies for the key predictor (`sbp_1`) that:
- **Point Estimate**: If we had two subjects with the same values of age, BMI, diabetes and tobacco status, but A had a baseline square root of SBP of, for example, 12 (so an SBP at baseline of 144) and B had a baseline square root of SBP one unit lower, so for example, 11 (so an SBP at baseline of 121) our `model_big` predicts that the square root of subject A's SBP at 18 months will be 0.34 points higher (90% CI: 0.28, 0.40) than that of subject B.
- **90% Confidence Interval**: Our `model_big` estimates the slope of the square root of `sbp_1` to be 0.34 in the participants in our study. When we generalize beyond study participants to the population they were selected at random from, then our data are compatible (at the 90% confidence level) with population slopes between 0.28 and 0.40 for our transformed `sbp_1`.
You should also provide a description of the meaning (especially the direction) of the **point estimates** of the other coefficients in your model being sure to interpret the coefficients as having meaning *holding all other predictors constant*, but I'll skip that here.
# The Smaller Model
Here, we will build a second linear regression model using a subset of our "kitchen sink" model predictors, chosen to maximize predictive value within our training sample.
- We'll specify the method you used to obtain this new model. (Backwards stepwise elimination is appealing but not great. It's perfectly fine to just include the key predictor and one other predictor you like, or to use best subsets to generate a subset for this new model, so long as your subset includes your key predictor.)
## Backwards Stepwise Elimination
```{r stepwise_bw_model}
step(model_big)
```
The backwards selection stepwise approach suggests a model with `sqrt(sbp1)` and `tobacco`, but not `age`, `bmi1` or `diabetes`.
## Fitting the "small" model
```{r}
model_small <- lm(sqrt(sbp2) ~ sqrt(sbp1) + tobacco, data = hbp_train)
model_performance(model_small)
```
## Effect Sizes: Coefficient Estimates
```{r}
model_parameters(model_small, ci = 0.90)
```
## Interpreting the Small Model Regression Equation
Here, we again need to specify the size and magnitude of all coefficients, providing estimated effect sizes with 90% confidence intervals.
I'll skip the necessary English sentences here in the demo that explain the meaning of the estimates in our model. You should provide a detailed explanation of the point estimates for all slopes, and of the confidence interval for the slope of your key predictor.
# In-Sample Comparison
## Compare Performance
```{r}
plot(compare_performance(model_big, model_small))
```
```{r}
compare_performance(model_big, model_small, rank = TRUE)
```
These results can be summarized as follows:
- `model_big` (naturally) has a stronger $R^2$ result, but it also has a better result for RMSE, AIC and BIC.
- `model_small` has a stronger result for Adjusted $R^2$ and Sigma.
## Assessing Assumptions
Here, we should run a set of residual plots for each model, with `check_model()` and interpret your findings in each case, carefully. I'll show my plots for `model_big` here.
### Checking `model_big`
```{r}
#| fig-height: 8
check_model(model_big)
```
I see no serious problems with the assumptions of linearity, Normality and constant variance, nor do I see any highly influential points in our big model.
### Does collinearity have a meaningful impact?
If we fit models with multiple predictors, then we might want to augment the plot above by assessing variance inflation factors to see the potential impact of collinearity.
```{r}
vif(model_big)
```
We'd need to see a generalized variance inflation factor above 5 for collinearity to be a meaningful concern, so we should be fine in our big model. Our small model also has multiple predictors, but collinearity cannot be an issue, since it's just a subset of our big model, which didn't have a collinearity problem.
## Comparing the Models
Based on the training sample, you should draw a conclusion. So far, I will support the larger model. It has (slightly) better performance on the fit quality measures, and each model shows no serious problems with regression assumptions.
# Model Validation
Now, we will use our two regression models to predict the value of our outcome using the predictor values in the test sample.
- We may need to back-transform the predictions to the original units if we wind up fitting a model to a transformed outcome.
- We'll definitely need to compare the two models in terms of our four main summaries, in a Table, which I will definitely want to see in your portfolio.
- We'll have to specify which model appears better at out-of-sample prediction according to these comparisons, and how we know that.
## Calculating Prediction Errors
### Big Model: Back-Transformation and Calculating Fits/Residuals
First, we need to create our transformed data in our test data.
```{r}
hbp_test <- hbp_test |> mutate(sbp1_tr = sqrt(sbp1), sbp2_tr = sqrt(sbp2))
```
We'll use the `augment` function from the `broom` package to help us here, and create `sbp2_fit` to hold the fitted values on the original `sbp2` scale after back-transformation (by squaring the predictions on the square root scale) and then `sbp2_res` to hold the residuals (prediction errors) we observe using the big model on the `hbp_test` data.
```{r}
aug_big <- augment(model_big, newdata = hbp_test) |>
mutate(mod_name = "big",
sbp2_fit = .fitted^2,
sbp2_res = sbp2 - sbp2_fit) |>
select(subj_id, mod_name, sbp2, sbp2_fit, sbp2_res, everything())
head(aug_big,3)
```
### Small Model: Back-Transformation and Calculating Fits/Residuals
We'll do the same thing, but using the small model in the `hbp_test` data.
```{r}
aug_small <- augment(model_small, newdata = hbp_test) |>
mutate(mod_name = "small",
sbp2_fit = .fitted^2,
sbp2_res = sbp2 - sbp2_fit) |>
select(subj_id, mod_name, sbp2, sbp2_fit, sbp2_res, everything())
head(aug_small,3)
```
### Combining the Results
```{r}
test_comp <- union(aug_big, aug_small) |>
arrange(subj_id, mod_name)
test_comp |> head()
```
Given this `test_comp` tibble, including predictions and residuals from the kitchen sink model on our test data, we can now:
1. Visualize the prediction errors from each model.
2. Summarize those errors across each model.
3. Identify the "worst fitting" subject for each model in the test sample.
The next few subsections actually do these things.
## Visualizing the Predictions
```{r}
ggplot(test_comp, aes(x = sbp2_fit, y = sbp2)) +
geom_point() +
geom_abline(slope = 1, intercept = 0, lty = "dashed") +
geom_smooth(method = "loess", col = "blue", se = FALSE, formula = y ~ x) +
facet_wrap( ~ mod_name, labeller = "label_both") +
labs(x = "Predicted sbp2",
y = "Observed sbp2",
title = "Observed vs. Predicted sbp2",
subtitle = "Comparing Big to Small Model in Test Sample",
caption = "Dashed line is where Observed = Predicted")
```
I'm not seeing a lot of difference between the models in terms of the adherence of the points to the dashed line. The models seem to be making fairly similar errors.
## Summarizing the Errors
Calculate the mean absolute prediction error (MAPE), the root mean squared prediction error (RMSPE) and the maximum absolute error across the predictions made by each model.
```{r}
test_comp |>
group_by(mod_name) |>
summarise(n = n(),
MAPE = mean(abs(sbp2_res)),
RMSPE = sqrt(mean(sbp2_res^2)),
max_error = max(abs(sbp2_res)),
R2_val = cor(sbp2, sbp2_fit)^2)
```
This is a table Dr. Love will **definitely** need to see during your presentation.
In this case, all four of these summaries are better for the bigger model.
These models suggest an average error in predicting systolic blood pressure (using MAPE) of more than 13 mm Hg. That's not great on the scale of systolic blood pressure, I think. In addition, our validated $R^2$ values here are only slightly worse (in either model) than what we saw in our training sample.
### Identify the largest errors
Identify the subject(s) where that maximum prediction error was made by each model, and the observed and model-fitted values of `sbp2` for that subject in each case.
```{r}
temp1 <- aug_big |>
filter(abs(sbp2_res) == max(abs(sbp2_res)))
temp2 <- aug_small |>
filter(abs(sbp2_res) == max(abs(sbp2_res)))
bind_rows(temp1, temp2)
```
- In our case, a different subject (`A0703` in the big model, and `A0265` in the small model) was most poorly fit by each model.
## Comparing the Models
I would select `model_big` here, on the basis of the similar performance in terms of the visualization of errors, and small improvements in all four of our main test sample summaries.
# Discussion
## Chosen Model
I chose the bigger model. You'll want to briefly reiterate the reasons why in this subsection, using results related to training-sample summaries, training-sample assumptions and model checks, and test-sample performance assessments. If you have evidence towards both models, decide what's more important to you, and pick a winner.
## Answering My Question
Now use the winning model to answer the research question, in a complete sentence of two.
## Next Steps
Describe an interesting next step, which might involve fitting a new model not available with your current cleaned data, or dealing with missing values differently, or obtaining new or better data, or something else. You should be able to describe why this might help.
## Reflection
Tell us what you know now that would have changed your approach to Study 2 had you known it at the start.
# Session Information
should be included at the end of your report, from the **xfun** package, please.
```{r}
session_info()
```