# rTisanePy

Authoring generalized linear mixed effects models from conceptual models. Based on [rTisane](https://github.com/emjun/rTisane).

rTisanePy lets you:

- Declare variables and specify causal and relational assumptions
- Query for a statistical model from a conceptual model
- Automatically identify confounders (following [Cinelli, Forney & Pearl, 2022](https://doi.org/10.1177/00491241221099552))
- Infer random effects from nesting and repeated measures
- Select candidate family/link functions based on DV type

## Installation

```bash
pip install rtisanepy
```

For development:

```bash
pip install -e ".[dev]"
```

Requires Python 3.10+ and `networkx`.

## Quick Start

```python
from rtisanepy import (
    Unit, continuous, categories,
    causes, equals, increases,
    ConceptualModel,
)

# Declare variables
student = Unit("student", cardinality=100)
classroom = Unit("classroom", cardinality=10)
student = Unit("student", cardinality=100, nests_within=classroom)

tutoring = categories(unit=student, name="Tutoring", cardinality=2)
ses = categories(unit=student, name="SES", order=["lower", "middle", "upper"])
test_score = continuous(unit=student, name="TestScore")

# Build a conceptual model
cm = (
    ConceptualModel()
    .assume(causes(ses, test_score))
    .assume(causes(ses, tutoring))
    .hypothesize(causes(tutoring, test_score,
                        when=equals(tutoring, "in-person"),
                        then=increases(test_score)))
)

# Query for a statistical model
model = cm.query(iv=tutoring, dv=test_score)

print(model.formula())       # TestScore ~ Tutoring + SES + (1 | classroom)
print(model.family)           # Inverse Gaussian
print(model.main_effects)     # ['Tutoring', 'SES']
print(model.random_effects)   # [RandomIntercept(group=classroom)]

# Override family/link
model2 = model.with_family("Gaussian", link="identity")
```

## API Overview

### Variables

| Constructor | Description |
|---|---|
| `Unit(name, cardinality, nests_within=None)` | Entity (participant, subject) |
| `Participant(name, cardinality, nests_within=None)` | Alias for Unit |
| `Time(name, order=None, cardinality=0)` | Time variable |
| `continuous(unit, name, number_of_instances=1)` | Continuous measure |
| `counts(unit, name, number_of_instances=1)` | Count measure |
| `categories(unit, name, *, cardinality=None, order=None)` | Categorical measure (ordered if `order` given) |

### Relationships

| Function | Description |
|---|---|
| `causes(cause, effect, *, when=None, then=None)` | Directed causal relationship |
| `relates(lhs, rhs, *, when=None, then=None)` | Undirected (ambiguous) relationship |
| `nests(base, group)` | Nesting relationship between units |

### Comparisons (for `when`/`then` annotations)

| Function | Description |
|---|---|
| `equals(variable, value)` | Variable equals a value |
| `not_equals(variable, value)` | Variable does not equal a value |
| `increases(variable)` | Variable increases |
| `decreases(variable)` | Variable decreases |

### ConceptualModel

```python
cm = ConceptualModel()
cm.assume(relationship)        # Add assumed relationship (returns self)
cm.hypothesize(relationship)   # Add hypothesized relationship (returns self)
cm.interacts(*vars, dv=dv)     # Add interaction annotation (returns self)
cm.query(iv=iv, dv=dv)         # Infer a StatisticalModel
```

At least one hypothesized relationship connecting `iv` and `dv` is required to query.

### StatisticalModel

| Attribute / Method | Description |
|---|---|
| `.formula()` | R-style formula string |
| `.main_effects` | List of main effect variable names |
| `.interaction_effects` | List of interaction terms |
| `.random_effects` | List of `RandomIntercept` / `RandomSlope` |
| `.family` | Selected family function |
| `.link` | Selected link function |
| `.family_candidates` | All candidate family/link pairs |
| `.summary()` | Machine-readable dict |
| `.with_family(family, link=None)` | Copy with overridden family/link |

## Running Tests

```bash
python -m pytest tests/ -v
```

## Citation

If you use rTisanePy in your research, please cite:

```bibtex
@software{rtisanepy,
  author = {Eunice Jun},
  title = {rTisanePy: A Python Tool for Authoring Statistical Models from Conceptual Models},
  url = {https://github.com/emjun/rTisanePy},
  year = {2026}
}
```

## References

- Eunice Jun, Audrey Seo, Jeffrey Heer, and René Just. 2024. [Tisane: Authoring Statistical Models via Formal Reasoning from Conceptual and Data Relationships.](https://doi.org/10.1145/3613904.3642347) *CHI 2024*.
- Carlos Cinelli, Andrew Forney, and Judea Pearl. 2022. [A Crash Course in Good and Bad Controls.](https://doi.org/10.1177/00491241221099552) *Sociological Methods & Research*.
