---
title: "Non-parametric Factorial Analysis with ART"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Non-parametric Factorial Analysis with ART}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 5
)
```

```{r setup, message = FALSE}
library(pepdiff)
library(dplyr)
```

## What is ART?

ART (Aligned Rank Transform) is a non-parametric method for factorial designs. It lets you analyse multi-factor experiments without assuming your data follows a particular distribution.

**The problem it solves:** Wilcoxon and other rank tests work great for two groups, but can't handle interactions. Parametric ANOVA handles interactions but assumes normality. ART gives you both: non-parametric robustness with factorial capability.

**How it works:**
1. **Align**: Subtract out all effects except the one you're testing, so residuals reflect only that effect
2. **Rank**: Convert aligned values to ranks
3. **ANOVA**: Run standard ANOVA on the ranks

The alignment step is crucial - it allows the test to isolate each effect (main effects, interactions) correctly.

## When to consider ART

- GLM fit diagnostics show problems (see `vignette("checking_fit")`)
- Heavy-tailed data with extreme values
- You're uncomfortable with distributional assumptions
- Ordinal data that doesn't meet interval scale assumptions

**Requirements:**

- Balanced-ish designs work best
- Complete cases for each peptide

## Example: 2×2 factorial design

A **2×2 factorial design** means two factors, each with two levels. Here: treatment (ctrl, trt) × timepoint (early, late). This gives four experimental conditions:

|           | early | late |
|-----------|-------|------|
| **ctrl**  | ✓     | ✓    |
| **trt**   | ✓     | ✓    |

Each peptide is measured in all four conditions (with replicates), letting us ask: does treatment have an effect? Does that effect depend on timepoint?

We'll use 5 replicates per condition and simulate data with heavy tails (extreme values) - exactly the scenario where ART shines.

### The simulated peptide groups

To illustrate how ART handles different effect patterns, we simulate 40 peptides in four groups:

| Group | Peptides | Effect Pattern | What ART should find |
|-------|----------|----------------|----------------------|
| **A** | PEP_001–010 | Treatment effect only | Main effect significant, same at both timepoints |
| **B** | PEP_011–020 | Timepoint effect only | No treatment effect (treatment comparison) |
| **C** | PEP_021–030 | Interaction | Treatment works at late but not early |
| **D** | PEP_031–040 | No effect | Nothing significant |

### What these patterns look like in raw data

Here's what the mean abundances look like for each group across the four conditions (using a baseline of ~10 for illustration):

| Group | ctrl @ early | ctrl @ late | trt @ early | trt @ late | Pattern |
|-------|--------------|-------------|-------------|------------|---------|
| **A** | 10 | 10 | **30** | **30** | trt 3× higher at both timepoints |
| **B** | 10 | **30** | 10 | **30** | late 3× higher, same for ctrl and trt |
| **C** | 10 | 10 | 10 | **40** | trt effect only at late (interaction) |
| **D** | 10 | 10 | 10 | 10 | no differences |

Notice how Group C is the tricky one: treatment has no effect at early (10 vs 10) but a 4-fold effect at late (10 vs 40). The main effect would average these, giving ~2-fold - which doesn't represent either timepoint accurately.

We'll refer back to these groups as we analyse the results.

```{r generate-data}
set.seed(789)

n_peptides <- 40
n_reps <- 5  # 5 reps per condition = 20 observations per peptide

peptides <- paste0("PEP_", sprintf("%03d", 1:n_peptides))
genes <- paste0("GENE_", LETTERS[((1:n_peptides - 1) %% 26) + 1])

# Step 1: Create base abundance for each peptide
peptide_info <- tibble(

  peptide = peptides,
  gene_id = genes,
  pep_num = 1:n_peptides,
  base = rgamma(n_peptides, shape = 6, rate = 0.6)
)

# Step 2: Create the experimental design
design <- expand.grid(
  peptide = peptides,
  treatment = c("ctrl", "trt"),
  timepoint = c("early", "late"),
  bio_rep = 1:n_reps,
  stringsAsFactors = FALSE
)

# Step 3: Join design with peptide info and apply effects
# Using heavier-tailed noise to justify ART
sim_data <- design %>%
  left_join(peptide_info, by = "peptide") %>%
  mutate(
    # Group A (peptides 1-10): Treatment effect only
    # trt is 3-fold higher than ctrl, same at both timepoints
    trt_effect = ifelse(pep_num <= 10 & treatment == "trt", 3, 1),

    # Group B (peptides 11-20): Timepoint effect only
    # late is 3-fold higher than early, same for both treatments
    time_effect = ifelse(pep_num > 10 & pep_num <= 20 & timepoint == "late", 3, 1),

    # Group C (peptides 21-30): Interaction
    # Treatment works at late (4-fold) but NOT at early
    int_effect = case_when(
      pep_num > 20 & pep_num <= 30 & treatment == "trt" & timepoint == "late" ~ 4,
      TRUE ~ 1
    ),

    # Group D (peptides 31-40): No effect
    # All effect multipliers are 1

    # Add heavy-tailed noise (occasional extreme values)
    # This is why we might prefer ART over GLM
    extreme = ifelse(runif(n()) < 0.05, runif(n(), 2, 4), 1),

    # Final abundance with Gamma noise and occasional extremes
    value = rgamma(n(), shape = 10, rate = 10 / (base * trt_effect * time_effect * int_effect)) * extreme
  ) %>%
  select(peptide, gene_id, treatment, timepoint, bio_rep, value)

# Import
temp_file <- tempfile(fileext = ".csv")
write.csv(sim_data, temp_file, row.names = FALSE)

dat <- read_pepdiff(
  temp_file,
  id = "peptide",
  gene = "gene_id",
  value = "value",
  factors = c("treatment", "timepoint"),
  replicate = "bio_rep"
)

dat
```

## Running ART analysis

```{r run-art, message = FALSE}
results_art <- compare(
  dat,
  compare = "treatment",
  ref = "ctrl",
  method = "art"
)

results_art
```

The interface is identical to GLM - just change `method = "art"`.

### Did ART find the treatment effects?

Since we simulated this data, we know the truth:

- **Group A**: 3-fold treatment effect at both timepoints
- **Group B**: No treatment effect (the timepoint change affects ctrl and trt equally)
- **Group C**: 4-fold treatment effect at late, but no effect at early
- **Group D**: No effect

Let's see what ART finds:

```{r view-results}
results_art$results %>%
  mutate(
    pep_num = as.numeric(gsub("PEP_", "", peptide)),
    group = case_when(
      pep_num <= 10 ~ "A: Treatment only",
      pep_num <= 20 ~ "B: Timepoint only",
      pep_num <= 30 ~ "C: Interaction",
      TRUE ~ "D: No effect"
    )
  ) %>%
  group_by(group) %>%
  summarise(
    n_significant = sum(significant),
    n_peptides = n(),
    median_fc = round(median(fold_change, na.rm = TRUE), 2)
  )
```

- **Group A**: Treatment effect detected ✓
- **Group B**: Correctly negative (no treatment effect) ✓
- **Group C**: Detected, but note the diluted fold change (~2 instead of 4) - the main effect averages across timepoints
- **Group D**: Correctly negative ✓

```{r plot-art-results, fig.height = 6}
plot(results_art)
```

## Stratified comparisons with `within`

Just like GLM, use `within` to get treatment effects at each timepoint separately:

```{r art-stratified, message = FALSE}
results_strat <- compare(
  dat,
  compare = "treatment",
  ref = "ctrl",
  within = "timepoint",
  method = "art"
)

results_strat
```

### Stratified results by group

Let's see how each group looks when we analyse timepoints separately:

```{r view-stratified}
results_strat$results %>%
  mutate(
    pep_num = as.numeric(gsub("PEP_", "", peptide)),
    group = case_when(
      pep_num <= 10 ~ "A: Treatment only",
      pep_num <= 20 ~ "B: Timepoint only",
      pep_num <= 30 ~ "C: Interaction",
      TRUE ~ "D: No effect"
    )
  ) %>%
  group_by(group, timepoint) %>%
  summarise(
    n_significant = sum(significant),
    median_fc = round(median(fold_change, na.rm = TRUE), 2),
    .groups = "drop"
  ) %>%
  tidyr::pivot_wider(
    names_from = timepoint,
    values_from = c(n_significant, median_fc)
  )
```

**What stratified analysis reveals:**

- **Group A**: FC ≈ 3 at both timepoints — the main effect was accurate
- **Group B**: FC ≈ 1 at both timepoints, 0 significant — correctly negative at both
- **Group C**: FC ≈ 1 at early, FC ≈ 4 at late — now we see the true effect!
- **Group D**: FC ≈ 1 at both, ~0 significant — correctly negative

For Group C, stratified analysis recovers the true 4-fold effect at late that was diluted in the main effect.

## Handling interactions

Group C illustrates an **interaction**: the treatment effect depends on timepoint. ART handles this the same way as GLM - use `within` to get conditional effects at each level.

For more details on interpreting interactions, see `vignette("glm_analysis")`.

## ART vs GLM comparison

Let's compare both methods on the same data:

```{r compare-methods, message = FALSE}
results_glm <- compare(dat, compare = "treatment", ref = "ctrl", method = "glm")
```

```{r compare-significant}
# Compare significant calls
comparison <- tibble(
  peptide = results_glm$results$peptide,
  glm_sig = results_glm$results$significant,
  art_sig = results_art$results$significant,
  glm_pval = round(results_glm$results$p_value, 4),
  art_pval = round(results_art$results$p_value, 4)
)

# Agreement
cat("Both significant:", sum(comparison$glm_sig & comparison$art_sig), "\n")
cat("GLM only:", sum(comparison$glm_sig & !comparison$art_sig), "\n")
cat("ART only:", sum(!comparison$glm_sig & comparison$art_sig), "\n")
cat("Neither:", sum(!comparison$glm_sig & !comparison$art_sig), "\n")
```

When both methods agree, you can be confident in the result. When they disagree, investigate the peptide:

```{r disagreements}
# Peptides where methods disagree
disagreements <- comparison %>%
  filter(glm_sig != art_sig) %>%
  arrange(pmin(glm_pval, art_pval))

if (nrow(disagreements) > 0) {
  print(disagreements)
} else {
  cat("No disagreements between methods\n")
}
```

## Diagnostics

```{r art-diagnostics}
results_art$diagnostics %>%
  head()
```

The `error` column contains diagnostic messages only when models fail - `NA` means success. Check convergence:

```{r check-convergence}
n_converged <- sum(results_art$diagnostics$converged)
n_failed <- sum(!results_art$diagnostics$converged)

cat("Converged:", n_converged, "\n")
cat("Failed:", n_failed, "\n")
```

The same diagnostic plots apply:

- Volcano should be symmetric
- P-value histogram should show uniform + spike pattern
- Fold change distribution should centre near zero

## Exporting results

The results are stored in a tidy tibble:

```{r results-structure}
glimpse(results_art$results)
```

Stratified results include the timepoint column:

```{r stratified-structure}
glimpse(results_strat$results)
```

Save to CSV for downstream analysis:

```{r export-results, eval = FALSE}
# All results
write.csv(results_art$results, "art_results.csv", row.names = FALSE)

# Significant hits only
results_art$results %>%
  filter(significant) %>%
  write.csv("art_significant.csv", row.names = FALSE)

# Stratified results
write.csv(results_strat$results, "art_stratified.csv", row.names = FALSE)
```

## Limitations of ART

**Speed:** ART is slower than GLM because it fits multiple aligned models per peptide. For large datasets, this matters.

**Interpretation:** Rank-based effects are less intuitive than fold changes. "Treatment increases ranks" is harder to communicate than "treatment doubles abundance".

**Balance:** ART works best with balanced designs (equal n per cell). Unbalanced designs can give unreliable interaction tests.

**Not a magic fix:** If your data has fundamental problems (outlier samples, batch effects), ART won't save you. Fix the problems first.

## Practical recommendations

1. **Start with GLM** - it's the default for good reason
2. **Check GLM fit** - run `plot_fit_diagnostics(results)` (see `vignette("checking_fit")`)
3. **If diagnostics look poor**, try ART on the same data
4. **Compare results** - where do methods agree and disagree?
5. **For publication**, report which method you used and why

**Decision tree:**
```
GLM diagnostics OK?
  → Yes: Use GLM
  → No: Try ART
         ↓
       ART diagnostics OK?
         → Yes: Use ART
         → No: You may need more data, or the effect isn't there
```

## When both methods fail

If neither GLM nor ART gives sensible results:

1. **Check data quality**: Outlier samples, batch effects, normalisation issues
2. **Consider sample size**: May not have power to detect effects
3. **Simplify the model**: Too many factors for the data?
4. **Accept the null**: Sometimes there's no effect to find

See peppwR for power analysis to understand what effect sizes your experiment can detect.

```{r cleanup, include = FALSE}
unlink(temp_file)
```
