← menstrualcycleR documentation
A Visual Explainer

PACTS

Phase-Aligned Cycle Time Scaling: how it improves on forward, backward, and combined cycle-day count methods.
Throughout: three example cycles: 24-day, 28-day, 34-day. For simplicity, all three have a 14-day luteal phase; follicular length varies (10, 14, 20 days). Consistent with population norms: ~69% of cycle-length variance comes from the follicular phase (Schmalenberger et al., 2021).
01 · FORWARD COUNT

Days counted from menses onset

Each day is labeled 1, 2, 3… starting at menses. Common, intuitive, and used in many cycle-tracking studies. But it implicitly assumes ovulation happens on a fixed day (e.g., day 14), which is true only for a 28-day cycle.

Forward count "Day 14" 24-day L 14 · F 10 next menses 28-day L 14 · F 14 next menses 34-day L 14 · F 20 next menses → 4 days past ovulation → at ovulation → 6 days before ovulation 1 7 14 21 28 34 Cycle day (counted forward from menses onset)
Follicular Luteal E2 P4 Menses Ovulation Reference: same label, different hormonal state
What breaks: follow the red dashed "Day 14" line down through all three rows: it lands at a different point on each hormone trajectory. In a 24-day cycle, day 14 is mid-luteal P4 territory; in a 28-day cycle, it sits at the ovulatory E2 peak; in a 34-day cycle, it's still in the flat-low pre-ovulatory window. Forward count cannot serve as a hormonally consistent timeline.
02 · BACKWARD COUNT

Days counted backward from the next menses

Days are labeled −1, −2, −3… from the day before the next menses. Because the luteal phase is relatively stable (mean 13.3 days, SD 2.1; Schmalenberger et al., 2021), this anchors ovulation reasonably well at day −15. The early follicular phase still floats.

Day −15 = ovulation in all cycles ✓ Day −25 34-day L 14 · F 20 next menses 28-day L 14 · F 14 next menses 24-day L 14 · F 10 next menses → mid-follicular → early follicular (day 4) → doesn't exist −34 −28 −25 −15 −5 −1 Cycle day (counted backward from next menses)
Follicular Luteal E2 P4 Menses Ovulation Aligned Misaligned
What works: the green dashed line at day −15 cuts through the ovulatory E2 peak in all three cycles: backward count aligns the luteal phase well because luteal length is stable. The mid-luteal P4 peak also lines up across cycles at about day −8.
What breaks: the red dashed line at day −25 lands on three different hormonal states: late follicular in the 34-day cycle, very early follicular in the 28-day cycle, and outside the cycle entirely in the 24-day. Backward count anchors only one end of the cycle.
03 · COMBINED COUNT

Forward and backward count together (e.g., C-PASS, −7 to +10)

Used in PMDD diagnosis and many cycle studies (Schmalenberger et al., 2021; Eisenlohr-Moul et al., 2017): combine backward count from next menses for the premenstrual window with forward count from current menses for the postmenstrual window. Captures the perimenstrual region well, but still has no ovulation anchor.

C-PASS window (−7 to +10) −7 (aligned) +10 (misaligned) 34-day L 14 · F 20 28-day L 14 · F 14 24-day L 14 · F 10 → mid-follicular (10 days pre-ov) → late follicular (4 days pre-ov) → at ovulation −15 −7 0 +10 +20 Backward count ← menses → Forward count
Follicular Luteal E2 P4 Menses Ovulation C-PASS window Aligned Misaligned
What works: the previous-luteal portion (left of menses) is aligned across all three cycles: the mid-luteal P4 peak lines up at day −7 regardless of total cycle length, because luteal length is stable. Backward-counted premenstrual days (−7 to −1) reliably index the same hormonal milieu.
What breaks: in the forward-counted post-menstrual half, the red dashed "+10" line lands at three different points on the hormone trajectory: ovulation in the 24-day cycle, late follicular in the 28-day, and still pre-ovulatory in the 34-day. The "+4 to +10 symptom-free reference week" assumed by C-PASS is not hormonally equivalent across cycles.
Norms (Schmalenberger et al., 2021): mean follicular = 15.7 days (SD 3.0, 95% CI 10–22); mean luteal = 13.3 days (SD 2.1, 95% CI 9–18). The follicular phase contributes ~69% of total cycle-length variance, which is why postmenstrual forward-counted reference days drift across cycles.
04 · PACTS

Same days, rescaled to a phase-proportional axis

PACTS keeps every observation but remaps the x-axis: menses → 0, ovulation → ±1. Days within each phase get spread proportionally. The same daily observations now sit at consistent biological positions across cycles, with no gap and no misalignment.

24-day L 13 · F 11 28-day L 14 · F 14 34-day L 15 · F 19 −1 −0.5 0 +0.5 +1 Standardized cycle time (PACTS, −1 to +1)
Luteal half (x < 0) Follicular half (x > 0) E2 P4 One day Menses (x = 0) Ovulation (x = ±1)
The payoff: the hormone trajectories now align across all three cycles: the E2 ovulatory peak, mid-luteal E2/P4 peaks, and perimenstrual drop all sit at the same x positions. Compare to §§01–03: no vertical reference line will cross three different hormonal states, because hormonally equivalent points have been assigned the same value. The daily dots below each strip show that the underlying observations are still individual days; PACTS only changes how their positions are encoded.
Note on luteal variability: these three cycles have biomarker-confirmed ovulation, so luteal phases vary realistically (13, 14, 15 days here; population mean ~13.3 ± 2.1, Schmalenberger et al., 2021). Most cycle-length variance still comes from the follicular phase (11, 14, 19 days here, range of 8) rather than the luteal (range of 2), consistent with the ~69% follicular-variance norm. If ovulation is imputed via the 15-day backward rule (EDO = next menses − 15), every luteal is forced to 14 days and the luteal halves end up with identical dot density across cycles regardless of true luteal length. menstrualcycleR flags these days in ovtoday_impute so the imputed proportion can be reported.
05 · DEFINING OVULATION

Four ways to set the ovulation anchor

PACTS requires an estimated day of ovulation (EDO) for every cycle. Use a biomarker when one is available; visually review daily E2/P4 trajectories when only hormones are available; impute via 15-day backward count otherwise. menstrualcycleR flags imputed days in ovtoday_impute so the proportion can be reported.

LH test
EDO = LH⁺ + 1
Day after a positive urinary LH-surge test.
BBT
EDO = nadir + 1
Day after the basal body temperature nadir, when P4 thermogenesis begins.
Imputed
EDO = next menses − 15
Backward count assuming a ~14-day luteal phase. Better than a midpoint assumption.
Visual review of daily hormones (E1G + LH + PDG)
EDO = the day BETWEEN the post-E1G-rise LH surge and the subsequent PDG rise
When daily urinary or salivary E1G (estradiol metabolite), LH, and PDG (progesterone metabolite) are all available, code EDO by visual inspection of each cycle's daily hormone trajectories alongside self-reported bleeding (Nagpal et al., 2025, §2.3.3). EDO = the first day in each cycle meeting all three criteria:
  1. Preovulatory E1G rise (without elevated PDG): in the week leading up to EDO, E1G has risen above its menstrual baseline while PDG remains at follicular-phase baseline.
  2. Distinct preovulatory LH surge (without elevated PDG): EDO is preceded or accompanied by an abrupt rise in LH (the LH surge is independent of LH peak; surges are detected as inflections, not absolute values), with PDG still at baseline.
  3. Post-ovulatory PDG rise: the day after EDO shows an upward deviation from the follicular-phase PDG trend, the start of the expected luteal rise. This typically happens within 3 days of the LH surge.
Typical day-by-day sequence: Day 1: E1G peak · Day 2: LH surge · Day 3: EDO · Day 4: PDG rise begins. Variations in Nagpal et al. 2025: if E1G peak, LH surge, and PDG rise all happen within 1 day, code EDO as the same day as the E1G/LH events. If there's a 2–3 day gap between LH surge and detectable PDG rise (rare, N = 3 in their validation sample), code EDO as the day before the PDG rise even if it sits 2–3 days after the LH surge.
Two trained raters should independently code each cycle and reconcile disagreements. Cycles lacking a sustained PDG rise are flagged as anovulatory and excluded from PACTS scaling.
Inclusion defaults: menstrualcycleR scales cycles 21–35 days, with luteal phases 7–18 days and follicular phases 8–25 days (Bull et al., 2019 norms from >600,000 cycles). As of v0.1.7, all six bounds are user-adjustable and independent of each other: the cycle-length bounds (lower_cyclength_bound / upper_cyclength_bound) and, separately, the phase-length windows (luteal_phase_min_days / max_days, follicular_phase_min_days / max_days). Through v0.1.6 the phase-length windows were hardcoded, not arguments at all — widen them directly now if your population's luteal or follicular phases genuinely run longer or shorter than these norms, rather than accepting a truncated cyclic_time. See ?pacts_scaling, section "Internal phase-length caps," for the full history and how cyclic_time_impute's narrower fallback (cyclic_time_impute_extended_phase) complements these.
06 · FOUR PACTS VARIABLES

Pick the variable that matches your model

Two centerings (menses vs ovulation) × two parameterizations (cyclic_time vs scaled_cycleday). Only the cyclic_time variants are cyclic: they force −1 and +1 to encode the same hormonal event so the function wraps continuously across the boundary, the requirement for cyclic cubic regression splines (bs = "cc" in mgcv). The scaled_cycleday variants are linear: they treat −1 and +1 as adjacent calendar days that happen to sit at opposite ends of the axis, with a real discontinuity between them. Curves below use the BioCycle normative serum reference (215 women).

cyclic_time · menses-centered, cyclic
−1 0 +1 Ov Menses Ov LUTEAL FOLLICULAR y(−1) = y(+1): both anchor to ovulation
E2 P4 Menses Ov
cyclic_time_ov · ovulation-centered, cyclic
−1 0 +1 Menses Ov Menses FOLLICULAR LUTEAL y(−1) = y(+1): both anchor to menses
E2 P4 Menses Ov
scaled_cycleday · menses-centered, linear
−1 0 +1 Post-ov Menses Ov LUTEAL FOLLICULAR linear: −1 and +1 are adjacent days, not one wrapped point
E2 P4 Menses Ov
scaled_cycleday_ov · ovulation-centered, linear
−1 0 +1 Menses Ov Pre-menses FOLLICULAR LUTEAL linear: −1 and +1 are adjacent days, not one wrapped point
E2 P4 Menses Ov
Choosing a variable: the cyclic property is the key distinction. Use cyclic_time / cyclic_time_ov with cyclic cubic splines (bs = "cc") in mgcv. Nagpal et al. (2025) used these for their primary GAMM analyses, and they're the only PACTS variants where the spline wraps (in practice the cyclic_time_impute variant is the usual choice, since it retains cycles with imputed ovulation). The scaled_cycleday / scaled_cycleday_ov variants are linear, usable for ordinary smoothers (bs = "cr" or bs = "tp") or descriptive plotting where the endpoints need not coincide, but they are legacy and are being phased out for primary lab analyses in favor of the cyclic variants. Centering follows from the research question: perimenstrual processes → menses-centered; periovulatory processes → ovulation-centered.
07 · CYCLIC PROPERTY (cyclic_time ONLY)

For cyclic_time / cyclic_time_ov, −1 and +1 are the same point

For the cyclic PACTS variables, the scale is circular rather than linear: the endpoints meet at the same hormonal anchor (ovulation when menses-centered; menses when ovulation-centered). This wrap is what lets cyclic cubic regression splines (bs = "cc" in mgcv) cross the boundary continuously, with no artificial seam. The scaled_cycleday variants do not have this property (see section 06).

−1 0 +1 Ov Menses Ov Linear view of cyclic_time wraps Menses (0) Ov (±1) Follicular Luteal Cyclic view of cyclic_time
Menses (x = 0) Ovulation (x = ±1) Follicular arc Luteal arc
Why this matters for modeling: when the spline is cyclic, an estimate at x = +0.99 is constrained to match an estimate at x = −0.99, because they sit on either side of the same ovulation point. This eliminates the discontinuity that an ordinary (non-cyclic) smoother would otherwise produce at the boundary. scaled_cycleday / scaled_cycleday_ov are not cyclic: fitting a bs = "cc" spline to them would mis-identify a real day-to-day jump as a wraparound.
08 · VALIDATION

PACTS routes more hormone variance through shared (fixed) effects

In GAMMs fit to daily urinary E1G across 44 cycles, PACTS reduced within-time-point variance in the follicular phase and explained more cycle-related variance through fixed effects than count-based time variables, meaning hormone trajectories are more consistent across individuals when time is rescaled by PACTS.

E1G GAMM: cycle-related variance partitioned by time variable PACTS 84.5% fixed 15.5% Combined 64.7% 35.3% Backward 51.5% 48.5% Forward 41.8% 58.2% random Fixed effect (shared across people) Random slope (idiosyncratic)
Fixed effect (shared) Random slope (idiosyncratic)
Values from Nagpal et al. (2025), Tables S1–S10.
A Menses Ovulation FOLLICULAR LUTEAL 1 7 14 21 28 Cycle day (menses count) Mean E1G (person z) WIDE, blurred peak B Menses Ovulation FOLLICULAR LUTEAL Menses 50%F Ov 50%L 100%L Percentage of phase elapsed (PACTS) Mean E1G (person z) TIGHT, sharp peak
Mean E1G (estradiol) ±1 SD (within-time-point spread) where count variance > PACTS
Real BEARS urinary E1G (estradiol), person-standardized. Counting days forward/backward from menses (A) lets ovulation float to a variable position, so the ovulatory rise smears and the within-time-point spread stays wide across the whole follicular window (blue); PACTS scaling (B) pins menses and ovulation, so the spread collapses and the mean E1G peak snaps into focus. Same data, both panels; only the time axis differs. After Nagpal et al. (2025), Fig. 3.
09 · WHY IT MATTERS

Precise cycle time sharpens dimensional frameworks

Because PACTS places every observation at a hormonally meaningful position, it sharpens frameworks that depend on good temporal resolution. The DASH-MC framework is a prime example: it maps three distinct dimensions of hormone sensitivity onto specific cycle windows, each with its own steroid trigger and hallmark symptoms.

ALLO SURGE LOW / FALLING E2 LOW, STABLE E2 SURGE Menses Ovulation Luteal Follicular
Estradiol (E2) Progesterone (P4) ALLO surge Overlap Low / falling E2 E2 surge
Dimension 1 · Luteal-onset
Trigger: ALLO surge
Most common symptoms: Irritability, interpersonal & sensory hypersensitivity, rejection sensitivity, conflict.
Biology: Delayed response to the P4-metabolite (ALLO) surge: altered GABAA-receptor regulation & paradoxical effects; reduced serotonergic function.
Dimension 2 · Perimenstrual-onset
Trigger: low / falling E2
Most common symptoms: Depression, anhedonia, impaired executive function, suicidal ideation & behavior.
Biology: E2 withdrawal: reduced dopamine signaling (frontal, striatal); possible reduced serotonin-transporter function.
Dimension 3 · Periovulatory-onset
Trigger: E2 surge
Most common symptoms: Maladaptive risk-taking, binge drinking, proactive aggression, risky sex.
Biology: E2 direct effects: enhanced dopamine signaling; increased reward responsivity.
The practical upshot: because each dimension sits in a specific cycle window, when a symptom peaks points to which hormonal shift is driving it, instead of lumping all cyclical symptoms together.
Recreated from Peters et al. (2025), Fig. 1: Dimensional Affective Sensitivity to Hormones across the Menstrual Cycle (DASH-MC). Molecular Psychiatry, 30, 251–262. Follicular phase = baseline symptom levels.