cohortproj 0.1.0__tar.gz

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.
Files changed (37) hide show
  1. cohortproj-0.1.0/CHANGELOG.md +30 -0
  2. cohortproj-0.1.0/CITATION.cff +21 -0
  3. cohortproj-0.1.0/LICENSE +21 -0
  4. cohortproj-0.1.0/MANIFEST.in +9 -0
  5. cohortproj-0.1.0/PKG-INFO +241 -0
  6. cohortproj-0.1.0/README.md +206 -0
  7. cohortproj-0.1.0/examples/quickstart.py +101 -0
  8. cohortproj-0.1.0/pyproject.toml +109 -0
  9. cohortproj-0.1.0/setup.cfg +4 -0
  10. cohortproj-0.1.0/src/cohortproj/__init__.py +134 -0
  11. cohortproj-0.1.0/src/cohortproj/__main__.py +7 -0
  12. cohortproj-0.1.0/src/cohortproj/_common.py +136 -0
  13. cohortproj-0.1.0/src/cohortproj/_version.py +1 -0
  14. cohortproj-0.1.0/src/cohortproj/cli.py +207 -0
  15. cohortproj-0.1.0/src/cohortproj/fertility.py +132 -0
  16. cohortproj-0.1.0/src/cohortproj/io.py +108 -0
  17. cohortproj-0.1.0/src/cohortproj/leslie.py +247 -0
  18. cohortproj-0.1.0/src/cohortproj/lifetable.py +394 -0
  19. cohortproj-0.1.0/src/cohortproj/multiregion.py +309 -0
  20. cohortproj-0.1.0/src/cohortproj/projection.py +364 -0
  21. cohortproj-0.1.0/src/cohortproj/py.typed +0 -0
  22. cohortproj-0.1.0/src/cohortproj/scenarios.py +224 -0
  23. cohortproj-0.1.0/src/cohortproj/structure.py +201 -0
  24. cohortproj-0.1.0/src/cohortproj/survival.py +72 -0
  25. cohortproj-0.1.0/src/cohortproj.egg-info/PKG-INFO +241 -0
  26. cohortproj-0.1.0/src/cohortproj.egg-info/SOURCES.txt +35 -0
  27. cohortproj-0.1.0/src/cohortproj.egg-info/dependency_links.txt +1 -0
  28. cohortproj-0.1.0/src/cohortproj.egg-info/entry_points.txt +2 -0
  29. cohortproj-0.1.0/src/cohortproj.egg-info/requires.txt +7 -0
  30. cohortproj-0.1.0/src/cohortproj.egg-info/top_level.txt +1 -0
  31. cohortproj-0.1.0/tests/conftest.py +43 -0
  32. cohortproj-0.1.0/tests/test_common.py +55 -0
  33. cohortproj-0.1.0/tests/test_io_cli.py +157 -0
  34. cohortproj-0.1.0/tests/test_leslie_multiregion.py +172 -0
  35. cohortproj-0.1.0/tests/test_lifetable.py +143 -0
  36. cohortproj-0.1.0/tests/test_projection.py +178 -0
  37. cohortproj-0.1.0/tests/test_structure_scenarios.py +144 -0
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.1.0] - 2026-11-15
8
+ ### Added
9
+ - Abridged and single-year period life tables from death rates or deaths and exposure,
10
+ with mid-point, constant-hazard, Coale-Demeny (ages 0 and 1-4), Keyfitz iterative or
11
+ user-supplied `nax`; optional `qx` input; collapsing of 0 and 1-4 into 0-4; mortality
12
+ scaling.
13
+ - Survival ratios for projection steps, including the open age group and births.
14
+ - Fertility measures: ASFR, TFR, GRR, NRR, mean age of childbearing, rescaling to a target
15
+ TFR, placement of reproductive-age schedules on full age groups.
16
+ - Cohort-component projection for one or both sexes with net migration as counts or
17
+ annual rates (end, start or half timing) and time-varying schedules.
18
+ - Leslie matrices, Leslie projection, stable population (growth rate, stable age
19
+ distribution, reproductive values, damping ratio, birth rate) and Lotka's intrinsic rate.
20
+ - Simple one-sex multiregional and two-region projection, equivalent growth matrix and
21
+ long-run regional shares.
22
+ - Age-structure indicators: dependency ratios, ageing index, support ratio, median and mean
23
+ age, sex ratios, population-pyramid data.
24
+ - Scenario runs with fertility, mortality and migration variants and a comparison table.
25
+ - CSV / JSON I/O and the command line: `cohortproj lifetable | project | stable | structure`.
26
+
27
+ ### Fixed
28
+ - `leslie_matrix(..., open_group=False)`: the survival ratio into the last group used
29
+ `nL` of the open group (`T_A`) over `nL(A-n)`, which can exceed 1 under low mortality;
30
+ it now uses `T_A / T_(A-n)`.
@@ -0,0 +1,21 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ authors:
4
+ - family-names: "Eminoğlu"
5
+ given-names: "Yusuf"
6
+ email: "geospacephilo@gmail.com"
7
+ orcid: "https://orcid.org/0009-0005-6000-2934"
8
+ title: "cohortproj"
9
+ version: "0.1.0"
10
+ date-released: "2026-11-15"
11
+ license: "MIT"
12
+ repository-code: "https://gitlab.com/geophilo1/cohortproj_sdk"
13
+ url: "https://pypi.org/project/cohortproj/"
14
+ abstract: "Cohort-component population projection following Preston, Heuveline and Guillot (2001): abridged and single-year life tables with standard nax choices, survival ratios, age-specific fertility, TFR, GRR and NRR, net migration as counts or rates, Leslie matrices, stable population and Lotka's intrinsic growth rate, a simple multiregional extension, age-structure indicators and scenario runs."
15
+ keywords:
16
+ - demography
17
+ - population projection
18
+ - cohort-component method
19
+ - life table
20
+ - Leslie matrix
21
+ - stable population
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yusuf Eminoğlu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include CITATION.cff
5
+ include pyproject.toml
6
+ include src/cohortproj/py.typed
7
+ recursive-include src/cohortproj *.py
8
+ recursive-include tests *.py
9
+ recursive-include examples *.py
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: cohortproj
3
+ Version: 0.1.0
4
+ Summary: Cohort-component population projection: abridged and single-year life tables, survival ratios, fertility measures, migration, Leslie matrices, stable population, a simple multiregional model, age-structure indicators and scenario runs.
5
+ Author-email: Yusuf Eminoğlu <geospacephilo@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://gitlab.com/geophilo1/cohortproj_sdk
8
+ Project-URL: Repository, https://gitlab.com/geophilo1/cohortproj_sdk
9
+ Project-URL: Issues, https://gitlab.com/geophilo1/cohortproj_sdk/-/work_items
10
+ Project-URL: Changelog, https://gitlab.com/geophilo1/cohortproj_sdk/-/blob/main/CHANGELOG.md
11
+ Keywords: demography,population-projection,cohort-component,life-table,leslie-matrix,stable-population,fertility,mortality,migration,multiregional,dependency-ratio,population-pyramid,urban-planning
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Environment :: Console
21
+ Classifier: Topic :: Scientific/Engineering
22
+ Classifier: Intended Audience :: Science/Research
23
+ Classifier: Intended Audience :: Developers
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: numpy>=1.21
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
32
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
33
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # cohortproj
37
+
38
+ [![PyPI](https://img.shields.io/pypi/v/cohortproj.svg)](https://pypi.org/project/cohortproj/)
39
+ [![Python](https://img.shields.io/pypi/pyversions/cohortproj.svg)](https://pypi.org/project/cohortproj/)
40
+ [![pipeline](https://gitlab.com/geophilo1/cohortproj_sdk/badges/main/pipeline.svg)](https://gitlab.com/geophilo1/cohortproj_sdk/-/pipelines)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitlab.com/geophilo1/cohortproj_sdk/-/blob/main/LICENSE)
42
+
43
+ **Cohort-component population projection, life tables and Leslie matrices in plain NumPy.**
44
+
45
+ `cohortproj` implements the standard demographic toolkit of Preston, Heuveline & Guillot,
46
+ *Demography: Measuring and Modeling Population Processes* (2001):
47
+
48
+ - **Life tables**: abridged (0, 1-4, 5-9, ...) and single-year period life tables from
49
+ death rates or deaths and exposure: `nqx`, `lx`, `ndx`, `nLx`, `Tx`, `ex`, with `nax`
50
+ by mid-point, constant hazard, Coale-Demeny (ages 0 and 1-4), Keyfitz's iterative
51
+ graduation, or user-supplied values.
52
+ - **Survival ratios** for projection steps, including the open age group and births.
53
+ - **Fertility**: age-specific rates, TFR, GRR, NRR, mean age of childbearing, rescaling to a
54
+ target TFR; the sex ratio at birth is always an explicit input.
55
+ - **Cohort-component projection** for one or both sexes, with net migration as counts or
56
+ annual rates and a stated timing convention, constant or time-varying schedules.
57
+ - **Leslie matrices**, the **stable population** (intrinsic growth rate, stable age
58
+ distribution, reproductive values, damping ratio) and the intrinsic rate from Lotka's
59
+ equation.
60
+ - **Multiregional extension**: a simple one-sex multi-region (and two-region) projection
61
+ with age-specific transition probabilities, its equivalent growth matrix and long-run
62
+ regional shares.
63
+ - **Age structure**: dependency ratios, ageing index, support ratio, median and mean age,
64
+ sex ratios and population-pyramid data.
65
+ - **Scenarios**: fertility, mortality and migration variants run side by side.
66
+
67
+ It needs only NumPy. A command-line tool reads CSV tables and writes JSON and CSV.
68
+
69
+ ## Installation
70
+
71
+ ```bash
72
+ pip install cohortproj
73
+ ```
74
+
75
+ Requires Python 3.9 or newer.
76
+
77
+ ## Quickstart
78
+
79
+ ```python
80
+ import numpy as np
81
+ import cohortproj as cp
82
+
83
+ # abridged life table from death rates (0, 1-4, 5-9, ..., 85+)
84
+ lt_ages = cp.abridged_ages(85)
85
+ female_lt = cp.life_table(mx_female, lt_ages, nax="coale-demeny", sex="female")
86
+ male_lt = cp.life_table(mx_male, lt_ages, nax="coale-demeny", sex="male")
87
+ female_lt.e0, female_lt.records()
88
+
89
+ # projection on 5-year groups 0-4 ... 85+ (abridged tables are collapsed to 0-4)
90
+ ages = cp.regular_ages(5, 85)
91
+ asfr = cp.expand_asfr([0.02, 0.09, 0.12, 0.09, 0.04, 0.01, 0.002], 15, ages)
92
+ proj = cp.project(female, ages, female_lt, asfr, srb=1.05, steps=6,
93
+ male=male, male_life=male_lt,
94
+ migration_female=mig_f, migration_male=mig_m,
95
+ timing="half", start_year=2025)
96
+ proj.total, proj.summary(), proj.records()
97
+
98
+ # stable population
99
+ les = cp.leslie_matrix(female_lt, asfr, srb=1.05)
100
+ st = cp.stable_population(les)
101
+ st.growth_rate, st.age_distribution, st.reproductive_values
102
+ cp.intrinsic_growth_rate(asfr, female_lt, srb=1.05) # Lotka
103
+
104
+ # age structure
105
+ s = cp.age_structure(proj.female[-1], ages, proj.male[-1])
106
+ s.median_age, s.dependency["old"]
107
+ cp.pyramid(proj.female[-1], proj.male[-1], ages)
108
+ ```
109
+
110
+ Scenarios:
111
+
112
+ ```python
113
+ base = cp.ProjectionInputs(female=female, ages=ages, female_life=female_lt, asfr=asfr,
114
+ srb=1.05, steps=6, male=male, male_life=male_lt)
115
+ runs = cp.run_scenarios(base, [
116
+ cp.Scenario("medium"),
117
+ cp.Scenario("low", tfr=cp.linear_path(1.9, 1.3, 6)),
118
+ cp.Scenario("higher mortality", mortality_factor=1.2),
119
+ cp.Scenario("closed", migration_factor=0.0),
120
+ ])
121
+ runs.table # year-by-year totals, TFR, median age, dependency ratios
122
+ runs.final("total")
123
+ ```
124
+
125
+ Two regions:
126
+
127
+ ```python
128
+ mr = cp.two_region_project(pop_urban, pop_rural, ages, lt_urban, lt_rural,
129
+ asfr_urban, asfr_rural, out_1_to_2=0.02, out_2_to_1=0.06,
130
+ srb=1.05, steps=10, names=("urban", "rural"))
131
+ mr.totals
132
+ cp.multiregional_stable([lt_urban, lt_rural], np.vstack([asfr_urban, asfr_rural]),
133
+ migration, srb=1.05, n_ages=ages.size).regional_shares
134
+ ```
135
+
136
+ ## Command line
137
+
138
+ ```bash
139
+ cohortproj lifetable life_female.csv --nax coale-demeny --sex female --csv lt.csv
140
+ cohortproj project input.csv --srb 1.05 --steps 6 --start-year 2025 \
141
+ --life-female life_female.csv --life-male life_male.csv --nax coale-demeny \
142
+ --timing half --csv projection_long.csv -o projection.json
143
+ cohortproj stable input.csv --srb 1.05 --life-female life_female.csv --matrix
144
+ cohortproj structure population.csv --csv pyramid.csv
145
+ ```
146
+
147
+ CSV columns: `age` (lower bounds or labels such as `5-9` and `85+`) plus `mx` (or `deaths`
148
+ and `exposure`, optional `ax` and `qx`) for life tables; `female`, `asfr` and optionally
149
+ `male`, `mx_female`, `mx_male`, `migration_female`, `migration_male` for projections.
150
+ Every command prints JSON or writes it with `-o`.
151
+
152
+ ## API overview
153
+
154
+ | Area | Functions / classes |
155
+ | --- | --- |
156
+ | Life tables | `life_table -> LifeTable` (`records()`, `collapse_infant()`, `scaled()`), `life_table_from_deaths`, `coale_demeny_a0`, `coale_demeny_a1`, `mx_to_qx`, `qx_to_mx`, `NAX_METHODS` |
157
+ | Survival | `survival_ratios -> SurvivalRatios` |
158
+ | Fertility | `asfr`, `tfr`, `grr`, `nrr`, `mean_age_childbearing`, `scale_to_tfr`, `expand_asfr`, `fraction_female` |
159
+ | Projection | `project -> Projection` (`total`, `summary()`, `records()`, `to_dict()`) |
160
+ | Leslie / stable | `leslie_matrix -> LeslieMatrix`, `project_leslie`, `stable_population -> StablePopulation`, `intrinsic_growth_rate` |
161
+ | Multiregional | `multiregional_project`, `two_region_project -> MultiRegionProjection`, `growth_matrix`, `multiregional_stable` |
162
+ | Structure | `dependency_ratios`, `median_age`, `mean_age`, `pyramid`, `sex_ratios`, `age_structure`, `structure_series` |
163
+ | Scenarios | `ProjectionInputs`, `Scenario`, `run_scenario`, `run_scenarios -> ScenarioComparison`, `linear_path` |
164
+ | Helpers / I/O | `abridged_ages`, `regular_ages`, `age_labels`, `read_table`, `write_records`, `write_json` |
165
+
166
+ ## Methods and sources
167
+
168
+ | Item | Definition | Source |
169
+ | --- | --- | --- |
170
+ | `nqx` | `n m / (1 + (n - a) m)`; open group `q = 1`, `L = l / m` | Preston et al. (2001), ch. 3 |
171
+ | `nax`, mid-point | `n / 2` | Preston et al. (2001), ch. 3 |
172
+ | `nax`, constant hazard | `1/m - n e^(-nm) / (1 - e^(-nm))`, `q = 1 - e^(-nm)` | Preston et al. (2001), §3.3 |
173
+ | `nax`, Coale-Demeny | `1a0`, `4a1` as linear functions of `1m0` (Table 3.3) | Coale & Demeny (1983) via Preston et al. (2001) |
174
+ | `nax`, Keyfitz | `(-(n/24) d(x-n) + (n/2) d(x) + (n/24) d(x+n)) / d(x)`, iterated | Keyfitz (1966); Preston et al. (2001) |
175
+ | Survival ratios | `nL(x+n)/nLx`; open `T_A/T_(A-n)`; births `nL0/(n l0)` | Preston et al. (2001), §6.2 |
176
+ | Births | `n sum F_x (N^F_x(t) + N^F_x(t+n)) / 2`, split by `1/(1+SRB)` | Preston et al. (2001), §6.2 |
177
+ | TFR, GRR, NRR | `n sum F`; `TFR/(1+SRB)`; `sum F L / l0 /(1+SRB)` | Preston et al. (2001), ch. 5 |
178
+ | Leslie matrix | first row `nL0/(2 l0) (F_x + F_(x+n) s_x) / (1+SRB)`, sub-diagonal `s_x` | Leslie (1945); Preston et al. (2001), §6.5 |
179
+ | Intrinsic rate | `sum e^(-r(x+n/2)) F_x L_x / l0 / (1+SRB) = 1` | Lotka (1939); Preston et al. (2001), ch. 7 |
180
+ | Multiregional | survive, then move survivors with age-specific transition probabilities | after Rogers (1975), simplified |
181
+ | Dependency ratios | `100 P(0-14)/P(15-64)`, `100 P(65+)/P(15-64)` (bounds are parameters) | United Nations convention |
182
+
183
+ ### Conventions and assumptions
184
+
185
+ - **Migration timing.** Net migrant counts are indexed by age group at the **end** of a
186
+ step. `timing="end"` adds them after survival; `"start"` applies the cohort's survival
187
+ ratio (they arrive at the start); `"half"` splits them between the two. Newborn migrants
188
+ are added at the end. Annual rates become counts as `rate n (N_x(t) + N*_x(t+n)) / 2`
189
+ with `N*` the closed projection.
190
+ - **Deaths** in the projection summary are the balancing item
191
+ `N(t) + births + migrants - N(t+n)`; with `"start"` or `"half"` timing they include
192
+ deaths of migrants.
193
+ - **Leslie and projection agree exactly**: for a female population without migration the
194
+ Leslie matrix reproduces `project()` to rounding.
195
+ - **No defaults for population assumptions**: the sex ratio at birth, `nax` values beyond
196
+ the Coale-Demeny ages, fertility and migration schedules and scenario variants are
197
+ always the user's choice. The 15 / 65 dependency bounds follow the UN convention and can
198
+ be changed.
199
+ - `nqx` is capped at 1 when very high death rates would push it above 1; check such input.
200
+
201
+ ## Limitations
202
+
203
+ - The multiregional model is a projection device with transition probabilities, not
204
+ Rogers' full multiregional life table, and is one-sex (female).
205
+ - Projection steps must equal the age-group width (5-year groups with 5-year steps,
206
+ single years with 1-year steps).
207
+ - No model life tables, mortality forecasting (Lee-Carter) or fertility models are bundled;
208
+ supply the schedules.
209
+ - The mean age of an open age group needs a representative age (`open_mean`); the default
210
+ is only a rough approximation.
211
+
212
+ ## References
213
+
214
+ - Coale, A. J. & Demeny, P. (1983). *Regional Model Life Tables and Stable Populations*,
215
+ 2nd ed. Academic Press, New York.
216
+ - Keyfitz, N. (1966). A life table that agrees with the data. *Journal of the American
217
+ Statistical Association* 61(314), 305-312.
218
+ - Leslie, P. H. (1945). On the use of matrices in certain population mathematics.
219
+ *Biometrika* 33(3), 183-212.
220
+ - Lotka, A. J. (1939). *Théorie analytique des associations biologiques. Deuxième partie:
221
+ analyse démographique avec application particulière à l'espèce humaine*. Hermann, Paris.
222
+ - Preston, S. H., Heuveline, P. & Guillot, M. (2001). *Demography: Measuring and Modeling
223
+ Population Processes*. Blackwell, Oxford.
224
+ - Rogers, A. (1975). *Introduction to Multiregional Mathematical Demography*. Wiley, New
225
+ York.
226
+
227
+ ## Academic citation
228
+
229
+ ```bibtex
230
+ @software{eminoglu_cohortproj,
231
+ author = {Eminoğlu, Yusuf},
232
+ title = {cohortproj},
233
+ year = {2026},
234
+ version = {0.1.0},
235
+ url = {https://gitlab.com/geophilo1/cohortproj_sdk}
236
+ }
237
+ ```
238
+
239
+ ## License
240
+
241
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,206 @@
1
+ # cohortproj
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/cohortproj.svg)](https://pypi.org/project/cohortproj/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/cohortproj.svg)](https://pypi.org/project/cohortproj/)
5
+ [![pipeline](https://gitlab.com/geophilo1/cohortproj_sdk/badges/main/pipeline.svg)](https://gitlab.com/geophilo1/cohortproj_sdk/-/pipelines)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitlab.com/geophilo1/cohortproj_sdk/-/blob/main/LICENSE)
7
+
8
+ **Cohort-component population projection, life tables and Leslie matrices in plain NumPy.**
9
+
10
+ `cohortproj` implements the standard demographic toolkit of Preston, Heuveline & Guillot,
11
+ *Demography: Measuring and Modeling Population Processes* (2001):
12
+
13
+ - **Life tables**: abridged (0, 1-4, 5-9, ...) and single-year period life tables from
14
+ death rates or deaths and exposure: `nqx`, `lx`, `ndx`, `nLx`, `Tx`, `ex`, with `nax`
15
+ by mid-point, constant hazard, Coale-Demeny (ages 0 and 1-4), Keyfitz's iterative
16
+ graduation, or user-supplied values.
17
+ - **Survival ratios** for projection steps, including the open age group and births.
18
+ - **Fertility**: age-specific rates, TFR, GRR, NRR, mean age of childbearing, rescaling to a
19
+ target TFR; the sex ratio at birth is always an explicit input.
20
+ - **Cohort-component projection** for one or both sexes, with net migration as counts or
21
+ annual rates and a stated timing convention, constant or time-varying schedules.
22
+ - **Leslie matrices**, the **stable population** (intrinsic growth rate, stable age
23
+ distribution, reproductive values, damping ratio) and the intrinsic rate from Lotka's
24
+ equation.
25
+ - **Multiregional extension**: a simple one-sex multi-region (and two-region) projection
26
+ with age-specific transition probabilities, its equivalent growth matrix and long-run
27
+ regional shares.
28
+ - **Age structure**: dependency ratios, ageing index, support ratio, median and mean age,
29
+ sex ratios and population-pyramid data.
30
+ - **Scenarios**: fertility, mortality and migration variants run side by side.
31
+
32
+ It needs only NumPy. A command-line tool reads CSV tables and writes JSON and CSV.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install cohortproj
38
+ ```
39
+
40
+ Requires Python 3.9 or newer.
41
+
42
+ ## Quickstart
43
+
44
+ ```python
45
+ import numpy as np
46
+ import cohortproj as cp
47
+
48
+ # abridged life table from death rates (0, 1-4, 5-9, ..., 85+)
49
+ lt_ages = cp.abridged_ages(85)
50
+ female_lt = cp.life_table(mx_female, lt_ages, nax="coale-demeny", sex="female")
51
+ male_lt = cp.life_table(mx_male, lt_ages, nax="coale-demeny", sex="male")
52
+ female_lt.e0, female_lt.records()
53
+
54
+ # projection on 5-year groups 0-4 ... 85+ (abridged tables are collapsed to 0-4)
55
+ ages = cp.regular_ages(5, 85)
56
+ asfr = cp.expand_asfr([0.02, 0.09, 0.12, 0.09, 0.04, 0.01, 0.002], 15, ages)
57
+ proj = cp.project(female, ages, female_lt, asfr, srb=1.05, steps=6,
58
+ male=male, male_life=male_lt,
59
+ migration_female=mig_f, migration_male=mig_m,
60
+ timing="half", start_year=2025)
61
+ proj.total, proj.summary(), proj.records()
62
+
63
+ # stable population
64
+ les = cp.leslie_matrix(female_lt, asfr, srb=1.05)
65
+ st = cp.stable_population(les)
66
+ st.growth_rate, st.age_distribution, st.reproductive_values
67
+ cp.intrinsic_growth_rate(asfr, female_lt, srb=1.05) # Lotka
68
+
69
+ # age structure
70
+ s = cp.age_structure(proj.female[-1], ages, proj.male[-1])
71
+ s.median_age, s.dependency["old"]
72
+ cp.pyramid(proj.female[-1], proj.male[-1], ages)
73
+ ```
74
+
75
+ Scenarios:
76
+
77
+ ```python
78
+ base = cp.ProjectionInputs(female=female, ages=ages, female_life=female_lt, asfr=asfr,
79
+ srb=1.05, steps=6, male=male, male_life=male_lt)
80
+ runs = cp.run_scenarios(base, [
81
+ cp.Scenario("medium"),
82
+ cp.Scenario("low", tfr=cp.linear_path(1.9, 1.3, 6)),
83
+ cp.Scenario("higher mortality", mortality_factor=1.2),
84
+ cp.Scenario("closed", migration_factor=0.0),
85
+ ])
86
+ runs.table # year-by-year totals, TFR, median age, dependency ratios
87
+ runs.final("total")
88
+ ```
89
+
90
+ Two regions:
91
+
92
+ ```python
93
+ mr = cp.two_region_project(pop_urban, pop_rural, ages, lt_urban, lt_rural,
94
+ asfr_urban, asfr_rural, out_1_to_2=0.02, out_2_to_1=0.06,
95
+ srb=1.05, steps=10, names=("urban", "rural"))
96
+ mr.totals
97
+ cp.multiregional_stable([lt_urban, lt_rural], np.vstack([asfr_urban, asfr_rural]),
98
+ migration, srb=1.05, n_ages=ages.size).regional_shares
99
+ ```
100
+
101
+ ## Command line
102
+
103
+ ```bash
104
+ cohortproj lifetable life_female.csv --nax coale-demeny --sex female --csv lt.csv
105
+ cohortproj project input.csv --srb 1.05 --steps 6 --start-year 2025 \
106
+ --life-female life_female.csv --life-male life_male.csv --nax coale-demeny \
107
+ --timing half --csv projection_long.csv -o projection.json
108
+ cohortproj stable input.csv --srb 1.05 --life-female life_female.csv --matrix
109
+ cohortproj structure population.csv --csv pyramid.csv
110
+ ```
111
+
112
+ CSV columns: `age` (lower bounds or labels such as `5-9` and `85+`) plus `mx` (or `deaths`
113
+ and `exposure`, optional `ax` and `qx`) for life tables; `female`, `asfr` and optionally
114
+ `male`, `mx_female`, `mx_male`, `migration_female`, `migration_male` for projections.
115
+ Every command prints JSON or writes it with `-o`.
116
+
117
+ ## API overview
118
+
119
+ | Area | Functions / classes |
120
+ | --- | --- |
121
+ | Life tables | `life_table -> LifeTable` (`records()`, `collapse_infant()`, `scaled()`), `life_table_from_deaths`, `coale_demeny_a0`, `coale_demeny_a1`, `mx_to_qx`, `qx_to_mx`, `NAX_METHODS` |
122
+ | Survival | `survival_ratios -> SurvivalRatios` |
123
+ | Fertility | `asfr`, `tfr`, `grr`, `nrr`, `mean_age_childbearing`, `scale_to_tfr`, `expand_asfr`, `fraction_female` |
124
+ | Projection | `project -> Projection` (`total`, `summary()`, `records()`, `to_dict()`) |
125
+ | Leslie / stable | `leslie_matrix -> LeslieMatrix`, `project_leslie`, `stable_population -> StablePopulation`, `intrinsic_growth_rate` |
126
+ | Multiregional | `multiregional_project`, `two_region_project -> MultiRegionProjection`, `growth_matrix`, `multiregional_stable` |
127
+ | Structure | `dependency_ratios`, `median_age`, `mean_age`, `pyramid`, `sex_ratios`, `age_structure`, `structure_series` |
128
+ | Scenarios | `ProjectionInputs`, `Scenario`, `run_scenario`, `run_scenarios -> ScenarioComparison`, `linear_path` |
129
+ | Helpers / I/O | `abridged_ages`, `regular_ages`, `age_labels`, `read_table`, `write_records`, `write_json` |
130
+
131
+ ## Methods and sources
132
+
133
+ | Item | Definition | Source |
134
+ | --- | --- | --- |
135
+ | `nqx` | `n m / (1 + (n - a) m)`; open group `q = 1`, `L = l / m` | Preston et al. (2001), ch. 3 |
136
+ | `nax`, mid-point | `n / 2` | Preston et al. (2001), ch. 3 |
137
+ | `nax`, constant hazard | `1/m - n e^(-nm) / (1 - e^(-nm))`, `q = 1 - e^(-nm)` | Preston et al. (2001), §3.3 |
138
+ | `nax`, Coale-Demeny | `1a0`, `4a1` as linear functions of `1m0` (Table 3.3) | Coale & Demeny (1983) via Preston et al. (2001) |
139
+ | `nax`, Keyfitz | `(-(n/24) d(x-n) + (n/2) d(x) + (n/24) d(x+n)) / d(x)`, iterated | Keyfitz (1966); Preston et al. (2001) |
140
+ | Survival ratios | `nL(x+n)/nLx`; open `T_A/T_(A-n)`; births `nL0/(n l0)` | Preston et al. (2001), §6.2 |
141
+ | Births | `n sum F_x (N^F_x(t) + N^F_x(t+n)) / 2`, split by `1/(1+SRB)` | Preston et al. (2001), §6.2 |
142
+ | TFR, GRR, NRR | `n sum F`; `TFR/(1+SRB)`; `sum F L / l0 /(1+SRB)` | Preston et al. (2001), ch. 5 |
143
+ | Leslie matrix | first row `nL0/(2 l0) (F_x + F_(x+n) s_x) / (1+SRB)`, sub-diagonal `s_x` | Leslie (1945); Preston et al. (2001), §6.5 |
144
+ | Intrinsic rate | `sum e^(-r(x+n/2)) F_x L_x / l0 / (1+SRB) = 1` | Lotka (1939); Preston et al. (2001), ch. 7 |
145
+ | Multiregional | survive, then move survivors with age-specific transition probabilities | after Rogers (1975), simplified |
146
+ | Dependency ratios | `100 P(0-14)/P(15-64)`, `100 P(65+)/P(15-64)` (bounds are parameters) | United Nations convention |
147
+
148
+ ### Conventions and assumptions
149
+
150
+ - **Migration timing.** Net migrant counts are indexed by age group at the **end** of a
151
+ step. `timing="end"` adds them after survival; `"start"` applies the cohort's survival
152
+ ratio (they arrive at the start); `"half"` splits them between the two. Newborn migrants
153
+ are added at the end. Annual rates become counts as `rate n (N_x(t) + N*_x(t+n)) / 2`
154
+ with `N*` the closed projection.
155
+ - **Deaths** in the projection summary are the balancing item
156
+ `N(t) + births + migrants - N(t+n)`; with `"start"` or `"half"` timing they include
157
+ deaths of migrants.
158
+ - **Leslie and projection agree exactly**: for a female population without migration the
159
+ Leslie matrix reproduces `project()` to rounding.
160
+ - **No defaults for population assumptions**: the sex ratio at birth, `nax` values beyond
161
+ the Coale-Demeny ages, fertility and migration schedules and scenario variants are
162
+ always the user's choice. The 15 / 65 dependency bounds follow the UN convention and can
163
+ be changed.
164
+ - `nqx` is capped at 1 when very high death rates would push it above 1; check such input.
165
+
166
+ ## Limitations
167
+
168
+ - The multiregional model is a projection device with transition probabilities, not
169
+ Rogers' full multiregional life table, and is one-sex (female).
170
+ - Projection steps must equal the age-group width (5-year groups with 5-year steps,
171
+ single years with 1-year steps).
172
+ - No model life tables, mortality forecasting (Lee-Carter) or fertility models are bundled;
173
+ supply the schedules.
174
+ - The mean age of an open age group needs a representative age (`open_mean`); the default
175
+ is only a rough approximation.
176
+
177
+ ## References
178
+
179
+ - Coale, A. J. & Demeny, P. (1983). *Regional Model Life Tables and Stable Populations*,
180
+ 2nd ed. Academic Press, New York.
181
+ - Keyfitz, N. (1966). A life table that agrees with the data. *Journal of the American
182
+ Statistical Association* 61(314), 305-312.
183
+ - Leslie, P. H. (1945). On the use of matrices in certain population mathematics.
184
+ *Biometrika* 33(3), 183-212.
185
+ - Lotka, A. J. (1939). *Théorie analytique des associations biologiques. Deuxième partie:
186
+ analyse démographique avec application particulière à l'espèce humaine*. Hermann, Paris.
187
+ - Preston, S. H., Heuveline, P. & Guillot, M. (2001). *Demography: Measuring and Modeling
188
+ Population Processes*. Blackwell, Oxford.
189
+ - Rogers, A. (1975). *Introduction to Multiregional Mathematical Demography*. Wiley, New
190
+ York.
191
+
192
+ ## Academic citation
193
+
194
+ ```bibtex
195
+ @software{eminoglu_cohortproj,
196
+ author = {Eminoğlu, Yusuf},
197
+ title = {cohortproj},
198
+ year = {2026},
199
+ version = {0.1.0},
200
+ url = {https://gitlab.com/geophilo1/cohortproj_sdk}
201
+ }
202
+ ```
203
+
204
+ ## License
205
+
206
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,101 @@
1
+ """cohortproj quickstart with synthetic schedules (no input files needed).
2
+
3
+ Run: python examples/quickstart.py
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import numpy as np
9
+
10
+ import cohortproj as cp
11
+
12
+
13
+ def gompertz(ages: np.ndarray, a: float, infant: float) -> np.ndarray:
14
+ m = a * np.exp(0.1 * ages)
15
+ m[0] = infant
16
+ m[1] = 0.001
17
+ return m
18
+
19
+
20
+ def main() -> None:
21
+ # 1. Abridged life tables (0, 1-4, 5-9, ..., 85+) with Coale-Demeny nax at young ages
22
+ lt_ages = cp.abridged_ages(85)
23
+ female_lt = cp.life_table(
24
+ gompertz(lt_ages, 5e-5, 0.015), lt_ages, nax="coale-demeny", sex="female"
25
+ )
26
+ male_lt = cp.life_table(gompertz(lt_ages, 8e-5, 0.018), lt_ages, nax="coale-demeny", sex="male")
27
+ print(f"e0 female {female_lt.e0:.1f}, male {male_lt.e0:.1f}")
28
+
29
+ # 2. Fertility on 5-year groups 0-4 ... 85+, scaled to a TFR of 1.9
30
+ ages = cp.regular_ages(5, 85)
31
+ shape = cp.expand_asfr([0.02, 0.09, 0.12, 0.09, 0.04, 0.01, 0.002], 15, ages)
32
+ asfr = cp.scale_to_tfr(shape, 1.9)
33
+ srb = 1.05 # the user's assumption
34
+ print(f"TFR {cp.tfr(asfr):.2f}, NRR {cp.nrr(asfr, female_lt, srb):.3f}")
35
+
36
+ # 3. Base population and net migration (counts per 5-year step, end-of-step ages)
37
+ female = np.linspace(42_000, 6_000, ages.size)
38
+ male = female * np.linspace(1.04, 0.7, ages.size)
39
+ migration = np.zeros(ages.size)
40
+ migration[4:8] = [1_500, 2_000, 1_200, 600]
41
+
42
+ proj = cp.project(
43
+ female,
44
+ ages,
45
+ female_lt,
46
+ asfr,
47
+ srb,
48
+ steps=6,
49
+ male=male,
50
+ male_life=male_lt,
51
+ migration_female=migration,
52
+ migration_male=migration,
53
+ timing="half",
54
+ start_year=2025,
55
+ )
56
+ for row in proj.summary():
57
+ print(
58
+ f"{row['year_start']:.0f}-{row['year_end']:.0f}: "
59
+ f"{row['population_start']:,.0f} -> {row['population_end']:,.0f} "
60
+ f"(births {row['births']:,.0f}, deaths {row['deaths']:,.0f})"
61
+ )
62
+ final = cp.age_structure(
63
+ proj.female[-1], ages, proj.male[-1] if proj.male is not None else None
64
+ )
65
+ print(
66
+ f"2055 median age {final.median_age:.1f}, old-age dependency {final.dependency['old']:.1f}"
67
+ )
68
+
69
+ # 4. Leslie matrix, stable population and Lotka's r
70
+ stable = cp.stable_population(cp.leslie_matrix(female_lt, asfr, srb))
71
+ lotka = cp.intrinsic_growth_rate(asfr, female_lt, srb)
72
+ print(f"Intrinsic r: Leslie {stable.growth_rate:.5f}, Lotka {lotka['r']:.5f}")
73
+
74
+ # 5. Scenarios
75
+ base = cp.ProjectionInputs(
76
+ female=female,
77
+ ages=ages,
78
+ female_life=female_lt,
79
+ asfr=asfr,
80
+ srb=srb,
81
+ steps=6,
82
+ male=male,
83
+ male_life=male_lt,
84
+ migration_female=migration,
85
+ migration_male=migration,
86
+ start_year=2025,
87
+ )
88
+ runs = cp.run_scenarios(
89
+ base,
90
+ [
91
+ cp.Scenario("medium"),
92
+ cp.Scenario("low fertility", tfr=cp.linear_path(1.9, 1.3, 6)),
93
+ cp.Scenario("no migration", migration_factor=0.0),
94
+ ],
95
+ )
96
+ for name, total in runs.final("total").items():
97
+ print(f" {name:14s} 2055 population {total:,.0f}")
98
+
99
+
100
+ if __name__ == "__main__":
101
+ main()