Scenarios Reference
Scenarios in CausalBoundingEngine organize algorithms by the causal setting and data structure. Each scenario defines which algorithms are applicable and provides a unified interface for accessing them.
Note
All scenarios use algorithms from published research. For complete citations and references, see the References and Credits page.
Scenario Architecture
Base Scenario Class
All scenarios inherit from the base Scenario class, which provides:
Data Management: Handles X, Y, and optional Z variables
Algorithm Dispatchers: ATE and PNS dispatchers for dynamic algorithm access
Algorithm Discovery: Methods to find available algorithms
from causalboundingengine.scenario import Scenario
class MyScenario(Scenario):
AVAILABLE_ALGORITHMS = {
'ATE': {
'algorithm_name': AlgorithmClass,
},
'PNS': {
'algorithm_name': AlgorithmClass,
}
}
Algorithm Dispatchers
Each scenario provides ATE and PNS dispatchers that expose algorithms as methods:
scenario = BinaryConf(X, Y)
# These calls are equivalent:
bounds1 = scenario.ATE.manski()
bounds2 = scenario.ATE.__getattr__('manski')()
# Dynamic access
algorithm_name = 'manski'
bounds3 = getattr(scenario.ATE, algorithm_name)()
Available Scenarios
BinaryConf: Binary Confounded
Purpose: Handle binary treatment and outcome data with potential unmeasured confounding.
- Causal Assumptions:
Binary treatment X ∈ {0, 1}
Binary outcome Y ∈ {0, 1}
Unmeasured confounders U may exist
No valid instruments available
Causal Graph:
U (unmeasured)
↓ ↓
X → Y
- Data Requirements:
X: NumPy array of 0s and 1s (treatment)Y: NumPy array of 0s and 1s (outcome)Same length for X and Y
Usage:
from causalboundingengine.scenarios import BinaryConf
import numpy as np
# Example data
X = np.array([0, 1, 1, 0, 1, 0, 1])
Y = np.array([0, 1, 0, 0, 1, 1, 1])
# Create scenario
scenario = BinaryConf(X, Y)
# Access algorithms
print("Available ATE algorithms:", scenario.get_algorithms('ATE'))
print("Available PNS algorithms:", scenario.get_algorithms('PNS'))
Available Algorithms:
Algorithm |
ATE |
PNS |
Notes |
|---|---|---|---|
manski |
✓ |
✗ |
Most conservative bounds |
tianpearl |
✗ |
✓ |
PNS bounds only |
entropybounds |
✓ |
✓ |
Requires theta parameter |
causaloptim |
✓ |
✓ |
Requires R |
zaffalonbounds |
✓ |
✓ |
Requires Java |
autobound |
✓ |
✓ |
General optimization approach |
Example Usage:
# Compute ATE bounds with different algorithms
manski_bounds = scenario.ATE.manski()
entropy_bounds = scenario.ATE.entropybounds(theta=0.5)
print(f"Manski: {manski_bounds}")
print(f"Entropy (θ=0.5): {entropy_bounds}")
# Compute PNS bounds
pns_tianpearl = scenario.PNS.tianpearl()
pns_entropy = scenario.PNS.entropybounds(theta=0.5)
print(f"PNS Tian-Pearl: {pns_tianpearl}")
print(f"PNS Entropy: {pns_entropy}")
- When to Use:
Standard observational studies
When confounding is suspected
No valid instruments available
Most common scenario
BinaryIV: Binary Instrumental Variable
Purpose: Handle binary treatment, outcome, and instrument data using instrumental variable assumptions.
- Causal Assumptions:
Binary instrument Z ∈ {0, 1}
Binary treatment X ∈ {0, 1}
Binary outcome Y ∈ {0, 1}
Standard IV assumptions:
Relevance: Z affects X
Exclusion: Z only affects Y through X
Exogeneity: Z is unconfounded
Causal Graph:
Z → X → Y
↑ ↑
U (unmeasured)
- Data Requirements:
Z: NumPy array of 0s and 1s (instrument)X: NumPy array of 0s and 1s (treatment)Y: NumPy array of 0s and 1s (outcome)All arrays must have the same length
Usage:
from causalboundingengine.scenarios import BinaryIV
import numpy as np
# Example IV data
Z = np.array([0, 1, 1, 0, 1, 0, 1]) # Instrument
X = np.array([0, 1, 1, 0, 1, 0, 0]) # Treatment (influenced by Z)
Y = np.array([0, 1, 0, 0, 1, 1, 0]) # Outcome
# Create IV scenario
scenario = BinaryIV(X, Y, Z)
Available Algorithms:
Algorithm |
ATE |
PNS |
Notes |
|---|---|---|---|
causaloptim |
✓ |
✓ |
Requires R, symbolic approach |
zaffalonbounds |
✓ |
✓ |
Requires Java, credal networks |
autobound |
✓ |
✓ |
Core Python, LP approach |
Example Usage:
# Compute bounds using IV algorithms
autobound_ate = scenario.ATE.autobound()
autobound_pns = scenario.PNS.autobound()
print(f"Autobound ATE: {autobound_ate}")
print(f"Autobound PNS: {autobound_pns}")
# If R is available
try:
causaloptim_ate = scenario.ATE.causaloptim()
print(f"Causaloptim ATE: {causaloptim_ate}")
except ImportError:
print("R support not available")
- When to Use:
Randomized controlled trials with non-compliance
Natural experiments with valid instruments
When IV assumptions can be justified
Need to account for endogeneity
IV Validation:
Before using BinaryIV, validate your instrument:
import numpy as np
from scipy.stats import chi2_contingency
def validate_instrument(Z, X, Y):
\"\"\"Basic IV validation checks.\"\"\"
# Relevance: Z should be associated with X
contingency_zx = np.array([[np.sum((Z==0) & (X==0)), np.sum((Z==0) & (X==1))],
[np.sum((Z==1) & (X==0)), np.sum((Z==1) & (X==1))]])
chi2_zx, p_zx = chi2_contingency(contingency_zx)[:2]
print(f"Relevance test (Z-X association): χ² = {chi2_zx:.3f}, p = {p_zx:.3f}")
# Exclusion is untestable, but we can check Z-Y association conditional on X
# This shouldn't be strong if exclusion holds
for x in [0, 1]:
mask = X == x
if np.sum(mask) > 10: # Enough observations
z_sub = Z[mask]
y_sub = Y[mask]
corr = np.corrcoef(z_sub, y_sub)[0, 1]
print(f"Z-Y correlation given X={x}: {corr:.3f}")
validate_instrument(Z, X, Y)
ContIV: Binary IV with Continuous Outcome
Purpose: Handle binary instrument and treatment with continuous outcome using instrumental variable assumptions.
- Causal Assumptions:
Binary instrument Z ∈ {0, 1}
Binary treatment X ∈ {0, 1}
Continuous outcome Y ∈ [0, 1] (bounded between 0 and 1)
Standard IV assumptions hold
- Data Requirements:
Z: NumPy array of 0s and 1s (binary instrument)X: NumPy array of 0s and 1s (binary treatment)Y: NumPy array of continuous values between 0 and 1 (outcome)All arrays must have the same length
Usage:
from causalboundingengine.scenarios import ContIV
import numpy as np
# Example IV data with continuous outcome
Z = np.array([0, 1, 1, 0, 1]) # Binary instrument
X = np.array([0, 1, 1, 0, 1]) # Binary treatment
Y = np.array([0.1, 0.8, 0.2, 0.9, 0.3]) # Continuous outcome (0-1)
# Create scenario
scenario = ContIV(X, Y, Z)
Available Algorithms:
Algorithm |
ATE |
PNS |
Notes |
|---|---|---|---|
zhangbareinboim |
✓ |
✗ |
Handles compliance types |
Example Usage:
# Compute ATE bounds for binary IV with continuous outcome
ate_bounds = scenario.ATE.zhangbareinboim()
print(f"Zhang-Bareinboim ATE bounds: {ate_bounds}")
- When to Use:
Binary instrumental variables with continuous outcomes
RCTs with binary treatment and continuous response measures
Economic applications with binary policy instruments
When outcome is naturally continuous but bounded
- Important Notes:
Z and X should be binary (0s and 1s only)
Y should be continuous values between 0 and 1
Algorithm may still run with non-binary Z/X but this is not the intended use
For fully continuous variables, consider discretization first
Data Preprocessing:
Ensure your outcome is properly bounded:
def prepare_continuous_outcome(Y, method='min_max'):
\"\"\"Prepare continuous outcome for ContIV.\"\"\"
if method == 'min_max':
# Min-max normalization to [0, 1]
Y_norm = (Y - Y.min()) / (Y.max() - Y.min())
elif method == 'sigmoid':
# Sigmoid transformation
Y_norm = 1 / (1 + np.exp(-Y))
else:
raise ValueError("method must be 'min_max' or 'sigmoid'")
return Y_norm
# Example usage
Y_raw = np.array([2.1, 5.8, 1.2, 7.9, 3.3]) # Raw continuous outcome
Y_bounded = prepare_continuous_outcome(Y_raw, method='min_max')
# Use with ContIV
scenario = ContIV(X, Y_bounded, Z)
Scenario Selection Guide
Decision Tree
What type of variables do you have?
All binary → Continue to step 2
Some continuous → Consider ContIV or discretize first
Do you have a valid instrument?
Yes, binary instrument, binary outcome → Use BinaryIV
Yes, binary instrument, continuous outcome → Use ContIV
No instrument → Use BinaryConf
Can you justify IV assumptions?
Relevance: Instrument affects treatment
Exclusion: Instrument only affects outcome through treatment
Exogeneity: Instrument is unconfounded
If unsure, use BinaryConf for robustness
Scenario Comparison
Aspect |
BinaryConf |
BinaryIV |
ContIV |
Notes |
|---|---|---|---|---|
Data Type |
Binary X, Y |
Binary Z, X, Y |
Binary Z, X; Continuous Y [0,1] |
ContIV for bounded outcomes |
Assumptions |
Minimal |
IV assumptions |
IV assumptions |
BinaryConf most robust |
Algorithms |
6 options |
3 options |
1 option |
More options = more flexibility |
Use Cases |
Observational |
RCTs, Natural exp. |
Economic studies |
Match study design |
Bounds |
Often wider |
Can be tighter |
Varies |
IV leverages more info |
Custom Scenarios
Creating New Scenarios
You can create custom scenarios for specialized use cases:
from causalboundingengine.scenario import Scenario
from causalboundingengine.algorithms.manski import Manski
from causalboundingengine.algorithms.tianpearl import TianPearl
class CustomScenario(Scenario):
\"\"\"Custom scenario with specific algorithm subset.\"\"\"
AVAILABLE_ALGORITHMS = {
'ATE': {
'manski': Manski,
},
'PNS': {
'tianpearl': TianPearl,
}
}
# Use custom scenario
scenario = CustomScenario(X, Y, additional_data=some_data)
bounds = scenario.ATE.manski()
Extending Existing Scenarios
Add algorithms to existing scenarios:
from causalboundingengine.scenarios import BinaryConf
from causalboundingengine.algorithms.my_algorithm import MyAlgorithm
# Extend BinaryConf
class ExtendedBinaryConf(BinaryConf):
AVAILABLE_ALGORITHMS = {
**BinaryConf.AVAILABLE_ALGORITHMS,
'ATE': {
**BinaryConf.AVAILABLE_ALGORITHMS['ATE'],
'my_algorithm': MyAlgorithm,
}
}
scenario = ExtendedBinaryConf(X, Y)
bounds = scenario.ATE.my_algorithm()