analyzers
analyzers
Common analyzers for STI analyses
Classes
| Name | Description |
|---|---|
| DebutAge | Analyze the proportion of agents who have sexually debuted by age. |
| NetworkDegree | Analyze lifetime partner count distributions in a StructuredSexual network. |
| PartnershipFormationAnalyzer | Track partnership formation per network, gender, and age bin. |
| RelationshipDurations | Analyze relationship durations in a StructuredSexual network. |
| TimeBetweenRelationships | Analyzes the time between relationships in a structuredsexual network. |
| art_coverage | Track ART coverage (number and proportion) by sex and age bin. |
| coinfection_stats | Generates stats for the coinfection of two diseases. |
| partner_age_diff | Analyze age differences between sexual partners. |
| result_grouper | Base analyzer providing conditional probability utilities for grouped results. |
| sw_stats | Track new infections and transmissions among sex workers and their clients. |
DebutAge
analyzers.DebutAge(bins=None, cohort_starts=None, *args, **kwargs)Analyze the proportion of agents who have sexually debuted by age.
Tracks the share of agents who are sexually active (past their debut age) at each single-year age bin, disaggregated by sex and birth cohort. Useful for validating debut age distributions against survey data such as DHS.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| bins | array | Age bins to evaluate, e.g. np.arange(12, 31). Defaults to ages 12-30. |
None |
| cohort_starts | array | Birth-year cohort start years. Defaults to all cohorts that fit within the simulation timespan. | None |
Methods
| Name | Description |
|---|---|
| plot | Plot the proportion of active agents by cohort and debut age |
plot
analyzers.DebutAge.plot()Plot the proportion of active agents by cohort and debut age
NetworkDegree
analyzers.NetworkDegree(
year=None,
bins=None,
relationship_types=None,
*args,
**kwargs,
)Analyze lifetime partner count distributions in a StructuredSexual network.
At a specified year, records the number of lifetime partners per agent, disaggregated by sex and relationship type (stable, casual, one-time, sex work). Results are binned into a histogram for plotting and comparison with survey data.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| year | float | Calendar year at which to record partner counts. Defaults to the last year of the simulation. | None |
| bins | array | Bin edges for the partner count histogram. Defaults to [0, 1, ..., 20, 100]. |
None |
| relationship_types | list | Relationship types to track, e.g. ['stable', 'casual']. Use 'partners' for combined stable + casual counts. |
None |
Methods
| Name | Description |
|---|---|
| init_pre | Initialize the analyzer |
| init_results | Add results for n_rships, separated for males and females |
| plot | Plot histograms and stats by sex and relationship type |
| step | record lifetime_partners for the user-specified year |
init_pre
analyzers.NetworkDegree.init_pre(sim, **kwargs)Initialize the analyzer
init_results
analyzers.NetworkDegree.init_results()Add results for n_rships, separated for males and females Optionally disaggregate for risk level / age?
plot
analyzers.NetworkDegree.plot()Plot histograms and stats by sex and relationship type
step
analyzers.NetworkDegree.step()record lifetime_partners for the user-specified year
PartnershipFormationAnalyzer
analyzers.PartnershipFormationAnalyzer(
age_bins=None,
networks=None,
*args,
**kwargs,
)Track partnership formation per network, gender, and age bin.
Works with any analyzer that subclasses stisim BaseNetwork (that does not have class attribute records_all_expirations set to False on purpose. This attribute (when False) explicitly tells PartnershipFormationAnalyzer the network is not configured to work with it (yet)).
Stores one row per distinct edge with its active interval, rather than re-snapshotting the active edge set every timestep. Formation is detected at the step where ti_formed == ti; the end of the interval comes from the network’s expired_this_loop (populated by _on_edge_dissolution when record_expired=True), so step() only touches the per-step flows (newly-formed and newly-expired edges), never the active stock. Each partner’s sex and age-at-formation are read from the edge/people directly, so the analyzer makes no p1=male/p2=female assumption and works for any gender combination (male-female, male-male).
Recorded state lives in self.results['relationships'][nw]: a list of one mutable row per distinct edge::
[p1, p2, ti_formed, ti_expired, age_p1, age_p2, female_p1, female_p2]
ti_expired is the timestep at which the edge was observed in expired_this_loop (so its active interval is the half-open [ti_formed, ti_expired)), or None while the edge is still active at the end of the run. Getters map None -> final_ti + 1 so an ongoing edge tests active through the last timestep.
.. warning:: Single-edge-per-pair-per-timestep assumption. Edges are keyed by (p1, p2, ti_formed) (see _indicies_of_rels_in_table_for_nw). If a network ever forms more than one edge between the same pair of agents at the same ti, those edges collide on this key and expiry data is corrupted: only the last-formed colliding edge remains reachable for ti_expired recording, so the others keep ti_expired = None (wrongly treated as active through run end) and their expiry events are dropped. Formation counts are unaffected (every edge is still appended as its own row), so only the active-interval results (get_unique_partners_active_per_agent and any duration computed from ti_expired) are affected. The bundled networks do not do this; the assumption holds as long as a pair forms at most one edge per network per timestep (the no-concurrent-duplicate-edge invariant noted in BaseNetwork.add_pairs). Forming a second edge between the same pair at a different ti is fine (distinct key).
Duration semantics. ti_expired is defined as one past the last timestep the edge was present and transmission-capable during the disease transmission step (step 9 of the 16-step integration loop). Any duration reported or computed from this analyzer is therefore::
duration = ti_expired - ti_formed
= number of active transmission steps the relationship existed
where an “active transmission step” means the edge was present in the network with both partners alive at step 9 (i.e. structurally available to transmit) – not that a transmission event actually fired (that further depends on beta, acts, condom use, and infection status). This count is consistent regardless of when a relationship formed, when it dissolved, or why it dissolved:
- duration expiry at R: active
{F..R-1}->duration = R - F; - partner death/removal at T: the dying agent’s
aliveflag flips at step 10 (after step 9), so the edge is still active at T -> active{F..T},ti_expired = T+1,duration = (T+1) - F; - formed and ended-by-death in the same timestep T: active
{T}->duration = 1(never a zero/empty interval); - still active at run end: active
{F..final_ti}->duration = (final_ti+1) - F(right-censored at the run boundary).
Note this realized duration can be shorter than the network’s drawn dur parameter (the intended duration) when a partner dies or leaves early; the analyzer records the realized count, not the drawn one.
Example (self.results layout, network ‘mfnetwork’)::
ana.results['relationships']['mfnetwork'][0]
# -> [9, 17, 0, 4, 27.23, 19.62, False, True]
# p1=9, p2=17, ti_formed=0, ti_expired=4 (active ti 0..3),
# ages at formation, female flags. ti_expired is None if still active
# at run end.
Reporting is via getters (all return {network_name: result}; female selects the subject sex):
get_n_partnerships_formed(female, age_bins=None, window_months=None)–{nw: {age_bin: array}}. Withwindow_months=Nonethe inner array is the full per-timestep series (lengthn_ti); with an int it is a single trailing-window sum (length 1).get_n_partnerships_formed_per_agent(female, window_months=None)–{nw: array}of non-unique formation counts per agent over the window.get_unique_partners_active_per_agent(female, window_months=None)–{nw: array}of unique partners per agent on edges active during the window (half-open interval test).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| age_bins | list[str] | Age bin strings parsable by ss.parse_age_range (e.g. ['0-5', '5-10', ..., '70-75']). Bins may overlap; a partner is counted in every bin containing its age. Defaults to five-year bins from '0-5' through '70-75'. |
None |
| networks | list[str] | None | Network names to track. None (default) tracks every stisim BaseNetwork-derived sexual network in the sim. Named missing networks raise ValueError; named networks that are not BaseNetwork-derived warn and are skipped. A network that cannot record all edge expirations (records_all_expirations is False, e.g. MSMScaleFreeNetwork) is skipped with a warning, since its active-interval results would be incomplete. The analyzer requires BaseNetwork because it reads BaseNetwork-only edge meta (ti_formed, age_p1, age_p2) and the expired_this_loop buffer, and sets record_expired = True on each tracked network. |
None |
Methods
| Name | Description |
|---|---|
| finalize | Resolve still-buffered relationships that expired at the end of the simulation. |
| get_n_partnerships_formed | Total count of partnerships formed (non-unique pairings), per network and age bin. |
| get_n_partnerships_formed_per_agent | Partnerships formed (non-unique) per agent over a trailing window, for agents alive at simulation end. |
| get_unique_partners_active_per_agent | Unique partner counts per agent over a trailing window, for agents alive at simulation end. |
finalize
analyzers.PartnershipFormationAnalyzer.finalize()Resolve still-buffered relationships that expired at the end of the simulation.
After the loop, the only edges left in a network’s self.expired_this_loop are those removed by death at the final step 15 — i.e. after the last step() read. They were active through the disease phase of the final ti (step 9), so they get ti_expired = final_ti + 1. Relationships/edges still active at sim end were never buffered for removal recording, so they keep ti_expired = None (the right-censored marker; getter methods treat None as still-active relationships at simulation end).
get_n_partnerships_formed
analyzers.PartnershipFormationAnalyzer.get_n_partnerships_formed(
female=False,
age_bins=None,
window_months=None,
)Total count of partnerships formed (non-unique pairings), per network and age bin.
When window_months is not used (None), one timeseries of partnership formation counts will be returned per age bin. When window_months is an int, N, it will return single-element lists of partnership formation counts during the final N months of the simulation, one single-element list per age bin.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| female | bool | Subject sex (True=female, False=male). | False |
| age_bins | list[str] | None | Bins to report; None (default) uses the analyzer’s construction bins. A partner is counted in every bin containing its age at formation (overlapping bins each count it); a partner whose age is outside every bin is not counted. |
None |
| window_months | int | None | If None (default), the inner array is the full per-timestep series (length n_ti, indexed by ti). If an int, the inner array is a single trailing-window sum (length 1). |
None |
Returns
| Name | Type | Description |
|---|---|---|
dict {network_name: {age_bin: ndarray}}. The inner array is |
||
length n_ti when window_months is None, else length 1. |
Example (per timestep, whole run)::
ana.get_n_partnerships_formed(female=False, age_bins=['15-50', '40-50'])
# -> {'mfnetwork': {'15-50': array([0, 2, 0, 0, 4]), # by timestep
# '40-50': array([0, 0, 1, 0, 0])}}
Example (single trailing-window sum)::
ana.get_n_partnerships_formed(female=False, age_bins=['15-50', '40-50'],
window_months=12)
# -> {'mfnetwork': {'15-50': array([6]), # sum over last 12 steps
# '40-50': array([1])}}
get_n_partnerships_formed_per_agent
analyzers.PartnershipFormationAnalyzer.get_n_partnerships_formed_per_agent(
female=False,
window_months=None,
)Partnerships formed (non-unique) per agent over a trailing window, for agents alive at simulation end.
Equivalent to asking living agents, “How many partnerships have you formed over the last X months?”
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| female | bool | Subject sex. | False |
| window_months | int | None | Trailing ti window length for partnership counting, None for whole simulation. | None |
Returns
| Name | Type | Description |
|---|---|---|
dict {network_name: array} indexed like the subject-sex |
||
surviving-agent uids (ana._final_uids['f'\|'m']). |
Example (whole run)::
ana.get_n_partnerships_formed_per_agent(female=False)
# -> {'mfnetwork': array([0, 0, 1, 0, 4, ...])}
# aligned to ana._final_uids['m']; e.g. that male formed 4
# partnerships over the run (non-unique: a re-formed pair counts twice).
get_unique_partners_active_per_agent
analyzers.PartnershipFormationAnalyzer.get_unique_partners_active_per_agent(
female=False,
window_months=None,
)Unique partner counts per agent over a trailing window, for agents alive at simulation end.
Equivalent to asking living agents, “How many unique partners have you had over the last X months?”
An edge counts as active in the trailing window iff it had not expired before the window opened, i.e. ti_expired > win_start (its half-open active interval [ti_formed, ti_expired) overlaps the window). The window always ends at the final timestep, so no upper bound on ti_formed is needed; an ongoing edge carries ti_expired = final_ti + 1 and therefore always counts. For window_months=None the window is the whole run (win_start = 0). Sex-general: the partner is the other endpoint regardless of its sex, so this works for same-sex networks too. Dedups by partner uid (a network is assumed not to hold concurrent duplicate edges between the same pair, so unique partners == unique active partnerships).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| female | bool | Subject sex. | False |
| window_months | int | None | Trailing ti window length for partner counting, None for whole simulation. | None |
Returns
| Name | Type | Description |
|---|---|---|
dict {network_name: array} indexed like the subject-sex |
||
| surviving-agent uids. |
Example (trailing 12 timesteps)::
ana.get_unique_partners_active_per_agent(female=False, window_months=12)
# -> {'mfnetwork': array([1, 0, 2, ...])}
# aligned to ana._final_uids['m']; e.g. the male at index 2 had 2
# distinct partners across edges active during the last 12 steps.
RelationshipDurations
analyzers.RelationshipDurations(*args, **kwargs)Analyze relationship durations in a StructuredSexual network.
Records the mean and median duration of all relationships (stable, casual, etc.) at each timestep, disaggregated by sex. Durations are extracted from the network’s relationship_durs tracking dict.
Methods
| Name | Description |
|---|---|
| get_relationship_durations | Returns the durations of all relationships, separated by sex. |
get_relationship_durations
analyzers.RelationshipDurations.get_relationship_durations()Returns the durations of all relationships, separated by sex.
If include_current is False, return the duration of only relationships that have ended
Returns
| Name | Type | Description |
|---|---|---|
| female_durations | list of durations of relationships | |
| male_durations | list of durations of relationships |
TimeBetweenRelationships
analyzers.TimeBetweenRelationships(relationship_type='stable', *args, **kwargs)Analyzes the time between relationships in a structuredsexual network. Each timestep, for each debuted agent, check if they are in a relationship of the provided type. If not, increment the counter Otherwise, reset the counter to 0 and append the counter to the list of times between relationships for that agent.
Methods
| Name | Description |
|---|---|
| step | For each debuted agent, check if they are in a relationship. |
step
analyzers.TimeBetweenRelationships.step()For each debuted agent, check if they are in a relationship. If they are not, increment the time since last relationship by 1. If they are and time since last relationship is greater than 0, append the time to the list of times between relationships.
art_coverage
analyzers.art_coverage(age_bins=None, *args, **kwargs)Track ART coverage (number and proportion) by sex and age bin.
Results are stored as time series per stratum, accessible via: analyzer.results[‘n_art_f_15_25’] # Women 15-25 on ART (count) analyzer.results[‘p_art_m_25_35’] # Men 25-35 on ART (proportion of infected)
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| age_bins | list | age bin edges, e.g. [15, 25, 35, 45, 65]. Default: [15, 25, 35, 45, 65]. Bins are half-open intervals: [lo, hi), i.e. lo <= age < hi. | None |
Methods
| Name | Description |
|---|---|
| plot | Plot ART coverage over time. |
plot
analyzers.art_coverage.plot(by_age=True)Plot ART coverage over time.
Creates a 2-panel figure: aggregate coverage (left) and by age/sex (right). If by_age=False, only plots aggregate.
Example::
sim.run()
sim.analyzers.art_coverage.plot()
coinfection_stats
analyzers.coinfection_stats(
disease1,
disease2,
disease1_infected_state_name='infected',
disease2_infected_state_name='infected',
age_limits=None,
denom=None,
*args,
**kwargs,
)Generates stats for the coinfection of two diseases. This is useful for looking at the coinfection of HIV and syphilis, for example.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| disease1 | str | ss.Disease |
name of the first disease | required |
| disease2 | str | ss.Disease |
name of the second disease | required |
| disease1_infected_state_name | str | name of the infected state for disease1 (default: ‘infected’) | 'infected' |
| disease2_infected_state_name | str | name of the infected state for disease2 (default: ‘infected’) | 'infected' |
| age_limits | list | list of two integers that define the age limits for the denominator. | None |
| denom | function |
function that returns a boolean array of the denominator, usually the relevant population. default: lambda self: (self.sim.people.age >= 15) & (self.sim.people.age < 50) | None |
| *args, **kwargs | optional, passed to ss.Analyzer constructor | required |
partner_age_diff
analyzers.partner_age_diff(
year=2000,
age_bins=['teens', 'young', 'adult'],
network='structuredsexual',
*args,
**kwargs,
)Analyze age differences between sexual partners.
Records the mean, median, and standard deviation of male-female age differences (male age minus female age) at each timestep. At a specified year, stores full age-difference distributions disaggregated by female age group for detailed plotting.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| year | float | Calendar year at which to store detailed age-difference distributions. Defaults to 2000. | 2000 |
| age_bins | list | Female age group names matching the network’s f_age_group_bins keys. Defaults to ['teens', 'young', 'adult']. |
['teens', 'young', 'adult'] |
| network | str | Name of the network to analyze. Defaults to 'structuredsexual'. |
'structuredsexual' |
Methods
| Name | Description |
|---|---|
| init_results | Initialize the results for the age differences. |
| plot | Plot histograms of the age differences between partners. |
| step | Record the age differences between partners in the specified year. |
init_results
analyzers.partner_age_diff.init_results()Initialize the results for the age differences.
plot
analyzers.partner_age_diff.plot()Plot histograms of the age differences between partners.
step
analyzers.partner_age_diff.step()Record the age differences between partners in the specified year.
result_grouper
analyzers.result_grouper()Base analyzer providing conditional probability utilities for grouped results.
Provides a cond_prob static method that computes the proportion of a numerator group within a denominator group. Intended as a base class for analyzers that compute stratified prevalence or infection statistics.
sw_stats
analyzers.sw_stats(diseases=None, *args, **kwargs)Track new infections and transmissions among sex workers and their clients.
At each timestep, records the number and share of new infections and transmissions attributable to female sex workers (FSW), clients, and non-sex-worker populations, disaggregated by disease.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| diseases | list | List of disease names (str) to track, e.g. ['hiv', 'syphilis']. |
None |