The two-sample t-test (also known as independent samples t-test) is used to determine whether there is a statistically significant difference between the means of two independent groups. In this analysis, we will examine whether there are significant differences in the total length of slimy sculpin fish between two different lakes.
The two-sample t-test makes the following comparison:
\[H_0: \mu_1 = \mu_2\]\[H_A: \mu_1 \neq \mu_2\]
Where:
- \(H_0\) is the null hypothesis stating that the population means are equal
- \(H_A\) is the alternative hypothesis stating that the population means are different
- \(\mu_1\) is the population mean of the first group
- \(\mu_2\) is the population mean of the second group
Formula
The formula for the two-sample t-test with equal variances (pooled variance) is:
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
✖ dplyr::recode() masks car::recode()
✖ purrr::some() masks car::some()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# Load the datasculpin_df <-read_csv("data/t_test_sculpin_s07_ne14.csv")
Rows: 110 Columns: 5
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): lake, species
dbl (3): site, length_mm, mass_g
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Preview the datahead(sculpin_df)
# A tibble: 6 × 5
site lake species length_mm mass_g
<dbl> <chr> <chr> <dbl> <dbl>
1 109 NE 14 slimy sculpin 47 0.7
2 109 NE 14 slimy sculpin 49 0.9
3 109 NE 14 slimy sculpin 46 0.7
4 109 NE 14 slimy sculpin 28 0.15
5 109 NE 14 slimy sculpin 45 0.65
6 109 NE 14 slimy sculpin 40 0.3
Let’s create a box plot with individual data points to visualize the distribution of total length in the two lakes:
# Create boxplot with individual pointssculpin_df %>%ggplot(aes(x = lake, y = length_mm, fill = lake)) +geom_boxplot(alpha =0.7, outlier.shape =NA) +geom_point(position =position_dodge2(width =0.3), alpha =0.5, size =2) +labs(title ="Total Length of Slimy Sculpin Fish by Lake",x ="Lake",y ="Total Length (mm)",fill ="Lake" ) +theme_minimal() +theme(plot.title =element_text(hjust =0.5, face ="bold"),legend.position ="right" )
Mean and Standard Error Plot
Now, let’s create a plot showing the mean and standard error for each lake, with individual data points in the background:
# Create mean and standard error plot with data pointssculpin_df %>%ggplot( aes(x = lake, y = length_mm, color = lake)) +# Add individual data points in the backgroundgeom_point(position =position_dodge2(width =0.3), alpha =0.5, size =1.5) +# Add mean and standard errorstat_summary(fun = mean, geom ="point", size =4) +stat_summary(fun.data = mean_se, geom ="errorbar", width =0.1) +labs(title ="Mean Total Length (± SE) of Slimy Sculpin Fish by Lake",x ="Lake",y ="Total Length (mm)",color ="Lake" ) +theme_minimal() +theme(plot.title =element_text(hjust =0.5, face ="bold"),legend.position ="right" )
Testing t-Test Assumptions
Before conducting the t-test, we need to verify that our data meets the underlying assumptions:
Assumptions of the Two-Sample t-Test
Independence: The observations within each group are independent, and the two groups are independent of each other.
Normality: The data in each group follow a normal distribution.
Homogeneity of Variances: The variances of the two groups are approximately equal (for the standard t-test).
Let’s test each of these assumptions:
1. Independence Assumption
Independence is a design issue and can’t be tested statistically. We assume our sampling design ensures independence between and within groups.
2. Normality Assumption
We’ll check normality using:
- Histograms
- Q-Q plots
- Shapiro-Wilk test
Histograms
sculpin_df %>%ggplot(aes(length_mm, fill = lake))+geom_histogram()+facet_wrap(~lake)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Shapiro-Wilk normality test
data: .
W = 0.9479, p-value = 0.08258
nicer way #1
# using the broom packagesculpin_df %>%group_by(lake) %>%group_modify(~ broom::tidy(shapiro.test(.x$length_mm)))
# A tibble: 2 × 4
# Groups: lake [2]
lake statistic p.value method
<chr> <dbl> <dbl> <chr>
1 NE 14 0.948 0.0826 Shapiro-Wilk normality test
2 S 07 0.980 0.313 Shapiro-Wilk normality test
nicer way #2
sculpin_df %>%group_by(lake) %>%group_walk(~ {cat("Shapiro-Wilk test for Lake", .y$lake, ":\n") test_result <-shapiro.test(.x$length_mm)print(test_result)cat("\n") })
Shapiro-Wilk test for Lake NE 14 :
Shapiro-Wilk normality test
data: .x$length_mm
W = 0.9479, p-value = 0.08258
Shapiro-Wilk test for Lake S 07 :
Shapiro-Wilk normality test
data: .x$length_mm
W = 0.98035, p-value = 0.3125
3. Homogeneity of Variances
We’ll check for homogeneity of variances using: - Visual inspection of boxplots (already done above) - Levene’s test
# Levene's test for homogeneity of variancesleveneTest(length_mm ~ lake, data = sculpin_df)
Warning in leveneTest.default(y = y, group = group, ...): group coerced to
factor.
Levene's Test for Homogeneity of Variance (center = median)
Df F value Pr(>F)
group 1 2.029 0.1572
108
Interpretation of Assumption Tests
Based on the results of our assumption tests:
Independence: We assume this is met based on the data collection process, as samples from each lake were collected independently of one another.
Normality:
The Q-Q plots show that the data points largely follow the theoretical normal distribution line for both lakes, with some minor deviations at the extremes.
The Shapiro-Wilk test results will help us formally assess normality. If the p-value is greater than 0.05, we fail to reject the null hypothesis that the data is normally distributed.
For samples larger than 30, the Central Limit Theorem suggests that the sampling distribution of means will be approximately normal regardless of the underlying distribution.
Homogeneity of Variances:
Levene’s test evaluates whether the variances between groups are equal.
A p-value greater than 0.05 indicates that we cannot reject the null hypothesis of equal variances.
As a rule of thumb, if the variance ratio is less than 4:1, the t-test is reasonably robust to violations of this assumption.
If this assumption is violated, we should consider using Welch’s t-test instead, which does not assume equal variances.
Two-Sample t-Test
Now that we’ve checked the assumptions, we can perform the two-sample t-test:
# Perform the t-test with equal variance (standard t-test)t_test_equal_var <-t.test( length_mm ~ lake,data = sculpin_df,var.equal =TRUE# Use pooled variance)# Display the resultst_test_equal_var
Two Sample t-test
data: length_mm by lake
t = -3.4314, df = 108, p-value = 0.0008519
alternative hypothesis: true difference in means between group NE 14 and group S 07 is not equal to 0
95 percent confidence interval:
-13.080929 -3.501818
sample estimates:
mean in group NE 14 mean in group S 07
47.27027 55.56164
# For comparison, also perform Welch's t-test (unequal variances)t_test_welch <-t.test( length_mm ~ lake,data = sculpin_df,var.equal =FALSE# Use Welch's correction)t_test_welch
Welch Two Sample t-test
data: length_mm by lake
t = -3.6483, df = 85.45, p-value = 0.0004533
alternative hypothesis: true difference in means between group NE 14 and group S 07 is not equal to 0
95 percent confidence interval:
-12.809687 -3.773061
sample estimates:
mean in group NE 14 mean in group S 07
47.27027 55.56164
Line-by-Line Interpretation of t-Test Results
Let’s break down the t-test output:
Test Type: Two Sample t-test
Formula: length_mm ~ lake means we’re testing if total length differs by lake
Data: Our filtered sculpin dataset
t-value: The calculated t-statistic
Degrees of Freedom (df): n₁ + n₂ - 2
p-value: The probability of observing this data (or more extreme) if the null hypothesis is true
Alternative Hypothesis: The means are different
95% Confidence Interval: The estimated range for the true difference in means
Sample Estimates: The means of each group
Visual Representation of t-Test Results
sculpin_df %>%ggplot( aes(x = lake, y = length_mm, fill = lake)) +geom_boxplot(alpha =0.7, outlier.shape =NA) +geom_point(position =position_dodge2(width =0.3), alpha =0.5, size =2) +labs(x ="Lake",y ="Total Length (mm)",fill ="Lake") +theme_light() +theme(plot.title =element_text(hjust =0.5, face ="bold"),legend.position ="right" ) +scale_fill_brewer(palette ="Set2")
sculpin_df %>%ggplot(aes(x = lake, y = length_mm, color=lake, shape = lake, fill = lake)) +stat_summary(fun = mean, geom ="point", alpha =0.7, size=3) +# bars for meansstat_summary(fun.data = mean_se, geom ="errorbar", width =0.2) +# error bars for SElabs(x ="Lake",y ="Total Length (mm)",fill ="Lake",color="Lake",shape ="Lake" ) +coord_cartesian(ylim =c(0, 60))+theme_light() +theme(plot.title =element_text(hjust =0.5, face ="bold"),legend.position ="right" )
Conclusion and Scientific Reporting
# Calculate means and standard errors for reportingmean_se_by_lake <- sculpin_df %>%group_by(lake) %>%summarize(n =n(),mean =mean(length_mm),sd =sd(length_mm),se = sd /sqrt(n) )mean_se_by_lake
# A tibble: 2 × 5
lake n mean sd se
<chr> <int> <dbl> <dbl> <dbl>
1 NE 14 37 47.3 10.5 1.72
2 S 07 73 55.6 12.7 1.48
The total length of slimy sculpin fish differs significantly between Lake S 07 and Lake NE 14 (two-sample t-test: t(df) = t_statistic, p < 0.001). Fish from Lake S 07 were on average (mean_diff) mm longer than those from Lake NE 14 (mean ± SE: mm).
How to Report These Results in a Scientific Publication
When reporting these results in a scientific publication, follow this format:
“Slimy sculpin (Cottus cognatus) from Lake S 07 were significantly larger than those from Lake NE 14
respectively; two-sample t-test: t(df) = t_statistic, p < 0.001). This represents an approximatelypercent_diff% difference in total length between the two populations.”
For figures, include:
A boxplot or mean/SE plot showing the difference
Clear labels and scales
Sample sizes
Statistical test information in the figure caption
A typical caption would read:
Note I would also add the mean and SE of each lake
“Figure X. Total length (mean ± SE) of slimy sculpin fish from two Arctic lakes. Fish from Lake S 07 (n = 73) were significantly larger than those from Lake NE 14 (n = 37) (two-sample t-test: t(108) = 3.46, p < 0.001).”