Skip to content

Commit

Permalink
Merge branch 'main' into fix-usa-bug
Browse files Browse the repository at this point in the history
  • Loading branch information
jm-rivera committed Nov 11, 2024
2 parents 0e53d1c + 55b30c0 commit b228613
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 7 deletions.
4 changes: 2 additions & 2 deletions pydeflate/core/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,9 @@ def _calculate_deflator_value(
pd.Series: Series with combined deflator values.
"""
return (
(10_000 * exchange_rate) / (price_def * exchange_def)
(exchange_def * exchange_rate) / price_def
if self.to_current
else (price_def * exchange_def) / (10_000 * exchange_rate)
else price_def / (exchange_def * exchange_rate)
)

def _merge_components(self, df: pd.DataFrame, other: pd.DataFrame):
Expand Down
8 changes: 4 additions & 4 deletions pydeflate/core/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,17 @@ def exchange_rate(self, from_currency: str, to_currency: str):
pydeflate_EXCHANGE=lambda d: d.pydeflate_EXCHANGE / d.pydeflate_EXCHANGE_to
)

# Drop unnecessary columns
merged = merged.drop(columns=merged.filter(regex="_to$").columns, axis=1)

# Compute the exchange rate deflator
merged = compute_exchange_deflator(
merged,
exchange="pydeflate_EXCHANGE",
exchange="pydeflate_EXCHANGE_to",
year="pydeflate_year",
grouper=["pydeflate_entity_code", "pydeflate_iso3"],
)

# Drop unnecessary columns
merged = merged.drop(columns=merged.filter(regex="_to$").columns, axis=1)

return merged

def exchange(
Expand Down
11 changes: 10 additions & 1 deletion pydeflate/sources/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,13 @@ def _add_deflator(
exchange: str = "EXCHANGE",
year: str = "year",
) -> pd.DataFrame:

# if needed, clean exchange name
if exchange.endswith("_to") or exchange.endswith("_from"):
exchange_name = exchange.rsplit("_", 1)[0]
else:
exchange_name = exchange

# Identify the base year for the deflator
if measure is not None:
base_year = identify_base_year(group, measure=measure, year=year)
Expand All @@ -222,7 +229,9 @@ def _add_deflator(

# If base value is found and valid, calculate the deflator
if base_value.size > 0 and pd.notna(base_value[0]):
group[f"{exchange}_D"] = round(100 * group[exchange] / base_value[0], 6)
group[f"{exchange_name}_D"] = round(
100 * group[exchange] / base_value[0], 6
)

return group

Expand Down
146 changes: 146 additions & 0 deletions tests/test_dac_totals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import pandas as pd

from pydeflate import oecd_dac_deflate

data = {
"year": [2020, 2021, 2022, 2023],
"indicator": ["total_oda_official_definition"] * 4,
"donor_code": [12] * 4,
"currency": ["USD"] * 4,
"prices": ["current"] * 4,
"value": [18568.19, 15712.01, 15761.81, 19110.59],
"expected_value": [16312, 12885, 13717, 15374],
}

df = pd.DataFrame(data)

data_usd = {
"year": [2020, 2021, 2022, 2023],
"indicator": ["total_oda_official_definition"] * 4,
"donor_code": [302] * 4,
"currency": ["USD"] * 4,
"prices": ["current"] * 4,
"value": [35576.31, 47804.8, 60522.41, 66040.03],
"expected_value": [41326, 53097, 62800, 66040],
}


df_usd = pd.DataFrame(data_usd)

data_eur = {
"year": [2020, 2021, 2022, 2023],
"indicator": ["total_oda_official_definition"] * 4,
"donor_code": [742] * 4,
"currency": ["EUR"] * 4,
"prices": ["current"] * 4,
"value": [1974, 2429, 2672, 2986],
"expected_value": [1995, 2403, 2619, 2896],
}
df_eur = pd.DataFrame(data_eur)


data_can = {
"year": [2020, 2021, 2022, 2023],
"indicator": ["total_oda_official_definition"] * 4,
"donor_code": [5] * 4,
"currency": ["CAD"] * 4,
"prices": ["current"] * 4,
"value": [38503, 41707, 46393, 49495],
"expected_value": [31404, 34049, 38957, 36682],
}
df_can = pd.DataFrame(data_can)


data_lcu = {
"year": [2020, 2021, 2022, 2023],
"indicator": ["total_oda_official_definition"] * 4,
"donor_code": [4] * 4,
"currency": ["EUR"] * 4,
"prices": ["current"] * 4,
"value": [12394, 13112, 15228, 14266],
"expected_value": [13625, 14210, 16031, 14266],
}
df_lcu = pd.DataFrame(data_lcu)


def run_constant_test(
data,
source_currency,
target_currency,
tolerance=0.05,
base_year=2023,
id_column="donor_code",
target_value_column="value",
):
"""
Runs a test for deflation calculation with given parameters and tolerance.
Args:
data (pd.DataFrame): The input DataFrame containing the data to deflate.
source_currency (str): The source currency code.
target_currency (str): The target currency code.
tolerance (float, optional): The allowed tolerance for deviation. Defaults to 0.05.
base_year (int, optional): The base year for deflation. Defaults to 2023.
id_column (str, optional): Column name for IDs. Defaults to "donor_code".
target_value_column (str, optional): Column name for the target value. Defaults to "value".
Raises:
AssertionError: If any row exceeds the tolerance threshold.
"""
# Perform the deflation calculation
test_df = oecd_dac_deflate(
data=data,
base_year=base_year,
source_currency=source_currency,
target_currency=target_currency,
id_column=id_column,
use_source_codes=True,
target_value_column=target_value_column,
)

# Calculate the percentage deviation
deviations = abs(
(test_df[target_value_column] - test_df["expected_value"])
/ test_df["expected_value"]
)

# Filter out rows where value is NaN and deviations exceed tolerance
mask = test_df[target_value_column].notna() & (deviations >= tolerance)
failing_rows = test_df[mask]

# Assert that no rows exceed the tolerance
assert failing_rows.empty, (
f"Deviation exceeded {tolerance*100:.2f}% in the following "
f"donors:\n{failing_rows[id_column].unique()}"
)


# Define test cases with parameters
def test_to_constant():
run_constant_test(
data=df, source_currency="USA", target_currency="GBP", tolerance=0.01
)


def test_to_constant_usd():
run_constant_test(
data=df_usd, source_currency="USA", target_currency="USA", tolerance=0.01
)


def test_to_constant_eur():
run_constant_test(
data=df_eur, source_currency="EUR", target_currency="EUR", tolerance=0.05
)


def test_to_constant_can():
run_constant_test(
data=df_can, source_currency="CAN", target_currency="USA", tolerance=0.05
)


def test_to_constant_lcu():
run_constant_test(
data=df_lcu, source_currency="EUR", target_currency="LCU", tolerance=0.05
)

0 comments on commit b228613

Please sign in to comment.