oda-data 2.7.0__tar.gz → 2.7.1__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.
- {oda_data-2.7.0 → oda_data-2.7.1}/PKG-INFO +1 -1
- oda_data-2.7.1/pyproject.toml +217 -0
- oda_data-2.7.0/pyproject.toml → oda_data-2.7.1/pyproject.toml.orig +1 -1
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/api/sources.py +73 -2
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/research/sector_imputations.py +23 -1
- {oda_data-2.7.0 → oda_data-2.7.1}/README.md +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/__init__.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/api/__init__.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/api/constants.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/api/oecd.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/api/representations.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/cache/__init__.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/cache/_migrate.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/cache/api.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/cache/config.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/cache/types.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/__init__.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/channels.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/common.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/crs_channel_mapping.csv +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/dtypes.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/schema.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/validation.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/config.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/__init__.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/common.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/__init__.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/common.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/crs_functions.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/crs_indicators.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/oecd.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/one.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/__init__.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/common.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/dac1_functions.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/dac1_indicators.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/oecd.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/one.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/__init__.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/common.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/dac2a_functions.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/dac2a_indicators.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/oecd.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/one.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/indicator.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/research/__init__.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/research/eu.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/research/policy_markers.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/logger.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/oda_data_indicators.csv +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/crs_flow_types.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/crs_modalities.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/crs_policy_markers.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/crs_purpose.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/crs_type_of_finance.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/dac1_aid_flow_types.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/dac2a_aid_type.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/flow_types.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/indicators.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/key_columns.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/provider_groupings.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/recipient_groupings.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/v1_indicators_mapping.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/__init__.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/cache.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/compatibility.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/gni.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/groupings.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/__init__.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/add.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/create_mapping.py +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/crs_names.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/dac1_names.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/dac2a_names.json +0 -0
- {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/sector_lists.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: oda_data
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.1
|
|
4
4
|
Summary: A python package to work with Official Development Assistance data from the OECD DAC.
|
|
5
5
|
Author: Jorge Rivera, Miguel Haro Ruiz
|
|
6
6
|
Author-email: Jorge Rivera <jorge.rivera@one.org>, Miguel Haro Ruiz <miguel.haroruiz@one.org>
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "oda_data"
|
|
3
|
+
version = "2.7.1"
|
|
4
|
+
description = "A python package to work with Official Development Assistance data from the OECD DAC."
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
classifiers = [
|
|
8
|
+
"Development Status :: 5 - Production/Stable",
|
|
9
|
+
"Intended Audience :: Developers",
|
|
10
|
+
"Intended Audience :: Science/Research",
|
|
11
|
+
"License :: OSI Approved :: MIT License",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3.11",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
17
|
+
"Topic :: Scientific/Engineering",
|
|
18
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"cachetools>=6.2.0",
|
|
22
|
+
"filelock>=3.20.0",
|
|
23
|
+
"oda-reader>=1.6.0",
|
|
24
|
+
"platformdirs>=4.0.0",
|
|
25
|
+
"pandas[excel,parquet,performance]>=2.2.0",
|
|
26
|
+
"pydeflate>=2.3.3",
|
|
27
|
+
"reader>=3.18",
|
|
28
|
+
"thefuzz>=0.22.1",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.license]
|
|
32
|
+
text = "MIT"
|
|
33
|
+
|
|
34
|
+
[[project.authors]]
|
|
35
|
+
name = "Jorge Rivera"
|
|
36
|
+
email = "jorge.rivera@one.org"
|
|
37
|
+
|
|
38
|
+
[[project.authors]]
|
|
39
|
+
name = "Miguel Haro Ruiz"
|
|
40
|
+
email = "miguel.haroruiz@one.org"
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://github.com/ONEcampaign/oda_data_package"
|
|
44
|
+
Repository = "https://github.com/ONEcampaign/oda_data_package"
|
|
45
|
+
Documentation = "https://github.com/ONEcampaign/oda_data_package"
|
|
46
|
+
|
|
47
|
+
[build-system]
|
|
48
|
+
requires = ["uv_build>=0.11.8,<0.12"]
|
|
49
|
+
build-backend = "uv_build"
|
|
50
|
+
|
|
51
|
+
[dependency-groups]
|
|
52
|
+
dev = [
|
|
53
|
+
"pre-commit>=4.5.0",
|
|
54
|
+
"ruff>=0.14.9",
|
|
55
|
+
"ty>=0.0.33",
|
|
56
|
+
"mkdocs-material>=9.7.0",
|
|
57
|
+
]
|
|
58
|
+
test = [
|
|
59
|
+
"pytest>=8.0.0",
|
|
60
|
+
"pytest-mock>=3.12.0",
|
|
61
|
+
"pytest-cov>=4.1.0",
|
|
62
|
+
"pytest-xdist>=3.5.0",
|
|
63
|
+
"freezegun>=1.4.0",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[tool.ruff]
|
|
67
|
+
line-length = 88
|
|
68
|
+
target-version = "py311"
|
|
69
|
+
extend-include = ["*.ipynb"]
|
|
70
|
+
exclude = [
|
|
71
|
+
".git",
|
|
72
|
+
".venv",
|
|
73
|
+
"venv",
|
|
74
|
+
"__pycache__",
|
|
75
|
+
"build",
|
|
76
|
+
"dist",
|
|
77
|
+
"*.egg-info",
|
|
78
|
+
".pytest_cache",
|
|
79
|
+
"htmlcov",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
[tool.ruff.lint]
|
|
83
|
+
select = [
|
|
84
|
+
"E",
|
|
85
|
+
"W",
|
|
86
|
+
"F",
|
|
87
|
+
"I",
|
|
88
|
+
"N",
|
|
89
|
+
"UP",
|
|
90
|
+
"B",
|
|
91
|
+
"C4",
|
|
92
|
+
"SIM",
|
|
93
|
+
"PL",
|
|
94
|
+
"RUF",
|
|
95
|
+
"ERA",
|
|
96
|
+
"PERF",
|
|
97
|
+
"ANN",
|
|
98
|
+
]
|
|
99
|
+
ignore = [
|
|
100
|
+
"E501",
|
|
101
|
+
"PLR0913",
|
|
102
|
+
"PLR0912",
|
|
103
|
+
"PLR0914",
|
|
104
|
+
"PLR0915",
|
|
105
|
+
"PLR2004",
|
|
106
|
+
"PLC0415",
|
|
107
|
+
"ANN401",
|
|
108
|
+
]
|
|
109
|
+
fixable = ["ALL"]
|
|
110
|
+
unfixable = []
|
|
111
|
+
|
|
112
|
+
[tool.ruff.lint.per-file-ignores]
|
|
113
|
+
"tests/**/*.py" = [
|
|
114
|
+
"ANN",
|
|
115
|
+
"E501",
|
|
116
|
+
"PLR2004",
|
|
117
|
+
"RUF002",
|
|
118
|
+
"RUF003",
|
|
119
|
+
"PLW0603",
|
|
120
|
+
"ERA001",
|
|
121
|
+
]
|
|
122
|
+
"src/oda_data/__init__.py" = [
|
|
123
|
+
"PLW0603",
|
|
124
|
+
"PLW0602",
|
|
125
|
+
"F811",
|
|
126
|
+
]
|
|
127
|
+
"src/oda_data/cache/config.py" = ["PLW0603"]
|
|
128
|
+
"src/oda_data/cache/api.py" = ["PLW0603"]
|
|
129
|
+
"src/oda_data/tools/cache.py" = ["PLW2901"]
|
|
130
|
+
"src/oda_data/api/oecd.py" = ["PLR0912"]
|
|
131
|
+
"src/oda_data/indicators/crs/crs_functions.py" = ["B023"]
|
|
132
|
+
"src/oda_data/indicators/research/sector_imputations.py" = ["B023"]
|
|
133
|
+
"src/oda_data/tools/sector_lists.py" = ["N816"]
|
|
134
|
+
|
|
135
|
+
[tool.ruff.lint.pydocstyle]
|
|
136
|
+
convention = "google"
|
|
137
|
+
|
|
138
|
+
[tool.ruff.format]
|
|
139
|
+
quote-style = "double"
|
|
140
|
+
indent-style = "space"
|
|
141
|
+
skip-magic-trailing-comma = false
|
|
142
|
+
line-ending = "auto"
|
|
143
|
+
|
|
144
|
+
[tool.ty.environment]
|
|
145
|
+
python-version = "3.11"
|
|
146
|
+
|
|
147
|
+
[tool.ty.src]
|
|
148
|
+
include = ["src/oda_data"]
|
|
149
|
+
|
|
150
|
+
[tool.ty.analysis]
|
|
151
|
+
replace-imports-with-any = [
|
|
152
|
+
"pandas",
|
|
153
|
+
"pandas.**",
|
|
154
|
+
"numpy",
|
|
155
|
+
"numpy.**",
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
[tool.ty.rules]
|
|
159
|
+
possibly-missing-attribute = "ignore"
|
|
160
|
+
possibly-unresolved-reference = "ignore"
|
|
161
|
+
|
|
162
|
+
[tool.pytest.ini_options]
|
|
163
|
+
testpaths = ["tests"]
|
|
164
|
+
python_files = ["test_*.py"]
|
|
165
|
+
python_classes = ["Test*"]
|
|
166
|
+
python_functions = ["test_*"]
|
|
167
|
+
addopts = """
|
|
168
|
+
-v
|
|
169
|
+
--strict-markers
|
|
170
|
+
--tb=short
|
|
171
|
+
--cov=src/oda_data
|
|
172
|
+
--cov-report=term-missing
|
|
173
|
+
--cov-report=html
|
|
174
|
+
--cov-branch
|
|
175
|
+
-m "not integration and not network"
|
|
176
|
+
"""
|
|
177
|
+
markers = [
|
|
178
|
+
"""slow: marks tests as slow (deselect with '-m "not slow"')""",
|
|
179
|
+
"integration: marks tests that call real OECD API (deselected by default)",
|
|
180
|
+
"network: marks tests requiring live network access (opt-in via RUN_NETWORK_TESTS=1; deselected by default)",
|
|
181
|
+
"unit: marks tests as unit tests",
|
|
182
|
+
"cache: marks tests related to caching functionality",
|
|
183
|
+
"validation: marks tests related to input validation",
|
|
184
|
+
"cleaning: marks tests related to data cleaning",
|
|
185
|
+
]
|
|
186
|
+
norecursedirs = [
|
|
187
|
+
".git",
|
|
188
|
+
".venv",
|
|
189
|
+
"venv",
|
|
190
|
+
"__pycache__",
|
|
191
|
+
"*.egg-info",
|
|
192
|
+
".pytest_cache",
|
|
193
|
+
"htmlcov",
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
[tool.coverage.run]
|
|
197
|
+
source = ["src/oda_data"]
|
|
198
|
+
branch = true
|
|
199
|
+
omit = [
|
|
200
|
+
"*/tests/*",
|
|
201
|
+
"*/__init__.py",
|
|
202
|
+
"*/config.py",
|
|
203
|
+
"*/logger.py",
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
[tool.coverage.report]
|
|
207
|
+
precision = 2
|
|
208
|
+
show_missing = true
|
|
209
|
+
exclude_lines = [
|
|
210
|
+
"pragma: no cover",
|
|
211
|
+
"def __repr__",
|
|
212
|
+
"raise AssertionError",
|
|
213
|
+
"raise NotImplementedError",
|
|
214
|
+
"if __name__ == .__main__.:",
|
|
215
|
+
"if TYPE_CHECKING:",
|
|
216
|
+
"@abstractmethod",
|
|
217
|
+
]
|
|
@@ -308,6 +308,17 @@ class DACSource(Source):
|
|
|
308
308
|
)
|
|
309
309
|
self.de_sectors = check_strings(sectors)
|
|
310
310
|
|
|
311
|
+
def _extra_hash_components(self) -> list[tuple]:
|
|
312
|
+
"""Extra (name, predicate, value) tuples folded into the cache-key hash.
|
|
313
|
+
|
|
314
|
+
They bypass the read-filter path, never reaching a parquet predicate
|
|
315
|
+
or a query string. Folding them into the hash lets a constructor
|
|
316
|
+
flag that changes row-level behaviour, without being expressed as a
|
|
317
|
+
filter tuple, still produce a different cache key. Subclasses
|
|
318
|
+
override this; the default is a no-op.
|
|
319
|
+
"""
|
|
320
|
+
return []
|
|
321
|
+
|
|
311
322
|
def _get_filtered_download_filters(self) -> dict:
|
|
312
323
|
"""Generates a dictionary of filters for downloading the dataset.
|
|
313
324
|
|
|
@@ -438,7 +449,9 @@ class DACSource(Source):
|
|
|
438
449
|
"""
|
|
439
450
|
# Create filters and generate cache key
|
|
440
451
|
filters = self._get_read_filters(additional_filters=additional_filters)
|
|
441
|
-
param_hash = generate_param_hash(
|
|
452
|
+
param_hash = generate_param_hash(
|
|
453
|
+
(filters if filters else []) + self._extra_hash_components()
|
|
454
|
+
)
|
|
442
455
|
|
|
443
456
|
# When refresh=True, skip the upper cache tiers entirely so the bulk
|
|
444
457
|
# fetcher's refresh=True actually re-downloads and the upper tiers see
|
|
@@ -640,6 +653,7 @@ class CRSData(DACSource):
|
|
|
640
653
|
years: list[int] | range | int | None = None,
|
|
641
654
|
providers: list[int] | int | None = None,
|
|
642
655
|
recipients: list[int] | int | None = None,
|
|
656
|
+
exclude_multilateral_core: bool = True,
|
|
643
657
|
) -> None:
|
|
644
658
|
"""
|
|
645
659
|
Initialize CRSData.
|
|
@@ -648,9 +662,17 @@ class CRSData(DACSource):
|
|
|
648
662
|
years (list[int] | range): List or range of years to filter.
|
|
649
663
|
providers (Optional[list[int]]): List of provider codes.
|
|
650
664
|
recipients (Optional[list[int]]): List of recipient codes.
|
|
665
|
+
exclude_multilateral_core (bool): Whether to exclude CRS rows that report
|
|
666
|
+
a donor's core (unearmarked) contribution to a multilateral
|
|
667
|
+
organisation (``bi_multi == 2``). The OECD's CRS changelog states
|
|
668
|
+
these rows must be excluded to avoid double-counting bilateral
|
|
669
|
+
flows. Defaults to True. Rows with a missing ``bi_multi`` value
|
|
670
|
+
are always kept. Pass False to get the raw CRS totals including
|
|
671
|
+
those core-contribution rows.
|
|
651
672
|
"""
|
|
652
673
|
super().__init__()
|
|
653
674
|
self._init_filters(years=years, providers=providers, recipients=recipients)
|
|
675
|
+
self.exclude_multilateral_core = exclude_multilateral_core
|
|
654
676
|
self._param_hash = None
|
|
655
677
|
|
|
656
678
|
def _create_bulk_fetcher(self) -> Callable[[Path], None]:
|
|
@@ -660,6 +682,55 @@ class CRSData(DACSource):
|
|
|
660
682
|
"""
|
|
661
683
|
return create_crs_bulk_fetcher()
|
|
662
684
|
|
|
685
|
+
def _extra_hash_components(self) -> list[tuple]:
|
|
686
|
+
return [("exclude_multilateral_core", "==", self.exclude_multilateral_core)]
|
|
687
|
+
|
|
688
|
+
def _exclude_core_contributions(self, df: pd.DataFrame) -> pd.DataFrame:
|
|
689
|
+
"""Drop rows reporting a donor's core contribution to a multilateral organisation.
|
|
690
|
+
|
|
691
|
+
No-op when ``exclude_multilateral_core`` is False, or when the ``bi_multi``
|
|
692
|
+
column isn't present (e.g. it was excluded from a column projection).
|
|
693
|
+
Rows with a missing ``bi_multi`` value are kept. A plain ``!= 2`` filter
|
|
694
|
+
would drop them, since a null comparison is null rather than true.
|
|
695
|
+
"""
|
|
696
|
+
if not self.exclude_multilateral_core:
|
|
697
|
+
return df
|
|
698
|
+
column = self.schema.BI_MULTI
|
|
699
|
+
if column not in df.columns:
|
|
700
|
+
return df
|
|
701
|
+
return df.loc[(df[column] != 2) | df[column].isna()].reset_index(drop=True)
|
|
702
|
+
|
|
703
|
+
def _fetch_from_bulk_cache(
|
|
704
|
+
self,
|
|
705
|
+
filters: list[tuple] | None,
|
|
706
|
+
columns: list | None,
|
|
707
|
+
*,
|
|
708
|
+
refresh: bool = False,
|
|
709
|
+
) -> pd.DataFrame:
|
|
710
|
+
"""Read from the bulk cache, then apply the core-contribution exclusion.
|
|
711
|
+
|
|
712
|
+
``bi_multi`` is read even when not in the requested ``columns``, so the
|
|
713
|
+
exclusion can be applied before column projection, then dropped again if
|
|
714
|
+
the caller didn't ask for it.
|
|
715
|
+
"""
|
|
716
|
+
read_columns = columns
|
|
717
|
+
added_bi_multi = False
|
|
718
|
+
if (
|
|
719
|
+
self.exclude_multilateral_core
|
|
720
|
+
and columns is not None
|
|
721
|
+
and self.schema.BI_MULTI not in columns
|
|
722
|
+
):
|
|
723
|
+
read_columns = [*columns, self.schema.BI_MULTI]
|
|
724
|
+
added_bi_multi = True
|
|
725
|
+
|
|
726
|
+
df = super()._fetch_from_bulk_cache(filters, read_columns, refresh=refresh)
|
|
727
|
+
df = self._exclude_core_contributions(df)
|
|
728
|
+
|
|
729
|
+
if added_bi_multi:
|
|
730
|
+
df = df.drop(columns=[self.schema.BI_MULTI])
|
|
731
|
+
|
|
732
|
+
return df
|
|
733
|
+
|
|
663
734
|
def download(self) -> pd.DataFrame:
|
|
664
735
|
"""Downloads CRS data via API (filtered query).
|
|
665
736
|
|
|
@@ -672,7 +743,7 @@ class CRSData(DACSource):
|
|
|
672
743
|
filters=self._get_filtered_download_filters(),
|
|
673
744
|
)
|
|
674
745
|
logger.info("CRS data downloaded successfully.")
|
|
675
|
-
return clean_raw_df(df)
|
|
746
|
+
return self._exclude_core_contributions(clean_raw_df(df))
|
|
676
747
|
|
|
677
748
|
|
|
678
749
|
class MultiSystemData(DACSource):
|
|
@@ -145,9 +145,15 @@ def spending_by_purpose(
|
|
|
145
145
|
oda_only: bool = False,
|
|
146
146
|
currency: str = "USD",
|
|
147
147
|
base_year: int | None = None,
|
|
148
|
+
exclude_multilateral_core: bool = True,
|
|
148
149
|
) -> pd.DataFrame:
|
|
149
150
|
"""Retrieves and processes spending data by purpose.
|
|
150
151
|
|
|
152
|
+
By default, CRS rows that report a donor's core (unearmarked) contribution to a
|
|
153
|
+
multilateral organisation (`bi_multi == 2`) are excluded. Those contributions are
|
|
154
|
+
redistributed across purposes separately, by `imputed_multilateral_by_purpose`.
|
|
155
|
+
A caller that leaves them in this bilateral total would double count them.
|
|
156
|
+
|
|
151
157
|
Args:
|
|
152
158
|
years (list | int | range, optional): Years to filter the data. Defaults to None.
|
|
153
159
|
providers (list | int | None, optional): Providers to filter the data. Defaults to None.
|
|
@@ -155,6 +161,10 @@ def spending_by_purpose(
|
|
|
155
161
|
oda_only (bool, optional): Whether to include only ODA-related data. Defaults to False.
|
|
156
162
|
currency (str, optional): Target currency. Defaults to "USD".
|
|
157
163
|
base_year (int | None, optional): Base year for conversion. Defaults to None.
|
|
164
|
+
exclude_multilateral_core (bool, optional): Whether to exclude CRS rows
|
|
165
|
+
reporting a donor's core contribution to a multilateral organisation
|
|
166
|
+
(`bi_multi == 2`). Defaults to True. Pass False for a total that
|
|
167
|
+
includes those core-contribution rows.
|
|
158
168
|
|
|
159
169
|
Returns:
|
|
160
170
|
pd.DataFrame: Dataframe with spending by purpose.
|
|
@@ -175,7 +185,11 @@ def spending_by_purpose(
|
|
|
175
185
|
filters = [("category", "in", [10, 60])] if oda_only else []
|
|
176
186
|
|
|
177
187
|
# Set up the CRS data object
|
|
178
|
-
crs = CRSData(
|
|
188
|
+
crs = CRSData(
|
|
189
|
+
providers=providers,
|
|
190
|
+
years=years,
|
|
191
|
+
exclude_multilateral_core=exclude_multilateral_core,
|
|
192
|
+
)
|
|
179
193
|
|
|
180
194
|
# Read the data and group by provider and purpose
|
|
181
195
|
data = (
|
|
@@ -340,6 +354,14 @@ def imputed_multilateral_by_purpose(
|
|
|
340
354
|
) -> pd.DataFrame:
|
|
341
355
|
"""Computes imputed multilateral spending by purpose.
|
|
342
356
|
|
|
357
|
+
The multilateral spending shares this imputes onto (via
|
|
358
|
+
`multilateral_spending_shares_by_channel_and_purpose_smoothed`, which calls
|
|
359
|
+
`spending_by_purpose`) exclude CRS rows reporting a donor's core contribution to a
|
|
360
|
+
multilateral organisation (`bi_multi == 2`) by default. Those core contributions
|
|
361
|
+
are already captured here, on the `core_multilateral_contributions_by_provider`
|
|
362
|
+
leg, via MultiSystem data. Including them on the CRS leg too would double count
|
|
363
|
+
them.
|
|
364
|
+
|
|
343
365
|
Args:
|
|
344
366
|
years (list | int | range, optional): Years to filter the data. Defaults to None.
|
|
345
367
|
providers (list | int | None, optional): Providers to filter the data. Defaults to None.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|