Random assignment and sampling

The assignment functions return NumPy arrays. Every stochastic function accepts either a NumPy Generator or an integer seed through rng; no function mutates NumPy’s global random state.

Complete versus simple assignment

Under simple assignment, units draw conditions independently. Under complete assignment, arm totals are fixed. If a requested probability does not imply an integer arm total, complete assignment stochastically rounds the total so that each unit’s marginal assignment probability remains exactly the requested value.

import altair as alt
import numpy as np
import polars as pl
import declaredesign as dd

rng = np.random.default_rng(2026)
records = []
for simulation in range(600):
    records.extend([
        {
            "design": "Simple",
            "treated": int(dd.simple_ra(101, prob=0.35, rng=rng).sum()),
        },
        {
            "design": "Complete",
            "treated": int(dd.complete_ra(101, prob=0.35, rng=rng).sum()),
        },
    ])

assignment_counts = pl.DataFrame(records)
assignment_counts.group_by("design").agg(
    pl.col("treated").mean().alias("mean treated"),
    pl.col("treated").std().alias("sd treated"),
)
shape: (2, 3)
design mean treated sd treated
str f64 f64
"Simple" 35.201667 4.909634
"Complete" 35.328333 0.469999

Complete random assignment removes arm-size variation without changing marginal assignment probabilities.

Blocked and clustered designs

blocks = np.repeat(["north", "south"], [40, 60])
blocked = dd.block_ra(blocks, block_m=[20, 30], rng=9)

clusters = np.repeat(np.arange(20), 5)
clustered = dd.cluster_ra(clusters, m=8, rng=9)

pl.DataFrame({
    "block": blocks,
    "blocked_Z": blocked,
    "cluster": clusters,
    "clustered_Z": clustered,
}).group_by("block").agg(pl.col("blocked_Z").sum())
shape: (2, 2)
block blocked_Z
str i64
"south" 30
"north" 20

block_and_cluster_ra() assigns whole clusters within blocks. Sampling analogues use the *_rs suffix and return zero-one inclusion arrays. Reusable declare_ra() and declare_rs() objects store a design separately from a draw.