Source code for climada.entity.disc_rates.base

"""
This file is part of CLIMADA.

Copyright (C) 2017 ETH Zurich, CLIMADA contributors listed in AUTHORS.

CLIMADA is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free
Software Foundation, version 3.

CLIMADA is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with CLIMADA. If not, see <https://www.gnu.org/licenses/>.

---

Define DiscRates class.
"""

__all__ = ['DiscRates']

import copy
from array import array
import logging
from typing import Optional
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import xlsxwriter

import climada.util.checker as u_check
import climada.util.finance as u_fin
import climada.util.hdf5_handler as u_hdf5

LOGGER = logging.getLogger(__name__)

"""MATLAB variable names"""
DEF_VAR_MAT = {'sup_field_name': 'entity',
               'field_name': 'discount',
               'var_name': {'year': 'year',
                            'disc': 'discount_rate'
                           }
              }

"""Excel variable names"""
DEF_VAR_EXCEL = {'sheet_name': 'discount',
                 'col_name': {'year': 'year',
                              'disc': 'discount_rate'
                             }
                }


[docs] class DiscRates(): """ Defines discount rates and basic methods. Loads from files with format defined in FILE_EXT. Attributes --------- years: np.array list of years rates: np.array list of discount rates for each year (between 0 and 1) """
[docs] def __init__( self, years : Optional[np.ndarray] = None, rates : Optional[np.ndarray] = None ): """ Fill discount rates with values and check consistency data Parameters ---------- years : numpy.ndarray(int) Array of years. Default is numpy.array([]). rates : numpy.ndarray(float) Discount rates for each year in years. Default is numpy.array([]). Note: rates given in float, e.g., to set 1% rate use 0.01 """ self.years = np.array([]) if years is None else years self.rates = np.array([]) if rates is None else rates
[docs] def clear(self): """Reinitialize attributes.""" # Following values are given for each defined year self.years = np.array([], int) self.rates = np.array([], float)
[docs] def check(self): """ Check attributes consistency. Raises ------ ValueError """ u_check.size(len(self.years), self.rates, 'DiscRates.rates')
[docs] def select(self, year_range): """ Select discount rates in given years. Parameters ---------- year_range: np.array(int) continuous sequence of selected years. Returns: climada.entity.DiscRates The selected discrates in the year_range """ pos_year = np.isin(year_range, self.years) if not np.all(pos_year): LOGGER.info('No discount rates for given years.') return None pos_year = np.isin(self.years, year_range) return DiscRates(years=self.years[pos_year], rates=self.rates[pos_year])
[docs] def append(self, disc_rates): """ Check and append discount rates to current DiscRates. Overwrite discount rate if same year. Parameters ---------- disc_rates: climada.entity.DiscRates DiscRates instance to append Raises ------ ValueError """ disc_rates.check() if self.years.size == 0: self.__dict__ = copy.deepcopy(disc_rates.__dict__) return new_year = array('l') new_rate = array('d') for year, rate in zip(disc_rates.years, disc_rates.rates): found = np.where(year == self.years)[0] if found.size > 0: self.rates[found[0]] = rate else: new_year.append(year) new_rate.append(rate) self.years = np.append(self.years, new_year).astype(int, copy=False) self.rates = np.append(self.rates, new_rate)
[docs] def net_present_value(self, ini_year, end_year, val_years): """ Compute net present value between present year and future year. Parameters ---------- ini_year: float initial year end_year: float end year val_years: np.array cash flow at each year btw ini_year and end_year (both included) Returns ------- net_present_value: float net present value between present year and future year. """ year_range = np.arange(ini_year, end_year + 1) if year_range.size != val_years.size: raise ValueError('Wrong size of yearly values.') sel_disc = self.select(year_range) if sel_disc is None: raise ValueError('No information of discount rates for provided years:' f' {ini_year} - {end_year}') return u_fin.net_present_value(sel_disc.years, sel_disc.rates, val_years)
[docs] def plot(self, axis=None, figsize=(6, 8), **kwargs): """ Plot discount rates per year. Parameters ---------- axis: matplotlib.axes._subplots.AxesSubplot, optional axis to use figsize: tuple(int, int), optional size of the figure. The default is (6,8) kwargs: optional keyword arguments passed to plotting function axis.plot Returns ------- axis: matplotlib.axes._subplots.AxesSubplot axis handles of the plot """ if not axis: _, axis = plt.subplots(1, 1, figsize=figsize) axis.set_title('Discount rates') axis.set_xlabel('Year') axis.set_ylabel('discount rate (%)') axis.plot(self.years, self.rates * 100, **kwargs) axis.set_xlim((self.years.min(), self.years.max())) return axis
[docs] @classmethod def from_mat(cls, file_name, var_names=None): """ Read MATLAB file generated with previous MATLAB CLIMADA version. Parameters ---------- file_name: str filename including path and extension description: str, optional description of the data. The default is '' var_names: dict, optional name of the variables in the file. Default: >>> DEF_VAR_MAT = { ... 'sup_field_name': 'entity', ... 'field_name': 'discount', ... 'var_name': { ... 'year': 'year', ... 'disc': 'discount_rate', ... } ... } Returns ------- climada.entity.DiscRates : The disc rates from matlab """ if var_names is None: var_names = DEF_VAR_MAT disc = u_hdf5.read(file_name) try: disc = disc[var_names['sup_field_name']] except KeyError: pass try: disc = disc[var_names['field_name']] years = np.squeeze(disc[var_names['var_name']['year']]). \ astype(int, copy=False) rates = np.squeeze(disc[var_names['var_name']['disc']]) except KeyError as err: raise KeyError("Not existing variable: %s" % str(err)) from err return cls(years=years, rates=rates)
[docs] def read_mat(self, *args, **kwargs): """This function is deprecated, use ``DiscRates.from_mat`` instead.""" LOGGER.warning( "The use of DiscRates.read_mat is deprecated." "Use DiscRates.from_mat instead." ) self.__dict__ = DiscRates.from_mat(*args, **kwargs).__dict__
[docs] @classmethod def from_excel(cls, file_name, var_names=None): """ Read excel file following template and store variables. Parameters ---------- file_name: str filename including path and extension description: str, optional description of the data. The default is '' var_names: dict, optional name of the variables in the file. The Default is >>> DEF_VAR_EXCEL = { ... 'sheet_name': 'discount', ... 'col_name': { ... 'year': 'year', ... 'disc': 'discount_rate', ... } ... } Returns ------- climada.entity.DiscRates : The disc rates from excel """ if var_names is None: var_names = DEF_VAR_EXCEL dfr = pd.read_excel(file_name, var_names['sheet_name']) try: years = dfr[var_names['col_name']['year']].values. \ astype(int, copy=False) rates = dfr[var_names['col_name']['disc']].values except KeyError as err: raise KeyError("Not existing variable: %s" % str(err)) from err return cls(years=years, rates=rates)
[docs] def read_excel(self, *args, **kwargs): """This function is deprecated, use DiscRates.from_excel instead.""" LOGGER.warning("The use of DiscRates.read_excel is deprecated." "Use DiscRates.from_excel instead.") self.__dict__ = DiscRates.from_excel(*args, **kwargs).__dict__
[docs] def write_excel(self, file_name, var_names=None): """ Write excel file following template. Parameters ---------- file_name: str filename including path and extension var_names: dict, optional name of the variables in the file. The Default is >>> DEF_VAR_EXCEL = { ... 'sheet_name': 'discount', ... 'col_name': { ... 'year': 'year', ... 'disc': 'discount_rate', ... } ... } """ if var_names is None: var_names = DEF_VAR_EXCEL disc_wb = xlsxwriter.Workbook(file_name) disc_ws = disc_wb.add_worksheet(var_names['sheet_name']) header = [var_names['col_name']['year'], var_names['col_name']['disc']] for icol, head_dat in enumerate(header): disc_ws.write(0, icol, head_dat) for i_yr, (disc_yr, disc_rt) in enumerate(zip(self.years, self.rates), 1): disc_ws.write(i_yr, 0, disc_yr) disc_ws.write(i_yr, 1, disc_rt) disc_wb.close()
[docs] @classmethod def from_csv( cls, file_name, year_column="year", disc_column="discount_rate", **kwargs ): """ Read DiscRate from a csv file following template and store variables. Parameters ---------- file_name: str filename including path and extension year_column: str, optional name of the column that contains the years, Default: "year" disc_column: str, optional name of the column that contains the discount rates, Default: "discount_rate" **kwargs: any additional arguments, e.g., `sep`, `delimiter`, `head`, are forwarded to ``pandas.read_csv`` Returns ------- climada.entity.DiscRates : The disc rates from the csv file """ dfr = pd.read_csv(file_name, **kwargs) try: years = dfr[year_column].values.astype(int, copy=False) rates = dfr[disc_column].values except KeyError as err: raise ValueError( f"missing column in csv file ({year_column} or {disc_column})" ) from err return cls(years=years, rates=rates)
[docs] def write_csv( self, file_name, year_column="year", disc_column="discount_rate", **kwargs ): """ Write DiscRate to a csv file following template and store variables. Parameters ---------- file_name: str filename including path and extension year_column: str, optional name of the column that contains the years, Default: "year" disc_column: str, optional name of the column that contains the discount rates, Default: "discount_rate" **kwargs: any additional arguments, e.g., `sep`, `delimiter`, `head`, are forwarded to ``pandas.read_csv`` """ dfr = pd.DataFrame( { year_column: self.years, disc_column: self.rates, } ) dfr.to_csv(file_name, **kwargs)