Confounder Control

The Problem

Food sensitivity data collected without controlling for confounders is nearly useless. A person with poor sleep, high stress, and a histamine-heavy dinner will have a bad symptom day — but the model can’t distinguish how much of that is the food vs. the sleep vs. the stress.

Worse, these confounders are correlated with food choices. Stress days often involve eating differently (comfort foods, more alcohol, fast food). Ignoring confounders doesn’t just add noise — it adds biased noise.

The Confounders We Track

Sleep

Why it matters: Sleep deprivation increases systemic inflammation, lowers pain threshold, and elevates baseline histamine through mast cell sensitization. A person who slept 4 hours will score their symptoms higher on average regardless of what they ate. See Sleep and Histamine for the full biochemistry — histamine is a wakefulness neurotransmitter, and sleep deprivation directly increases mast cell reactivity, creating a feedback loop.

What we track:

  • sleep_hours — total sleep (decimal, e.g. 6.5)
  • sleep_quality — 1-5 self-reported quality

Data source: Manual entry or Apple HealthKit / Google Health Connect auto-population.

Stress

Why it matters: Psychological stress triggers mast cell degranulation directly via corticotropin-releasing hormone (CRH). It also increases gut permeability, alters motility, and amplifies visceral hypersensitivity. High-stress days systematically inflate symptom scores. See The HPA Axis and Mast Cells — CRH is a literal mast cell trigger via CRH Receptors, not a vague mind-body effect.

What we track:

  • stress_level — 1-5 self-reported

Exercise

Why it matters: Moderate exercise is anti-inflammatory. Intense exercise raises histamine (exercise-induced histamine release from mast cells), temporarily elevates gut permeability, and can trigger exercise-induced anaphylaxis in MCAS-adjacent individuals.

What we track:

  • exercise_intensity — none / light / moderate / intense

Model treatment: Moderate exercise is a positive control modifier (more reliable day). Intense exercise is a confound similar to high stress — flag and partially discount.

Ambient Temperature and Weather

Why it matters: Heat is a direct mast cell trigger — increased core and skin temperature increases membrane fluidity and can trigger degranulation. Cold exposure activates mast cells via distinct ion channel mechanisms. Barometric pressure changes are anecdotally reported by MCAS patients as flare triggers, though the mechanism is less clear (possibly related to sinus pressure changes activating nasal/airway mast cells, or effects on vascular tone).

What we track: daily_control_flags.flag_type = 'temperature_extreme' with values: hot, cold, pressure_change

Data source: Manual entry initially. Weather API auto-population is a straightforward future integration — store daily high/low temperature and barometric pressure delta from the user’s location and flag automatically when thresholds are exceeded.

Model treatment: Days with temperature extremes are partially discounted for all categories, since thermal mast cell activation adds mediator load independent of food.

Menstrual Cycle Phase

Why it matters: Estrogen upregulates histamine receptors and stimulates mast cell degranulation. See Estrogen and Mast Cells for the full mechanism, including the Histamine-Estrogen Feedback Loop.

The cycle phases affect mast cell biology as follows:

  • Follicular phase (days ~1-13): Estrogen rises gradually. Mast cell priming increases as estrogen climbs, but progesterone is low — no stabilizing counterbalance. However, absolute estrogen is still moderate for most of this phase.
  • Ovulatory (day ~14): Estrogen peaks sharply. This can be a symptom spike point due to acute estrogen-driven mast cell priming.
  • Luteal phase (days ~14-28): Progesterone rises significantly and provides a partial mast cell stabilizing effect (see Progesterone and Mast Cells). Estrogen has a secondary mid-luteal rise. The estrogen-to-progesterone ratio is the key variable — when this ratio favors estrogen (early luteal before progesterone peaks, or late luteal as progesterone drops faster than estrogen), mast cell symptoms can flare.
  • Perimenstrual (days ~25-3): Both hormones drop. Loss of progesterone’s stabilizing effect + estrogen withdrawal + prostaglandin release during menstruation. This is when many MCAS patients report their worst symptom days.

Correction from earlier version

A previous version stated the luteal phase has “low progesterone, relatively higher estrogen” — this is backwards. The luteal phase is the HIGH progesterone phase. The problematic period is the perimenstrual transition where progesterone drops precipitously. This distinction matters for the model: luteal-phase symptom data may actually be more reliable (progesterone is stabilizing), while perimenstrual data should be discounted for histamine-category analysis.

What we track: daily_control_flags.flag_type = 'menstrual_phase' with values: follicular / ovulatory / luteal / perimenstrual / menstrual

Data source: Manual entry. Apple Health and Google Health Connect expose menstrual tracking data from their respective health apps, which may be sufficient for auto-population. Clue’s public API was shut down years ago and has not been reliably restored — do not depend on it.

Model treatment: Perimenstrual and menstrual phases are the primary discount targets for histamine-category signal. Ovulatory phase (estrogen peak) is a secondary discount. Luteal phase (high progesterone) may actually represent a better testing window for histamine sensitivity, since the progesterone stabilizing effect creates a more controlled baseline — worth investigating with real data.

Illness and Medications

What we track: daily_control_flags with types: illness, antihistamine, nsaid, probiotic, mast_cell_stabilizer, etc.

Model treatment: Days with active illness are heavily discounted (inflammation baseline is elevated). NSAID use is flagged for salicylate category (NSAIDs are salicylates — see Salicylates for the COX shunting mechanism, and DAO for NSAID-driven DAO inhibition).

Antihistamine handling is category-specific

Antihistamines mask histamine-mediated symptoms but don’t affect FODMAP, oxalate, lectin, glutamate, or capsaicin pathways. The quality score should exclude antihistamine days from histamine-category correlations only, not from all analysis. Similarly, mast cell stabilizers (cromolyn, ketotifen) affect histamine AND other mast cell mediators (prostaglandins, leukotrienes), so they should discount histamine and salicylate categories but not FODMAP or oxalate.

This means the quality score needs to be per-category, not global. The MVP can use a simplified version (global score + categorical overrides for medication flags), but the architecture should support per-category scoring from the start.

Alcohol

Why it matters: Alcohol is both high-histamine, a histamine liberator, AND a DAO blocker. It’s one of the strongest confounders for histamine testing specifically. It also increases intestinal permeability acutely, which can affect all categories.

What we track: daily_control_flags.flag_type = 'alcohol' with optional value for quantity

Model treatment: Any alcohol discounts histamine-category signal heavily. Heavy alcohol (>2 drinks) discounts all categories due to gut permeability effects.

The Composite Quality Score

Each day gets a quality score that reflects how reliable that day’s symptom data is for statistical purposes.

MVP: Per-Category Quality Score

def quality_score(control, category: nil)
  sleep_factor = control.sleep_quality / 5.0
  stress_factor = 1.0 - ((control.stress_level - 1) / 4.0)
 
  base_score = (sleep_factor + stress_factor) / 2.0
 
  # Global flags — discount all categories
  base_score *= 0.3 if control.flags.illness?
  base_score *= 0.5 if control.flags.intense_exercise?
  base_score *= 0.7 if control.flags.temperature_extreme?
 
  # Category-specific flags
  if category&.slug == 'histamine'
    base_score *= 0.0 if control.flags.antihistamine?
    base_score *= 0.3 if control.flags.alcohol?
    base_score *= 0.5 if control.flags.perimenstrual? || control.flags.menstrual?
    base_score *= 0.5 if control.flags.mast_cell_stabilizer?
  elsif category&.slug == 'salicylate'
    base_score *= 0.3 if control.flags.nsaid? # NSAIDs ARE salicylates
    base_score *= 0.5 if control.flags.mast_cell_stabilizer?
  end
 
  # Heavy alcohol discounts all categories
  base_score *= 0.5 if control.flags.heavy_alcohol? && category&.slug != 'histamine'
 
  base_score.clamp(0.0, 1.0)
end

How It’s Used

The quality score is used as a weight in the correlation calculation, NOT as a multiplier on the symptom score. See MVP: Weighted Correlation with FDR Correction for the correct weighted correlation approach.

# The quality score weights the observation's INFLUENCE, not its VALUE.
# A quality=0.3 day still has its full symptom score — it just
# contributes less to the fitted correlation.
correlation = WeightedPearson(
  x: ingredient_exposure,
  y: symptom_scores,      # raw, unmodified
  weights: quality_scores  # per-category weights
)

This isn’t perfect statistics — it’s pragmatic weighting appropriate for a consumer app. The v0.2+ upgrade path is to ANCOVA (treat confounders as covariates directly) or mixed effects modeling (treat user as a random effect and confounders as fixed effects).

Why Not Just Exclude Bad Days?

Exclusion loses data. Weighting preserves the observation while appropriately discounting its reliability. Over 30 days, most people will have a mix of quality days — weighted averaging uses all of it, just proportionally.

Also: if someone has consistently poor sleep and consistently high symptoms, that’s a real signal that the model should surface — not suppress. The v0.2 mixed effects model can directly estimate the sleep-symptom relationship as a covariate, turning this confounder into useful information: “Your symptoms correlate more strongly with sleep quality than with any food” is a valuable finding.

UI Implications

The daily check-in should be fast — 30 seconds max. Pre-population from HealthKit handles sleep automatically for most users. The remaining manual fields: stress level (one tap 1-5), exercise (one tap), and optional flags.

The user never sees “quality score” or “weighted correlation.” They see: “Your data from Tuesday is marked as lower confidence due to poor sleep.”

Biochemical Basis

Each confounder tracked here has a specific biochemical mechanism affecting mast cell activity. The bucket model provides the theoretical framework: these confounders change the size of the bucket (capacity to handle mediators) independent of food. The app’s confounder weighting is a statistical implementation of that biochemical reality.

Further Reading