Day 2: Significant testing & effect size calculating

0. Overview

What you’ll practice:

  • Visual checks of normality (histogram, Q–Q plot) + Shapiro–Wilk test

  • A simple two-group test (t-test or Wilcoxon) on WSA (water-stable aggregates)

  • A clean boxplot + jitter visualization

  • A first look at bootstrapping for evaluating effect sizes — R Function writing practice

1. Normality check (on one group/variable)

Let’s examine control group Decom (decomposition rate). Define it first:

control_data <- dat[dat[,"remark"]=="CT",]
control_soil_decom <- control_data$Decom
summary(control_soil_decom)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.6011  0.6472  0.6595  0.6623  0.6793  0.7124

Histogram

hist(control_soil_decom, col = "steelblue", main = "Control (CT): Decomposition",
     xlab = "Decom (control)")

Q-Q plot

qqnorm(control_soil_decom); qqline(control_soil_decom, col = "red", lwd = 2)

Shapiro-Wilk (normality) — use mainly for small–moderate n

shapiro.test(control_soil_decom)

Interpreting:

  • Q–Q plot: departures from the straight line indicate non-normality.
  • Shapiro p < 0.05 → evidence against normal residuals; consider robust tests.

2. Two-group hypothesis test

We’ll compare WSA between control (CT) and drought (D)

control_WSA <- dat[dat$remark == "CT", "WSA"]
drought_WSA <- dat[dat$remark == "D", "WSA"]

# Welch two-sample t-test (default var.equal = FALSE)
t_test_out <- t.test(control_WSA, drought_WSA, var.equal = FALSE)
t_test_out
## 
##  Welch Two Sample t-test
## 
## data:  control_WSA and drought_WSA
## t = 3.5388, df = 8.4451, p-value = 0.006998
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
##  0.05115701 0.23762291
## sample estimates:
## mean of x mean of y 
## 0.6062719 0.4618820

If WSA shows strong skew/outliers or variances differ wildly, use a non-parametric test:

wilcox.test(control_WSA, drought_WSA)
## 
##  Wilcoxon rank sum exact test
## 
## data:  control_WSA and drought_WSA
## W = 124, p-value = 0.00164
## alternative hypothesis: true location shift is not equal to 0

Which to report?

  • If assumptions aren’t badly violated → Welch t-test (reports mean difference, CI).
  • If heavy skew/outliers → Wilcoxon (reports location shift in medians).

3. Visualize: boxplot + jitter plot

library(ggplot2)

#Subsetting data
control_data   <- dat[dat$remark == "CT", ]
treatment_data <- dat[dat$remark == "D", ]

# Merge two data frames vertically (same columns)
data_merg <- rbind(control_data, treatment_data)

p1 <- ggplot(data_merg, aes(x = remark, y = WSA, fill = remark)) +
  geom_boxplot(outlier.alpha = 0.5, width = 0.6) +
  geom_jitter(width = 0.08, alpha = 0.6) +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "WSA by Treatment (CT vs D)",
       x = "Treatment", y = "Water-Stable Aggregates (WSA)") +
  theme_bw() + theme(legend.position = "none")
p1

4. Evaluating effect size through Bootstrapping

Here we introduce the bootstrapping technics for evaluating effect size. To evaluate the effect size of one treatment group, we need to calculate the bootstrapped mean of this treatment group (e.g. treatment_A) and the control group. The effect size of treatment_A will be evaluated by the mean difference between treatment response and response of control group: Effect_size_A = mean_A - mean_control

Because bootstrapping method involves iteratively resampling a dataset (with replacement), we’ll achieve this by using for loop in R. Here is a brief introduction of for loop.

R for loop structure

for( val in sequence ){ sequence is a vector and val takes on each of its value during the loop
statement In each iteration, statement is evaluated
} define the end of the loop

Small demonstration of for loop

Below is an example of using for loop to count numbers larger than 5 in a vector.

x <-  c(2,3,5,9,8,11,6)
count <- 0
for (val in x) {
  if(val > 5) count = count +1
}
print(count)
## [1] 4

4.1 Calculate Bootstrapped Mean

The responses of sampling group Treatment_A are generated through a normal distribution rnorm(s_size, mean = 4, sd = 2). You can also extract responses of a treatment group from the real dataset (e.g. MultiFactor_data.csv.) for practicing, but to show the robustness of bootstrapping method, we are using data simulation here.

We will estimate the mean and standard deviation from Treatment_A using bootstarpping method and compare it to traditional statistics.

When using the bootstrap you must choose the resampling size (usually equal to the original sample size) and the number of repeats (iterations).

## ---------------------------
## Simple nonparametric bootstrap demo
## ---------------------------

set.seed(5) # Make results reproducible (same random draws every run)

n_iter  <- 1000 # Number of bootstrap resamples
s_size  <- 20 # Size of each resample (should match original sample size)

# Simulate one sample
Treatment_A <- rnorm(s_size, mean = 4, sd = 2)  # 20 draws from normal distribution N(4, 2^2)
# Treatment_A <- c(Treatment_A, 30)             # <- add an outlier and re-run

# Preallocate storage for speed & clarity
mean_values <- vector() # define vector to store mean values of resampled datasets
sd_values   <- vector() # define vector to store standard deviation of resampled datasets

for (k in 1:n_iter) {
  # Draw a bootstrap sample: sample WITH replacement from the original sample
  data_tmp <- sample(Treatment_A, s_size, replace = TRUE) 
  
  # Store the statistic(s) of interest from this resample
  mean_values[k] <- mean(data_tmp) # the resampled MEAN
  sd_values[k]   <- sd(data_tmp) # the resampled SD
}

# --- Bootstrap summaries ---

# The bootstrap estimate of the mean (often close to the original sample mean)
bootstrapped_mean <- mean(mean_values)

# The average of resampled SDs (typical *within-sample* spread), NOT the SE of the mean
bootstrapped_sd   <- mean(sd_values)

# Original (non-bootstrapped) summaries from the observed sample
origi_mean        <- mean(Treatment_A)
origi_sd          <- sd(Treatment_A)

# A simple 95% percentile CI for the mean from the bootstrap distribution:
bootstrap_ci_mean <- quantile(mean_values, c(0.025, 0.975), names = FALSE)

# Collect key outputs
c(original_mean = origi_mean,
  boot_mean     = bootstrapped_mean,
  original_sd   = origi_sd,
  boot_sd   = bootstrapped_sd,
  CI_mean_L     = bootstrap_ci_mean[1],
  CI_mean_U     = bootstrap_ci_mean[2])
## original_mean     boot_mean   original_sd       boot_sd     CI_mean_L 
##      3.438884      3.462488      1.858808      1.793328      2.710497 
##     CI_mean_U 
##      4.265180
  1. 🔧 Try: change s_size and n_iter and see stability.
  1. 🔧 Try: Add an outlier to the treatment group Treatment_A and compare the results.
  1. 🧗 Exercise: wrap this into a small function that can be reused (Optional).

R function structure

function_name <- function( function parameters ){ define parameters
function body write function body with defined parameters
returen( function output ) define the output of the function
} define the end of the function

4.2 Bootstrapping effect sizes (two groups)

Now, Let’s bootstrap effect size for WSA (resampling from two treatment groups CT and D)

4.3 A reusable bootstrap function (Optional)

# ------------------------------------------------------------
# bootstrap_effects()
# Purpose:
#   Compute the observed mean difference (y - x) and Cohen's d
#   for two independent groups, and get 95% bootstrap CIs for
#   each effect size using a simple nonparametric (percentile)
#   bootstrap (resample within groups with replacement).
#
# Inputs:
#   x, y    : numeric vectors (independent samples; NAs allowed)
#   n_iter  : number of bootstrap resamples (default 1000)
#   seed    : RNG seed for reproducibility
#
# Outputs (list):
#   $mean_difference : c(estimate, CI_L, CI_U)
#   $cohens_d        : c(estimate, CI_L, CI_U)
#
# Notes:
#   - Cohen's d uses the pooled SD (assumes equal variances).
#   - CIs use the percentile method (simple and common for teaching).
#   - For small n, consider Hedges' g (bias-corrected d).
# ------------------------------------------------------------

bootstrap_effects <- function(x, y, n_iter = 1000, seed = 1) {
  set.seed(seed)
  x <- x[!is.na(x)]; y <- y[!is.na(y)]  #Drop missing values so resampling doesn't pull NAs
  n1 <- length(x); n2 <- length(y) # Sample sizes

  # ---- Observed (non-bootstrapped) effect sizes ----
  # Mean difference (y - x): positive => y has higher mean
  md_obs <- mean(y) - mean(x)
  
  # Pooled standard deviation (Cohen's d standardizer)
  # var() in R uses (n-1) denominator, so this matches the pooled-variance formula.
  sp     <- sqrt( ((n1-1)*var(x) + (n2-1)*var(y)) / (n1 + n2 - 2) )
  
  # Cohen's d (standardized mean difference)
  d_obs  <- (mean(y) - mean(x)) / sp
  
  # ---- Bootstrap loop ----
  # Allocate vectors to store bootstrap replicates
  md_boot <- numeric(n_iter); d_boot <- numeric(n_iter)
  
  for (k in 1:n_iter) {
    # Resample with replacement within each group (nonparametric bootstrap)
    x_k <- sample(x, n1, replace = TRUE)
    y_k <- sample(y, n2, replace = TRUE)
    
    # Effect size for this bootstrap sample
    md_boot[k] <- mean(y_k) - mean(x_k)
    
    # Recompute pooled SD from the bootstrap resamples
    sp_k <- sqrt( ((n1-1)*var(x_k) + (n2-1)*var(y_k)) / (n1 + n2 - 2) )
    d_boot[k]  <- (mean(y_k) - mean(x_k)) / sp_k
  }
  
  # ---- 95% percentile confidence intervals ----
  md_ci <- quantile(md_boot, c(.025,.975), names = FALSE)
  d_ci  <- quantile(d_boot,  c(.025,.975), names = FALSE)

  # ---- Return a tidy list ----
  output_list <- list(
    mean_difference = c(estimate = md_obs, CI_L = md_ci[1], CI_U = md_ci[2]),
    cohens_d        = c(estimate = d_obs,  CI_L = d_ci[1],  CI_U = d_ci[2])
  )
  return(output_list)
}

# Example:
# bootstrap_effects(control_WSA, drought_WSA, n_iter = 5000, seed = 123)

Interpretation guide

  • Mean difference (D − CT) in WSA: positive → drought higher than control; negative → lower.

  • Cohen’s d (standardized): ~0.2 small, ~0.5 medium, ~0.8 large (rough rules of thumb).

  • Report estimate and 95% CI (bootstrap percentile).

5. Summary

  • Data handling: Show how you loaded and inspected the dataset; state which groups and variable you analyzed (e.g., WSA for CT vs D).

  • Assumptions: Mention normality/variance checks; justify t-test vs Wilcoxon choice.

  • Results: Report mean difference with 95% CI and the test p-value.

  • Effect size: Add Cohen’s d with bootstrap 95% CI for interpretability.

  • Figure: Include the boxplot + jitter to visualize group differences.