# simulate_competing_risks()


Generate synthetic competing-risks data from a latent-Weibull model.


Usage

``` python
simulate_competing_risks(
    n=500,
    n_causes=2,
    *,
    shapes=None,
    scales=None,
    censoring_scale=1.5,
    n_covariates=0,
    coef=None,
    states=None,
    seed=None,
)
```


Each cause has its own Weibull distribution with specified shape and scale parameters. For each subject, a latent event time is drawn independently for every cause, and the cause with the shortest latent time is the one observed. An independent censoring time is drawn from a Weibull(1, scale) distribution (i.e., exponential). If the censoring time is shortest, the subject is censored.

When covariates are requested, cause-specific proportional hazards are applied: the Weibull scale for cause k is adjusted as b_k \cdot \exp(-\mathbf{x}^\top \boldsymbol{\beta}\_k / a_k), where a_k and b_k are the shape and scale for cause k. Covariates are drawn from a standard normal distribution.


## Parameters


`n: int = ``500`  
Number of subjects (default 500).

`n_causes: int = ``2`  
Number of competing event types (default 2).

`shapes: tuple[float, …] | None = None`  
Weibull shape parameter for each cause. Length must equal `n_causes`. Defaults to `(1.0, 1.5, ...)` cycling through `[1.0, 1.5, 0.8]`. A shape of 1.0 gives an exponential (constant hazard). Values above 1 give increasing hazard. Values below 1 give decreasing hazard.

`scales: tuple[float, …] | None = None`  
Weibull scale parameter for each cause. Length must equal `n_causes`. Defaults to 10.0 for every cause.

`censoring_scale: float | None = ``1.5`  
Controls censoring intensity. The censoring time is drawn from an exponential distribution with scale equal to `censoring_scale` times the mean latent event time. Larger values produce less censoring. Set to `None` or `0` to disable censoring entirely. Default is 1.5, which gives roughly 20-30% censoring depending on the shape/scale configuration.

`n_covariates: int = ``0`  
Number of covariates to generate (default 0). When positive, standard-normal covariates are drawn and cause-specific proportional-hazards effects are applied.

`coef: Any | None = None`  
Coefficient matrix of shape `(n_covariates, n_causes)` for the proportional-hazards effect on each cause. When `None` (default), coefficients are drawn from Uniform(-0.5, 0.5).Ignored when `n_covariates=0`.

`states: tuple[str, …] | None = None`  
Names for each cause (e.g., `("relapse", "death")`). Length must equal `n_causes`. Defaults to `("cause1", "cause2", ...)`.

`seed: int | None = None`  
Random seed for reproducibility.


## Returns


`SimulatedCompetingRisks`  
A dataclass with [surv](Outcome.md#greenwood.Outcome.surv) (the multi-state [Surv](Surv.md#greenwood.Surv) response), `covariates` (the covariate matrix or `None`), `latent_times` (per-cause latent times), and `censoring_time`.


## Examples

Generate a simple two-cause competing-risks dataset and fit an Aalen-Johansen estimator:


``` python
import greenwood as gw

sim = gw.simulate_competing_risks(n=300, n_causes=2, seed=42)
sim.surv
```


    Surv(type=mright, n=300, events=171, states=('cause1', 'cause2'))


``` python
aj = gw.AalenJohansen().fit(sim.surv)
aj
```


    AalenJohansen (Aalen-Johansen cumulative incidence)

    states: cause1, cause2
    n = 300

            final CIF
    cause1     0.5327
    cause2     0.4673


Generate data with covariates for a Fine-Gray model:


``` python
sim = gw.simulate_competing_risks(
    n=500,
    n_causes=2,
    n_covariates=3,
    states=("relapse", "death"),
    seed=99,
)
fg = gw.FineGray(cause="relapse").fit(sim.surv, covariates=sim.covariates)
gw.tidy(fg, format="polars")
```


shape: (3, 7)

| term | estimate  | std_error | statistic | p_value  | conf_low  | conf_high |
|------|-----------|-----------|-----------|----------|-----------|-----------|
| str  | f64       | f64       | f64       | f64      | f64       | f64       |
| "x0" | -0.182905 | 0.073823  | -2.477608 | 0.013227 | -0.327596 | -0.038214 |
| "x1" | 0.339535  | 0.079627  | 4.264052  | 0.00002  | 0.183468  | 0.495601  |
| "x2" | 0.020141  | 0.082495  | 0.244152  | 0.807113 | -0.141546 | 0.181829  |


Control the event-time distribution and censoring rate:


``` python
import numpy as np

# Cause 1: increasing hazard (shape > 1), Cause 2: constant hazard
sim = gw.simulate_competing_risks(
    n=1000,
    n_causes=2,
    shapes=(2.0, 1.0),
    scales=(15.0, 10.0),
    censoring_scale=0.8,  # heavier censoring
    seed=7,
)
# Check the censoring fraction
frac_censored = (sim.surv.status == 0).mean()
print(f"Censored: {frac_censored:.1%}")
```


    Censored: 60.1%
