Metadata-Version: 2.4
Name: spiceaisle
Version: 0.1.0
Summary: Package for SPICE-Net, a neural network based estimation framework to estimate causal effects using proxy variables.
Author: Silvan Vollmer
License-Expression: MIT
Project-URL: Homepage, https://silvanvollmer.github.io
Project-URL: Paper, https://arxiv.org/abs/2604.09135
Keywords: unobserved confounding,single proxy,identifiability,causal effects,measurement error
Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: OS Independent
Requires-Python: >=3.14
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: torch>=2.11.0
Requires-Dist: numpy>=2.4.4
Requires-Dist: scikit-learn>=1.8.0
Dynamic: license-file

# spiceaisle: A Python Package for SPICE-Net to estimate causal effects under unobserved confounding using proxies

This is a PyTorch implementation of SPICE-Net based on the paper https://arxiv.org/abs/2604.09135, to estimate the causal effect
of a treatment X on an outcome Y under unobserved confounding U by the use of proxy variables W.

SPICE-Net is a two-step procedure. First, `spicenet` learns the conditional density of U given X and Y up to a linear transformation.
Second, we can use samples from this conditional density to estimate the causal effect of X on Y, with any standard 
causal effect estimator for observed confounding, for example with the implemented `regression_adjustment`.

If you use SPICE-Net in your research, please consider citing:
```bibtex
@article{vollmer2026identifying,
  title={Identifying Causal Effects Using a Single Proxy Variable},
  author={Vollmer, Silvan and Pfister, Niklas and Weichwald, Sebastian},
  journal={arXiv preprint arXiv:2604.09135},
  year={2026}
}
```

## Installation and Usage

```
pip install spiceaisle
```

```python
# Example for causal effect estimation using SPICE-Net
import torch
from spiceaisle import spicenet, regression_adjustment
import numpy as np
from sklearn.neural_network import MLPRegressor

# Example training data
n = 2000
u_data = torch.randn(n, 1)  # unobserved confounder
w_data = u_data + torch.randn(n, 1)  # proxy variable
x_data = u_data + torch.randn(n, 1)  # treatment
y_data = u_data + x_data + torch.randn(n, 1)  # outcome

# Example test data
n_test = 500
u_data_test = torch.randn(n_test, 1)  # unobserved confounder
w_data_test = u_data_test + torch.randn(n_test, 1)  # proxy variable
x_data_test = u_data_test + torch.randn(n_test, 1)  # treatment
y_data_test = u_data_test + x_data_test + torch.randn(n_test, 1)  # outcome

# 1. First step of SPICE-Net: Learn the conditional density (using Engression https://github.com/xwshen51/engression/tree/main)
model = spicenet(
    layer_dims=[5, 10, 20, 10, 5],  # (list): List of integers specifying the dimensions of each layer.
    x_data=x_data,  # (torch.Tensor): Input tensor for treatment (X).
    y_data=y_data,  # (torch.Tensor): Input tensor for outcome (Y).
    w_data=w_data,  # (torch.Tensor): Input tensor for proxy (W).
    noise_nodes_dim=5,
    # (int, optional): Dimension of the Gaussian noise nodes to be added at each layer. Defaults to 5.
    noise_nodes_std=1,  # (float, optional): Standard deviation of the Gaussian noise nodes. Defaults to 1.
    distribution_e="Normal",  # (str): Type of distribution for the error term.
    params_e={
        # (dict): Parameters for the error term distribution (set to "learn" if you want to use SPICE-Net-Approx).
        "loc": [0],
        "scale": [1]
    },
    params_e_initialize={
        # (dict): Parameters for the initialization of the error term distribution (only used for SPICE-Net-Approx).
        "loc": [0],
        "scale": [1]
    },
    lr=None,
    # (float, optional): (Initial) learning rate for the optimizer. Defaults to Default of https://docs.pytorch.org/docs/stable/generated/torch.optim.Adam.html
    adaptive_lr=True,  # (bool, optional): Whether to use adaptive learning rate. Defaults to True.
    patience_lr=500,  # (int, optional): Patience for adaptive learning rate scheduler. Defaults to 1000.
    beta=1,  # (float, optional): Power parameter in the energy score loss. Defaults to 1.
    num_epochs=4000,  # (int, optional): Number of epochs for training. Defaults to 2000.
    verbose=True,  # (bool, optional): Whether to print training progress. Defaults to False.
    confounder_dim=None,
    # (int): Dimension of the unobserved confounder variable. Defaults to None, which sets it equal to dimension of W.
    batch_size=200  # (int, optional): Batch size for training. Defaults to 200.
)

# train the model
model.train()

# predict for test data
u_samples_spice = model.predict_new_data(x_data, y_data)
u_samples_spice_np = u_samples_spice.detach().cpu().numpy()

# 2. Second step of SPICE-Net: Estimate the causal function 

spice_preds = regression_adjustment(
    x_data=x_data,
    y_data=y_data,
    adj_samples=u_samples_spice_np,
    x_data_test=x_data_test,
)

# Adjustment using the true unobserved confounder 
adj_u_preds = regression_adjustment(
    x_data=x_data,
    y_data=y_data,
    adj_samples=u_data,
    x_data_test=x_data_test,
)

# Adjustment using the proxy variable
adj_w_preds = regression_adjustment(
    x_data=x_data,
    y_data=y_data,
    adj_samples=w_data,
    x_data_test=x_data_test,
)

# 3. Results (MSE)
x_test_np = np.ravel(x_data_test).reshape(-1, 1)

mse_spice = np.mean((spice_preds - x_test_np) ** 2)
mse_adj_u = np.mean((adj_u_preds - x_test_np) ** 2)
mse_adj_w = np.mean((adj_w_preds - x_test_np) ** 2)

print("SPICE-Net adjustment MSE:", mse_spice)
print("True-confounder adjustment MSE:", mse_adj_u)
print("Proxy adjustment MSE:", mse_adj_w)

```

## Requirements

- Python >= 3.14
- PyTorch >= 2.11.0
- NumPy >= 2.4.4
- scikit-learn >= 1.8.0
