Repository: pyjanitor-devs/pyjanitor

Issue:
[ENH] Inflation Adjustment
# Brief Description

<!-- Please provide a brief description of what you'd like to propose. -->

Adding inflation adjustment would be a nice addition to the `finance` tools in `pyjanitor`. The package already has an external API it uses for currency conversion, so adding another external API wouldn't be totally unreasonable.

[StatBureau](https://www.statbureau.org/en/inflation-api) provides an Inflation API, but it requires non-commercial use. There is probably something out there we can use.

Is there interest in adding this to `pyjanitor`, and is anyone eager to try implementing it?

# Example API

<!-- One of the selling points of pyjanitor is the API. Hence, we guard the API very carefully, and want to
make sure that it is accessible and understandable to many people. Please provide a few examples of what the API
of the new function you're proposing will look like. We have provided an example that you should modify. -->


```python
# inflation adjust currency given currency name and years
df.adjust_inflation(column_name=['USD_2018'],
                    currency='USD',
                    reference_year=1967,
                    current_year=2018,
                    new_column_name=['USD_1967'])

```



Discussion:
It looks like the World Bank's API could be used. The API is URL-based and doesn't require any API keys.  If you had the country and reference and current years, you could grab the values out of the XML at a URL like the following:
https://api.worldbank.org/v2/country/USA/indicator/FP.CPI.TOTL.ZG?date=2000:2001

Note that "USA" can be replaced with any desired country, "FP.CPI.TOTL.ZG" is the World Bank's indicator code for annual % inflation based on consumer prices, and "date=2000:2001" specifies that we want the inflation values for 2000 and 2001 (these years would be replaced by the reference and current years in the method call).

If that sounds like a reasonable approach, I'll try to find some time in the next couple of days to mock up the method.
Great find! And it appears to have a Creative Commons Attribution 4.0 (CC-BY 4.0) license, so everyone should be fine to use it. 

Looking forward to what you come up with!

Resolving patch:
diff --git a/janitor/finance.py b/janitor/finance.py
--- a/janitor/finance.py
+++ b/janitor/finance.py
@@ -50,6 +50,274 @@
     "ZAR",
 }
 
+# Dictionary of recognized World Bank countries and their abbreviations
+wb_country_dict = {
+    "Aruba": "ABW",
+    "Afghanistan": "AFG",
+    "Angola": "AGO",
+    "Albania": "ALB",
+    "Andorra": "AND",
+    "Arab World": "ARB",
+    "United Arab Emirates": "ARE",
+    "Argentina": "ARG",
+    "Armenia": "ARM",
+    "American Samoa": "ASM",
+    "Antigua and Barbuda": "ATG",
+    "Australia": "AUS",
+    "Austria": "AUT",
+    "Azerbaijan": "AZE",
+    "Burundi": "BDI",
+    "Belgium": "BEL",
+    "Benin": "BEN",
+    "Burkina Faso": "BFA",
+    "Bangladesh": "BGD",
+    "Bulgaria": "BGR",
+    "Bahrain": "BHR",
+    "Bahamas, The": "BHS",
+    "Bosnia and Herzegovina": "BIH",
+    "Belarus": "BLR",
+    "Belize": "BLZ",
+    "Bermuda": "BMU",
+    "Bolivia": "BOL",
+    "Brazil": "BRA",
+    "Barbados": "BRB",
+    "Brunei Darussalam": "BRN",
+    "Bhutan": "BTN",
+    "Botswana": "BWA",
+    "Central African Republic": "CAF",
+    "Canada": "CAN",
+    "Central Europe and the Baltics": "CEB",
+    "Switzerland": "CHE",
+    "Channel Islands": "CHI",
+    "Chile": "CHL",
+    "China": "CHN",
+    "Cote d'Ivoire": "CIV",
+    "Cameroon": "CMR",
+    "Congo, Dem. Rep.": "COD",
+    "Congo, Rep.": "COG",
+    "Colombia": "COL",
+    "Comoros": "COM",
+    "Cabo Verde": "CPV",
+    "Costa Rica": "CRI",
+    "Caribbean small states": "CSS",
+    "Cuba": "CUB",
+    "Curacao": "CUW",
+    "Cayman Islands": "CYM",
+    "Cyprus": "CYP",
+    "Czech Republic": "CZE",
+    "Germany": "DEU",
+    "Djibouti": "DJI",
+    "Dominica": "DMA",
+    "Denmark": "DNK",
+    "Dominican Republic": "DOM",
+    "Algeria": "DZA",
+    "East Asia & Pacific (excluding high income)": "EAP",
+    "Early-demographic dividend": "EAR",
+    "East Asia & Pacific": "EAS",
+    "Europe & Central Asia (excluding high income)": "ECA",
+    "Europe & Central Asia": "ECS",
+    "Ecuador": "ECU",
+    "Egypt, Arab Rep.": "EGY",
+    "Euro area": "EMU",
+    "Eritrea": "ERI",
+    "Spain": "ESP",
+    "Estonia": "EST",
+    "Ethiopia": "ETH",
+    "European Union": "EUU",
+    "Fragile and conflict affected situations": "FCS",
+    "Finland": "FIN",
+    "Fiji": "FJI",
+    "France": "FRA",
+    "Faroe Islands": "FRO",
+    "Micronesia, Fed. Sts.": "FSM",
+    "Gabon": "GAB",
+    "United Kingdom": "GBR",
+    "Georgia": "GEO",
+    "Ghana": "GHA",
+    "Gibraltar": "GIB",
+    "Guinea": "GIN",
+    "Gambia, The": "GMB",
+    "Guinea-Bissau": "GNB",
+    "Equatorial Guinea": "GNQ",
+    "Greece": "GRC",
+    "Grenada": "GRD",
+    "Greenland": "GRL",
+    "Guatemala": "GTM",
+    "Guam": "GUM",
+    "Guyana": "GUY",
+    "High income": "HIC",
+    "Hong Kong SAR, China": "HKG",
+    "Honduras": "HND",
+    "Heavily indebted poor countries (HIPC)": "HPC",
+    "Croatia": "HRV",
+    "Haiti": "HTI",
+    "Hungary": "HUN",
+    "IBRD only": "IBD",
+    "IDA & IBRD total": "IBT",
+    "IDA total": "IDA",
+    "IDA blend": "IDB",
+    "Indonesia": "IDN",
+    "IDA only": "IDX",
+    "Isle of Man": "IMN",
+    "India": "IND",
+    "Not classified": "INX",
+    "Ireland": "IRL",
+    "Iran, Islamic Rep.": "IRN",
+    "Iraq": "IRQ",
+    "Iceland": "ISL",
+    "Israel": "ISR",
+    "Italy": "ITA",
+    "Jamaica": "JAM",
+    "Jordan": "JOR",
+    "Japan": "JPN",
+    "Kazakhstan": "KAZ",
+    "Kenya": "KEN",
+    "Kyrgyz Republic": "KGZ",
+    "Cambodia": "KHM",
+    "Kiribati": "KIR",
+    "St. Kitts and Nevis": "KNA",
+    "Korea, Rep.": "KOR",
+    "Kuwait": "KWT",
+    "Latin America & Caribbean (excluding high income)": "LAC",
+    "Lao PDR": "LAO",
+    "Lebanon": "LBN",
+    "Liberia": "LBR",
+    "Libya": "LBY",
+    "St. Lucia": "LCA",
+    "Latin America & Caribbean": "LCN",
+    "Least developed countries: UN classification": "LDC",
+    "Low income": "LIC",
+    "Liechtenstein": "LIE",
+    "Sri Lanka": "LKA",
+    "Lower middle income": "LMC",
+    "Low & middle income": "LMY",
+    "Lesotho": "LSO",
+    "Late-demographic dividend": "LTE",
+    "Lithuania": "LTU",
+    "Luxembourg": "LUX",
+    "Latvia": "LVA",
+    "Macao SAR, China": "MAC",
+    "St. Martin (French part)": "MAF",
+    "Morocco": "MAR",
+    "Monaco": "MCO",
+    "Moldova": "MDA",
+    "Madagascar": "MDG",
+    "Maldives": "MDV",
+    "Middle East & North Africa": "MEA",
+    "Mexico": "MEX",
+    "Marshall Islands": "MHL",
+    "Middle income": "MIC",
+    "North Macedonia": "MKD",
+    "Mali": "MLI",
+    "Malta": "MLT",
+    "Myanmar": "MMR",
+    "Middle East & North Africa (excluding high income)": "MNA",
+    "Montenegro": "MNE",
+    "Mongolia": "MNG",
+    "Northern Mariana Islands": "MNP",
+    "Mozambique": "MOZ",
+    "Mauritania": "MRT",
+    "Mauritius": "MUS",
+    "Malawi": "MWI",
+    "Malaysia": "MYS",
+    "North America": "NAC",
+    "Namibia": "NAM",
+    "New Caledonia": "NCL",
+    "Niger": "NER",
+    "Nigeria": "NGA",
+    "Nicaragua": "NIC",
+    "Netherlands": "NLD",
+    "Norway": "NOR",
+    "Nepal": "NPL",
+    "Nauru": "NRU",
+    "New Zealand": "NZL",
+    "OECD members": "OED",
+    "Oman": "OMN",
+    "Other small states": "OSS",
+    "Pakistan": "PAK",
+    "Panama": "PAN",
+    "Peru": "PER",
+    "Philippines": "PHL",
+    "Palau": "PLW",
+    "Papua New Guinea": "PNG",
+    "Poland": "POL",
+    "Pre-demographic dividend": "PRE",
+    "Puerto Rico": "PRI",
+    "Korea, Dem. People's Rep.": "PRK",
+    "Portugal": "PRT",
+    "Paraguay": "PRY",
+    "West Bank and Gaza": "PSE",
+    "Pacific island small states": "PSS",
+    "Post-demographic dividend": "PST",
+    "French Polynesia": "PYF",
+    "Qatar": "QAT",
+    "Romania": "ROU",
+    "Russian Federation": "RUS",
+    "Rwanda": "RWA",
+    "South Asia": "SAS",
+    "Saudi Arabia": "SAU",
+    "Sudan": "SDN",
+    "Senegal": "SEN",
+    "Singapore": "SGP",
+    "Solomon Islands": "SLB",
+    "Sierra Leone": "SLE",
+    "El Salvador": "SLV",
+    "San Marino": "SMR",
+    "Somalia": "SOM",
+    "Serbia": "SRB",
+    "Sub-Saharan Africa (excluding high income)": "SSA",
+    "South Sudan": "SSD",
+    "Sub-Saharan Africa": "SSF",
+    "Small states": "SST",
+    "Sao Tome and Principe": "STP",
+    "Suriname": "SUR",
+    "Slovak Republic": "SVK",
+    "Slovenia": "SVN",
+    "Sweden": "SWE",
+    "Eswatini": "SWZ",
+    "Sint Maarten (Dutch part)": "SXM",
+    "Seychelles": "SYC",
+    "Syrian Arab Republic": "SYR",
+    "Turks and Caicos Islands": "TCA",
+    "Chad": "TCD",
+    "East Asia & Pacific (IDA & IBRD countries)": "TEA",
+    "Europe & Central Asia (IDA & IBRD countries)": "TEC",
+    "Togo": "TGO",
+    "Thailand": "THA",
+    "Tajikistan": "TJK",
+    "Turkmenistan": "TKM",
+    "Latin America & the Caribbean (IDA & IBRD countries)": "TLA",
+    "Timor-Leste": "TLS",
+    "Middle East & North Africa (IDA & IBRD countries)": "TMN",
+    "Tonga": "TON",
+    "South Asia (IDA & IBRD)": "TSA",
+    "Sub-Saharan Africa (IDA & IBRD countries)": "TSS",
+    "Trinidad and Tobago": "TTO",
+    "Tunisia": "TUN",
+    "Turkey": "TUR",
+    "Tuvalu": "TUV",
+    "Tanzania": "TZA",
+    "Uganda": "UGA",
+    "Ukraine": "UKR",
+    "Upper middle income": "UMC",
+    "Uruguay": "URY",
+    "United States": "USA",
+    "Uzbekistan": "UZB",
+    "St. Vincent and the Grenadines": "VCT",
+    "Venezuela, RB": "VEN",
+    "British Virgin Islands": "VGB",
+    "Virgin Islands (U.S.)": "VIR",
+    "Vietnam": "VNM",
+    "Vanuatu": "VUT",
+    "World": "WLD",
+    "Samoa": "WSM",
+    "Kosovo": "XKX",
+    "Yemen, Rep.": "YEM",
+    "South Africa": "ZAF",
+    "Zambia": "ZMB",
+    "Zimbabwe": "ZWE",
+}
+
 
 def _check_currency(currency: str):
     if currency not in currency_set:
@@ -59,6 +327,21 @@ def _check_currency(currency: str):
         )
 
 
+def _check_wb_country(country: str):
+    if (country not in wb_country_dict.keys()) & (
+        country not in wb_country_dict.values()
+    ):
+        raise ValueError(
+            f"country {country} not in supported World Bank country dict, "
+            f"{wb_country_dict}"
+        )
+
+
+def _check_wb_years(year: int):
+    if year < 1960:
+        raise ValueError(f"year value must be 1960 or later")
+
+
 @lru_cache(maxsize=32)
 def _convert_currency(
     from_currency: str = None,
@@ -204,3 +487,167 @@ def convert_currency(
         df[column_name] = df[column_name] * rate
 
     return df
+
+
+@lru_cache(maxsize=32)
+def _inflate_currency(
+    country: str = None, currency_year: int = None, to_year: int = None
+):
+    """
+    Currency inflation for Pandas DataFrame column.
+    Helper function for `inflate_currency` method.
+    The API used is the World Bank Indicator API:
+    https://datahelpdesk.worldbank.org/knowledgebase/articles/889392-about-the-indicators-api-documentation
+    """
+
+    # Check all inputs are correct data type
+    check("country", country, [str])
+    check("currency_year", currency_year, [int])
+    check("to_year", to_year, [int])
+
+    # Get WB country abbreviation
+    _check_wb_country(country)
+    if country in wb_country_dict.keys():
+        country = wb_country_dict[country]
+    else:
+        # `country` is already a correct abbreviation; do nothing
+        pass
+
+    _check_wb_years(currency_year)
+    _check_wb_years(to_year)
+
+    url = (
+        "https://api.worldbank.org/v2/country/"
+        + country
+        + "/indicator/FP.CPI.TOTL?date="
+        + str(min(currency_year, to_year))
+        + ":"
+        + str(max(currency_year, to_year))
+        + "&format=json"
+    )
+
+    result = requests.get(url)
+
+    if result.status_code != 200:
+        raise ConnectionError(
+            "WB Indicator API failed to receive a 200 "
+            "response from the server. "
+            "Please try again later."
+        )
+
+    # The API returns a list of two items;
+    # the second item in the list is what we want
+    inflation_dict = json.loads(result.text)[1]
+
+    # Error checking
+    if inflation_dict is None:
+        raise ValueError(
+            "The WB Indicator API returned nothing. "
+            "This likely means the currency_year and "
+            "to_year are outside of the year range for "
+            "which the WB has inflation data for the "
+            "specified country."
+        )
+
+    # Create new dict with only the year and inflation values
+    inflation_dict_ready = {
+        int(inflation_dict[i]["date"]): float(inflation_dict[i]["value"])
+        for i in range(len(inflation_dict))
+        if inflation_dict[i]["value"] is not None
+    }
+
+    # Error catching
+    if currency_year not in inflation_dict_ready.keys():
+        raise ValueError(
+            f"The WB Indicator API does not have inflation "
+            f"data for {currency_year} for {country}."
+        )
+    if to_year not in inflation_dict_ready.keys():
+        raise ValueError(
+            f"The WB Indicator API does not have inflation "
+            f"data for {to_year} for {country}."
+        )
+
+    inflator = (
+        inflation_dict_ready[to_year] / inflation_dict_ready[currency_year]
+    )
+    return inflator
+
+
+@pf.register_dataframe_method
+def inflate_currency(
+    df: pd.DataFrame,
+    column_name: str = None,
+    country: str = None,
+    currency_year: int = None,
+    to_year: int = None,
+    make_new_column: bool = False,
+) -> pd.DataFrame:
+    """
+    Inflates a column of monetary values from one year to another, based on
+    the currency's country.
+
+    The provided country can be any economy name or code from the World Bank
+    list of economies:
+    https://databank.worldbank.org/data/download/site-content/CLASS.xls.
+
+    This method mutates the original DataFrame.
+
+    Functional usage example:
+
+    .. code-block:: python
+
+        import pandas as pd
+        import janitor.finance
+
+        df = pd.DataFrame(...)
+
+        df = janitor.finance.inflate_currency(
+            df=df,
+            column_name='profit',
+            country='USA',
+            currency_year=2015,
+            to_year=2018,
+            make_new_column=True
+        )
+
+    Method chaining usage example:
+
+    .. code-block:: python
+
+        import pandas as pd
+        import janitor.finance
+
+        df = pd.DataFrame(...)
+        df = df.inflate_currency(
+            column_name='profit',
+            country='USA',
+            currency_year=2015,
+            to_year=2018,
+            make_new_column=True
+        )
+
+    :param df: A pandas dataframe.
+    :param column_name: Name of the column containing monetary
+        values to inflate.
+    :param country: The country associated with the currency being inflated.
+        May be any economy or code from the World Bank list of economies:
+        https://databank.worldbank.org/data/download/site-content/CLASS.xls.
+    :param currency_year: The currency year to inflate from.
+        The year should be 1960 or later.
+    :param to_year: The currency year to inflate to.
+        The year should be 1960 or later.
+    :param make_new_column: Generates new column for inflated currency if
+        True, otherwise, inflates currency in place.
+    """
+
+    inflator = _inflate_currency(country, currency_year, to_year)
+
+    if make_new_column:
+        new_column_name = column_name + "_" + str(to_year)
+        df[new_column_name] = df[column_name] * inflator
+
+    else:
+        df[column_name] = df[column_name] * inflator
+
+    return df


Test patch:
diff --git a/tests/finance/test_inflate_currency.py b/tests/finance/test_inflate_currency.py
new file mode 100644
--- /dev/null
+++ b/tests/finance/test_inflate_currency.py
@@ -0,0 +1,137 @@
+from datetime import date, datetime
+
+import pytest
+import requests
+
+from janitor.finance import _inflate_currency, inflate_currency
+
+
+@pytest.mark.finance
+def test_make_currency_inflator_api_request():
+    """
+    Test for currency inflator API request.
+    This test exists because we rely solely on the service by
+    the World Bank's Indicator API:
+    https://datahelpdesk.worldbank.org/knowledgebase/articles/889392-about-the-indicators-api-documentation
+    """
+    r = requests.get(
+        "https://api.worldbank.org/v2/country/USA/indicator/"
+        "FP.CPI.TOTL.ZG?date=2010:2018&format=json"
+    )
+    assert r.status_code == 200
+
+
+@pytest.mark.finance
+def test_make_new_inflated_currency_col(dataframe):
+    df = dataframe.inflate_currency(
+        "a",
+        country="USA",
+        currency_year=2018,
+        to_year=2018,
+        make_new_column=True,
+    )
+    assert all(df["a"] == df["a_2018"])
+
+
+@pytest.mark.finance
+def test_inflate_existing_currency_col(dataframe):
+    initialval = dataframe["a"].sum()
+    # Pulled raw values from API website for USA 2018 and 2015
+    inflator = _inflate_currency("USA", currency_year=2018, to_year=2015)
+    df = dataframe.inflate_currency(
+        "a",
+        country="USA",
+        currency_year=2018,
+        to_year=2015,
+        make_new_column=False,
+    )
+    updatedval = df["a"].sum()
+    assert (initialval * inflator) == pytest.approx(updatedval)
+
+
+@pytest.mark.finance
+def test_expected_result(dataframe):
+    initialval = dataframe["a"].sum()
+    # Pulled raw values from API website for USA 2018 and 2015
+    inflator = _inflate_currency("USA", currency_year=2018, to_year=2015)
+    df = dataframe.inflate_currency(
+        "a",
+        country="USA",
+        currency_year=2018,
+        to_year=2015,
+        make_new_column=True,
+    )
+    updatedval = dataframe["a_2015"].sum()
+    assert (initialval * inflator) == pytest.approx(updatedval)
+
+
+@pytest.mark.finance
+def test_expected_result_with_full_country_name(dataframe):
+    initialval = dataframe["a"].sum()
+    # Pulled raw values from API website for USA 2018 and 2015
+    inflator = _inflate_currency(
+        "United States", currency_year=2018, to_year=2015
+    )
+    df = dataframe.inflate_currency(
+        "a",
+        country="United States",
+        currency_year=2018,
+        to_year=2015,
+        make_new_column=True,
+    )
+    updatedval = dataframe["a_2015"].sum()
+    assert (initialval * inflator) == pytest.approx(updatedval)
+
+
+@pytest.mark.finance
+def test_wb_country_check(dataframe):
+    with pytest.raises(ValueError):
+        assert dataframe.inflate_currency(
+            "a", country="USAA", currency_year=2018, to_year=2018
+        )
+
+
+@pytest.mark.finance
+def test_year_check(dataframe):
+    with pytest.raises(ValueError):
+        assert dataframe.inflate_currency(
+            "a", country="USA", currency_year=1950, to_year=2018
+        )
+
+
+@pytest.mark.finance
+def test_datatypes_check(dataframe):
+    with pytest.raises(TypeError):
+        assert dataframe.inflate_currency(
+            "a", country=123, currency_year=1960, to_year=2018
+        )
+        assert dataframe.inflate_currency(
+            "a", country="USA", currency_year="b", to_year=2018
+        )
+        assert dataframe.inflate_currency(
+            "a", country="USA", currency_year=1960, to_year="b"
+        )
+
+
+@pytest.mark.finance
+def test_api_result_check(dataframe):
+    with pytest.raises(ValueError):
+        assert dataframe.inflate_currency(
+            "a", country="USA", currency_year=2030, to_year=2050
+        )
+
+
+@pytest.mark.finance
+def test_to_year_available(dataframe):
+    with pytest.raises(ValueError):
+        assert dataframe.inflate_currency(
+            "a", country="GHA", currency_year=2010, to_year=1962
+        )
+
+
+@pytest.mark.finance
+def test_currency_year_available(dataframe):
+    with pytest.raises(ValueError):
+        assert dataframe.inflate_currency(
+            "a", country="GHA", currency_year=1962, to_year=2010
+        )