Source code for pyam.timeseries

# -*- coding: utf-8 -*-

import numpy as np
from pyam.logger import logger
from pyam.utils import isstr, cast_years_to_int

# %%


[docs]def fill_series(x, year): """Returns the value of a timeseries (indexed over years) for a year by linear interpolation. Parameters ---------- x: pandas.Series a timeseries to be interpolated year: int year of interpolation """ x = x.dropna() if year in x.index and not np.isnan(x[year]): return x[year] else: prev = [i for i in x.index if i < year] nxt = [i for i in x.index if i > year] if prev and nxt: p = max(prev) n = min(nxt) return ((n - year) * x[p] + (year - p) * x[n]) / (n - p) else:
return np.nan
[docs]def cumulative(x, first_year, last_year): """Returns the cumulative sum of a timeseries (indexed over years), implements linear interpolation between years, ignores nan's in the range. The function includes the last-year value of the series, and raises a warning if start_year or last_year is outside of the timeseries range and returns nan Parameters ---------- x: pandas.Series a timeseries to be summed over time first_year: int first year of the sum last_year: int last year of the sum (inclusive) """ # if the timeseries does not cover the range `[first_year, last_year]`, # return nan to avoid erroneous aggregation if min(x.index) > first_year: logger().warning('the timeseries `{}` does not start by {}'.format( x.name or x, first_year)) return np.nan if max(x.index) < last_year: logger().warning('the timeseries `{}` does not extend until {}' .format(x.name or x, last_year)) return np.nan # cast tiemseries colums to `int` if necessary if not x.index.dtype == 'int64': cast_years_to_int(x, index=True) x[first_year] = fill_series(x, first_year) x[last_year] = fill_series(x, last_year) years = [i for i in x.index if i >= first_year and i <= last_year and ~np.isnan(x[i])] years.sort() # loop over years if not np.isnan(x[first_year]) and not np.isnan(x[last_year]): value = 0 for (i, yr) in enumerate(years[:-1]): next_yr = years[i+1] # the summation is shifted to include the first year fully in sum, # otherwise, would return a weighted average of `yr` and `next_yr` value += ((next_yr - yr - 1) * x[next_yr] + (next_yr - yr + 1) * x[yr]) / 2 # the loop above does not include the last element in range # (`last_year`), therefore added explicitly value += x[last_year]
return value def cross_threshold(x, threshold=0, direction=['from above', 'from below']): """Returns a list of the years in which a timeseries (indexed over years) crosses a given threshold Parameters ---------- x: pandas.Series a timeseries indexed over years threshold: float, default 0 the threshold that the timeseries is checked against direction: str, optional, default `['from above', 'from below']` whether to return all years where the threshold is crossed or only where threshold is crossed in a specific direction """ prev_yr, prev_val = None, None years = [] direction = [direction] if isstr(direction) else list(direction) if not set(direction).issubset(set(['from above', 'from below'])): raise ValueError('invalid direction `{}`'.format(direction)) for yr, val in zip(x.index, x.values): if np.isnan(val): # ignore nans in the timeseries continue if prev_val is None: prev_yr, prev_val = yr, val continue if not np.sign(prev_val - threshold) == np.sign(val - threshold): if ('from above' in direction and prev_val > val) \ or ('from below' in direction and prev_val < val): change = (val - prev_val) / (yr - prev_yr) # add one because int() rounds down cross_yr = prev_yr + int((threshold - prev_val) / change) + 1 years.append(cross_yr) prev_yr, prev_val = yr, val return years