stisim 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
stisim/__init__.py ADDED
@@ -0,0 +1,25 @@
1
+ from .version import __version__, __versiondate__, __license__
2
+
3
+ from .calibration import *
4
+ from .connectors import *
5
+ from .diseases import *
6
+ from .demographics import *
7
+ from .interventions import *
8
+ from .networks import *
9
+ from .utils import *
10
+ from .analyzers import *
11
+ from .parameters import *
12
+ from .sim import *
13
+
14
+ # Assign the root folder
15
+ import sciris as sc
16
+ root = sc.thispath(__file__).parent
17
+ data = root/'data'
18
+
19
+ # Import the version and print the license
20
+ print(__license__)
21
+
22
+ # Double-check key requirements -- should match setup.py
23
+ sc.require(['starsim>=3.0.0', 'sciris>=3.1.6', 'pandas>=2.0.0', 'scipy', 'numba', 'networkx'], message=f'The following dependencies for STIsim {__version__} were not met: <MISSING>.')
24
+ del sc # Don't keep this in the module
25
+
stisim/analyzers.py ADDED
@@ -0,0 +1,518 @@
1
+ """
2
+ Common analyzers for STI analyses
3
+ """
4
+
5
+ # %% Imports and settings
6
+ import numpy as np
7
+ import sciris as sc
8
+ import starsim as ss
9
+ import pandas as pd
10
+
11
+ import stisim as sti
12
+ import pylab as pl
13
+
14
+ __all__ = ["result_grouper", "coinfection_stats", "sw_stats", "RelationshipDurations", "NetworkDegree", "DebutAge", "partner_age_diff"]
15
+
16
+
17
+ class result_grouper(ss.Analyzer):
18
+ @staticmethod
19
+ def cond_prob(numerator, denominator):
20
+ numer = len((denominator & numerator).uids)
21
+ denom = len(denominator.uids)
22
+ out = sc.safedivide(numer, denom)
23
+ return out
24
+
25
+
26
+ class coinfection_stats(result_grouper):
27
+ """
28
+ Generates stats for the coinfection of two diseases.
29
+ This is useful for looking at the coinfection of HIV and syphilis, for example.
30
+
31
+ Args:
32
+ disease1 (str | ss.Disease): name of the first disease
33
+ disease2 (str | ss.Disease): name of the second disease
34
+ disease1_infected_state_name (str): name of the infected state for disease1 (default: 'infected')
35
+ disease2_infected_state_name (str): name of the infected state for disease2 (default: 'infected')
36
+ age_limits (list): list of two integers that define the age limits for the denominator.
37
+ denom (function): function that returns a boolean array of the denominator, usually the relevant population.
38
+ default: lambda self: (self.sim.people.age >= 15) & (self.sim.people.age < 50)
39
+ *args, **kwargs : optional, passed to ss.Analyzer constructor
40
+ """
41
+ def __init__(self, disease1, disease2, disease1_infected_state_name='infected', disease2_infected_state_name='infected',
42
+ age_limits=None, denom=None, *args, **kwargs):
43
+ super().__init__(*args, **kwargs)
44
+ self.name = 'coinfection_stats'
45
+ if disease1 is None or disease2 is None:
46
+ raise ValueError('Coinfection stats requires exactly 2 diseases')
47
+
48
+ self.disease1 = disease1
49
+ self.disease2 = disease2
50
+
51
+ # if the diseases are objects, get their names and store them instead of the objects
52
+ if isinstance(self.disease1, ss.Disease):
53
+ self.disease1 = self.disease1.name
54
+ if isinstance(self.disease2, ss.Disease):
55
+ self.disease2 = self.disease2.name
56
+
57
+ self.disease1_infected_state_name = disease1_infected_state_name
58
+ self.disease2_infected_state_name = disease2_infected_state_name
59
+ self.age_limits = age_limits or [15, 50]
60
+ default_denom = lambda self: (self.sim.people.age >= self.age_limits[0]) & (self.sim.people.age < self.age_limits[0])
61
+ self.denom = denom or default_denom
62
+
63
+ return
64
+
65
+ def init_results(self):
66
+ super().init_results()
67
+ results = [
68
+ ss.Result(f'{self.disease1}_prev_no_{self.disease2}', dtype=float, scale=False),
69
+ ss.Result(f'{self.disease1}_prev_has_{self.disease2}', dtype=float, scale=False),
70
+ ss.Result(f'{self.disease1}_prev_no_{self.disease2}_f', dtype=float, scale=False),
71
+ ss.Result(f'{self.disease1}_prev_has_{self.disease2}_f', dtype=float, scale=False),
72
+ ss.Result(f'{self.disease1}_prev_no_{self.disease2}_m', dtype=float, scale=False),
73
+ ss.Result(f'{self.disease1}_prev_has_{self.disease2}_m', dtype=float, scale=False),
74
+ ]
75
+ self.define_results(*results)
76
+ return
77
+
78
+ def step(self):
79
+ sim = self.sim
80
+ ti = self.ti
81
+ disease1name = self.disease1
82
+ disease2name = self.disease2
83
+ disease1obj = getattr(self.sim.diseases, self.disease1)
84
+ disease2obj = getattr(self.sim.diseases, self.disease2)
85
+
86
+ ppl = sim.people
87
+
88
+ denom = self.denom(self)
89
+ has_disease2 = getattr(disease2obj, self.disease2_infected_state_name) # Adults with HIV
90
+ has_disease1 = getattr(disease1obj, self.disease1_infected_state_name) # Adults with syphilis
91
+
92
+ has_disease1_f = denom & has_disease1 & ppl.female # Women with dis1
93
+ has_disease2_m = denom & has_disease1 & ppl.male # Men with dis1
94
+ has_disease2_f = denom & has_disease2 & ppl.female # Women with dis2
95
+ has_disease2_m = denom & has_disease2 & ppl.male # Men with dis2
96
+ no_disease2 = denom & ~has_disease2 # Adults without dis2
97
+ no_disease2_f = no_disease2 & ppl.female # Women without dis2
98
+ no_disease2_m = no_disease2 & ppl.male # Men without dis2
99
+
100
+ self.results[f'{disease1name}_prev_no_{disease2name}'][ti] = self.cond_prob(has_disease1, no_disease2)
101
+ self.results[f'{disease1name}_prev_has_{disease2name}'][ti] = self.cond_prob(has_disease1, has_disease2)
102
+ self.results[f'{disease1name}_prev_no_{disease2name}_f'][ti] = self.cond_prob(has_disease1_f, no_disease2_f)
103
+ self.results[f'{disease1name}_prev_has_{disease2name}_f'][ti] = self.cond_prob(has_disease1_f, has_disease2_f)
104
+ self.results[f'{disease1name}_prev_no_{disease2name}_m'][ti] = self.cond_prob(has_disease2_m, no_disease2_m)
105
+ self.results[f'{disease1name}_prev_has_{disease2name}_m'][ti] = self.cond_prob(has_disease2_m, has_disease2_m)
106
+
107
+ return
108
+
109
+
110
+ class sw_stats(result_grouper):
111
+ def __init__(self, diseases=None, *args, **kwargs):
112
+ super().__init__(*args, **kwargs)
113
+ self.name = 'sw_stats'
114
+ self.diseases = diseases
115
+ return
116
+
117
+ def init_results(self):
118
+ results = sc.autolist()
119
+ for d in self.diseases:
120
+ results += [
121
+ ss.Result('share_new_infections_fsw_'+d, scale=False, summarize_by='mean'),
122
+ ss.Result('share_new_infections_client_'+d,scale=False, summarize_by='mean'),
123
+ ss.Result('new_infections_fsw_'+d, dtype=int),
124
+ ss.Result('new_infections_client_'+d, dtype=int),
125
+ ss.Result('new_infections_non_fsw_'+d, dtype=int),
126
+ ss.Result('new_infections_non_client_'+d, dtype=int),
127
+ ss.Result('new_transmissions_fsw_'+d, dtype=int),
128
+ ss.Result('new_transmissions_client_'+d, dtype=int),
129
+ ss.Result('new_transmissions_non_fsw_'+d, dtype=int),
130
+ ss.Result('new_transmissions_non_client_'+d, dtype=int),
131
+ ]
132
+ self.define_results(*results)
133
+ return
134
+
135
+ def step(self):
136
+ sim = self.sim
137
+ ti = self.ti
138
+
139
+ if ti > 0:
140
+
141
+ for d in self.diseases:
142
+ dis = sim.diseases[d]
143
+ nw = sim.networks.structuredsexual
144
+ adult = sim.people.age > 0
145
+ fsw = nw.fsw & adult
146
+ client = nw.client & adult
147
+ non_fsw = sim.people.female & ~nw.fsw & adult
148
+ non_client = sim.people.male & ~nw.client & adult
149
+ newly_infected = (dis.ti_exposed == ti) & adult
150
+ new_trans = dis.ti_transmitted_sex == ti
151
+ total_acq = len(newly_infected.uids)
152
+
153
+ newly_transmitting_fsw = (dis.ti_transmitted_sex == ti) & fsw
154
+ newly_transmitting_clients = (dis.ti_transmitted_sex == ti) & client
155
+ newly_transmitting_non_fsw = (dis.ti_transmitted_sex == ti) & non_fsw
156
+ newly_transmitting_non_client = (dis.ti_transmitted_sex == ti) & non_client
157
+
158
+ new_transmissions_fsw = dis.new_transmissions_sex[newly_transmitting_fsw]
159
+ new_transmissions_client = dis.new_transmissions_sex[newly_transmitting_clients]
160
+ new_transmissions_non_fsw = dis.new_transmissions_sex[newly_transmitting_non_fsw]
161
+ new_transmissions_non_client = dis.new_transmissions_sex[newly_transmitting_non_client]
162
+
163
+ self.results['share_new_infections_fsw_'+d][ti] = self.cond_prob(fsw, newly_infected)
164
+ self.results['share_new_infections_client_'+d][ti] = self.cond_prob(client, newly_infected)
165
+
166
+ self.results['new_infections_fsw_'+d][ti] = len((fsw & newly_infected).uids)
167
+ self.results['new_infections_client_'+d][ti] = len((client & newly_infected).uids)
168
+ self.results['new_infections_non_fsw_'+d][ti] = len((non_fsw & newly_infected).uids)
169
+ self.results['new_infections_non_client_'+d][ti] = len((non_client & newly_infected).uids)
170
+
171
+ self.results['new_transmissions_fsw_'+d][ti] = sum(new_transmissions_fsw)
172
+ self.results['new_transmissions_client_'+d][ti] = sum(new_transmissions_client)
173
+ self.results['new_transmissions_non_fsw_'+d][ti] = sum(new_transmissions_non_fsw)
174
+ self.results['new_transmissions_non_client_'+d][ti] = sum(new_transmissions_non_client)
175
+
176
+ total_trans = sum(new_transmissions_fsw) + sum(new_transmissions_client) + sum(new_transmissions_non_fsw) + sum(new_transmissions_non_client)
177
+ if total_trans != len(newly_infected.uids):
178
+ errormsg = f'Infections acquired should equal number transmitted: {total_acq} vs {total_trans}'
179
+ raise ValueError(errormsg)
180
+
181
+ return
182
+
183
+
184
+ class RelationshipDurations(ss.Analyzer):
185
+ """
186
+ Analyzes the durations of relationships in a structuredsexual network.
187
+ """
188
+ def __init__(self, *args, **kwargs):
189
+ super().__init__(*args, **kwargs)
190
+ return
191
+
192
+ def init_results(self):
193
+ self.define_results(
194
+ ss.Result('mean_duration', dtype=float, scale=False),
195
+ ss.Result('median_duration', dtype=float, scale=False),
196
+ )
197
+ return
198
+
199
+ def step(self):
200
+ pass
201
+
202
+ def update_results(self):
203
+ sim = self.sim
204
+ ti = self.ti
205
+ nw = sim.networks.structuredsexual
206
+ rel_durations = self.get_relationship_durations()
207
+ self.results['mean_duration'][ti] = np.mean(rel_durations)
208
+ self.results['median_duration'][ti] = np.median(rel_durations)
209
+ return
210
+
211
+ def plot(self):
212
+ sim = self.sim
213
+ ti = self.ti
214
+ female_relationship_durs, male_relationship_durs = self.get_relationship_durations()
215
+ all_durations = female_relationship_durs + male_relationship_durs
216
+ pl.figure(1)
217
+ pl.hist(all_durations, bins=(max(all_durations) - min(all_durations)))
218
+ pl.xlabel('Relationship Duration')
219
+ pl.ylabel('Frequency')
220
+ pl.title('Distribution of Relationship Durations')
221
+
222
+ pl.figure(2)
223
+ pl.hist(female_relationship_durs, bins=(max(all_durations) - min(all_durations)))
224
+ pl.xlabel('Female Relationship Duration')
225
+ pl.ylabel('Frequency')
226
+ pl.title('Distribution of Female Relationship Durations')
227
+
228
+ pl.figure(3)
229
+ pl.hist(male_relationship_durs, bins=(max(all_durations) - min(all_durations)))
230
+ pl.xlabel('Male Relationship Duration')
231
+ pl.ylabel('Frequency')
232
+ pl.title('Distribution of Male Relationship Durations')
233
+ pl.show()
234
+
235
+
236
+ def get_relationship_durations(self):
237
+ """
238
+ Returns the durations of all relationships, separated by sex.
239
+
240
+ If include_current is False, return the duration of only relationships that have ended
241
+
242
+ returns:
243
+ female_durations: list of durations of relationships
244
+ male_durations: list of durations of relationships
245
+ """
246
+
247
+ # Get the current duration of all relationships
248
+ male_durations = []
249
+ female_durations = []
250
+ for pair, relationships in self.sim.networks.structuredsexual.relationship_durs.items():
251
+ durs = [relationship['dur'] for relationship in relationships]
252
+
253
+ # assign the durations to male and female lists
254
+ for uid in pair:
255
+ if self.sim.people.female[uid]:
256
+ female_durations.extend(durs)
257
+ else:
258
+ male_durations.extend(durs)
259
+
260
+ return female_durations, male_durations
261
+
262
+
263
+ class NetworkDegree(ss.Analyzer):
264
+ def __init__(self, year=None, bins=None, relationship_types=None, *args, **kwargs):
265
+ super().__init__(*args, **kwargs)
266
+ self.year = year
267
+
268
+ if bins is None:
269
+ bins = np.concatenate([np.arange(21),[100]])
270
+ self.bins = bins
271
+
272
+ if relationship_types is None:
273
+ relationship_types = ['stable', 'casual'] # Other options are 'partners' (stable+casual), 'onetime', 'sw'
274
+
275
+ self.relationship_types = []
276
+ if 'partners' in relationship_types:
277
+ relationship_types.remove('partners')
278
+ self.relationship_types.append('lifetime_partners')
279
+ [self.relationship_types.append(f'lifetime_{relationship_type}_partners') for relationship_type in relationship_types]
280
+
281
+ for relationship_type in self.relationship_types:
282
+ setattr(self, f'{relationship_type}_f', [])
283
+ setattr(self, f'{relationship_type}_m', [])
284
+
285
+ return
286
+
287
+ def init_results(self):
288
+ """
289
+ Add results for `n_rships`, separated for males and females
290
+ Optionally disaggregate for risk level / age?
291
+ """
292
+ super().init_results()
293
+ for relationship_type in self.relationship_types:
294
+ self.results += [
295
+ ss.Result(f'{relationship_type}_f', dtype=int, scale=False, shape=len(self.bins)),
296
+ ss.Result(f'{relationship_type}_m', dtype=int, scale=False, shape=len(self.bins)),
297
+ ]
298
+ return
299
+
300
+ def init_pre(self, sim, **kwargs):
301
+ """
302
+ Initialize the analyzer
303
+ """
304
+ super().init_pre(sim, **kwargs)
305
+ self.year = sim.t.yearvec[-1]
306
+ return
307
+
308
+
309
+ def step(self):
310
+ """
311
+ record lifetime_partners for the user-specified year
312
+ """
313
+ if self.sim.t.yearvec[self.ti] == self.year:
314
+ for relationship_type in self.relationship_types:
315
+ # Get the number of partners, disaggregated by sex. We can't use a Result object for this because we
316
+ # don't know how many agents there will be at any given time step. We can use Results for the binned
317
+ # counts.
318
+
319
+ female_partners = getattr(self.sim.networks.structuredsexual, relationship_type)[self.sim.people.female]
320
+ male_partners = getattr(self.sim.networks.structuredsexual, relationship_type)[self.sim.people.male]
321
+
322
+ getattr(self, f'{relationship_type}_f').extend(female_partners)
323
+ getattr(self, f'{relationship_type}_m').extend(male_partners)
324
+
325
+ # bin the data by number of partners
326
+ female_counts, female_bins = np.histogram(female_partners, bins=self.bins)
327
+ male_counts, male_bins = np.histogram(male_partners, bins=self.bins)
328
+
329
+ for i, female_count, male_count in zip(range(len(self.bins)), female_counts, male_counts):
330
+ self.results[f'{relationship_type}_f'][i] = female_count
331
+ self.results[f'{relationship_type}_m'][i] = male_count
332
+ return
333
+
334
+ def plot(self):
335
+ """
336
+ Plot histograms and stats by sex and relationship type
337
+ """
338
+
339
+ for relationship_type in self.relationship_types:
340
+ fig, axes = pl.subplots(1, 2, figsize=(9, 5), layout="tight")
341
+ axes = axes.flatten()
342
+ for ai, sex in enumerate(['f', 'm']):
343
+ counts = self.results[f'{relationship_type}_{sex}'].values
344
+ bins=self.bins
345
+
346
+ total = sum(counts)
347
+ counts = counts / total
348
+ counts[-2] = counts[-2:].sum()
349
+ counts = counts[:-1]
350
+
351
+ axes[ai].bar(bins[:-1], counts)
352
+ axes[ai].set_xlabel(f'Number of {relationship_type}')
353
+ axes[ai].set_title(f'Distribution of partners, {sex}')
354
+ axes[ai].set_ylim([0, 1])
355
+
356
+ sex_counts = np.array(getattr(self, f'{relationship_type}_{sex}'))
357
+ stats = f"Mean: {np.mean(sex_counts):.1f}\n"
358
+ stats += f"Median: {np.median(sex_counts):.1f}\n"
359
+ stats += f"Std: {np.std(sex_counts):.1f}\n"
360
+ stats += f"%>20: {np.count_nonzero(sex_counts >= 20) / total * 100:.2f}\n"
361
+ axes[ai].text(15, 0.5, stats)
362
+
363
+ pl.show()
364
+
365
+ return
366
+
367
+
368
+ class partner_age_diff(ss.Analyzer):
369
+ def __init__(self, year=2000, age_bins=['teens', 'young', 'adult'], network='structuredsexual', *args, **kwargs):
370
+ super().__init__(*args, **kwargs)
371
+ self.year = year
372
+ self.network = network
373
+ self.age_diffs = {}
374
+ self.age_bins = age_bins
375
+ return
376
+
377
+ def init_results(self):
378
+ """
379
+ Initialize the results for the age differences.
380
+ """
381
+ self.define_results(
382
+ ss.Result('age_diff_mean', dtype=float, scale=False),
383
+ ss.Result('age_diff_median', dtype=float, scale=False),
384
+ ss.Result('age_diff_std', dtype=float, scale=False),
385
+ )
386
+ return
387
+
388
+ def step(self):
389
+ """
390
+ Record the age differences between partners in the specified year.
391
+ """
392
+
393
+ net = self.sim.networks[self.network]
394
+ relationships = net.edges.dur > 1
395
+ p1 = net.p1[relationships]
396
+ p2 = net.p2[relationships]
397
+
398
+ age_diffs = (self.sim.people.age[p1] - self.sim.people.age[p2])
399
+
400
+ f_ages = self.sim.people.age[p2]
401
+
402
+ # bin the female ages by the bins used in the structured sexual network
403
+ # age_bins = sorted([bin[0] for bin in self.sim.networks.structuredsexual.pars.f_age_group_bins.values()])
404
+ age_bin_limits = [net.pars.f_age_group_bins[bin][0] for bin in self.age_bins]
405
+ age_bin_indices = np.digitize(f_ages, age_bin_limits) - 1
406
+
407
+ self.results['age_diff_mean'][self.ti] = np.mean(age_diffs)
408
+ self.results['age_diff_median'][self.ti] = np.median(age_diffs)
409
+ self.results['age_diff_std'][self.ti] = np.std(age_diffs)
410
+
411
+ if self.sim.t.yearvec[self.ti] == self.year:
412
+ for bin in self.age_bins:
413
+ self.age_diffs[bin] = age_diffs[age_bin_indices == self.age_bins.index(bin)]
414
+
415
+ def plot(self):
416
+ """
417
+ Plot histograms of the age differences between partners.
418
+ """
419
+ if len(self.age_diffs) > 0:
420
+ pl.figure(figsize=(8, 5))
421
+ pl.hist(list(self.age_diffs.values()), label=list(self.age_diffs.keys()), bins=30, edgecolor='black', alpha=0.7)
422
+ pl.legend()
423
+ pl.xlabel('Age Difference (years)')
424
+ pl.ylabel('Frequency')
425
+ pl.title(f'Age Differences Between Partners in {self.year} (Male Age - Female Age)')
426
+ pl.grid(True)
427
+ pl.show()
428
+ else:
429
+ print("No age differences recorded for the specified year.")
430
+
431
+ return
432
+
433
+
434
+ class DebutAge(ss.Analyzer):
435
+ """
436
+ Analyzes the debut age of relationships in a structuredsexual network.
437
+ """
438
+ def __init__(self, bins=None, cohort_starts=None, *args, **kwargs):
439
+ super().__init__(*args, **kwargs)
440
+
441
+ self.bins = bins or np.arange(12, 31, 1)
442
+ self.binspan = self.bins[-1] - self.bins[0]
443
+ self.cohort_starts = cohort_starts
444
+
445
+ return
446
+
447
+ def init_pre(self, sim, force=False):
448
+ if self.cohort_starts is None:
449
+ first_cohort = sim.t.start.years
450
+ last_cohort = sim.t.stop.years - self.binspan
451
+ self.cohort_starts = sc.inclusiverange(first_cohort, last_cohort)
452
+ self.cohort_ends = self.cohort_starts + self.binspan
453
+ self.n_cohorts = len(self.cohort_starts)
454
+ self.cohort_years = np.array([sc.inclusiverange(i, i + self.binspan) for i in self.cohort_starts])
455
+
456
+ self.prop_active_f = np.zeros((self.n_cohorts, self.binspan + 1))
457
+ self.prop_active_m = np.zeros((self.n_cohorts, self.binspan + 1))
458
+ super().init_pre(sim, force=force)
459
+ return
460
+
461
+ def init_results(self):
462
+ super().init_results()
463
+ self.define_results(
464
+ ss.Result('prop_active_f', dtype=float, scale=False, shape=len(self.cohort_starts)),
465
+ ss.Result('prop_active_m', dtype=float, scale=False, shape=len(self.cohort_starts)),
466
+ )
467
+ return
468
+
469
+ def step(self):
470
+
471
+ sim = self.sim
472
+ ppl = sim.people
473
+ if sim.t.yearvec[sim.ti] in self.cohort_years:
474
+ cohort_inds, bin_inds = sc.findinds(self.cohort_years, sim.t.yearvec[sim.ti])
475
+ for ci, cohort_ind in enumerate(cohort_inds):
476
+ bin_ind = bin_inds[ci]
477
+ bin = self.bins[bin_ind]
478
+
479
+ # all females cohort:
480
+ conditions_f = ppl.female * ppl.alive * (ppl.age >= (bin - 1)) * (
481
+ ppl.age < bin)
482
+ cohort_f_count = sum(conditions_f)
483
+ # all active females in cohort:
484
+ num_conditions_f = conditions_f * sim.networks.structuredsexual.over_debut
485
+ debut_f_count = sum(num_conditions_f)
486
+
487
+ self.prop_active_f[cohort_ind, bin_ind] = (debut_f_count) / (cohort_f_count) if cohort_f_count > 0 else 0
488
+
489
+ # all males cohort:
490
+ conditions_m = ~sim.people.female * sim.people.alive * (sim.people.age >= (bin - 1)) * (
491
+ sim.people.age < bin)
492
+ cohort_m_count = sum(conditions_m)
493
+ # all active males in cohort:
494
+ num_conditions_m = conditions_m * sim.networks.structuredsexual.over_debut
495
+ debut_m_count = sum(num_conditions_m)
496
+ self.prop_active_m[cohort_ind, bin_ind] = (debut_m_count) / (cohort_m_count) if cohort_m_count > 0 else 0
497
+ return
498
+
499
+ def plot(self):
500
+ """
501
+ Plot the proportion of active agents by cohort and debut age
502
+ """
503
+ pl.figure(1)
504
+ for row in self.prop_active_f:
505
+ pl.plot(self.bins, row)
506
+ pl.xlabel('Age')
507
+ pl.ylabel('Share')
508
+ pl.title('Proportion of females who are sexually active')
509
+ pl.show()
510
+
511
+ pl.figure(2)
512
+ for row in self.prop_active_m:
513
+ pl.plot(self.bins, row)
514
+ pl.xlabel('Age')
515
+ pl.ylabel('Share')
516
+ pl.title('Proportion of males who are sexually active')
517
+ pl.show()
518
+