Design Module
The xpyrment.design module contains submodules and components for design.
design
Experimental design, user routing, traffic splits, randomization, and Design of Experiments (DoE).
This package provides utilities for configuring study designs, routing experimental traffic, setting up
randomization schemes, and classical Design of Experiments (DoE) matrices:
- hash_assign: Cryptographic deterministic hashing engine to route units state-lessly.
- TrafficSplitter: Coordinates multi-variant allocations, long-term holdouts, and progressive ramps.
- stratified_randomization: Ensures structural balance on pre-experiment covariates.
- doe: Comprehensive classical design matrices (factorial, fractional, Taguchi, DSD, CCD, mixture, etc.).
| MODULE | DESCRIPTION |
|---|---|
doe |
Classical Design of Experiments (DoE) generators and optimization engines. |
power |
Analytical Power Analysis & Sample Size Estimator (Block 44). |
randomization |
Deterministic, hash-based randomization engines for user and unit assignments. |
splits |
Traffic split coordinators, holdout groups, and exposure ramp schedules. |
stratification |
Stratified and clustered randomization engines for balanced covariate distributions. |
| CLASS | DESCRIPTION |
|---|---|
TrafficSplitter |
Manages traffic split fractions, global holdout configurations, and progressive exposure ramp schedules. |
AnalyticalPowerCalculator |
Computes sample sizes, statistical power, and Minimum Detectable Effects (MDE). |
| FUNCTION | DESCRIPTION |
|---|---|
hash_assign |
Assigns a unit to a variant deterministically using MD5 hashing and modulo arithmetic. |
stratified_randomization |
Performs stratified randomization to ensure balance on continuous/categorical covariates. |
TrafficSplitter
TrafficSplitter(
allocations: Dict[str, float],
holdout_percentage: float = 0.0,
ramp_schedule: List[float] = None,
)
Manages traffic split fractions, global holdout configurations, and progressive exposure ramp schedules.
A TrafficSplitter divides incoming traffic into discrete experimental buckets. It supports fractional
allocations, standard control and treatment arms, and long-term holdout groups.
Business and Operational Context
- Fractional Allocations: Allows unequal variants (e.g., 90% control, 10% treatment) to limit exposure during early launch states.
- Long-Term Holdouts: A portion of users can be entirely withheld from all experiments within a product area (the "holdout group") to measure the long-term cumulative effects and potential interaction effects of independent product changes over months.
- Exposure Ramp Schedules: Step-wise exposure schedules (e.g., exposing 1% of users, then 10%, then 50%, then 100%) act as standard release gates. This mitigates operational risk by validating telemetry and checking for guardrail breaches before exposing the entire user base.
| ATTRIBUTE | DESCRIPTION |
|---|---|
allocations |
A dictionary mapping variant names to their allocation weight fractions (values bounded in \([0, 1]\)).
TYPE:
|
holdout_percentage |
Percentage of global traffic completely excluded from selection and routed to a static holdout arm. Bounded in \([0, 1]\).
TYPE:
|
Examples:
Example
Validates that the sum of variant allocations and holdout percentages totals exactly 1.0.
| PARAMETER | DESCRIPTION |
|---|---|
allocations
|
Mapping of variant labels to active traffic split weights.
TYPE:
|
holdout_percentage
|
Fractional traffic diverted into a holdout group. Defaults to 0.0.
TYPE:
|
ramp_schedule
|
Custom progressive exposure percentages. Defaults to None.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If any individual allocation weight is negative. |
ValueError
|
If the total allocation weight including the holdout percentage does not sum to 1.0. |
ValueError
|
If ramp_schedule has values outside [0.0, 1.0], is non-monotonic, or does not end in 1.0. |
| METHOD | DESCRIPTION |
|---|---|
get_ramp_schedule |
Generates progressive exposure ramp-up schedule coordinates. |
Source code in src\xpyrment\design\splits.py
get_ramp_schedule
Generates progressive exposure ramp-up schedule coordinates.
Ramping exposure represents a crucial risk-management strategy. This method returns a list of active exposure fractions defining sequential release gating stages.
Mathematical Representation
Let \(R\) be the ramp-up schedule array: $$ R = [r_1, r_2, \dots, r_m] $$ where \(r_j \in [0, 1]\) represents the fraction of total traffic exposed to the experiment during step \(j\), such that \(r_j \le r_{j+1}\).
| RETURNS | DESCRIPTION |
|---|---|
List[float]
|
List[float]: Ordered list of target traffic fractions representing progressive exposure stages. |
Source code in src\xpyrment\design\splits.py
AnalyticalPowerCalculator
Computes sample sizes, statistical power, and Minimum Detectable Effects (MDE).
TODO: Extend power calculations to unequal allocation ratios with multiple treatment arms using Dunnett's adjustment. TODO: Add exact simulation-based power curves utilizing empirical bootstrap baseline variance matrices.
| PARAMETER | DESCRIPTION |
|---|---|
alpha
|
Targeted significance/Type I error rate. Defaults to 0.05.
TYPE:
|
power
|
Targeted power/1 - Type II error rate. Defaults to 0.80.
TYPE:
|
| METHOD | DESCRIPTION |
|---|---|
compute_sample_size |
Computes sample size per group for a two-sample t-test. |
compute_mde |
Computes Minimum Detectable Effect (MDE) under configured constraints. |
compute_power |
Computes statistical power given sample size and target treatment effect size. |
adjust_for_clusters |
Adjusts sample size requirements for clustered designs using the VIF model. |
Source code in src\xpyrment\design\power.py
compute_sample_size
Computes sample size per group for a two-sample t-test.
ratio = n_treatment / n_control
Source code in src\xpyrment\design\power.py
compute_mde
Computes Minimum Detectable Effect (MDE) under configured constraints.
Source code in src\xpyrment\design\power.py
compute_power
Computes statistical power given sample size and target treatment effect size.
Source code in src\xpyrment\design\power.py
adjust_for_clusters
Adjusts sample size requirements for clustered designs using the VIF model.
VIF = 1 + (M - 1) * ICC
Source code in src\xpyrment\design\power.py
hash_assign
Assigns a unit to a variant deterministically using MD5 hashing and modulo arithmetic.
To distribute units (e.g., user IDs or device hashes) uniformly and orthogonally across multiple concurrent experiments without storing state, we concatenate a static experiment-level unique identifier (the "salt") with the unit's unique identifier. The resulting string is hashed, and the lower slice is mapped into the variant array using modulo arithmetic.
Mathematical Representation
Let \(u\) be the unit identifier, \(S\) be the unique experiment salt, and \(V = (v_1, v_2, \dots, v_k)\) be the ordered array of \(k\) variants. The assignment key is formed by concatenation: $$ K = S \mathbin{\Vert} \text{str}(u) $$ We compute the MD5 digest of \(K\) (yielding a 128-bit hex string) and extract the first 8 characters, representing a 32-bit integer \(H\): $$ H = \text{hex_to_int}(\text{MD5}(K)[0:8]) $$ The target variant index \(i\) is calculated using the modulo operator: $$ i = H \pmod{k} $$ The assigned variant is \(V_i\).
Properties of Hash-based Assignment
- Repeatability: For a given unit ID \(u\) and salt \(S\), the returned variant is always identical, eliminating the need for distributed database lookups.
- Uniformity: The MD5 hash exhibits avalanche-effect characteristics, distributing assignment probabilities uniformly: \(P(\text{Variant} = v) \approx 1/k\).
- Orthogonal Decorrelation: By using different salts for different experiments (\(S_A \neq S_B\)), user allocations in experiment A are statistically independent of their allocations in experiment B, preventing cross-contamination and carrying effects across concurrent runs.
| PARAMETER | DESCRIPTION |
|---|---|
unit_id
|
Unique identifier for the experimental unit (e.g., visitor ID, account code).
TYPE:
|
salt
|
A unique string identifying the active experiment. Acting as the cryptographic salt, this prevents correlation between different active experiments.
TYPE:
|
variants
|
Ordered list of variant labels (e.g.,
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The selected variant label from the
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If the |
Examples:
Example
Source code in src\xpyrment\design\randomization.py
stratified_randomization
stratified_randomization(
df: DataFrame,
strata_cols: list,
variants: Optional[List[str]] = None,
treatment_col: str = "variant",
random_state: Optional[int] = None,
) -> DataFrame
Performs stratified randomization to ensure balance on continuous/categorical covariates.
In simple randomization, small sample sizes or highly variable covariates can lead to accidental imbalance across treatment arms, introducing selection bias. Stratified randomization resolves this by dividing the population into mutually exclusive, homogeneous subgroups (strata) based on the provided covariates, and then executing independent randomization within each individual stratum.
Mathematical and Algorithmic Background
Let \(D\) be the dataset of size \(N\). Let \(C = \{c_1, c_2, \dots, c_p\}\) be the set of stratification columns. 1. Strata Construction: We partition \(D\) into \(K\) disjoint subsets (strata), \(\{D_1, D_2, \dots, D_K\}\), such that within each subset \(D_j\), all units share identical values for all stratification columns \(C\): $$ D = \bigcup_{j=1}^{K} D_j \quad \text{where} \quad D_a \cap D_b = \emptyset \ \ \forall \ a \neq b $$ 2. Intra-Stratum Randomization: For each stratum \(D_j\), units are randomly permuted and assigned to treatment arms. This guarantees that if treatment arm proportions are \(\{w_1, w_2, \dots, w_k\}\), then within every stratum \(D_j\), the assignment counts follow: $$ n_{j, \text{arm } i} \approx w_i \times |D_j| $$ This reduces the variance of the treatment effect estimator by removing the variance contribution of the stratification covariates.
| PARAMETER | DESCRIPTION |
|---|---|
df
|
The input DataFrame containing experimental units and their covariate values.
TYPE:
|
strata_cols
|
List of column names in
TYPE:
|
variants
|
Ordered list of variant labels. Defaults to
TYPE:
|
treatment_col
|
Column name where the assigned variant label will be written. Defaults to
TYPE:
|
random_state
|
Integer seed to initialize local random state generator for reproducibility.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
DataFrame
|
pd.DataFrame: A new DataFrame with treatment assignments balanced across the specified strata. |