Skip to main content Skills Marketplace Descubre y explora habilidades de IA creadas por la comunidad.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Copiar promptMostrar detalles del prompt Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/xjtulyc/awesome-rosetta-skills --skill experimental-designEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Descargar Zip Descargando... Más de este repositorio Astronomical data analysis with astropy and astroquery — FITS I/O, WCS transforms, catalog cross-matching, aperture photometry, and CMB power spectra.
Seismological data analysis with ObsPy — FDSN waveform download, response removal, phase picking, moment tensor inversion, and seismicity mapping.
Download and analyze oceanographic data from Copernicus Marine Service and Argo floats using copernicusmarine, gsw, and xarray.
Ocupaciones relacionadas SOC
Basado en la clasificación ocupacional SOC
name experimental-design description Design rigorous experiments: sample size calculation, randomization strategies, pre-registration, and AB test duration estimation.
tags ["experimental-design","sample-size","randomization","pre-registration","ab-testing","power-analysis"] version 1.0.0 authors [{"name":"awesome-rosetta-skills contributors","github":"@awesome-rosetta-skills"}] license MIT platforms ["claude-code","codex","gemini-cli","cursor"] dependencies {"python":["statsmodels>=0.14.0","numpy>=1.24.0","pandas>=2.0.0","scipy>=1.10.0","osfclient>=0.0.5"]} last_updated 2026-03-17
Experimental Design
Plan experiments with correct statistical power, principled randomization, and
transparent pre-registration before data collection begins.
Key Concepts
Term Definition α (alpha) Type I error rate — false positive probability (typically 0.05) β (beta) Type II error rate — false negative probability (typically 0.20) Power (1−β) Probability of detecting a true effect (typically 0.80 or 0.90) MDES Minimum Detectable Effect Size — smallest effect your design can detect ICC Intra-cluster correlation — needed for cluster-randomised trials
Setup
pip install statsmodels numpy pandas scipy osfclient
Core Implementation
"""
experimental_design.py
Sample size, randomization, and pre-registration utilities.
"""
import hashlib
import math
import os
import warnings
from typing import Dict , List , Optional , Tuple , Union
import numpy as np
import pandas as pd
from scipy import stats
from statsmodels.stats.power import (
TTestIndPower,
TTestPower,
FTestAnovaPower,
NormalIndPower,
)
from statsmodels.stats.proportion import proportion_effectsize
def calculate_sample_size ( ) -> [ , ]:
alternative = two_tailed
p1 p2 :
effect_size = (proportion_effectsize(p1, p2))
test_type =
effect_size :
ValueError( )
test_type == :
analysis = TTestIndPower()
n = analysis.solve_power(effect_size=effect_size, alpha=alpha, power=power,
ratio=ratio, alternative=alternative)
test_type == :
analysis = TTestPower()
n = analysis.solve_power(effect_size=effect_size, alpha=alpha, power=power,
alternative=alternative)
ratio =
test_type == :
analysis = TTestPower()
n = analysis.solve_power(effect_size=effect_size, alpha=alpha, power=power,
alternative=alternative)
ratio =
test_type == :
analysis = FTestAnovaPower()
n = analysis.solve_power(effect_size=effect_size, alpha=alpha, power=power,
k_groups=k_groups)
test_type ( , ):
analysis = NormalIndPower()
n = analysis.solve_power(effect_size=effect_size, alpha=alpha, power=power,
ratio=ratio, alternative=alternative)
:
ValueError( )
n_ceil = math.ceil(n)
total_n = math.ceil(n_ceil * ( + ratio)) test_type ( , ) n_ceil
result = {
: n_ceil,
: total_n,
: (effect_size, ),
: alpha,
: power,
: test_type,
: ratio,
}
verbose:
( )
( )
( )
( )
( )
( )
p1 :
( )
()
result
( ) -> :
analysis = TTestIndPower() test_type == TTestPower()
es = analysis.solve_power(nobs1=n_per_group, alpha=alpha, power=power,
ratio= , alternative= )
(es, )
( ) -> :
<= expected_attrition_rate < :
ValueError( )
math.ceil(n / ( - expected_attrition_rate))
( ) -> pd.DataFrame:
rng = np.random.default_rng(seed)
group_labels :
group_labels = [ i (n_groups)]
assignments = rng.choice(group_labels, size=n)
df = pd.DataFrame({ : ( , n + ), : assignments})
df
( ) -> pd.DataFrame:
block_size % n_groups != :
ValueError( )
rng = np.random.default_rng(seed)
group_labels :
group_labels = [ i (n_groups)]
per_arm = block_size // n_groups
block_template = group_labels * per_arm
assignments = []
(assignments) < n:
block = block_template.copy()
rng.shuffle(block)
assignments.extend(block)
assignments = assignments[:n]
df = pd.DataFrame({ : ( , n + ), : assignments})
df
( ) -> pd.DataFrame:
group_labels :
group_labels = [ i (n_groups)]
result_frames = []
df = participants.copy().reset_index(drop= )
df[ ] = df[stratify_cols].astype( ).agg( .join, axis= )
stratum, group_df df.groupby( ):
n_stratum = (group_df)
assigned = block_randomize(
n=n_stratum,
block_size=block_size,
n_groups=n_groups,
group_labels=group_labels,
seed= ( (stratum)) % ( ** ) seed seed + ( (stratum)) % ,
)
group_df = group_df.reset_index(drop= )
group_df[ ] = assigned[ ].values
result_frames.append(group_df)
result = pd.concat(result_frames).drop(columns=[ ]).sort_index()
result
( ) -> pd.DataFrame:
rng = np.random.default_rng(seed)
group_labels :
group_labels = [ i (n_groups)]
shuffled_clusters = (clusters)
rng.shuffle(shuffled_clusters)
assignments = []
i, cluster (shuffled_clusters):
assignments.append({ : cluster, : group_labels[i % n_groups]})
pd.DataFrame(assignments)
( ) -> [ , ]:
p1 = baseline_rate
p2 = baseline_rate * ( + minimum_detectable_effect)
p2 = (p2, )
size_result = calculate_sample_size(
p1=p1, p2=p2, alpha=alpha / (n_variants - ),
power=power, verbose=
)
required_n = size_result[ ] * n_variants
daily_n = (daily_traffic * allocation_fraction / n_variants) * n_variants
days_needed = math.ceil(required_n / daily_n)
{
: required_n,
: daily_n,
: days_needed,
: p1,
: (p2, ),
: (alpha / (n_variants - ), ),
}
( ) -> :
lines = [
,
,
,
,
,
,
]
i, h (hypotheses, ):
lines.append( )
lines += [
,
,
,
primary_outcome,
,
,
,
sample_size_justification,
,
,
,
randomization_method,
,
,
,
blinding,
,
,
,
analysis_plan,
,
,
,
]
content = .join(lines)
(output_path, , encoding= ) fh:
fh.write(content)
( )
content
effect_size: Optional [float ] = None ,
alpha: float = 0.05 ,
power: float = 0.80 ,
test_type: str = "two-sample-t" ,
ratio: float = 1.0 ,
k_groups: int = 2 ,
p1: Optional [float ] = None ,
p2: Optional [float ] = None ,
two_tailed: bool = True ,
verbose: bool = True ,
Dict
str
float
"""
Calculate required sample size for common experimental designs.
Parameters
----------
effect_size : float, optional
Standardised effect size (Cohen's d, f, h, w).
Required unless p1 and p2 are provided.
alpha : float
Type I error rate.
power : float
Desired statistical power (1 - beta).
test_type : str
One of: 'two-sample-t', 'paired-t', 'one-sample-t',
'anova', 'proportion-z', 'proportion-chi2'
ratio : float
n_group2 / n_group1 (for unequal allocation).
k_groups : int
Number of groups (for ANOVA only).
p1, p2 : float, optional
Event rates for proportion tests (auto-computes Cohen's h).
two_tailed : bool
Whether to use two-tailed test.
Returns
-------
dict with n_per_group, total_n, effect_size, alpha, power, test_type
"""
"two-sided"
if
else
"larger"
if
is
not
None
and
is
not
None
abs
"proportion-z"
if
is
None
raise
"Provide effect_size or both p1 and p2."
if
"two-sample-t"
elif
"paired-t"
1.0
elif
"one-sample-t"
1.0
elif
"anova"
elif
in
"proportion-z"
"proportion-chi2"
else
raise
f"Unknown test_type: {test_type} "
1
if
not
in
"paired-t"
"one-sample-t"
else
"n_per_group"
"total_n"
"effect_size"
round
4
"alpha"
"power"
"test_type"
"ratio"
if
print
f"Sample size calculation ({test_type} )"
print
f" Effect size : {effect_size:.3 f} "
print
f" Alpha : {alpha} "
print
f" Power : {power} "
print
f" N per group : {n_ceil} "
print
f" Total N : {total_n} "
if
is
not
None
print
f" Rates : p1={p1} , p2={p2} "
print
return
def
mdes
n_per_group: int ,
alpha: float = 0.05 ,
power: float = 0.80 ,
test_type: str = "two-sample-t" ,
float
"""
Minimum Detectable Effect Size given a fixed sample size.
"""
if
"two-sample-t"
else
1.0
"two-sided"
return
round
4
def
inflate_for_attrition
n: int , expected_attrition_rate: float = 0.15
int
"""
Inflate sample size to account for expected dropout / missing data.
Parameters
----------
n : int
Required complete-case sample size.
expected_attrition_rate : float
Fraction of participants expected to drop out (0–1).
"""
if
not
0
1
raise
"attrition_rate must be in [0, 1)."
return
1
def
simple_randomize
n: int ,
n_groups: int = 2 ,
group_labels: Optional [List [str ]] = None ,
seed: Optional [int ] = None ,
"""Simple (unrestricted) randomization."""
if
is
None
f"Group_{i} "
for
in
range
"participant_id"
range
1
1
"assignment"
return
def
block_randomize
n: int ,
block_size: int = 4 ,
n_groups: int = 2 ,
group_labels: Optional [List [str ]] = None ,
seed: Optional [int ] = None ,
"""
Permuted block randomization.
Ensures balance within each block. Block size must be a multiple of n_groups.
Parameters
----------
n : int
Total number of participants to randomize.
block_size : int
Size of each block (must be divisible by n_groups).
n_groups : int
Number of treatment arms.
group_labels : list, optional
seed : int, optional
Returns
-------
DataFrame with participant_id and assignment columns.
"""
if
0
raise
f"block_size ({block_size} ) must be divisible by n_groups ({n_groups} )."
if
is
None
f"Group_{i} "
for
in
range
while
len
"participant_id"
range
1
1
"assignment"
return
def
stratified_randomize
participants: pd.DataFrame,
stratify_cols: List [str ],
block_size: int = 4 ,
n_groups: int = 2 ,
group_labels: Optional [List [str ]] = None ,
seed: Optional [int ] = None ,
"""
Stratified block randomization.
Randomizes participants within strata defined by stratify_cols, using
permuted blocks within each stratum to maintain balance.
Parameters
----------
participants : DataFrame
Must contain an 'id' column and the stratify_cols.
stratify_cols : list of str
Columns to stratify on (e.g. ['site', 'sex', 'age_group']).
block_size : int
n_groups : int
group_labels : list, optional
seed : int, optional
Returns
-------
DataFrame with original columns plus 'assignment'.
"""
if
is
None
f"Group_{i} "
for
in
range
True
"_stratum"
str
"|"
1
for
in
"_stratum"
len
abs
hash
2
31
if
is
None
else
abs
hash
1000
True
"assignment"
"assignment"
"_stratum"
return
def
cluster_randomize
clusters: List [str ],
n_groups: int = 2 ,
group_labels: Optional [List [str ]] = None ,
seed: Optional [int ] = None ,
"""
Cluster randomization — randomize entire clusters to arms.
Parameters
----------
clusters : list of str
Cluster identifiers (e.g. school names, clinic IDs).
n_groups : int
group_labels : list, optional
seed : int, optional
"""
if
is
None
f"Group_{i} "
for
in
range
list
for
in
enumerate
"cluster"
"assignment"
return
def
ab_test_duration
baseline_rate: float ,
minimum_detectable_effect: float ,
daily_traffic: int ,
allocation_fraction: float = 1.0 ,
alpha: float = 0.05 ,
power: float = 0.80 ,
n_variants: int = 2 ,
Dict
str
float
"""
Estimate how many days an A/B test should run.
Parameters
----------
baseline_rate : float
Baseline conversion / success rate (0–1).
minimum_detectable_effect : float
Relative change to detect (e.g. 0.05 = 5% lift).
daily_traffic : int
Total eligible users per day.
allocation_fraction : float
Fraction of traffic included in the test.
alpha : float
power : float
n_variants : int
Number of arms including control.
Returns
-------
dict with required_n, daily_n_in_test, days_needed
"""
1
min
0.9999
1
False
"n_per_group"
int
return
"required_n_total"
"daily_n_in_test"
"days_needed"
"baseline_rate"
"target_rate"
round
4
"alpha_adjusted"
round
1
4
def
generate_preregistration_document
study_title: str ,
hypotheses: List [str ],
primary_outcome: str ,
sample_size_justification: str ,
analysis_plan: str ,
randomization_method: str ,
blinding: str ,
output_path: str = "preregistration.md" ,
str
"""
Generate a structured pre-registration markdown document.
Upload manually to OSF (https://osf.io) or via the OSF Python client.
"""
f"# Pre-registration: {study_title} "
""
f"**Date:** {pd.Timestamp.now().strftime('%Y-%m-%d' )} "
""
"## Hypotheses"
""
for
in
enumerate
1
f"{i} . {h} "
""
"## Primary Outcome"
""
""
"## Sample Size Justification"
""
""
"## Randomization"
""
""
"## Blinding"
""
""
"## Analysis Plan"
""
""
"---"
"_This document was auto-generated and should be reviewed before submission._"
"\n"
with
open
"w"
"utf-8"
as
print
f"Pre-registration document written to: {output_path} "
return
Example 1 — Two-Arm RCT (Clinical Trial) import pandas as pd
from experimental_design import (
calculate_sample_size,
inflate_for_attrition,
block_randomize,
generate_preregistration_document,
)
result = calculate_sample_size(p1=0.40 , p2=0.55 , alpha=0.05 , power=0.80 )
n_inflated = inflate_for_attrition(result["n_per_group" ], expected_attrition_rate=0.15 )
print (f"Enrol {n_inflated} per arm ({n_inflated * 2 } total) to account for 15% attrition." )
allocation = block_randomize(
n=n_inflated * 2 ,
block_size=4 ,
n_groups=2 ,
group_labels=["Control" , "Intervention" ],
seed=2024 ,
)
print (allocation.head(8 ))
print (allocation["assignment" ].value_counts())
generate_preregistration_document(
study_title="Effect of Intervention X on Recovery Rate" ,
hypotheses=[
"Intervention X will increase 30-day recovery rate compared to control." ,
"No difference in serious adverse events between arms." ,
],
primary_outcome="30-day binary recovery status (recovered / not recovered)." ,
sample_size_justification=(
f"N={n_inflated} per arm (inflated from {result['n_per_group' ]} "
f"for 15% attrition). Based on 40% vs 55% recovery rates, "
f"alpha=0.05, power=0.80."
),
analysis_plan=(
"Primary analysis: chi-square test. Secondary: logistic regression "
"adjusting for age and baseline severity."
),
randomization_method="Permuted block randomization, block size 4, centralised allocation." ,
blinding="Outcome assessors blinded; participants and clinicians not blinded." ,
output_path="rct_preregistration.md" ,
)
Example 2 — Multi-Arm Web Experiment with Stratification import pandas as pd
from experimental_design import (
calculate_sample_size,
stratified_randomize,
ab_test_duration,
)
result = calculate_sample_size(
effect_size=0.25 ,
alpha=0.05 ,
power=0.80 ,
test_type="anova" ,
k_groups=3 ,
)
print (result)
rng_sim = pd.np.random.default_rng(99 ) if hasattr (pd, 'np' ) else __import__ ('numpy' ).random.default_rng(99 )
import numpy as np
rng_sim = np.random.default_rng(99 )
n_users = result["total_n" ]
participants = pd.DataFrame({
"id" : range (1 , n_users + 1 ),
"country" : np.random.choice(["US" , "UK" , "DE" ], size=n_users, p=[0.5 , 0.3 , 0.2 ]),
"user_type" : np.random.choice(["free" , "paid" ], size=n_users, p=[0.7 , 0.3 ]),
})
allocation = stratified_randomize(
participants=participants,
stratify_cols=["country" , "user_type" ],
block_size=6 ,
n_groups=3 ,
group_labels=["Control" , "Variant_A" , "Variant_B" ],
seed=2024 ,
)
print (allocation.groupby(["country" , "user_type" , "assignment" ]).size().unstack())
duration = ab_test_duration(
baseline_rate=0.03 ,
minimum_detectable_effect=0.20 ,
daily_traffic=5000 ,
allocation_fraction=0.80 ,
alpha=0.05 ,
power=0.80 ,
n_variants=3 ,
)
print (f"Run for at least {duration['days_needed' ]} days." )
Blinding Protocols Trial Type Who Is Blinded Open-label Nobody (appropriate when blinding is impractical) Single-blind Participants only Double-blind Participants + investigators Triple-blind Participants + investigators + outcome assessors Cluster-blind Outcome assessors blinded to cluster allocation
Pre-registration on OSF
pip install osfclient
export OSF_TOKEN=<paste-your-osf-token>
osf -p <your-project-id> upload rct_preregistration.md osfstorage/prereg/rct_preregistration.md
osf -p <your-project-id> ls
Checklist Before Data Collection