fabricate() evaluates columns in declaration order. A column callable receives the current Polars frame and a NumPy generator, so later variables can depend on earlier variables without string evaluation.
import numpy as np
import polars as pl
import declaredesign as dd
population = dd.fabricate(
2_000 ,
rng= 44 ,
X= lambda data, rng: rng.normal(size= data.height),
sector= lambda data, rng: dd.draw_categorical(
data.height,
[0.45 , 0.35 , 0.20 ],
categories= ["services" , "manufacturing" , "public" ],
rng= rng,
),
earnings= lambda data, rng: np.exp(
9.5 + 0.25 * data["X" ].to_numpy() + rng.normal(scale= 0.5 , size= data.height)
),
)
population.head()
shape: (5, 4)
i64
f64
str
f64
1
1.445963
"services"
22365.442614
2
0.102305
"manufacturing"
14918.505095
3
0.326753
"services"
13916.566996
4
1.136462
"services"
13629.444446
5
0.824244
"services"
32887.180235
Nested levels and intraclass correlation
schools = dd.fabricate(50 , rng= 5 , school_quality= lambda data, rng: rng.normal(size= data.height))
students = dd.add_level(
schools,
N= 30 ,
level= "student" ,
rng= 6 ,
baseline= lambda data, rng: (
data["school_quality" ].to_numpy() + rng.normal(size= data.height)
),
)
students.select("ID" , "student_ID" , "student_parent_ID" , "school_quality" , "baseline" ).head()
shape: (5, 5)
i64
i64
i64
f64
f64
1
1
1
-0.801931
0.251184
1
2
1
-0.801931
0.97456
1
3
1
-0.801931
-3.355223
1
4
1
-0.801931
-0.939896
1
5
1
-0.801931
0.211788
draw_normal_icc() and draw_binary_icc() expose the within-cluster dependence directly. draw_multivariate() returns a Polars frame with a requested covariance matrix. Other helpers cover binary, binomial, categorical, count, ordered, Likert, and quantile-valued variables.
Potential outcomes and reveal
population = dd.potential_outcomes(
population,
outcome= "Y" ,
assignment= "Z" ,
conditions= [0 , 1 ],
model= lambda data, z, rng: (
0.2 * data["X" ].to_numpy() + 1.25 * z + rng.normal(scale= 0.2 , size= data.height)
),
rng= 10 ,
)
population = population.with_columns(pl.Series("Z" , dd.complete_ra(population.height, rng= 11 )))
observed = dd.reveal_outcomes(population, outcome= "Y" , assignment= "Z" )
observed.select("Z" , "Y_Z_0" , "Y_Z_1" , "Y" ).head()
shape: (5, 4)
i64
f64
f64
f64
1
0.068525
1.82455
1.82455
1
-0.124544
1.147167
1.147167
1
-0.09101
1.64837
1.64837
1
0.280688
1.710156
1.710156
1
0.115133
1.493938
1.493938
Potential outcomes are ordinary columns named by outcome, assignment variable, and condition. Reveal is therefore transparent and testable.