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.
- cohortproj-0.1.0/CHANGELOG.md +30 -0
- cohortproj-0.1.0/CITATION.cff +21 -0
- cohortproj-0.1.0/LICENSE +21 -0
- cohortproj-0.1.0/MANIFEST.in +9 -0
- cohortproj-0.1.0/PKG-INFO +241 -0
- cohortproj-0.1.0/README.md +206 -0
- cohortproj-0.1.0/examples/quickstart.py +101 -0
- cohortproj-0.1.0/pyproject.toml +109 -0
- cohortproj-0.1.0/setup.cfg +4 -0
- cohortproj-0.1.0/src/cohortproj/__init__.py +134 -0
- cohortproj-0.1.0/src/cohortproj/__main__.py +7 -0
- cohortproj-0.1.0/src/cohortproj/_common.py +136 -0
- cohortproj-0.1.0/src/cohortproj/_version.py +1 -0
- cohortproj-0.1.0/src/cohortproj/cli.py +207 -0
- cohortproj-0.1.0/src/cohortproj/fertility.py +132 -0
- cohortproj-0.1.0/src/cohortproj/io.py +108 -0
- cohortproj-0.1.0/src/cohortproj/leslie.py +247 -0
- cohortproj-0.1.0/src/cohortproj/lifetable.py +394 -0
- cohortproj-0.1.0/src/cohortproj/multiregion.py +309 -0
- cohortproj-0.1.0/src/cohortproj/projection.py +364 -0
- cohortproj-0.1.0/src/cohortproj/py.typed +0 -0
- cohortproj-0.1.0/src/cohortproj/scenarios.py +224 -0
- cohortproj-0.1.0/src/cohortproj/structure.py +201 -0
- cohortproj-0.1.0/src/cohortproj/survival.py +72 -0
- cohortproj-0.1.0/src/cohortproj.egg-info/PKG-INFO +241 -0
- cohortproj-0.1.0/src/cohortproj.egg-info/SOURCES.txt +35 -0
- cohortproj-0.1.0/src/cohortproj.egg-info/dependency_links.txt +1 -0
- cohortproj-0.1.0/src/cohortproj.egg-info/entry_points.txt +2 -0
- cohortproj-0.1.0/src/cohortproj.egg-info/requires.txt +7 -0
- cohortproj-0.1.0/src/cohortproj.egg-info/top_level.txt +1 -0
- cohortproj-0.1.0/tests/conftest.py +43 -0
- cohortproj-0.1.0/tests/test_common.py +55 -0
- cohortproj-0.1.0/tests/test_io_cli.py +157 -0
- cohortproj-0.1.0/tests/test_leslie_multiregion.py +172 -0
- cohortproj-0.1.0/tests/test_lifetable.py +143 -0
- cohortproj-0.1.0/tests/test_projection.py +178 -0
- 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
|
cohortproj-0.1.0/LICENSE
ADDED
|
@@ -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,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
|
+
[](https://pypi.org/project/cohortproj/)
|
|
39
|
+
[](https://pypi.org/project/cohortproj/)
|
|
40
|
+
[](https://gitlab.com/geophilo1/cohortproj_sdk/-/pipelines)
|
|
41
|
+
[](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
|
+
[](https://pypi.org/project/cohortproj/)
|
|
4
|
+
[](https://pypi.org/project/cohortproj/)
|
|
5
|
+
[](https://gitlab.com/geophilo1/cohortproj_sdk/-/pipelines)
|
|
6
|
+
[](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()
|