# coding: utf-8
"""
Main API Functionality
~~~~~~~~~~~~~~~~~~~~~~
"""
# Imports
import pycountry
import numpy as np
import pandas as pd
from pprint import pprint
from warnings import warn
from datetime import datetime
# Support Tools
from easymoney.support_tools import mint
from easymoney.support_tools import min_max
from easymoney.support_tools import closest_date
from easymoney.support_tools import year_extract
from easymoney.support_tools import min_max_dates
from easymoney.support_tools import closest_value
from easymoney.support_tools import fast_date_range
from easymoney.support_tools import date_format_check
from easymoney.support_tools import sort_range_reverse
# Pycountry Wrap
from easymoney.pycountry_wrap import PycountryWrap
# options_tools
from easymoney.options_tools import options_ranking
from easymoney.options_tools import year_date_overlap
from easymoney.options_tools import alpha2_by_cpi_years
# Easy Pandas
from easymoney.easy_pandas import pandas_null_drop
from easymoney.easy_pandas import pandas_pretty_print
# Online Data Sources
from easymoney.sources.ecb_interface import ecb_xml_exchange_data
from easymoney.sources.world_bank_interface import world_bank_pull
[docs]class EasyPeasy(object):
"""
Tools for Monetary Information and Conversions.
:param precision: number of places to round to when returning results. Defaults to 2.
:type precision: ``int``
:param fall_back: if True, fall back to closest possible date for which data is available. Defaults to True.
:type fall_back: ``bool``
:param fuzzy_match_threshold: a threshold for fuzzy matching confidence (requires the ``fuzzywuzzy`` package).
The value must be an number between 0 and 100. The *suggested* minimum values is 85.
This will only impact attempts to match on natural names, e.g., attempting to match
'Canada' by passing 'Canadian'. If True, a threshold of 90 will be set. Defaults to False.
.. warning::
Fuzzy matching may yield inaccurate results.
When possible, use terminology *exactly* as it appears in ``options()``.
:type fuzzy_threshold: ``int``, ``float`` or ``bool``
:param data_path: alternative path to the database file(s). Defaults to None.
:type data_path: ``str``
"""
# Fix: `EasyPeasy()` does not handle currencies like 'EEK' properly.
# They may not been appearing in options() correctly.
# See: _user_currency_input() below.
def __init__(self, precision=2, fall_back=True, fuzzy_threshold=False, data_path=None):
"""
Initialize the ``EasyPeasy()`` class.
"""
self._precision = precision
self._fall_back = fall_back
fuzzy_search_threshold = fuzzy_threshold
recommended_fuzzy_threshold = 90
min_suggested_fuzzy_threshold = 85
# Check fuzzy_threshold
if fuzzy_threshold is not False and not isinstance(fuzzy_threshold, (float, int)):
raise ValueError("`fuzzy_threshold` must be either `False`, a `float` or an `int`.")
elif fuzzy_threshold is True:
fuzzy_search_threshold = recommended_fuzzy_threshold
elif fuzzy_threshold is not False and isinstance(fuzzy_threshold, (float, int)) and fuzzy_threshold <= 0:
raise ValueError("`fuzzy_threshold` must be greater than 0.")
elif fuzzy_threshold is not False and isinstance(fuzzy_threshold, (float, int)) and fuzzy_threshold > 100:
raise ValueError("`fuzzy_threshold` must be less than 100.")
elif fuzzy_threshold is not False and isinstance(fuzzy_threshold, (float, int)) \
and fuzzy_threshold < min_suggested_fuzzy_threshold:
warn("\nLow `fuzzy_threshold` values, such as %s, have an elevated "
"likelihood of innaccurate results." % (str(fuzzy_threshold)))
path_to_data = data_path if isinstance(data_path, str) else None
self._pycountry_wrap = PycountryWrap(path_to_data, fuzzy_search_threshold)
self._pycountries_alpha_2 = set([c.alpha_2 for c in list(pycountry.countries)])
# CPI Dictionaries
self._cpi_dict = world_bank_pull(return_as='dict')
self._alpha2_cpi_record = alpha2_by_cpi_years(regions=self._pycountries_alpha_2, cpi_dictionary=self._cpi_dict)
# Exchange Dict
self._exchange_dict, self._ecb_currency_codes, self._currency_date_record = ecb_xml_exchange_data(return_as='dict')
# Min max for _currency_date_record
self._currency_date_record_range = {k: fast_date_range(v, '%d/%m/%Y') for k, v in self._currency_date_record.items()}
# Column Order for options
self._table_col_order = ['RegionFull', 'Region', 'Alpha2', 'Alpha3', 'Currencies',
'InflationDates', 'ExchangeDates', 'Overlap']
def _params_check(self, amount="void", pretty_print="void"):
"""
Check `amount` and `pretty_print` have been passed valid values.
:param amount: numeric amount
:type amount: ``float`` or ``int``
:param pretty_print: must be a boolean
:type pretty_print: ``bool``
"""
# Check amount is float
if amount != "void" and not isinstance(amount, (float, int)):
raise ValueError("amount must be numeric (intiger or float).")
# Check pretty_print is a bool
if pretty_print != "void" and not isinstance(pretty_print, bool):
raise ValueError("pretty_print must be either True or False.")
[docs] def region_map(self, region, map_to='alpha_2'):
"""
Map a 'region' to any one of: ISO Alpha 2, ISO Alpha 3, it's Name or Offical Name.
Examples:
- ``EasyPeasy().region_map(region='CA', map_to='alpha_2')`` :math:`=` 'CA'
- ``EasyPeasy().region_map(region='Canada', map_to='alpha_3')`` :math:`=` 'CAN'
:param region: a 'region' in the format of a ISO Alpha2, ISO Alpha3 or currency code, as well as natural name.
:type region: ``str``
:param map_to: - for region: 'alpha_2', 'alpha_3', 'name' or 'official_name'.
- for currency: 'currency_alpha_3', 'currency_numeric' or 'currency_name'.
Defaults to 'alpha_2'.
:type map_to: ``str``
:return: the desired mapping from region to ISO Alpha2.
:rtype: ``str`` or ``tuple``
"""
# handle currency transitions here.
return self._pycountry_wrap.map_region_to_type(region=region, extract_type=map_to)
def _cpi_years(self, region, warn=True):
"""
Get Years for which CPI information is available for a given region.
:param region: ISO Alpha 2 Code.
:type region: ``str``
:param warn: warn if data could not be obtained.
:type warn: ``bool``
:return: list of years for which CPI information is available.
:rtype: ``list``
"""
cpi_years_list = self._alpha2_cpi_record.get(region, [])
if len(cpi_years_list):
return cpi_years_list
elif warn:
raise KeyError("Could not obtain inflation (CPI) information for '%s' from the\n" \
"International Monetary Fund database currently cached." % (region))
else:
return None
def _cpi_match(self, region, year):
"""
Match region to the best possible year.
:param region: region of the form allowed by `EasyPeasy().region_map()`
:type region: ``str``
:param year: a year for which CPI information is desired.
Can also be one of: 'oldest' or 'latest'.
:type year: ``int`` or ``str``
:return: best matching of CPI information for the given region w.r.t. the year supplied.
:rtype: ``float``, ``int`` or ``str``
"""
# Initialize
fall_back_year = None
natural_region_name = None
# replace year_b if it is 'oldest' or 'latest'
available_years = list(map(int, self._cpi_years(region)))
error_msg = "\nInflation (CPI) data for %s in '%s' could not be obtained from the\n" \
"International Monetary Fund database currently cached."
warn_msg = error_msg + "\nFalling back to %s."
if year == 'oldest':
return min(available_years)
elif year == 'latest':
return max(available_years)
elif int(float(year)) not in available_years:
if self._fall_back:
natural_region_name = self._pycountry_wrap.map_region_to_type(region, 'name')
fall_back_year = closest_value(float(year), available_years)
warn(warn_msg % (year, natural_region_name, str(fall_back_year)))
return fall_back_year
else:
raise AttributeError(error_msg % (year, natural_region_name))
else:
return year
def _cpi_region_year(self, region, year):
"""
Get the Consumer Price Index (CPI) in a given region for a given year.
:param region: region of the form allowed by `EasyPeasy().region_map()`
:type region: ``str``
:param year: a year for which CPI information is desired.
Can also be one of: 'oldest' or 'latest'.
:type year: ``int`` or ``str``
:return: CPI for a given year.
:rtype: ``float``
"""
cpi = self._cpi_dict.get(str(int(float(year))), {}).get(self.region_map(region, 'alpha_2'), None)
if cpi is not None:
return float(cpi)
else:
raise KeyError("Could not obtain inflation information for '%s' in '%s'." % (str(region), str(year)))
[docs] def inflation(self, region, year_a, year_b=None, return_raw_cpi_dict=False, pretty_print=False):
"""
Calculator to compute the inflation rate from Consumer Price Index (CPI) information.
Inflation Formula:
.. math:: Inflation_{region} = \dfrac{c_{1} - c_{2}}{c_{2}} \cdot 100
| :math:`where`:
| :math:`c_{1}` = CPI of the region in *year_b*.
| :math:`c_{2}` = CPI of the region in *year_a*.
:param region: a region.
:type region: ``str``
:param year_a: start year.
:type year_a: ``int``
:param year_b: end year. Defaults to None -- can only be left to this default if *return_raw_cpi_dict* is True.
:type year_b: ``int``
:param return_raw_cpi_dict: If True, returns the CPI information in a dict. Defaults to False.
:type return_raw_cpi_dict: ``bool``
:param pretty_print: if True, pretty prints the result otherwise returns the result as a float. Defaults to False.
:type pretty_print: ``bool``
:return: (a) the rate of inflation between year_a and year_h.
(b) a dictionary of CPI information with the years as keys, CPI as values.
:rtype: ``float``, ``dict`` or ``NaN``
"""
# Check Params
self._params_check(pretty_print=pretty_print)
# Map Region to its alpha 2 code
mapped_region = self.region_map(region, 'alpha_2')
# Set to_year
to_year = self._cpi_match(mapped_region, year_b) if year_b is not None else None
# Set from_year
if year_a is not None:
from_year = self._cpi_match(mapped_region, year_a)
else:
raise ValueError("year_a cannot be NoneType.")
# Get the CPI for to_year and year_a
c1 = self._cpi_region_year(mapped_region, to_year) if to_year is not None else None
c2 = self._cpi_region_year(mapped_region, from_year)
# Return dict, if requested
if return_raw_cpi_dict != False:
d = dict(zip(filter(None, [to_year, from_year]), filter(None, [c1, c2])))
if return_raw_cpi_dict == 'complete':
return d, {"year_a": from_year, "year_b": to_year}
elif return_raw_cpi_dict == True:
return d
# Compute the rate of Inflation (and round).
if (any([pd.isnull(c1), pd.isnull(c2)])) or (c2 == 0.0):
return np.NaN
else:
rate = round(((c1 - c2) / float(c2)) * 100, self._precision)
# Return or Pretty Print.
if not pretty_print:
return rate
else:
print(rate, "%")
[docs] def inflation_calculator(self, amount, region, year_a, year_b, pretty_print=False):
"""
Adjusts a given amount of money for inflation.
:param amount: a monetary amount.
:type amount: ``float`` or ``int``
:param region: a geographical region.
:type region: ``str``
:param year_a: start year.
:type year_a: ``int``
:param year_b: end year.
:type year_b: ``int``
:param pretty_print: if True, pretty prints the result otherwise returns the result as a float.
Defaults to False.
:type pretty_print: ``bool``
:return: :math:`amount \cdot inflation \space rate`.
:rtype: ``float`` or ``NaN``
"""
# Check Params
self._params_check(amount, pretty_print)
# Input checking
if year_a == year_b:
return mint(amount, self._precision, self.region_map(region, map_to='currency_alpha_3'), pretty_print)
# Get the CPI information
inflation_dict, years = self.inflation(region, year_a, year_b, return_raw_cpi_dict='complete')
# Block division by zero
if inflation_dict[years['year_a']] == 0:
warn("Problem obtaining required inflation information.")
return np.NaN
# Scale w.r.t. inflation.
adjusted_amount = inflation_dict[years['year_b']] / float(inflation_dict[years['year_a']]) * amount
# Print or Return
return mint(adjusted_amount, self._precision, self.region_map(region, map_to='currency_alpha_3'), pretty_print)
def _exchange_dates(self, currencies, min_max_rslt=False, date_format='%d/%m/%Y'):
"""
Get all dates for which there is data for a given list of currencies
:param currencies: a list of currencies
:type currencies: ``list``
:param min_max_rslt: compute the earliest and latest date for which exchange rate information is available.
Defaults to False.
:type min_max_rslt: ``bool``
:param date_format: a data format. Defaults to '%d/%m/%Y'.
:type date_format: ``str``
:return:
:rtype: 1D ``list`` or 2D ``list``
"""
d = self._currency_date_record_range if min_max_rslt else self._currency_date_record
dates = [d.get(c.upper(), None) for c in currencies]
# Remove None
dates = list(filter(None, dates))
if not len(dates):
return None
return dates[0] if len(dates) == 1 else dates
def _user_currency_input(self, currency_or_region):
"""
Converts User supplied reference to a currency into an actual ISO Alpha 3 Currency Code.
:param currency_or_region: reference to a currency
:type currency_or_region: ``str``
:return: ISO Alpha 3 Currency Code
:rtype: ``pycountry object``
"""
# Note: 'temp. fix' has been added to handle currencies like 'EEK'.
# This capability should be integrated into region_map() in the future.
try:
return pycountry.currencies.lookup(currency_or_region).alpha_3
except:
if currency_or_region in self._ecb_currency_codes: # temp fix
return currency_or_region
else:
return self.region_map(currency_or_region, "currency_alpha_3")
def _base_cur_to_lcu(self, currency, date):
"""
Convert from a base currency (Euros) to a local currency unit, e.g., CAD.
:param currency: a currency code or region
:type currency: ``str``
:param date: date of allowed form (currently limited to DD/MM/YYYY).
:type date: ``str``
:return: exchange_rate w.r.t. the Euro as a base currency.
"""
error_msg = "\nCould not obtain the exchange rate for '%s' on %s from the\n" \
"European Central Bank database currently cached."
warn_msg = error_msg + "\nFalling back to %s."
# Handle Base Currency
if currency.upper() == 'EUR':
return 1.0
available_data = self._currency_date_record.get(currency, None)
if available_data == None:
raise AttributeError("Data could not obtained for '%s' from the\n" \
"European Central Bank database currently cached." % (currency))
if date == 'oldest':
exchange_date = self._currency_date_record_range.get(currency)[0]
elif date == 'latest':
exchange_date = self._currency_date_record_range.get(currency)[1]
elif isinstance(date, str) and date not in available_data:
if self._fall_back:
exchange_date = closest_date(date, available_data)
warn(warn_msg % (currency, date, exchange_date))
else:
raise AttributeError(msg % (currency, exchange_date))
elif isinstance(date, str) and date_format_check(date, from_format="%d/%m/%Y"):
exchange_date = date
else:
raise ValueError("Invalid Date Supplied. Dates must be of the form DD/MM/YYYY.")
exchange_rate = self._exchange_dict.get(exchange_date, {}).get(currency, None)
if not isinstance(exchange_rate, type(None)):
return exchange_rate
else:
raise AttributeError(error_msg % (currency, exchange_date))
[docs] def currency_converter(self, amount, from_currency, to_currency, date="latest", pretty_print=False):
"""
Function to perform currency conversion based on, **not** directly reported from, data obtained
from the European Central Bank (ECB).
Base Currency: EUR.
**Formulae Used**
Let :math:`LCU` be defined as:
.. math:: LCU(\phi_{EUR}, CUR) = ExchangeRate_{\phi_{EUR} → CUR}
| :math:`where`:
| :math:`CUR` is some local currency unit.
| :math:`\phi = 1`, as in the ECB data.
|
That is, less formally:
.. math:: LCU(\phi_{EUR}, CUR) = \dfrac{x \space \space CUR}{1 \space EUR}
:math:`Thus`:
From :math:`CUR_{1}` to :math:`CUR_{2}`:
.. math:: amount_{CUR_{2}} = \dfrac{1}{LCU(\phi_{EUR}, CUR_{1})} \cdot LCU(\phi_{EUR}, CUR_{2}) \cdot amount_{CUR_{1}}
:param amount: an amount of money to be converted.
:type amount: ``float`` or ``int``
:param from_currency: the currency of the amount.
:type from_currency: ``str``
:param to_currency: the currency the amount is to be converted into.
:type to_currency: ``str``
:param date: date of data to perform the conversion with. Dates must be of the form: ``DD/MM/YYYY``.
:type date: ``str``
:param pretty_print: if True, pretty prints the table otherwise returns the table as a pandas DataFrame. Defaults to False.
:type pretty_print: ``bool``
:return: converted currency.
:rtype: ``float``
"""
# Check Params
self._params_check(amount, pretty_print)
# to/from_currency --> Currency Alpha 3 Code
ca3 = [self._user_currency_input(c) if c not in self._ecb_currency_codes else c for c in (to_currency, from_currency)]
to_currency_fn, from_currency_fn = ca3
# Return amount unaltered if self-conversion was requested.
if from_currency_fn == to_currency_fn:
return mint(amount, self._precision, to_currency_fn, pretty_print)
if any(x == None or x is None for x in [to_currency_fn, from_currency_fn]):
raise ValueError("Could not convert '%s' to '%s'." % (from_currency, to_currency))
# from_currency --> Base Currency --> to_currency
conversion_to_invert = self._base_cur_to_lcu(from_currency_fn, date)
if conversion_to_invert == 0.0:
raise ZeroDivisionError("Cannot converted from '%s' on %s." % (from_currency, date))
converted_amount = (conversion_to_invert ** -1) * self._base_cur_to_lcu(to_currency_fn, date) * float(amount)
# Return results (or pretty print)
return mint(converted_amount, self._precision, to_currency_fn, pretty_print)
[docs] def normalize(self
, amount
, region
, from_year
, to_year="latest"
, base_currency="EUR"
, exchange_date="latest"
, pretty_print=False):
"""
| Convert a Nominal Amount of money to a Real Amount in the same, or another, currency.
|
| This requires both inflation (for *currency*) and exchange rate information (*currency* to *base_currency*).
See ``options(info = 'all', overlap_only = True)`` for an exhaustive listing of valid values to pass to this method.
|
| Currency Normalization occurs in two steps:
| 1. Adjust the currency for inflation, e.g., 100 (2010 :math:`CUR_{1}`) → x (2015 :math:`CUR_{1}`).
| 2. Convert the adjusted amount into the *base_currency*.
:param amount: a numeric amount of money.
:type amount: ``float`` or ``int``
:param currency: a region or currency.
Legal options: Region Name, ISO Alpha2, Alpha3 or Currency Code (see ``options()``).
:type currency: ``str``
:param from_year: a year. For valid values see ``options()``.
:type from_year: ``int``
:param to_year: a year. For valid values see ``options()``.
Defaults to 'latest' (which will use the most recent data available).
:type to_year: ``str`` or ``int``
:param base_currency: a region or currency. Legal: Region Name, ISO Alpha2, Alpha3 or Currency Code
(see ``options()``). Defaults to 'EUR'.
:type base_currency: ``str``
:param pretty_print: Pretty print the result if True; return amount if False. Defaults to False.
:type pretty_print: ``bool``
:return: amount adjusted for inflation and converted into the base currency.
:rtype: ``float``
"""
# Check Params
self._params_check(amount, pretty_print)
exchange_year = year_extract(exchange_date)
if exchange_date not in ['oldest', 'latest'] and not str(exchange_year).isdigit():
warn("\n`exchange_date` is likely formatted improperly.")
# Check delta between `to_year` and `exchange_date`.
if abs(float(to_year) - float(exchange_year)) > 1: # note, this doesn't currently handle oldest v. latest.
warn("\nThe year for which the input amount is being adjusted\n"
"for inflation is %s, whereas the exchange rate year is %s." % (str(to_year), str(exchange_year)))
# Adjust input for inflation
real_amount = self.inflation_calculator(amount, region, year_a=from_year, year_b=to_year)
# Compute Exchange
normalize_amount = self.currency_converter(real_amount, region, base_currency, date=exchange_date)
# Return results (or pretty print)
return mint(normalize_amount, self._precision, self._user_currency_input(base_currency), pretty_print)
def _options_info_error(self, rformat):
"""
Error Messages for invalid information requests.
:param rformat: one of: 'list' or 'table'.
:type rformat: ``str``
"""
options_error_msg = "Invalid Information Request.\n" \
"Please supply:\n" \
" - 'exchange' to `info` for a %s of currencies for which exchange rates are available.\n" \
" - 'inflation' to `info` for a %s of countries for which inflation information is available." \
% (rformat, rformat)
if rformat == 'list':
append = "\nTo see a complete summary, please set `rformat` equal to 'table' and set `info` equal to 'all'."
elif rformat == 'table':
append = "\n - 'all' to `info` for a complete summary of available data."
raise ValueError(options_error_msg + append)
def _options_table(self, info, table_overlap_only=False, range_table_dates=True):
"""
Tool to Generate Information for ``options()`` in table form.
:param info: 'exchange', 'inflation' or 'all' ('all' requires rformat is set to 'table').
:type info: ``str``
:param table_overlap_only:
:param table_overlap_only: when info is set to 'all', keep only those rows for which exchange rate and inflation data overlap.
:type table_overlap_only: ``bool``
:param range_table_dates: if True, only report the minimum and maximum date for which data is available;
if False, all dates for which data is available will be reported. Defaults to True.
:type range_table_dates: ``bool``
:return: dataframe summarizing databases currently cached by ``EasyPeasy()``.
:rtype: ``Pandas DataFrame``
"""
# Note: does not currently handle currency transitions
# Use CurrencyRelationshipsDB as Base
d = [i for i in list(self._pycountry_wrap.alpha2_currency_dict.items()) if i[0] in self._pycountries_alpha_2]
options_df = pd.DataFrame(d).rename(columns={0 : "Alpha2", 1 : "Currencies"})
# Add Names
options_df['Region'] = [self._pycountry_wrap.map_region_to_type(c, 'name') for c in list(options_df['Alpha2'])]
options_df['RegionFull'] = [self._pycountry_wrap.map_region_to_type(c, 'official_name') for c in list(options_df['Alpha2'])]
# Add Alpha3
options_df['Alpha3'] = [self._pycountry_wrap.map_region_to_type(c, 'alpha_3') for c in list(options_df['Alpha2'])]
# Map available Inflation Data onto this Base
options_df['InflationDates'] = options_df['Alpha2'].map(
lambda x: sort_range_reverse(self._cpi_years(x, warn=False), ('reverse' if not range_table_dates else 'range')), 'ignore'
)
# Map available Exchange Rate Data onto this Base
options_df['ExchangeDates'] = options_df['Currencies'].map(
lambda x: self._exchange_dates(x, min_max_rslt=range_table_dates), na_action='ignore'
)
# Add Overlap
options_df['Overlap'] = options_df.apply(
lambda x: year_date_overlap(x['InflationDates'], x['ExchangeDates'], date_format="%d/%m/%Y"), axis=1
)
# Fill NaNs
options_df = options_df.fillna(np.NaN)
# Weight Rows by Completeness; Sort by Data Overlap and Alpha2 Code
options_df['TempRanking'] = options_df.apply(lambda x: options_ranking(x['InflationDates'], x['ExchangeDates']), axis=1)
options_df = options_df.sort_values(['TempRanking', 'Region'], ascending=[False, True])
if table_overlap_only and info == 'all':
options_df = pandas_null_drop(options_df, subset=['InflationDates', 'ExchangeDates'])
elif table_overlap_only and info != 'all':
warn("`table_overlap_only` can only take effect if `info` is equal to 'all'.")
# Subset
col_order = self._table_col_order
if info == 'exchange':
col_order = [c for c in self._table_col_order if c != 'InflationDates']
elif info == 'inflation':
col_order = [c for c in self._table_col_order if c != 'ExchangeDates']
elif info != 'all':
self._options_info_error('table')
return options_df.drop('TempRanking', axis=1)[col_order].reset_index(drop=True)
def _options_lists(self, info):
"""
Tool to Generate Information for ``options()`` in list form.
:param info: 'exchange', 'inflation' or 'all' ('all' requires rformat is set to 'table').
:type info: ``str``
:return: list of requested information.
:rtype: ``list``
"""
if info.strip().lower() not in ['exchange', 'inflation']:
self._options_info_error('list')
d = self._exchange_dict if info.strip().lower() == 'exchange' else self._cpi_dict
full = [list(v.keys()) for k, v in d.items()]
return sorted(set([i for s in full for i in s]))
[docs] def options(self, info='all', rformat='table', pretty_print=True, table_overlap_only=False, range_table_dates=True):
"""
An easy interface to explore all of the terminology EasyPeasy understands
as well the dates for which data is available.
:param info: 'exchange', 'inflation' or 'all' ('all' requires rformat is set to 'table').
:type info: ``str``
:param rformat: 'table' for a table or 'list' for just the currency codes, alone. Defaults to 'table'.
:type rformat: ``str``
:param pretty_print: if True, prints the list or table. If False, returns the list or table (as a Pandas DataFrame).
Defaults to True.
:type pretty_print: ``bool``
:param table_overlap_only: when info is set to 'all', keep only those rows for which exchange rate and inflation data overlap.
:type table_overlap_only: ``bool``
:param range_table_dates: if True, only report the minimum and maximum date for which data is available;
if False, all dates for which data is available will be reported. Defaults to True.
:type range_table_dates: ``bool``
:return: information table or list
:rtype: ``Pandas DataFrame`` or ``list``
"""
pretty_df = None
if rformat == 'list':
request = self._options_lists(info)
return request if not pretty_print else pprint(request, width=65, compact=True)
elif rformat == 'table':
request = self._options_table(info, table_overlap_only, range_table_dates)
if pretty_print:
pretty_df = request.drop('RegionFull', axis=1)
pretty_df['Currencies'] = pretty_df['Currencies'].str.join("; ")
pandas_pretty_print(pretty_df, col_align={'Region' : 'left'})
else:
return request
else:
raise ValueError("`rformat` must be one of:\n"
" - 'list', for a list of the requested information.\n"
" - 'table', for a table (dataframe) of the requested information.")