# Simple Linear Regression Analysis in R# This script demonstrates the theory and implementation of simple linear regression# Load required librarieslibrary(ggplot2)library(dplyr)
Attaching package: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
library(gridExtra)
Attaching package: 'gridExtra'
The following object is masked from 'package:dplyr':
combine
#=======================================================# 1. Creating a Synthetic Dataset#=======================================================set.seed(123) # For reproducibilityn <-100# Number of observations# Independent variable (X)x <-seq(1, 100, length.out = n)# Create a linear relationship with some noisetrue_slope <-2.5true_intercept <-10noise <-rnorm(n, mean =0, sd =15)y <- true_intercept + true_slope * x + noise# Create a dataframedf <-data.frame(x = x, y = y)#=======================================================# 2. Visualizing the Data#=======================================================# Create a scatter plotplt_scatter <-ggplot(df, aes(x = x, y = y)) +geom_point(color ="blue", alpha =0.7) +labs(title ="Scatter Plot of X vs Y",x ="Independent Variable (X)",y ="Dependent Variable (Y)") +theme_minimal()print(plt_scatter)
# Plot MSE history to show convergenceplt_mse <-ggplot(data.frame(iteration =1:1000, mse = gd_results$mse_history), aes(x = iteration, y = mse)) +geom_line(color ="blue") +labs(title ="Gradient Descent Convergence",x ="Iteration",y ="Mean Squared Error") +theme_minimal()print(plt_mse)
#=======================================================# 6. Using Built-in R Function for Linear Regression#=======================================================# Fit a linear model using lm()lm_model <-lm(y ~ x, data = df)# Print summarycat("Linear Model Summary:\n")
Linear Model Summary:
print(summary(lm_model))
Call:
lm(formula = y ~ x, data = df)
Residuals:
Min 1Q Median 3Q Max
-36.803 -8.285 -0.519 9.727 31.423
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 9.45394 2.76431 3.42 0.000914 ***
x 2.53767 0.04752 53.40 < 2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 13.72 on 98 degrees of freedom
Multiple R-squared: 0.9668, Adjusted R-squared: 0.9664
F-statistic: 2851 on 1 and 98 DF, p-value: < 2.2e-16
# Get coefficientslm_intercept <-coef(lm_model)[1]lm_slope <-coef(lm_model)[2]cat("\nComparing all methods:\n")
#=======================================================# 7. Checking Linear Regression Assumptions#=======================================================# 1. Linearity: Create residual vs. fitted plotplt_residuals_fitted <-ggplot(data.frame(fitted =fitted(lm_model), residuals =residuals(lm_model)), aes(x = fitted, y = residuals)) +geom_point(alpha =0.7) +geom_hline(yintercept =0, linetype ="dashed", color ="red") +labs(title ="Residuals vs. Fitted Values",subtitle ="Checking Linearity & Homoscedasticity",x ="Fitted Values",y ="Residuals") +theme_minimal()# 2. Normality of residuals: Create a histogram and QQ plotplt_hist <-ggplot(data.frame(residuals =residuals(lm_model)), aes(x = residuals)) +geom_histogram(bins =30, fill ="skyblue", color ="black", alpha =0.7) +labs(title ="Histogram of Residuals",subtitle ="Checking Normality",x ="Residuals",y ="Frequency") +theme_minimal()# Create QQ plot dataqq_data <-data.frame(theoretical =qnorm(ppoints(length(residuals(lm_model)))),sample =sort(residuals(lm_model)))plt_qq <-ggplot(qq_data, aes(x = theoretical, y = sample)) +geom_point(alpha =0.7) +geom_abline(intercept =0, slope =1, color ="red", linetype ="dashed") +labs(title ="Q-Q Plot of Residuals",subtitle ="Checking Normality",x ="Theoretical Quantiles",y ="Sample Quantiles") +theme_minimal()# 3. Independence: Not easily testable with a plot for this simple data# 4. Homoscedasticity: Already covered in residuals vs. fitted plot# Display diagnostic plotsgrid.arrange(plt_residuals_fitted, plt_hist, plt_qq, ncol =2)
#=======================================================# 8. Making Predictions with the Model#=======================================================# Create new data for predictionnew_x <-data.frame(x =c(25, 50, 75))# Predict using the modelnew_x$predicted_y <-predict(lm_model, newdata = new_x)cat("Predictions for new data:\n")
# Visualize predictions on the original plotplt_predictions <- plt_with_line +geom_point(data = new_x, aes(x = x, y = predicted_y), color ="green", size =3) +labs(title ="Linear Regression with Predictions",subtitle =paste("Equation: y =", round(lm_intercept, 2), "+", round(lm_slope, 2), "* x"))print(plt_predictions)
#=======================================================# 9. Real-World Example: Car MPG vs. Weight#=======================================================# Load the mtcars datasetdata(mtcars)# Create a scatter plotplt_cars <-ggplot(mtcars, aes(x = wt, y = mpg)) +geom_point(aes(size = hp), alpha =0.7) +labs(title ="Car Weight vs. Miles Per Gallon",subtitle ="Size represents horsepower",x ="Weight (1000 lbs)",y ="Miles Per Gallon") +theme_minimal()print(plt_cars)
# Fit a linear modelcar_model <-lm(mpg ~ wt, data = mtcars)# Print summarycat("\nCar Model Summary:\n")
Car Model Summary:
print(summary(car_model))
Call:
lm(formula = mpg ~ wt, data = mtcars)
Residuals:
Min 1Q Median 3Q Max
-4.5432 -2.3647 -0.1252 1.4096 6.8727
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 37.2851 1.8776 19.858 < 2e-16 ***
wt -5.3445 0.5591 -9.559 1.29e-10 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 3.046 on 30 degrees of freedom
Multiple R-squared: 0.7528, Adjusted R-squared: 0.7446
F-statistic: 91.38 on 1 and 30 DF, p-value: 1.294e-10
# Add regression line to the plotplt_cars_with_line <- plt_cars +geom_smooth(method ="lm", se =TRUE, color ="blue") +labs(subtitle =paste("Equation: mpg =", round(coef(car_model)[1], 2), "+", round(coef(car_model)[2], 2), "* weight"))print(plt_cars_with_line)
`geom_smooth()` using formula = 'y ~ x'
# Create a prediction functionpredict_mpg <-function(weight) {return(coef(car_model)[1] +coef(car_model)[2] * weight)}# Example predictionnew_weight <-3.5# 3,500 lbspredicted_mpg <-predict_mpg(new_weight)cat("\nPredicted MPG for a car weighing", new_weight *1000, "lbs:", round(predicted_mpg, 2))
Predicted MPG for a car weighing 3500 lbs: 18.58
15.2 Regression vs Stratifcation vs Matching
# Load necessary librarieslibrary(ggplot2)library(dplyr)library(MatchIt)library(broom)# Set seed for reproducibilityset.seed(123)#-------------------------------------------------------------------------# Create a simulated dataset for our examples#-------------------------------------------------------------------------# Sample sizen <-500# Simulate age (older patients more likely to get treatment B)age <-rnorm(n, mean =50, sd =10)# Simulate sex (binary for simplicity)sex <-rbinom(n, 1, 0.5)# Treatment assignment depends on age (older patients more likely to get B)# and sex (females more likely to get B)p_treatment_B <-plogis(-3+0.05* age +0.8* sex)treatment <-rbinom(n, 1, p_treatment_B)treatment_label <-ifelse(treatment ==1, "B", "A")# True treatment effect is 2true_effect <-2# Outcome depends on treatment, age, and sex# Age has a non-linear effectoutcome <-10+ true_effect * treatment +0.2* age +2* sex +0.01* age^2+rnorm(n, 0, 5)# Create the datasetdata <-data.frame(patient_id =1:n,age = age,sex =factor(sex, labels =c("Male", "Female")),treatment =factor(treatment_label, levels =c("A", "B")),outcome = outcome)#-------------------------------------------------------------------------# 1. Simple regression without adjustment#-------------------------------------------------------------------------model_unadjusted <-lm(outcome ~ treatment, data = data)summary_unadjusted <-summary(model_unadjusted)cat("Unadjusted Treatment Effect:\n")
Unadjusted Treatment Effect:
print(coef(summary_unadjusted)["treatmentB", ])
Estimate Std. Error t value Pr(>|t|)
8.030098e+00 1.154821e+00 6.953543e+00 1.123572e-11
#-------------------------------------------------------------------------# 2. Regression with adjustment for sex and age#-------------------------------------------------------------------------model_adjusted <-lm(outcome ~ treatment + sex + age, data = data)summary_adjusted <-summary(model_adjusted)cat("\nAdjusted Treatment Effect (linear adjustment for age):\n")
Adjusted Treatment Effect (linear adjustment for age):
print(coef(summary_adjusted)["treatmentB", ])
Estimate Std. Error t value Pr(>|t|)
2.290267e+00 4.807315e-01 4.764130e+00 2.494562e-06
# Check for non-linear age effectmodel_nonlinear <-lm(outcome ~ treatment + sex + age +I(age^2), data = data)summary_nonlinear <-summary(model_nonlinear)cat("\nTesting if non-linear age adjustment improves model:\n")
Testing if non-linear age adjustment improves model:
anova(model_adjusted, model_nonlinear)
Analysis of Variance Table
Model 1: outcome ~ treatment + sex + age
Model 2: outcome ~ treatment + sex + age + I(age^2)
Res.Df RSS Df Sum of Sq F Pr(>F)
1 496 12987
2 495 12246 1 740.6 29.935 7.106e-08 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#-------------------------------------------------------------------------# 3. Stratification approach by sex#-------------------------------------------------------------------------# Compute treatment effect within each sexstrat_by_sex <- data %>%group_by(sex) %>%do(model =lm(outcome ~ treatment, data = .)) %>%mutate(treatment_effect =coef(model)["treatmentB"])cat("\nStratified Treatment Effects by Sex:\n")
# A tibble: 2 × 2
# Rowwise:
sex treatment_effect
<fct> <dbl>
1 Male 7.71
2 Female 7.60
# Average of stratified effectsavg_strat_effect_sex <-mean(strat_by_sex$treatment_effect)cat("\nAverage of sex-stratified treatment effects:", avg_strat_effect_sex, "\n")
Average of sex-stratified treatment effects: 7.652672
#-------------------------------------------------------------------------# 4. Stratification approach by age quintiles#-------------------------------------------------------------------------# Create age quintilesdata$age_quintile <-cut(data$age, breaks =quantile(data$age, probs =seq(0, 1, 0.2)),labels =paste0("Q", 1:5),include.lowest =TRUE)# Compute treatment effect within each age quintilestrat_by_age <- data %>%group_by(age_quintile) %>%do(model =lm(outcome ~ treatment, data = .)) %>%mutate(treatment_effect =coef(model)["treatmentB"],n_patients =sum(!is.na(model$model$outcome)))cat("\nStratified Treatment Effects by Age Quintile:\n")
# Average of stratified effectsavg_strat_effect_age <-mean(strat_by_age$treatment_effect)cat("\nAverage of age-quintile-stratified treatment effects:", avg_strat_effect_age, "\n")
Average of age-quintile-stratified treatment effects: 3.348508
#-------------------------------------------------------------------------# 5. Matching approach - FIXED VERSION#-------------------------------------------------------------------------# Match on age (within 2 years)# Fix: When using mahalanobis distance, caliper must be namedm_out <-matchit(treatment =="B"~ age, data = data,method ="nearest", distance ="mahalanobis",caliper =c(age =2/sd(data$age))) # Named caliper for age# Get matched datamatched_data <-match.data(m_out)# Analyze matched datamatched_model <-lm(outcome ~ treatment, data = matched_data, weights = weights)summary_matched <-summary(matched_model)cat("\nMatched Analysis Treatment Effect:\n")
Matched Analysis Treatment Effect:
print(coef(summary_matched)["treatmentB", ])
Estimate Std. Error t value Pr(>|t|)
2.52506949 1.15911806 2.17844031 0.02995363
cat("Number of matched pairs:", m_out$nn["Matched", "Control"], "\n")
Number of matched pairs:
#-------------------------------------------------------------------------# 6. Visualization of different adjustment approaches#-------------------------------------------------------------------------# Prepare results for plottingresults <-data.frame(Method =c("Unadjusted", "Adjusted (Linear)", "Adjusted (Non-linear)", "Sex-stratified", "Age-quintile-stratified", "Matched"),Estimate =c(coef(model_unadjusted)["treatmentB"],coef(model_adjusted)["treatmentB"],coef(model_nonlinear)["treatmentB"], avg_strat_effect_sex, avg_strat_effect_age,coef(matched_model)["treatmentB"]),SE =c(summary_unadjusted$coefficients["treatmentB", "Std. Error"], summary_adjusted$coefficients["treatmentB", "Std. Error"], summary_nonlinear$coefficients["treatmentB", "Std. Error"],NA, # Would need bootstrap for proper SENA, # Would need bootstrap for proper SE summary_matched$coefficients["treatmentB", "Std. Error"]))results$Lower <- results$Estimate -1.96* results$SEresults$Upper <- results$Estimate +1.96* results$SE# Plot the resultsggplot(results, aes(x = Method, y = Estimate)) +geom_point(size =3) +geom_errorbar(aes(ymin = Lower, ymax = Upper), width =0.2) +geom_hline(yintercept = true_effect, linetype ="dashed", color ="red") +labs(title ="Comparison of Treatment Effect Estimates",subtitle ="Red dashed line indicates true effect",y ="Estimated Treatment Effect",x ="") +theme_minimal() +coord_flip()
#-------------------------------------------------------------------------# 7. Example from the n-table: checking BMI adequacy#-------------------------------------------------------------------------# Simulate height and weight datan_bmi <-300log_height <-rnorm(n_bmi, mean =log(170), sd =0.1)log_weight <--2* log_height +rnorm(n_bmi, mean =log(70) +2*log(170), sd =0.2)# Create some outcome related to BMIbmi_outcome <--2* log_height + log_weight +rnorm(n_bmi, mean =0, sd =1)# Check if coefficient ratio is approximately -2bmi_model <-lm(bmi_outcome ~ log_height + log_weight)summary_bmi <-summary(bmi_model)cat("\nTesting if BMI adequately summarizes height and weight:\n")
Testing if BMI adequately summarizes height and weight:
cat("Ratio of coefficients (log_height/log_weight):", coef(bmi_model)["log_height"]/coef(bmi_model)["log_weight"], "\n")
Ratio of coefficients (log_height/log_weight): -3.704918
cat("If this ratio is close to -2, BMI adequately summarizes height and weight\n")
If this ratio is close to -2, BMI adequately summarizes height and weight
#-------------------------------------------------------------------------# 8. Example: determining whether change or recent measurement is important#-------------------------------------------------------------------------# Simulate weights at two time pointsn_weight <-250weight_1y_ago <-rnorm(n_weight, mean =75, sd =10)weight_current <- weight_1y_ago +rnorm(n_weight, mean =-2, sd =3) # slight weight loss on average# Create outcome more strongly related to current weightweight_outcome <-0.1* weight_1y_ago +0.9* weight_current +rnorm(n_weight, mean =0, sd =5)# Fit model with both weightsweight_model <-lm(weight_outcome ~ weight_1y_ago + weight_current)summary_weight <-summary(weight_model)cat("\nDetermining importance of change vs. recent measurement:\n")
Determining importance of change vs. recent measurement:
cat("Weight 1y ago coefficient:", coef(weight_model)["weight_1y_ago"], "\n")