analysis.R

# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2025 SWGY, Inc
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#install.packages("dplyr")
#install.packages("tidyr")
library(tidyr)
library(dplyr)

df <- read.csv("data/blast-results-2025-04-19-2024.csv")
# Consider each blast location a "subject"
# Split the data set based on distance from the origin
df$distance <- sqrt(df$bp_x^2 + df$bp_y^2 + df$bp_z^2)

near_df <- subset(df, distance <= 2.5)  # Close blasts
intermediate_df <- subset(df, distance >= 2.5)  # Intermediate distance

# Pivot to "long" format so each row represents an armor condition
near_df$subject_id <- seq_len(nrow(near_df))
near_long <- pivot_longer(
      near_df,
      cols = c("full_armor", "helmet_only", "vest_only", "no_armor"),
      names_to = "ArmorCondition",
      values_to = "Impulse")

intermediate_df$subject_id <- seq_len(nrow(intermediate_df))
intermediate_long <- pivot_longer(
      intermediate_df,
      cols = c("full_armor", "helmet_only", "vest_only", "no_armor"),
      names_to = "ArmorCondition",
      values_to = "Impulse")

res_aov_near <- aov(
               Impulse ~ ArmorCondition + Error(subject_id / ArmorCondition),
               data = near_long)

summary(res_aov_near)

res_aov_intermediate <- aov(
               Impulse ~ ArmorCondition + Error(subject_id / ArmorCondition),
               data = intermediate_long)

summary(res_aov_intermediate)

# The intermediate data set shows armor condition is significant (p=1.26e-09, F=14.82)

# Let's explore more...
# This data set has only intermediate range blasts and more of them:
intermediate_df2 <- read.csv("data/blast-results-2025-04-21-1055.csv")
intermediate_df2$subject_id <- seq_len(nrow(intermediate_df2))

intermediate_long2 <- pivot_longer(
      intermediate_df2,
      cols = c("full_armor", "helmet_only", "vest_only", "no_armor"),
      names_to = "ArmorCondition",
      values_to = "Impulse")

intermediate_wide2 <- pivot_wider(
    data = intermediate_long2,
    id_cols = "subject_id",
    names_from = "ArmorCondition",
    values_from = "Impulse")

# 1) run and store each p‑value
p_full_helmet <- t.test(intermediate_wide2$full_armor,   intermediate_wide2$helmet_only, paired=TRUE)$p.value
p_full_vest   <- t.test(intermediate_wide2$full_armor,   intermediate_wide2$vest_only,   paired=TRUE)$p.value
p_full_none   <- t.test(intermediate_wide2$full_armor,   intermediate_wide2$no_armor,    paired=TRUE)$p.value
p_helmet_vest <- t.test(intermediate_wide2$helmet_only,  intermediate_wide2$vest_only,   paired=TRUE)$p.value
p_helmet_none <- t.test(intermediate_wide2$helmet_only,  intermediate_wide2$no_armor,    paired=TRUE)$p.value
p_vest_none   <- t.test(intermediate_wide2$vest_only,    intermediate_wide2$no_armor,    paired=TRUE)$p.value

# 2) combine into a named vector
pvals <- c(
  `full vs helmet` = p_full_helmet,
  `full vs vest`   = p_full_vest,
  `full vs none`   = p_full_none,
  `helmet vs vest` = p_helmet_vest,
  `helmet vs none` = p_helmet_none,
  `vest vs none`   = p_vest_none
)

# 3) Holm-adjust
adj <- p.adjust(pvals, method = "holm")

# 4) print them individually
for(name in names(pvals)) {
  cat(sprintf(
    "%-15s  raw p = %0.4g;  Holm adj p = %0.4g\n",
    name, pvals[name], adj[name]
  ))
}

# Calculate the specific impact of the helmet:
# mean impulse reduction (w/ SD and CI)
# mean attenuation (w/ interquartile)
# Cohen's D
# Distribution characterization

# 1) Compute absolute & percent reduction for each subject
wide2 <- intermediate_wide2 %>% mutate(
         abs_reduction = no_armor - helmet_only,
         pct_reduction = 100 * abs_reduction / no_armor
  )

# 2) Number of subjects
n <- nrow(wide2)

# 3) Absolute reduction: mean, SD, SE, 95% CI
mean_abs   <- mean(wide2$abs_reduction, na.rm = TRUE)
sd_abs     <- sd(wide2$abs_reduction,   na.rm = TRUE)
se_abs     <- sd_abs / sqrt(n)
ci_abs     <- mean_abs + c(-1,1) * qt(0.975, df = n-1) * se_abs

# 4) Percent reduction: mean, median, IQR
mean_pct   <- mean(wide2$pct_reduction, na.rm = TRUE)
median_pct <- median(wide2$pct_reduction, na.rm = TRUE)
iqr_pct    <- IQR(wide2$pct_reduction, na.rm = TRUE)

# 5) Cohen's d for the paired reduction (standardized effect)
#    (Here we use the simple definition d = mean_abs / sd_abs)
cohen_d    <- mean_abs / sd_abs

# 6) Fraction of blast points with ≥10% reduction
frac_10    <- mean(wide2$pct_reduction >= 10, na.rm = TRUE)

# 7) Print everything neatly
cat(sprintf(
            "Absolute reduction: mean = %.3f (SD = %.3f), 95%% CI = [%.3f, %.3f]\n",
            mean_abs, sd_abs, ci_abs[1], ci_abs[2]
            ))
cat(sprintf(
            "Percent reduction: mean = %.1f%%, median = %.1f%%, IQR = %.1f%%–%.1f%%\n",
            mean_pct, median_pct, quantile(wide2$pct_reduction, .25),
            quantile(wide2$pct_reduction, .75)
            ))
cat(sprintf(
            "Cohen's d (paired) = %.2f\n", cohen_d
            ))
cat(sprintf(
            "Proportion with ≥10%% reduction = %.1f%%\n", 100 * frac_10
            ))

# calculate cohen's d on the percent reduction
wide2 <- intermediate_wide2 %>%
  mutate(
    pct_reduction = 100 * (no_armor - helmet_only) / no_armor
  )

# 1) summary stats for percent reduction
mean_pct <- mean(wide2$pct_reduction,   na.rm=TRUE)
sd_pct   <- sd(  wide2$pct_reduction,   na.rm=TRUE)

# 2) Cohen’s d on percent reduction
cohen_d_pct <- mean_pct / sd_pct

# 3) print it out
cat(sprintf(
  "Percent reduction: mean = %.1f%% (SD = %.1f%%) → Cohen's d = %.2f\n",
  mean_pct, sd_pct, cohen_d_pct
))