# A tibble: 6 × 5
group tree_no tree_char side length_mm
<chr> <dbl> <chr> <chr> <dbl>
1 five 1 tree_1 sunny 22.7
2 five 1 tree_1 sunny 21.1
3 five 1 tree_1 sunny 18.6
4 five 1 tree_1 sunny 18.6
5 five 1 tree_1 sunny 21.0
6 five 1 tree_1 sunny 18.9
An issue we have is pseudoreplication
What is our sample unit? branches? needles? trees? sides?
We need to get the averages of the data so it is no psudoreplicated!
We will group data by group, tree, tree_char, side and take the averages and save the average as length_mm
We will also need to make dataframes of sunny and shady side of the data
# Now we need to average the sunny and shady sides as these are all pseudoreplicatedp_df <- pine_df %>%group_by(group, tree_no, tree_char, side) %>%summarise(length_mm =mean(length_mm, na.rm=TRUE))
`summarise()` has grouped output by 'group', 'tree_no', 'tree_char'. You can
override using the `.groups` argument.
Before conducting a t-test, we need to check if our data meets the necessary assumptions:
Normality: The data should be approximately normally distributed
Independence: Observations should be independent
No extreme outliers: Outliers can heavily influence t-test results
Let’s check the normality assumption for shady needle lengths:
Practice Exercise 2: Checking Normality of all data but does not do by group!
# Create a QQ plot to check normality# QQ plots compare our data to a theoretical normal distribution# Points should roughly follow the line if data is normally distributedqqPlot(ps_df$length_mm, main ="QQ Plot for Needle Lengths",ylab ="Sample Quantiles")
[1] 8 11
Shapiro-Wilk test on all data
What do we look at here and how do we interpret?
Hoping for non significant!!!
# Also perform a formal test of normality using the Shapiro-Wilk test# Null hypothesis: Data is normally distributed# If p > 0.05, we don't reject the assumption of normalityshapiro_test <-shapiro.test(ps_df$length_mm)print(shapiro_test)
Shapiro-Wilk normality test
data: ps_df$length_mm
W = 0.92754, p-value = 0.2228
QQ Plot for Sunny Data
Practice Exercise 8: Test normality of pine needle lengths on sunny side
qqplots
Note you need to test each groups separately…
# QQ Plot for sunny groupqqPlot(ps_sunny_df$length_mm, main ="QQ Plot for sunny needle length",ylab ="Sample Quantiles")
[1] 5 4
Shapiro-Wilk Test for Sunny data
Practice Exercise 9: Test normality of sunny needle lengths
Shapiro-Wilk test
Note you need to test each groups separately…
# Shapiro-Wilk test for sunny groupshapiro.test(ps_sunny_df$length_mm)
Shapiro-Wilk normality test
data: ps_sunny_df$length_mm
W = 0.89994, p-value = 0.2886
Combined QQ plots with GGPLOT
We can use ggplot which you are more familiar with to also do qqPlots
Note you can do this a lot easier with piping of the data…
Practice Exercise 12: Test Normality at one time
There are always a lot of ways to do everything in R and is sometimes frustrating
# there are always two ways# Test for normality using Shapiro-Wilk test for each wind group# All in one pipeline using tidyverse approachnormality_results <- ps_df %>%group_by(side) %>%summarize(shapiro_stat =shapiro.test(length_mm)$statistic,shapiro_p_value =shapiro.test(length_mm)$p.value,normal_distribution =if_else(shapiro_p_value >0.05, "Normal", "Non-normal")# above wwe are using an ifelse test which is a great oneliner )# Print the resultsprint(normality_results)
# A tibble: 2 × 4
side shapiro_stat shapiro_p_value normal_distribution
<chr> <dbl> <dbl> <chr>
1 shady 0.966 0.868 Normal
2 sunny 0.900 0.289 Normal
Test for Equal Variances of Sunny and Shady
Practice Exercise 13: Test equal variances
Levene’s test can be done on the original dataframe
# Method 1: Using car package's leveneTest# This is often preferred as it's more robust to departures from normalitylevene_result <-leveneTest(length_mm ~ side, data = ps_df)levene_result
Levene's Test for Homogeneity of Variance (center = median)
Df F value Pr(>F)
group 1 0.2062 0.6567
14
Check for outliers
# Check for outliers using a boxplotneedle_box_plot
Tip
How to interpret these results:
The QQ plot: Points should follow the straight line if data is normally distributed
Shapiro-Wilk test: If p > 0.05, we don’t reject the assumption of normality
Boxplot: Look for points beyond the whiskers as potential outliers
Part 4: One-Sample t-Test
A one-sample t-test compares a sample mean to a specific value.
Let’s test if the mean needle length on the shady side is 15 or not?
Practice Exercise: One-Sample t-Test
# Calculate the mean of shady needles?shady_mean <-mean(ps_shady_df$length_mm, na.rm=TRUE)shady_mean
[1] 17.60561
# what is the mean# 17.60561ps_shade_mean <-mean(ps_shady_df$length_mm, na.rm =TRUE)cat("Mean:", round(ps_shade_mean, 1), "mm\n")
Mean: 17.6 mm
# Perform a one-sample t-testt_test_result <-t.test(ps_shady_df$length_mm, mu =15)# View the test resultst_test_result
One Sample t-test
data: ps_shady_df$length_mm
t = 2.9414, df = 7, p-value = 0.02167
alternative hypothesis: true mean is not equal to 15
95 percent confidence interval:
15.51092 19.70030
sample estimates:
mean of x
17.60561
# Create a visualization of the testps_shady_df %>%ggplot(aes(x = length_mm)) +geom_histogram(binwidth =1, fill ="blue", alpha =0.7) +geom_vline(xintercept =15, color ="red", linetype ="dashed", linewidth =1) +geom_vline(xintercept = shady_mean, color ="green", linewidth =1) +annotate("text", x =15, y =5, label ="H0: μ = 15", color ="red", hjust =-0.1) +annotate("text", x = ps_shade_mean, y =5, label =paste("Sample mean =", round(ps_shade_mean, 1)), color ="green", hjust =-0.1) +labs(title ="One-Sample t-Test: Shady Needle Lengths",subtitle =paste("t =", round(t_test_result$statistic, 2),", p =", format.pval(t_test_result$p.value, digits =3)),x ="Length (mm)",y ="Count")
Tip
Interpret the results:
What was the null hypothesis? H0: μ = 15mm
What was the alternative hypothesis? H1: μ ≠ 15mm
What does the p-value tell us? (Is p < 0.05?)
Should we reject or fail to reject the null hypothesis?
What is the practical interpretation for biologists?
Part 5: Confidence Intervals
A confidence interval gives us a range of plausible values for the population mean.
For a 95% confidence interval using the t-distribution:
- \(t_{\alpha/2, n-1}\) is the critical t-value with n-1 degrees of freedom
Practice Exercise 4: Calculating Confidence Intervals
Let’s calculate the 95% confidence interval for shady needle lengths:
# Extract sample statisticsshady_stats <- ps_shady_df shady_mean <-mean(ps_shady_df$length_mm, na.rm=TRUE)shady_se <-sd(ps_shady_df$length_mm, na.rm=TRUE)/sum(!is.na(ps_shady_df$length_mm))^.5shady_n <-sum(!is.na(ps_shady_df$length_mm))# Find the critical t-value for 95% confidence with n-1 degrees of freedom# qt(0.975, df) gives the t-value for a 95% confidence interval (two-tailed)t_critical <-qt(0.975, df = shady_n -1)cat("Critical t-value for", shady_n-1, "degrees of freedom:", round(t_critical, 3), "\n")
Critical t-value for 7 degrees of freedom: 2.365
# Calculate the confidence intervalshady_ci_lower <- shady_mean - t_critical * shady_seshady_ci_upper <- shady_mean + t_critical * shady_se# Display the confidence intervalcat("95% Confidence Interval for Shady needle mean length:", round(shady_ci_lower, 1), "to", round(shady_ci_upper, 1), "mm\n")
95% Confidence Interval for Shady needle mean length: 15.5 to 19.7 mm
# Compare this to a confidence interval using the normal approximation (z = 1.96)z_ci_lower <- shady_mean -1.96* shady_sez_ci_upper <- shady_mean +1.96* shady_secat("95% CI using normal approximation:", round(z_ci_lower, 1), "to", round(z_ci_upper, 1), "mm\n")
95% CI using normal approximation: 15.9 to 19.3 mm
# Perform a two-sample t-test# H0: μ1 = μ2 (The mean needle lengths are equal)# H1: μ1 ≠ μ2 (The mean needle lengths are different)# var.equal=TRUE uses the standard t-test (pooled variance)# var.equal=FALSE uses Welch's t-test (for unequal variances)t_test_result <-t.test(length_mm ~ side, data = ps_df, var.equal =TRUE)# Display the test resultst_test_result
Two Sample t-test
data: length_mm by side
t = 1.1279, df = 14, p-value = 0.2783
alternative hypothesis: true difference in means between group shady and group sunny is not equal to 0
95 percent confidence interval:
-1.309330 4.214005
sample estimates:
mean in group shady mean in group sunny
17.60561 16.15328
Compare to a Welch’s Two Sample T Test when variance is unequal
# Perform a two-sample t-test# H0: μ1 = μ2 (The mean needle lengths are equal)# H1: μ1 ≠ μ2 (The mean needle lengths are different)# var.equal=TRUE uses the standard t-test (pooled variance)# var.equal=FALSE uses Welch's t-test (for unequal variances)welch_test_result <-t.test(length_mm ~ side, data = ps_df, var.equal =FALSE)# Display the test resultswelch_test_result
Welch Two Sample t-test
data: length_mm by side
t = 1.1279, df = 13.96, p-value = 0.2784
alternative hypothesis: true difference in means between group shady and group sunny is not equal to 0
95 percent confidence interval:
-1.310069 4.214743
sample estimates:
mean in group shady mean in group sunny
17.60561 16.15328
Compare to a Paired T Test
# Perform a two-sample t-test# H0: μ1 = μ2 (The mean needle lengths are equal)# H1: μ1 ≠ μ2 (The mean needle lengths are different)# var.equal=TRUE uses the standard t-test (pooled variance)# var.equal=FALSE uses Welch's t-test (for unequal variances)paired_test_result <-t.test(length_mm ~ side, data = ps_df, pair =TRUE)# Display the test resultspaired_test_result
Paired t-test
data: length_mm by side
t = 2.7818, df = 7, p-value = 0.02723
alternative hypothesis: true mean difference is not equal to 0
95 percent confidence interval:
0.2178092 2.6868652
sample estimates:
mean difference
1.452337
Visualize the plot
Tip
# Visualize the results with a mean and error bar plotggplot(stats_df, aes(x = side, y = mean_length, color=side, fill = side)) +geom_point(stat ="identity", alpha =0.7) +geom_errorbar(aes(ymin = mean_length - se_length, ymax = mean_length + se_length),width =0.2)
Interpretation:
Tip
Interpret the results:
What was the null hypothesis?
What was the alternative hypothesis?
What does the p-value tell us?
Should we reject or fail to reject the null hypothesis?
What is the practical interpretation for botanists?
Part 8: Communicating Statistical Results
In scientific writing, it’s important to report statistical results clearly and consistently.
Here’s a standard format for reporting t-test results:
For a one-sample t-test: “A one-sample t-test showed that the mean needle length shady side (M = [mean], SD = [sd]) was [significantly/not significantly] different from 15 mm, t([df]) = [t-value], p = [p-value].”
For a two-sample t-test: “A two-sample t-test revealed that pine needle lengths on the sunny side (M = [mean1], SD = [sd1]) were [significantly/not significantly] [longer/shorter] than on the shady side (M = [mean2], SD = [sd2]), t([df]) = [t-value], p = [p-value].”
Practice Exercise 8: Writing Statistical Results
Write properly formatted statements reporting the results of:
The one-sample t-test comparing mean shady needle lenght to 15mm
The two-sample t-test comparing pine needle lengths
The two-sample t-test comparing needle lengths between sides
Remember to include:
Means and standard deviations for each group
The t-value with degrees of freedom
The p-value and whether the result is significant
Reflection Questions
How does the t-distribution differ from the normal distribution, and why does this matter for small samples?
What assumptions must be met to use a t-test, and what alternatives exist if these assumptions are violated?
What is the difference between statistical significance and practical importance?
How would the confidence interval change if we used a 99% confidence level instead of 95%?
How would you explain the concept of a p-value to someone with no statistical background?