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.2 ✔ tibble 3.3.1
## ✔ lubridate 1.9.5 ✔ tidyr 1.3.2
## ✔ purrr 1.2.1
## ── 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
df <- tribble(
~Season, ~Horse, ~MRS, ~MAC, ~Blood_O2, ~Blood_noO2,
"Summer/Fall", "Apollo", 103, NA, 88, 84,
"Summer/Fall", "Badger", 14, NA, 148, 275,
"Summer/Fall", "Classic", 933, NA, 1580, 1623,
"Summer/Fall", "Cyrus", 279, NA, 1800, 1843,
"Summer/Fall", "Dawson", 6, NA, 13, 44,
"Summer/Fall", "Delilah", 13, NA, 91, 81,
"Summer/Fall", "Mystery", 54, NA, 68, 212,
"Summer/Fall", "Molly", 134, NA, 66, 1354,
"Summer/Fall", "River", 47, NA, 119, 232,
"Summer/Fall", "Sir Magic", 55, NA, 155, 351,
"Winter/Spring", "Apollo", 14, NA, 151, 77,
"Winter/Spring", "Badger", 7, NA, 7, 0,
"Winter/Spring", "Classic", 52, NA, 321, 257,
"Winter/Spring", "Cyrus", 436, NA, 354, 906,
"Winter/Spring", "Dawson", 26, NA, 96, 200,
"Winter/Spring", "Delilah", 42, NA, 60, 334,
"Winter/Spring", "Mystery", 13, NA, 26, 71,
"Winter/Spring", "Molly", 32, NA, 72, 1154,
"Winter/Spring", "River", 357, 850, 98, 87,
"Winter/Spring", "Sir Magic", 17, NA, 48, 42
)
df <- df %>%
mutate(
FWS = ifelse(Horse %in% c("Apollo", "Mystery", "Delilah"),
"Yes", "No")
)
# make season order explicit
df$Season <- factor(df$Season, levels = c("Summer/Fall", "Winter/Spring"))
# -----------------------------
# 2. Reshape to long format
# -----------------------------
long_df <- df %>%
pivot_longer(
cols = c(MRS, Blood_O2, Blood_noO2, MAC),
names_to = "Media",
values_to = "Count"
) %>%
mutate(
Media = recode(Media,
"MRS" = "MRS",
"Blood_O2" = "Blood +O2",
"Blood_noO2" = "Blood -O2",
"MAC" = "MacConkey"),
logCount = log10(Count + 1)
)
# main dataset excluding MacConkey for most analyses
main_df <- long_df %>%
filter(Media != "MacConkey")
library(ggplot2)
library(tidyverse)
# Define colors
media_colors <- c(
"Blood -O2" = "blue",
"Blood +O2" = "red",
"MRS" = "#2ca25f"
)
# Plot
p1_mean <- ggplot(main_df, aes(x = Season, y = logCount)) +
# individual horse lines (grey, dashed)
geom_line(aes(group = Horse),
color = "grey60",
linetype = "dashed",
alpha = 0.7) +
geom_point(aes(group = Horse),
color = "grey40",
size = 2) +
# mean line (colored)
stat_summary(
aes(color = Media, group = Media),
fun = mean,
geom = "line",
linewidth = 1.5
) +
# mean points
stat_summary(
aes(color = Media),
fun = mean,
geom = "point",
size = 3
) +
facet_wrap(~Media) +
scale_color_manual(values = media_colors) +
labs(
title = "Seasonal changes in culturable bacterial counts",
x = NULL,
y = "Log Colony Count" ) +
theme_bw(base_size = 12) +
theme(
strip.background = element_rect(fill = "grey95"),
panel.grid.minor = element_blank(),
legend.position = "none"
)
p1_mean
Bacterial counts generally decrease in winter, but not all horses respond the same way.
This figure shows how bacterial colony counts change from Summer/Fall to Winter/Spring for each horse across three types of media: anaerobic (Blood −O₂), aerobic (Blood +O₂), and MRS (lactic acid bacteria). Each dashed line represents an individual horse, while the colored line shows the overall average trend.
Across all three media types, there is a general decrease in colony counts from Summer/Fall to Winter/Spring. This suggests that overall culturable bacterial abundance tends to decline during the winter months.
However, there is also substantial variation between individual horses. Some horses show large decreases, while others remain relatively stable or even increase slightly. This indicates that while there is a general seasonal trend, individual responses are not uniform.
The most consistent decline appears in the anaerobic (Blood −O₂) and aerobic (Blood +O₂) bacteria, while MRS (lactic acid bacteria) shows a smaller and more variable change.
library(tidyverse)
df_total <- df %>%
mutate(
Total = Blood_O2 + Blood_noO2 + MRS
)
p_total_paired <- ggplot(df_total, aes(x = Season, y = Total, group = Horse)) +
geom_line(color = "grey70", alpha = 0.7) +
geom_point(size = 3) +
labs(
title = "Seasonal Change in Total Colony Count (Paired)",
x = NULL,
y = "Total Colony Count"
) +
theme_bw(base_size = 14)
p_total_paired
Total bacterial abundance tends to decrease in winter, but there is a lot of variation between individual horses.
This figure shows the total number of bacterial colonies (across all media types) for each horse in Summer/Fall compared to Winter/Spring. Each line represents an individual horse, allowing us to track how total bacterial abundance changes over time.
Overall, many horses show a decrease in total colony count from Summer/Fall to Winter/Spring, suggesting that bacterial abundance may decline during the winter months. However, this pattern is not consistent across all individuals. Some horses show only small changes, while others show large decreases or even slight increases.
There are also a few horses with very high counts in Summer/Fall that drop substantially in Winter/Spring, which contributes to the overall downward trend.
total_wide <- df_total %>%
select(Horse, Season, Total) %>%
pivot_wider(names_from = Season, values_from = Total)
wilcox.test(
total_wide$`Summer/Fall`,
total_wide$`Winter/Spring`,
paired = TRUE
)
##
## Wilcoxon signed rank exact test
##
## data: total_wide$`Summer/Fall` and total_wide$`Winter/Spring`
## V = 44, p-value = 0.1055
## alternative hypothesis: true location shift is not equal to 0
While some horses show decreases in total bacterial abundance in winter, there is no statistically significant overall seasonal change. This supports earlier findings that seasonal effects are more evident in specific bacterial groups (e.g., anaerobes) rather than total bacterial abundance.
To test whether total bacterial abundance changed between Summer/Fall and Winter/Spring, a paired Wilcoxon signed-rank test was performed. This test compares the same horses across the two seasons.
The result (p = 0.1055) is not statistically significant, meaning there is no strong evidence that total colony count changes consistently between seasons across all horses.
Although the figure suggests that many horses show a decrease in total colony count in winter, the statistical test shows that this pattern is not consistent enough across all individuals to be considered significant.
This is likely due to:
High variability between horses A small sample size, which reduces statistical power
## # A tibble: 10 × 2
## Horse n
## <chr> <int>
## 1 Apollo 2
## 2 Badger 2
## 3 Classic 2
## 4 Cyrus 2
## 5 Dawson 2
## 6 Delilah 2
## 7 Molly 2
## 8 Mystery 2
## 9 River 2
## 10 Sir Magic 2
ratio_wide <- ratio_df2 %>%
select(Horse, Season, logRatio) %>%
pivot_wider(names_from = Season, values_from = logRatio)
wilcox.test(
ratio_wide$`Summer/Fall`,
ratio_wide$`Winter/Spring`,
paired = TRUE
)
##
## Wilcoxon signed rank exact test
##
## data: ratio_wide$`Summer/Fall` and ratio_wide$`Winter/Spring`
## V = 39, p-value = 0.2754
## alternative hypothesis: true location shift is not equal to 0
The balance between anaerobic and aerobic bacteria does not change consistently from summer to winter.
A paired Wilcoxon signed-rank test was used to determine whether the balance between anaerobic and aerobic bacteria changed between Summer/Fall and Winter/Spring.
A paired Wilcoxon signed-rank test indicated no significant seasonal change in the anaerobic-to-aerobic ratio (V = 39, p = 0.275), suggesting that shifts in microbial balance are inconsistent across individuals.
Although individual horses show changes in their bacterial balance, these changes are not consistent in direction. Some horses become more anaerobic, while others become more aerobic, and many show only small changes.
Because of this variability, there is no overall population-level shift in bacterial balance between seasons.
# wide format for paired comparisons
paired_wide <- main_df %>%
select(Horse, Season, Media, logCount) %>%
pivot_wider(names_from = Season, values_from = logCount)
# paired t-tests by medium
paired_tests <- paired_wide %>%
group_by(Media) %>%
summarise(
p_value_t = t.test(`Summer/Fall`, `Winter/Spring`, paired = TRUE)$p.value,
p_value_wilcox = wilcox.test(`Summer/Fall`, `Winter/Spring`, paired = TRUE)$p.value
)
paired_tests
## # A tibble: 3 × 3
## Media p_value_t p_value_wilcox
## <chr> <dbl> <dbl>
## 1 Blood +O2 0.173 0.193
## 2 Blood -O2 0.165 0.160
## 3 MRS 0.404 0.375
Colony counts do not change consistently between seasons for any bacterial group.
Paired statistical tests were used to determine whether colony counts changed between Summer/Fall and Winter/Spring for each media type. Both parametric (paired t-test) and non-parametric (Wilcoxon signed-rank test) approaches were used.
Across all three media types (aerobic, anaerobic, and MRS), no significant seasonal differences were detected (all p-values > 0.05).
library(tidyverse)
summary_df <- df %>%
select(Horse, Season, MRS, Blood_O2, Blood_noO2) %>%
pivot_longer(
cols = c(MRS, Blood_O2, Blood_noO2),
names_to = "Media",
values_to = "Count"
)
summary_table <- summary_df %>%
group_by(Season, Media) %>%
summarise(
n = n(),
median = median(Count, na.rm = TRUE),
IQR_low = quantile(Count, 0.25, na.rm = TRUE),
IQR_high = quantile(Count, 0.75, na.rm = TRUE),
mean = mean(Count, na.rm = TRUE),
sd = sd(Count, na.rm = TRUE),
.groups = "drop"
) %>%
mutate(
Median_IQR = paste0(median, " (", IQR_low, "–", IQR_high, ")"),
Mean_SD = paste0(round(mean,1), " ± ", round(sd,1))
)
table_final <- summary_table %>%
select(Season, Media, n, Median_IQR, Mean_SD) %>%
arrange(Media, Season)
table_final
## # A tibble: 6 × 5
## Season Media n Median_IQR Mean_SD
## <fct> <chr> <int> <chr> <chr>
## 1 Summer/Fall Blood_O2 10 105 (73–153.25) 412.8 ± 676.4
## 2 Winter/Spring Blood_O2 10 84 (51–137.75) 123.3 ± 120.1
## 3 Summer/Fall Blood_noO2 10 253.5 (116–1103.25) 609.9 ± 703.8
## 4 Winter/Spring Blood_noO2 10 143.5 (72.5–314.75) 312.8 ± 396.1
## 5 Summer/Fall MRS 10 54.5 (22.25–126.25) 163.8 ± 282.3
## 6 Winter/Spring MRS 10 29 (14.75–49.5) 99.6 ± 158.2
table_wide <- summary_table %>%
select(Season, Media, Median_IQR) %>%
pivot_wider(
names_from = Season,
values_from = Median_IQR
)
table_wide
## # A tibble: 3 × 3
## Media `Summer/Fall` `Winter/Spring`
## <chr> <chr> <chr>
## 1 Blood_O2 105 (73–153.25) 84 (51–137.75)
## 2 Blood_noO2 253.5 (116–1103.25) 143.5 (72.5–314.75)
## 3 MRS 54.5 (22.25–126.25) 29 (14.75–49.5)
install.packages("gt") # only if needed
## Installing package into '/cloud/lib/x86_64-pc-linux-gnu-library/4.5'
## (as 'lib' is unspecified)
library(gt)
library(dplyr)
table_pretty <- summary_table %>%
mutate(
Media = recode(Media,
"Blood_O2" = "Aerobic (Blood +O2)",
"Blood_noO2" = "Anaerobic (Blood -O2)",
"MRS" = "Lactic Acid Bacteria (MRS)"
)
) %>%
select(Media, Season, n, Median_IQR) %>%
pivot_wider(
names_from = Season,
values_from = Median_IQR
)
gt_table <- table_pretty %>%
gt() %>%
tab_header(
title = md("**Seasonal Comparison of Culturable Bacteria**"),
subtitle = "Median (IQR) colony counts by media type"
) %>%
cols_label(
Media = "Bacterial Group",
`Summer/Fall` = "Summer/Fall",
`Winter/Spring` = "Winter/Spring"
) %>%
cols_align(
align = "center",
-Media
) %>%
tab_style(
style = list(
cell_text(weight = "bold")
),
locations = cells_column_labels(everything())
) %>%
tab_options(
table.font.size = 14,
heading.title.font.size = 16,
heading.subtitle.font.size = 13,
data_row.padding = px(6)
) %>%
cols_width(
Media ~ px(260),
everything() ~ px(140)
)
gt_table
| Seasonal Comparison of Culturable Bacteria | |||
| Median (IQR) colony counts by media type | |||
| Bacterial Group | n | Summer/Fall | Winter/Spring |
|---|---|---|---|
| Aerobic (Blood +O2) | 10 | 105 (73–153.25) | 84 (51–137.75) |
| Anaerobic (Blood -O2) | 10 | 253.5 (116–1103.25) | 143.5 (72.5–314.75) |
| Lactic Acid Bacteria (MRS) | 10 | 54.5 (22.25–126.25) | 29 (14.75–49.5) |
Bacterial counts tend to be lower in winter, especially for anaerobes, but there is a lot of variation between horses.
This table summarizes bacterial colony counts across seasons for three groups: aerobic bacteria, anaerobic bacteria, and lactic acid bacteria (MRS). Values are shown as the median with interquartile range (IQR), which reflects both the typical value and the variability among horses.
Across all three bacterial groups, the median colony count is lower in Winter/Spring compared to Summer/Fall. This suggests a general trend of decreased culturable bacterial abundance during the winter months.
The largest decrease is observed in anaerobic bacteria, which drop from a median of 253.5 in Summer/Fall to 143.5 in Winter/Spring. Aerobic and MRS bacteria also show decreases, though these are smaller in magnitude.
However, the IQR ranges are wide in both seasons for all groups, indicating substantial variability between individual horses. This variability likely contributes to the lack of statistically significant differences observed in the formal tests.
p_total_FWS <- ggplot(df_total, aes(x = FWS, y = Total, fill = FWS)) +
geom_boxplot(alpha = 0.6, outlier.shape = NA) +
geom_jitter(width = 0.1, size = 2.5) +
labs(
title = "Total Colony Count by FWS Status",
x = NULL,
y = "Total Colony Count"
) +
scale_fill_manual(values = c(
"No" = "grey70",
"Yes" = "purple"
)) +
theme_bw(base_size = 14) +
theme(legend.position = "none")
p_total_FWS
Horses with FWS tend to have lower and less variable total bacterial counts, but differences are not clearly consistent due to high variability in other horses.
This figure compares total bacterial colony counts between horses with fecal water syndrome (FWS) and those without. Each point represents an individual sample, and the boxplots show the distribution of values within each group.
Horses without FWS (“No”) show a much wider range of total colony counts, including several very high values. In contrast, horses with FWS (“Yes”) have lower and more tightly clustered counts.
While this suggests that FWS horses may have lower overall bacterial abundance, there is substantial variability in the non-FWS group, including several high outliers. This variability makes it difficult to determine whether there is a consistent difference between groups.
wilcox.test(Total ~ FWS, data = df_total)
##
## Wilcoxon rank sum exact test
##
## data: Total by FWS
## W = 63, p-value = 0.09133
## alternative hypothesis: true location shift is not equal to 0
There is a trend suggesting that horses with FWS may have lower total bacterial counts, but the difference is not statistically significant.
A Wilcoxon rank-sum test was used to compare total bacterial colony counts between horses with and without fecal water syndrome (FWS).
The result (p = 0.091) is not statistically significant, but it is close to the typical significance threshold (p = 0.05), indicating a trend toward a difference between groups.
Although not significant, this result suggests that horses with FWS may have lower total bacterial counts compared to horses without FWS. This pattern is also visible in the figure, where FWS horses show lower and more tightly clustered values.
However, there is substantial variability in the non-FWS group, including several high values, which reduces statistical power and prevents this difference from reaching significance.
library(ggrepel)
library(tidyverse)
delta_df <- df %>%
select(Horse, Season, FWS, Blood_O2, Blood_noO2, MRS) %>%
pivot_longer(
cols = c(Blood_O2, Blood_noO2, MRS),
names_to = "Media",
values_to = "Count"
) %>%
pivot_wider(
names_from = Season,
values_from = Count
) %>%
mutate(
delta = `Winter/Spring` - `Summer/Fall`
)
wilcox_results <- delta_df %>%
group_by(Media) %>%
summarise(
p_value = wilcox.test(delta ~ FWS)$p.value,
.groups = "drop"
)
wilcox_results
## # A tibble: 3 × 2
## Media p_value
## <chr> <dbl>
## 1 Blood_O2 0.517
## 2 Blood_noO2 0.0667
## 3 MRS 0.833
effect_df <- delta_df %>%
group_by(Media, FWS) %>%
summarise(
median_delta = median(delta, na.rm = TRUE),
.groups = "drop"
)
effect_df
## # A tibble: 6 × 3
## Media FWS median_delta
## <chr> <chr> <dbl>
## 1 Blood_O2 No -107
## 2 Blood_O2 Yes -31
## 3 Blood_noO2 No -275
## 4 Blood_noO2 Yes -7
## 5 MRS No -7
## 6 MRS Yes -41
ggplot(delta_df, aes(x = FWS, y = delta, fill = FWS)) +
geom_boxplot(alpha = 0.6) +
geom_jitter(width = 0.1, size = 2) +
facet_wrap(~Media, scales = "free_y") +
labs(
title = "Seasonal change in bacterial counts by FWS status",
x = NULL,
y = "Change in colony count (Winter - Summer)"
) +
theme_bw() +
theme(legend.position = "none")
ratio_df2 <- ratio_df2 %>%
left_join(df %>% select(Horse, FWS) %>% distinct(), by = "Horse")
library(ggrepel)
p4_labeled <- p4_final +
geom_text_repel(
data = ratio_df2 %>%
filter(FWS == "Yes", Season == "Winter/Spring"),
aes(label = Horse, color = change_group), # 🔥 match line color
size = 4,
fontface = "bold",
nudge_x = 0.1,
direction = "y",
box.padding = 0.3,
point.padding = 0.2,
segment.color = "grey50",
segment.size = 0.5,
max.overlaps = 20
) +
# 🔥 IMPORTANT: include grey for "No change"
scale_color_manual(
values = c(
"Ends aerobic" = "red",
"Ends anaerobic" = "blue",
"No change" = "grey60"
)
)
## Scale for colour is already present.
## Adding another scale for colour, which will replace the existing scale.
p4_labeled
Most horses do not change much across seasons, but a few individuals show large shifts in bacterial balance.
Seasonal shifts in the balance between aerobic and anaerobic bacteria were generally modest across individuals. However, a subset of horses exhibited large changes (≥2-fold), including two FWS horses (Delilah and Mystery), which shifted toward increased anaerobic dominance. These results suggest that while most microbiomes remain stable across seasons, individual animals—particularly those with FWS—may experience more pronounced shifts in bacterial composition.
Not all FWS horses behave the same The direction of change is not consistent across all animals
This figure shows how the balance between anaerobic (no oxygen) and aerobic (oxygen) bacteria changes from summer/fall to winter/spring for each horse.
The y-axis (log ratio) tells you which group dominates: Above 0 → anaerobic dominance (blue region) Below 0 → aerobic dominance (red region) Each line represents one horse: Gray dashed lines = little or no meaningful change Colored lines = large shift (≥2-fold change)
delta_variance <- delta_df %>%
group_by(Media, FWS) %>%
summarise(
variance = var(delta, na.rm = TRUE),
.groups = "drop"
)
delta_variance
## # A tibble: 6 × 3
## Media FWS variance
## <chr> <chr> <dbl>
## 1 Blood_O2 No 420943.
## 2 Blood_O2 Yes 3330.
## 3 Blood_noO2 No 274785.
## 4 Blood_noO2 Yes 40132
## 5 MRS No 144566.
## 6 MRS Yes 3521.
delta_df <- delta_df %>%
mutate(abs_delta = abs(delta))
wilcox.test(abs_delta ~ FWS, data = delta_df)
## Warning in wilcox.test.default(x = DATA[[1L]], y = DATA[[2L]], ...): cannot
## compute exact p-value with ties
##
## Wilcoxon rank sum test with continuity correction
##
## data: abs_delta by FWS
## W = 137, p-value = 0.05728
## alternative hypothesis: true location shift is not equal to 0
FWS may be associated with instability or variability in the microbiome FWS is not linked to a consistent direction of change, but it may be linked to how much the microbiome changes.
The magnitude of seasonal change in aerobic–anaerobic balance showed a trend toward being greater in FWS horses (Wilcoxon test, p = 0.057). Although not statistically significant, this suggests that FWS may be associated with increased variability in microbial community structure rather than consistent directional shifts.