oda-data 2.8.0__tar.gz → 2.8.2__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 (80) hide show
  1. {oda_data-2.8.0 → oda_data-2.8.2}/PKG-INFO +1 -1
  2. {oda_data-2.8.0 → oda_data-2.8.2}/pyproject.toml +1 -1
  3. {oda_data-2.8.0 → oda_data-2.8.2}/pyproject.toml.orig +1 -1
  4. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/api/constants.py +8 -1
  5. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/api/sources.py +0 -15
  6. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/crs/crs_functions.py +11 -6
  7. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/research/policy_markers.py +51 -33
  8. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/research/sector_imputations.py +11 -2
  9. {oda_data-2.8.0 → oda_data-2.8.2}/README.md +0 -0
  10. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/__init__.py +0 -0
  11. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/api/__init__.py +0 -0
  12. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/api/oecd.py +0 -0
  13. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/api/representations.py +0 -0
  14. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/cache/__init__.py +0 -0
  15. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/cache/_migrate.py +0 -0
  16. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/cache/api.py +0 -0
  17. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/cache/config.py +0 -0
  18. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/cache/types.py +0 -0
  19. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/clean_data/__init__.py +0 -0
  20. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/clean_data/channel_share_proxies.csv +0 -0
  21. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/clean_data/channels.py +0 -0
  22. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/clean_data/common.py +0 -0
  23. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/clean_data/crs_channel_mapping.csv +0 -0
  24. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/clean_data/crs_channel_mapping_vintage.json +0 -0
  25. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/clean_data/dtypes.py +0 -0
  26. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/clean_data/multilateral_channel_crosswalk.csv +0 -0
  27. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/clean_data/schema.py +0 -0
  28. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/clean_data/validation.py +0 -0
  29. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/config.py +0 -0
  30. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/__init__.py +0 -0
  31. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/common.py +0 -0
  32. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/crs/__init__.py +0 -0
  33. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/crs/common.py +0 -0
  34. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/crs/crs_indicators.json +0 -0
  35. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/crs/oecd.py +0 -0
  36. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/crs/one.py +0 -0
  37. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac1/__init__.py +0 -0
  38. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac1/common.py +0 -0
  39. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac1/dac1_functions.py +0 -0
  40. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac1/dac1_indicators.json +0 -0
  41. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac1/oecd.py +0 -0
  42. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac1/one.py +0 -0
  43. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac2a/__init__.py +0 -0
  44. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac2a/common.py +0 -0
  45. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac2a/dac2a_functions.py +0 -0
  46. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac2a/dac2a_indicators.json +0 -0
  47. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac2a/oecd.py +0 -0
  48. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/dac2a/one.py +0 -0
  49. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/indicator.py +0 -0
  50. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/research/__init__.py +0 -0
  51. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/research/eu.py +0 -0
  52. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/research/imputation_quality.py +0 -0
  53. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/indicators/research/imputation_shares.py +0 -0
  54. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/logger.py +0 -0
  55. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/oda_data_indicators.csv +0 -0
  56. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/crs_flow_types.json +0 -0
  57. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/crs_modalities.json +0 -0
  58. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/crs_policy_markers.json +0 -0
  59. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/crs_purpose.json +0 -0
  60. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/crs_type_of_finance.json +0 -0
  61. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/dac1_aid_flow_types.json +0 -0
  62. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/dac2a_aid_type.json +0 -0
  63. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/flow_types.json +0 -0
  64. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/indicators.json +0 -0
  65. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/key_columns.json +0 -0
  66. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/provider_groupings.json +0 -0
  67. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/recipient_groupings.json +0 -0
  68. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/settings/v1_indicators_mapping.json +0 -0
  69. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/tools/__init__.py +0 -0
  70. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/tools/cache.py +0 -0
  71. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/tools/compatibility.py +0 -0
  72. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/tools/gni.py +0 -0
  73. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/tools/groupings.py +0 -0
  74. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/tools/names/__init__.py +0 -0
  75. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/tools/names/add.py +0 -0
  76. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/tools/names/create_mapping.py +0 -0
  77. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/tools/names/crs_names.json +0 -0
  78. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/tools/names/dac1_names.json +0 -0
  79. {oda_data-2.8.0 → oda_data-2.8.2}/src/oda_data/tools/names/dac2a_names.json +0 -0
  80. {oda_data-2.8.0 → oda_data-2.8.2}/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.8.0
3
+ Version: 2.8.2
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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oda_data"
3
- version = "2.8.0"
3
+ version = "2.8.2"
4
4
  description = "A python package to work with Official Development Assistance data from the OECD DAC."
5
5
  requires-python = ">=3.11"
6
6
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oda_data"
3
- version = "2.8.0"
3
+ version = "2.8.2"
4
4
  description = "A python package to work with Official Development Assistance data from the OECD DAC."
5
5
  requires-python = ">=3.11"
6
6
 
@@ -13,7 +13,14 @@ MarkerScore = Literal[
13
13
  ]
14
14
 
15
15
  PolicyMarker = Literal[
16
- "gender", "environment", "nutrition", "disability", "biodiversity"
16
+ "gender",
17
+ "environment",
18
+ "climate_adaptation",
19
+ "climate_mitigation",
20
+ "biodiversity",
21
+ "desertification",
22
+ "disability",
23
+ "nutrition",
17
24
  ]
18
25
 
19
26
  CURRENCIES: dict[str, str] = {
@@ -18,7 +18,6 @@ from oda_data.clean_data.common import (
18
18
  convert_dot_stat_to_data_explorer_codes,
19
19
  )
20
20
  from oda_data.clean_data.schema import (
21
- CORE_CRS_MAPPING,
22
21
  AidDataSchema,
23
22
  ODASchema,
24
23
  )
@@ -42,20 +41,6 @@ from oda_data.tools.cache import (
42
41
  )
43
42
 
44
43
 
45
- def translate_cols_and_filters_to_raw(
46
- columns: list | None, filters: list[tuple] | None
47
- ) -> tuple[list | None, list[tuple] | None]:
48
- crs_mapping = {v: k for k, v in CORE_CRS_MAPPING.items()}
49
- cols = [crs_mapping.get(col, col) for col in columns] if columns else columns
50
- filters = (
51
- [(crs_mapping.get(col, col), op, val) for col, op, val in filters]
52
- if filters
53
- else None
54
- )
55
-
56
- return cols, filters
57
-
58
-
59
44
  def _filters_to_query(filters: list[tuple[str, str, list]]) -> str:
60
45
  """Transform a list of filters into a query string.
61
46
 
@@ -36,15 +36,20 @@ def _group_by_mapped_channel(df: pd.DataFrame) -> pd.DataFrame:
36
36
  def _rolling_period_total(df: pd.DataFrame, period_length: int = 3) -> pd.DataFrame:
37
37
  """Calculate a rolling total of `period_length` years.
38
38
 
39
- Thin wrapper around the shared, vectorised `rolling_window_total`
40
- (`oda_data.indicators.research.imputation_shares`), which replaced this
41
- function's own year-by-year deep-copy/concat loop. A (grouper, year)
42
- combination is only emitted once its group has a full `period_length`-
43
- year window within the years present in `df`.
39
+ Wraps the shared, vectorised `rolling_window_total`
40
+ (`oda_data.indicators.research.imputation_shares`). A (grouper, year)
41
+ combination is emitted once its group has a full `period_length`-year
42
+ window within the years present in `df`, and only when some value in that
43
+ window is nonzero. `rolling_window_total` fills a window in which the group
44
+ has no rows with zeros, and those rows would otherwise become zero-share
45
+ rows for years before or after the group existed.
44
46
  """
45
47
  values = list(crs_value_cols().values())
46
48
 
47
- return rolling_window_total(df, period_length=period_length, value_cols=values)
49
+ totals = rolling_window_total(df, period_length=period_length, value_cols=values)
50
+ has_value = totals[values].fillna(0).ne(0).any(axis=1)
51
+
52
+ return totals.loc[has_value].reset_index(drop=True)
48
53
 
49
54
 
50
55
  def _purpose_share(value_row: pd.DataFrame, value_col: str) -> pd.Series:
@@ -1,5 +1,6 @@
1
+ from typing import get_args
2
+
1
3
  import pandas as pd
2
- from pyarrow import ArrowInvalid
3
4
 
4
5
  from oda_data.api.constants import (
5
6
  EXTENDED_PROVIDER_PURPOSE_GROUPER,
@@ -8,18 +9,35 @@ from oda_data.api.constants import (
8
9
  Measure,
9
10
  PolicyMarker,
10
11
  )
11
- from oda_data.api.sources import translate_cols_and_filters_to_raw
12
12
  from oda_data.clean_data.common import convert_units
13
13
  from oda_data.clean_data.schema import ODASchema
14
14
 
15
-
16
- def _marker_score_map(marker: PolicyMarker | str) -> list[int] | None:
17
- return {
18
- "significant": [1],
19
- "principal": [2],
20
- "not_targeted": [0],
21
- "total_targeted": [1, 2],
22
- }.get(marker)
15
+ # CRS marker values kept by each score. Desertification also reports 3, a principal
16
+ # objective that supports an action programme to combat desertification. The
17
+ # not_screened and total_allocable scores have no values: not_screened keeps blank
18
+ # markers and total_allocable keeps every row.
19
+ _MARKER_SCORE_VALUES: dict[str, list[int] | None] = {
20
+ "significant": [1],
21
+ "principal": [2, 3],
22
+ "not_targeted": [0],
23
+ "total_targeted": [1, 2, 3],
24
+ "not_screened": None,
25
+ "total_allocable": None,
26
+ }
27
+
28
+
29
+ def _marker_score_map(marker_score: MarkerScore | str) -> list[int] | None:
30
+ """Return the CRS marker values a score keeps, or None if it filters no values.
31
+
32
+ Raises:
33
+ ValueError: If the score is not a MarkerScore.
34
+ """
35
+ if marker_score not in _MARKER_SCORE_VALUES:
36
+ raise ValueError(
37
+ f"Unknown marker_score {marker_score!r}; "
38
+ f"expected one of {list(_MARKER_SCORE_VALUES)}"
39
+ )
40
+ return _MARKER_SCORE_VALUES[marker_score]
23
41
 
24
42
 
25
43
  def _marker_modality_filter() -> list[tuple[str, str, list]]:
@@ -63,19 +81,30 @@ def bilateral_policy_marker(
63
81
  providers (list | int | None, optional): Providers to filter the data. Defaults to None.
64
82
  recipients (list | int | None, optional): Recipients to filter the data. Defaults to None.
65
83
  measure (Measure | str, optional): Measure type. Defaults to "gross_disbursement".
66
- marker (PolicyMarker): Policy marker. Options are: "gender", "environment", "nutrition",
67
- "disability", "biodiversity"
68
- marker_score (MarkerScore): Policy marker score.
69
- Options are: "significant", "principal", "not_targeted", "not_screened", "total_targeted", "total_allocable".
70
- oda_only (bool, optional): Whether to include only ODA-related data. Defaults to False.
84
+ marker (PolicyMarker): Policy marker. Options are: "gender", "environment",
85
+ "climate_adaptation", "climate_mitigation", "biodiversity", "desertification",
86
+ "disability", "nutrition".
87
+ marker_score (MarkerScore): Policy marker score. "principal", "significant",
88
+ "not_targeted" and "not_screened" (a blank marker) split allocable aid with no
89
+ overlap. "total_targeted" is principal plus significant, and "total_allocable"
90
+ is all allocable aid, screened or not.
91
+ oda_only (bool, optional): Whether to include only ODA flows. Defaults to True.
71
92
  currency (str, optional): Target currency. Defaults to "USD".
72
93
  base_year (int | None, optional): Base year for conversion. Defaults to None.
73
94
 
74
95
  Returns:
75
96
  pd.DataFrame: Dataframe with spending by purpose.
97
+
98
+ Raises:
99
+ ValueError: If the marker or the score is not one of the options above.
76
100
  """
77
101
  from oda_data.api.sources import CRSData
78
102
 
103
+ if marker not in get_args(PolicyMarker):
104
+ raise ValueError(
105
+ f"Unknown marker {marker!r}; expected one of {list(get_args(PolicyMarker))}"
106
+ )
107
+
79
108
  # Get the relevant measure
80
109
  measure = MEASURES["CRS"][measure]["column"]
81
110
 
@@ -108,25 +137,14 @@ def bilateral_policy_marker(
108
137
  crs = CRSData(providers=providers, years=years, recipients=recipients)
109
138
 
110
139
  # Read the data and group by provider and purpose
140
+ data = crs.read(
141
+ columns=[*grouper, measure, marker],
142
+ additional_filters=filters,
143
+ using_bulk_download=True,
144
+ )
111
145
 
112
- cols = [*grouper, measure]
113
-
114
- try:
115
- data = crs.read(
116
- columns=cols,
117
- additional_filters=filters,
118
- using_bulk_download=True,
119
- )
120
- except ArrowInvalid:
121
- c, f = translate_cols_and_filters_to_raw(cols, filters)
122
- data = crs.read(
123
- columns=c,
124
- additional_filters=f,
125
- using_bulk_download=True,
126
- )
127
-
128
- # if marker is not_screened, we need to filter out the screened data
129
- if marker == "not_screened":
146
+ # Keep only unscreened activities (blank marker)
147
+ if marker_score == "not_screened":
130
148
  data = data.loc[lambda d: d[marker].isna()]
131
149
 
132
150
  data = (
@@ -208,15 +208,18 @@ def _read_core_contributions(
208
208
 
209
209
 
210
210
  def _get_channel_share_proxies() -> pd.DataFrame:
211
- """Read the reviewed proxy table.
211
+ """Read the proxy table, keeping only rows marked `reviewed=true`.
212
212
 
213
213
  Returns:
214
214
  pd.DataFrame: columns channel_code, channel_name, proxy_channel_code,
215
215
  proxy_type ("parent_fund" or "fixed_purpose"), fixed_purpose_code,
216
216
  rationale, reviewed. `proxy_channel_code` is null for `fixed_purpose`
217
217
  rows; `fixed_purpose_code` is null for `parent_fund` rows.
218
+
219
+ Raises:
220
+ ValueError: If the table has no `reviewed` column.
218
221
  """
219
- return pd.read_csv(
222
+ proxies = pd.read_csv(
220
223
  ODAPaths.cleaning / PROXY_TABLE_FILE,
221
224
  dtype={
222
225
  ODASchema.CHANNEL_CODE: "Int64",
@@ -224,6 +227,12 @@ def _get_channel_share_proxies() -> pd.DataFrame:
224
227
  "fixed_purpose_code": "Int64",
225
228
  },
226
229
  )
230
+ if "reviewed" not in proxies.columns:
231
+ raise ValueError(
232
+ f"{PROXY_TABLE_FILE} has no 'reviewed' column, so unreviewed proxies "
233
+ "cannot be told apart from reviewed ones."
234
+ )
235
+ return proxies.loc[proxies["reviewed"].eq(True)].reset_index(drop=True)
227
236
 
228
237
 
229
238
  def _apply_proxies(
File without changes