10 Independent categorical outcomes

10.1 Pearson chi-square and Fisher exact tests

Pearson’s chi-square test compares the observed counts, \(O_{ij}\), with the counts expected if the two categorical variables were independent.

The expected count in cell \(i,j\) is

\[ E_{ij}=\frac{n_{i}n_{j}}{n}, \]

and the chi-square statistic is

\[ \chi^2=\sum_i\sum_j\frac{(O_{ij}-E_{ij})^2}{E_{ij}}. \]

A larger discrepancy between observed and expected counts produces a larger chi-square statistic and stronger evidence that the variables are associated.

Fisher’s exact test addresses the same general question without relying on the large-sample chi-square approximation, making it useful when expected counts are small.

10.2 Association between Treatment and clinical response

The following table compares treatment assignment with clinical response at 12 weeks.

resp_tab <- table(Treatment=dat$treatment_arm,
                  Response=dat$clinical_response_12w)
resp_tab
#>          Response
#> Treatment  0  1
#>   Control 88 32
#>   Drug_A  68 52
#>   Drug_B  50 70

Under the null hypothesis,

\[ H_0:\text{clinical response is independent of treatment}. \]

Before selecting the test, examine the expected counts.

resp_chi <- suppressWarnings(chisq.test(resp_tab, correct=FALSE))
round(resp_chi$expected,2)
#>          Response
#> Treatment     0     1
#>   Control 68.67 51.33
#>   Drug_A  68.67 51.33
#>   Drug_B  68.67 51.33

Expected counts are calculated under the assumption that treatment and clinical response are unrelated. They determine whether the chi-square approximation is sufficiently reliable.

10.3 Automated chi-square or Fisher selection

The following block checks the expected counts and performs the appropriate test.

expected <- resp_chi$expected
chi_ok <- all(expected >= 5)
# OR
chi_ok <- all(expected>=1) && mean(expected<5)<=.20

if(chi_ok){
  selected_test <- "Pearson chi-square test"
  fit <- resp_chi
} else {
  selected_test <- "Fisher exact test"
  fit <- fisher.test(resp_tab)
}

tibble(
  test=selected_test,
  minimum_expected=min(expected),
  percent_expected_below_5=100*mean(expected<5),
  p_value=fit$p.value
) |> kbl(digits=3, caption="Selected test for treatment and clinical response")
Table 10.1: Selected test for treatment and clinical response
test minimum_expected percent_expected_below_5 p_value
Pearson chi-square test 51.33 0 0

The decision is straightforward:

  • if expected counts are adequate, use Pearson’s chi-square test;
  • if expected counts are sparse, use Fisher’s exact test.

A small p-value indicates evidence that clinical response differs across treatment groups. The global test does not show which treatment differs from Control or how large the difference is, so effect estimates should also be reported.

10.4 Risk differences and risk ratios

For a binary treatment outcome, risks and their contrasts provide a direct measure of treatment effect.

resp_effects <- bind_rows(
  risk_contrast(dat,"clinical_response_12w","Drug_A"),
  risk_contrast(dat,"clinical_response_12w","Drug_B")
) |> mutate(across(c(risk_exposed,risk_reference,risk_difference,rd_low,rd_high), ~100*.x))

kbl(resp_effects, digits=2,
    caption="Clinical-response effects versus Control; risks and risk differences are percentage points")
Table 10.2: Clinical-response effects versus Control; risks and risk differences are percentage points
comparison risk_exposed risk_reference risk_difference rd_low rd_high risk_ratio rr_low rr_high
Drug_A vs Control 43.33 26.67 16.67 4.78 28.55 1.62 1.13 2.33
Drug_B vs Control 58.33 26.67 31.67 19.82 43.52 2.19 1.57 3.05

The risk difference measures the absolute difference in response probability:

\[ RD=P(Y=1\mid\text{treatment})-P(Y=1\mid\text{Control}), \]

whereas the risk ratio compares the two probabilities proportionally:

\[ RR=\frac{P(Y=1\mid\text{treatment})} {P(Y=1\mid\text{Control})}. \]

The global chi-square or Fisher test provides evidence of association; the risk difference and risk ratio describe the size of the treatment effect.