Using the library() function to load the tidyverse package which will be used for data manipulation.
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.1 ✔ readr 2.2.0
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ ggplot2 4.0.3 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.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
Creating the function simulate_plant_growth() to simulate plant growth data based on the soil moisture content. The simulation will generate 500 observations of soil moisture and plant growth rate The seed will be set as my student number in a hidden chunk of code.
simulate_plant_growth <- function(n = 500,
alpha = 2,
beta1 = 20,
rse = 8,
moisture_range = c(0.1, 5.0)) {
soil_moisture <- runif(n, min = moisture_range[1], max = moisture_range[2])
error <- rnorm(n, mean = 8, sd = rse)
growth_rate <- alpha + beta1 * soil_moisture + error
data.frame(
soil_moisture = soil_moisture,
growth_rate = growth_rate
)
}
The runif() function generates random soil moisture values from a uniform distribution and rnorm() genrrates random error values from a normal distribution. The growth rate is calculated using the intercept, slope, soil moisture values and random error.
Running the simulation by calling upon the simulate_plant_growth() function and storing the results in an object called data_single for data manipulation and plotting. The head() function is then used to observe the first six rows of the dataframe to check the simulated data.
data_single <- simulate_plant_growth()
head(data_single)
## soil_moisture growth_rate
## 1 2.5019810 52.02340
## 2 1.0383388 32.82435
## 3 2.8527429 61.71028
## 4 3.9070432 84.64274
## 5 0.8523106 23.48086
## 6 2.4398379 52.39973
The lm() function is used to fit a simple linear regression model with plant growth as the response variable and soil moisture as the explanatory variable. The summary() function is then used to display the estimated coefficients, standard errors, signifiance tests and model fit statistics. The anova() function creates an Analysis Of Variance table with the sums of squares, degrees of freedom, mean squares and F-statistic which will be used in calculations and data analysis.
fit_single <- lm(growth_rate ~ soil_moisture, data = data_single)
summary(fit_single)
##
## Call:
## lm(formula = growth_rate ~ soil_moisture, data = data_single)
##
## Residuals:
## Min 1Q Median 3Q Max
## -19.9871 -5.1989 -0.3607 4.6808 28.0428
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 9.8299 0.7817 12.58 <2e-16 ***
## soil_moisture 20.2425 0.2587 78.25 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 7.943 on 498 degrees of freedom
## Multiple R-squared: 0.9248, Adjusted R-squared: 0.9246
## F-statistic: 6123 on 1 and 498 DF, p-value: < 2.2e-16
anova(fit_single)
## Analysis of Variance Table
##
## Response: growth_rate
## Df Sum Sq Mean Sq F value Pr(>F)
## soil_moisture 1 386297 386297 6122.6 < 2.2e-16 ***
## Residuals 498 31421 63
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Extract fitted values and residuals
data_single <- data_single %>%
mutate(
fitted = predict(fit_single),
resid_model = growth_rate - fitted, # Residuals from line (SSE)
resid_mean = growth_rate - mean(growth_rate) # Deviations from Y_bar (SST)
)
The lm() function fitted the regression model and summary() and anova() displayed the statistical results. The %>% pipe was used to pass the data frame to the mutate() function which added three new columns. The predict() function calculated the rpedicted growth rates. the resid_model calulated the resiudals from the regression line. The resid_mean calculated the deviations from the overall mean.
Calculating the sums of squares, mean square error, F-statistic, and R-squared value manually to verify my manual calculations using the results from the regression model.
Calculating the total sum of squares and the residual sum of squares.
SST <- sum(data_single$resid_mean^2)
SSE <- sum(data_single$resid_model^2)
The sum() function adds the squared deviations together.
Calculating the regression sum of squares
SSR <- SST - SSE
SSR was calculated by subtracting the residual sum of squares (SSE) from the total sum of squares (SST).
Calculating R-squared manually
# Calculate R-squared manually
R2_manual <- 1 - (SSE / SST)
R-squared was calculated by subtracting the proportion of unexplained variation (SSE/SST) from one
Calculating the mean square error (MSE) by dividing SSE by the residual degrees of freedom.
# Calculate MSE
MSE <- SSE / df.residual(fit_single)
Extracting the ANOVA table to obtain the regression degrees of freedom to calculate the mean square regression (MSR). Calculating the F-statistic by dividing MSR by MSE.
anova_results <- anova(fit_single)
df_regression <- anova_results["soil_moisture", "Df"]
MSR <- SSR / df_regression
F_value <- MSR / MSE
SR was calculated by dividing SSR by the regression degrees of freedom. The F-statistic was calculated by dividing MSR by MSE.
Extracting the R-squared value calculated by the lm() function to compare it to the manually calculated R-squared. The cat() function is then being used to display the calculated values for comparison with my manually calculated values.
# Compare manual R-squared with lm() output
R2_lm <- summary(fit_single)$r.squared
cat(sprintf("SST: %.2f\n", SST))
## SST: 417717.92
cat(sprintf("SSE: %.2f\n", SSE))
## SSE: 31420.84
cat(sprintf("SSR: %.2f\n", SSR))
## SSR: 386297.08
cat(sprintf("MSE: %.2f\n", MSE))
## MSE: 63.09
cat(sprintf("F-statistic: %.2f\n", F_value))
## F-statistic: 6122.56
cat(sprintf("Manual R-squared: %.5f\n", R2_manual))
## Manual R-squared: 0.92478
cat(sprintf("lm() R-squared: %.5f\n", R2_lm))
## lm() R-squared: 0.92478
The SST(), SSE, SSR, MSE, F-statistic and the R-squared all match my calculated values indicating my calculations were correct.
Using the ggplot function to create a scatter plot displaying the relationship between soil mositure content and plant growth rate. The fitted regression line shows the predicted relationship and the confidence band represnts uncertainty around the estimated regression line. The red lines represent the residuals.
data_single$fitted <- fitted(fit_single)
ggplot(data_single, aes(x = soil_moisture, y = growth_rate)) +
geom_point(alpha = 0.6) +
geom_smooth(method = "lm", color = "darkblue", se = TRUE) +
geom_segment(aes(xend = soil_moisture, yend = fitted), alpha = 0.3, color = "red") +
theme_classic() +
labs(
title = "Plant Growth Rate vs. Soil Moisture Content",
x = "Soil Moisture Content",
y = "Growth Rate"
)
## `geom_smooth()` using formula = 'y ~ x'
The scatterplot shows a strong positive linear relationship between soil moisture content and plant growth rate. As soil moisture content increases, plant growth rate also increases.