From ad79fa29fe04cd10148f72a311834401094db4b3 Mon Sep 17 00:00:00 2001 From: Alina Voilova <91426818+Pizza2Pizza@users.noreply.github.com> Date: Sat, 20 Apr 2024 00:46:46 +0200 Subject: [PATCH] Intersect (#79) * added slice property & tests * changed toml * created branch for intersect_flex function * new test function * added tests for intersect_flex frames * all test are passing * test ignore all for intersect frame * fixed documentation * deleted req docs * added DST testcase * not working help * Added back missing file * added valuerror for sod with less than daily freq * deleted unnecessary files * tests for indexable * tests for indexable with 2 obj * added oneliner in docs and test for 3 obj. case --------- Co-authored-by: Alina Voilova Co-authored-by: rwijtvliet --- dev_scripts/checks.py | 42 --- docs/core/pfline.rst | 1 + docs/core/toplevel.rst | 5 +- docs/requirements-docs.txt | 14 - docs/savefig/fig_hedge.png | Bin 32711 -> 32477 bytes docs/savefig/fig_offtake.png | Bin 64411 -> 64272 bytes portfolyo/__init__.py | 7 +- portfolyo/core/shared/plot.py | 183 +---------- portfolyo/tools/intersect.py | 158 +++++++++- portfolyo/{core/shared => tools2}/concat.py | 8 +- portfolyo/tools2/intersect.py | 47 +++ portfolyo/tools2/plot.py | 148 +++++++++ setup.cfg | 2 +- tests/core/shared/test_concat_error_cases.py | 2 +- tests/core/shared/test_concat_pfline.py | 2 +- tests/core/shared/test_concat_pfstate.py | 2 +- tests/tools/test_intersect.py | 39 ++- tests/tools/test_intersect_flex.py | 303 +++++++++++++++++++ tests/tools/test_intersect_flex_frame.py | 175 +++++++++++ tests/tools2/test_indexable.py | 223 ++++++++++++++ 20 files changed, 1100 insertions(+), 261 deletions(-) delete mode 100644 dev_scripts/checks.py delete mode 100644 docs/requirements-docs.txt rename portfolyo/{core/shared => tools2}/concat.py (97%) create mode 100644 portfolyo/tools2/intersect.py create mode 100644 portfolyo/tools2/plot.py create mode 100644 tests/tools/test_intersect_flex.py create mode 100644 tests/tools/test_intersect_flex_frame.py create mode 100644 tests/tools2/test_indexable.py diff --git a/dev_scripts/checks.py b/dev_scripts/checks.py deleted file mode 100644 index 265a864..0000000 --- a/dev_scripts/checks.py +++ /dev/null @@ -1,42 +0,0 @@ -import pandas as pd -import portfolyo as pf -from portfolyo.core.shared import concat - - -def get_idx( - startdate: str, starttime: str, tz: str, freq: str, enddate: str -) -> pd.DatetimeIndex: - # Empty index. - if startdate is None: - return pd.DatetimeIndex([], freq=freq, tz=tz) - # Normal index. - ts_start = pd.Timestamp(f"{startdate} {starttime}", tz=tz) - ts_end = pd.Timestamp(f"{enddate} {starttime}", tz=tz) - return pd.date_range(ts_start, ts_end, freq=freq, inclusive="left") - - -index = pd.date_range("2020", "2024", freq="QS", inclusive="left") -# index2 = pd.date_range("2023", "2025", freq="QS", inclusive="left") -# pfl = pf.dev.get_flatpfline(index) -# pfl2 = pf.dev.get_flatpfline(index2) -# print(pfl) -# print(pfl2) - -# pfs = pf.dev.get_pfstate(index) - -# pfs2 = pf.dev.get_pfstate(index2) -# pfl3 = concat.general(pfl, pfl2) -# print(pfl3) - -# print(index) -# print(index2) - -whole_pfl = pf.dev.get_nestedpfline(index) -pfl_a = whole_pfl.slice[:"2021"] - -pfl_b = whole_pfl.slice["2021":"2022"] -pfl_c = whole_pfl.slice["2022":] -result = concat.concat_pflines(pfl_a, pfl_b, pfl_c) -result2 = concat.concat_pflines(pfl_b, pfl_c, pfl_a) -print(result) -print(result2) diff --git a/docs/core/pfline.rst b/docs/core/pfline.rst index 2c907cc..e4ccb54 100644 --- a/docs/core/pfline.rst +++ b/docs/core/pfline.rst @@ -270,6 +270,7 @@ Another slicing method is implemented with the ``.slice[]`` property. The improv + Concatenation ============= diff --git a/docs/core/toplevel.rst b/docs/core/toplevel.rst index 4b2c891..7bd08e7 100644 --- a/docs/core/toplevel.rst +++ b/docs/core/toplevel.rst @@ -23,8 +23,11 @@ Work on pandas objects Work on portfolyo objects ------------------------- -* ``portfolyo.concat()`` Concatenates PfLines into one PfLine. +* ``portfolyo.concat()`` Concatenates PfLines (or PfStates) into one PfLine (or PfState). * ``portfolyo.plot_pfstates()`` Plots several PfStates in one figure. +* ``portfolyo.intersection()`` Intersect several dataframes and/or series and/or Pflines and/or PfStates. + + diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt deleted file mode 100644 index c6f63b7..0000000 --- a/docs/requirements-docs.txt +++ /dev/null @@ -1,14 +0,0 @@ -sphinx -sphinx-autobuild -# These are dependencies of various sphinx extensions for documentation. -ipython==8.12 # readthedocs needs python <=3.8, and this only works with ipython <=8.12 -matplotlib -numpydoc -sphinx-copybutton -sphinx-exec-code -sphinx_rtd_theme - -insegel - -nbsphinx -pandoc \ No newline at end of file diff --git a/docs/savefig/fig_hedge.png b/docs/savefig/fig_hedge.png index 2fea7c3c50f503dfd026e57b4eb206d02733e835..13bb2086fccf5cbcc68506e910d9af5301a32bf2 100644 GIT binary patch literal 32477 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

+M}njjlJo#_ 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 literal 32711 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 diff --git a/docs/savefig/fig_offtake.png b/docs/savefig/fig_offtake.png index db42f87f227bb0992f7b46151920deda6d7b375b..160a93ed5bd2dd6ea2059bd96c3eac95956d924c 100644 GIT binary patch delta 35603 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 delta 35741 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 diff --git a/portfolyo/__init__.py b/portfolyo/__init__.py index 8eef784..1e48760 100644 --- a/portfolyo/__init__.py +++ b/portfolyo/__init__.py @@ -3,10 +3,11 @@ from . import _version, dev, testing, tools from .core import extendpandas # extend functionalty of pandas from .core import suppresswarnings +from .tools2.plot import plot_pfstates from .core.pfline import Kind, PfLine, Structure, create from .core.pfstate import PfState -from .core.shared.concat import general as concat -from .core.shared.plot import plot_pfstates +from .tools2.concat import general as concat +from .tools2.plot import plot_pfstates from .prices.hedge import hedge from .prices.utils import is_peak_hour from .tools.changefreq import averagable as asfreq_avg @@ -17,6 +18,8 @@ from .tools.tzone import force_agnostic, force_aware from .tools.unit import Q_, Unit, ureg from .tools.wavg import general as wavg +from .tools2.concat import general as concat +from .tools2.intersect import indexable as intersection # from .core.shared.concat import general as concat diff --git a/portfolyo/core/shared/plot.py b/portfolyo/core/shared/plot.py index 460cb67..a328d25 100644 --- a/portfolyo/core/shared/plot.py +++ b/portfolyo/core/shared/plot.py @@ -4,10 +4,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING import matplotlib -import numpy as np + from matplotlib import pyplot as plt from ... import tools @@ -115,51 +115,6 @@ def plot(self: PfLine, cols: str = None) -> plt.Figure: class PfStatePlot: - # def plot_to_ax( - # self: PfState, ax: plt.Axes, line: str = "offtake", col: str = None, **kwargs - # ) -> None: - # """Plot a timeseries of a PfState in the portfolio state to a specific axes. - - # Parameters - # ---------- - # ax : plt.Axes - # The axes object to which to plot the timeseries. - # line : str, optional - # The pfline to plot. One of {'offtake' (default), 'sourced', 'unsourced', - # 'netposition', 'procurement', 'sourcedfraction'}. - # col : str, optional - # The column to plot. Default: plot volume `w` [MW] (if available) or else - # price `p` [Eur/MWh]. - # Any additional kwargs are passed to the pd.Series.plot function. - # """ - # if line == "offtake": - # how = DEFAULTHOW.get(col, "step") - # (-self.offtake).plot_to_ax(ax, col, how) - # ax.bar_label( - # ax.containers[0], label_type="edge", fmt="%,.0f".replace(",", " ") - # ) - - # elif line.endswith("sourcedfraction"): # (un)sourcedfraction - # fractions = getattr(self, line) - # vis.plot_timeseries(ax, fractions, how="bar", color="grey") - # ax.bar_label( - # ax.containers[0], - # label_type="edge", - # labels=fractions.apply("{:.0%}".format), - # ) # print labels on top of each bar - - # elif line == "sourced": - # self.sourced.plot_to_ax( - # ax, - # col, - # ) - # if col == "p": - - # vis.plot_timeseries(ax, self.unsourcedprice["p"], how="bar", alpha=0.0) - # ax.bar_label( - # ax.containers[0], label_type="center", fmt="%.2f" - # ) # print labels on top of each bar - def plot(self: PfState) -> plt.Figure: """Plot the portfolio state. @@ -225,137 +180,3 @@ def plot(self: PfState) -> plt.Figure: fig.tight_layout() return fig - - -def plot_pfstates(dic: Dict[str, PfState], freq: str = "MS") -> plt.Figure: - """Plot multiple PfState instances. - - Parameters - ---------- - dic : Dict[str, PfState] - Dictionary with PfState instances as values, and their names as the keys. - - Returns - ------- - plt.Figure - The figure object to which the instances were plotted. - """ - - gridspec = {"width_ratios": [0.3, 1, 1], "height_ratios": [4, 1] * len(dic)} - figsize = (15, 5 * len(dic)) - fig, axes = plt.subplots(len(dic) * 2, 3, gridspec_kw=gridspec, figsize=figsize) - axesgroups = axes.flatten().reshape((len(dic), 6)) - - # Share x axes. - sharex = axesgroups[:, (1, 2, 4)].flatten() - for ax1, ax2 in zip(sharex[1:], sharex[:-1]): - ax1.sharex(ax2) - # Share y axes. - sharey = axesgroups[:, 2] - for ax1, ax2 in zip(sharey[1:], sharey[:-1]): - ax1.sharey(ax2) - - # TODO: resample all to have same index (frequency and length). - - for i, ((pfname, pfs), axes) in enumerate(zip(dic.items(), axesgroups)): - # If freq is MS or longer: use categorical axes. Plot volumes in MWh. - # If freq is D or shorter: use time axes. Plot volumes in MW. - is_category = tools.freq.shortest(pfs.index.freq, "MS") == "MS" - - # Portfolio name. - axes[0].text( - 0, - 1, - pfname.replace(" ", "\n"), - fontsize=12, - fontweight="bold", - verticalalignment="top", - horizontalalignment="left", - ) - axes[0].axis("off") - - # Volumes. - if is_category: - s, kwargs = -1 * pfs.offtakevolume.q, defaultkwargs("q", is_category) - else: - s, kwargs = -1 * pfs.offtakevolume.w, defaultkwargs("w", is_category) - vis.plot_timeseries(axes[1], s, **kwargs) - - # Sourced fraction. - vis.plot_timeseries( - axes[2], pfs.sourcedfraction, **defaultkwargs("f", is_category) - ) - - # Empty. - axes[3].axis("off") - - # Procurement Price. - vis.plot_timeseries(axes[4], pfs.pnl_cost.p, **defaultkwargs("p", is_category)) - - # Empty. - axes[5].axis("off") - - # Tick formatting. - axes[2].yaxis.set_major_formatter(matplotlib.ticker.PercentFormatter(1.0)) - axes[1].yaxis.set_major_formatter( - matplotlib.ticker.FuncFormatter( - lambda x, p: "{:,.0f}".format(x).replace(",", " ") - ) - ) - - for a, ax in enumerate(axes): - if i == 0 and a in [1, 2]: - ax.xaxis.set_tick_params(labelbottom=False, labeltop=True, pad=25) - else: - ax.xaxis.set_tick_params(labelbottom=False, labeltop=False) - - if i == 0: - axes[1].set_title("Offtake Volume &\nprocurement price", y=1.27) - axes[2].set_title("Sourced fraction", y=1.27) - - return - draw_horizontal_lines(fig, axes) # draw horizontal lines between portfolios - - -def draw_horizontal_lines(fig, axes): - """Function to draw horizontal lines between multiple portfolios. - This function does not return anything, but tries to plot a 2D line after every 2 axes, eg. - after (0,2), (0,4),... beacuse each portfolio requires 2x4 axes in the fig (where rows=2, columns=4). - - Parameters - ---------- - fig : plt.subplots() - axes : plt.subplots() - """ - # rearange the axes for no overlap - fig.tight_layout() - - # Get the bounding boxes of the axes including text decorations - r = fig.canvas.get_renderer() - bboxes = np.array( - [ - ax.get_tightbbox(r).transformed(fig.transFigure.inverted()) - for ax in axes.flat - ], - matplotlib.transforms.Bbox, - ).reshape(axes.shape) - - """TO CORRECT: the horizontal line is not exactly in the middle of two graphs. - It is more inclined towards the second or next graph in the queue. - Each pftstate has 4x4 grid and this is plotted in the same graph, but as subgraphs. - """ - - # Get the minimum and maximum extent, get the coordinate half-way between those - ymax = ( - np.array(list(map(lambda b: b.y1, bboxes.flat))).reshape(axes.shape).max(axis=1) - ) - ymin = ( - np.array(list(map(lambda b: b.y0, bboxes.flat))).reshape(axes.shape).min(axis=1) - ) - ys = np.c_[ymax[2:-1:2], ymin[1:-2:2]].mean(axis=1) - ys = [ymax[0], *ys] - - # Draw a horizontal lines at those coordinates - for y in ys: - line = plt.Line2D([0, 1], [y, y], transform=fig.transFigure, color="black") - fig.add_artist(line) diff --git a/portfolyo/tools/intersect.py b/portfolyo/tools/intersect.py index fa940a2..d28cdac 100644 --- a/portfolyo/tools/intersect.py +++ b/portfolyo/tools/intersect.py @@ -1,6 +1,10 @@ -from typing import List, Union - +from typing import List, Union, Tuple import pandas as pd +from portfolyo import tools + +from portfolyo.tools.right import stamp +from portfolyo.tools.freq import longest, longer_or_shorter +from datetime import datetime def indices(*idxs: pd.DatetimeIndex) -> pd.DatetimeIndex: @@ -57,8 +61,138 @@ def indices(*idxs: pd.DatetimeIndex) -> pd.DatetimeIndex: return pd.DatetimeIndex(sorted(list(values)), freq=freq, name=name, tz=tz) +def indices_flex( + *idxs: pd.DatetimeIndex, + ignore_freq: bool = False, + ignore_tz: bool = False, + ignore_start_of_day: bool = False, +) -> Tuple[pd.DatetimeIndex]: + """Intersect several DatetimeIndices, but allow for more flexibility of ignoring + certain properties. + + Parameters + ---------- + *idxs : pd.DatetimeIndex + The indices to intersect. + ignore_freq: bool, optional (default: False) + If True, do the intersection even if the frequencies do not match; drop the + time periods that do not (fully) exist in either of the frames. + ignore_tz: bool, optional (default: False) + If True, ignore the timezones; perform the intersection using 'wall time'. + ignore_start_of_day: bool, optional (default: False) + If True, perform the intersection even if the frames have a different start-of-day. + The start-of-day of the original frames is preserved, even if the frequency is shorter + than daily. + + Returns + ------- + Tuple[pd.DatetimeIndex] + The intersection for each datetimeindex (in same order as input idxs). + + See also + -------- + indices + """ + if len(idxs) == 0: + raise ValueError("Must specify at least one index.") + + if len(idxs) == 1: + return idxs[0] + # convert tuple object into a list + idxs = list(idxs) + + # If we land here, we have at least 2 indices. + distinct_freqs = set([i.freq for i in idxs]) + if len(distinct_freqs) != 1 and ignore_freq is False: + raise ValueError(f"Indices must have equal frequencies; got {distinct_freqs}.") + + distinct_tzs = set([i.tz for i in idxs]) + if len(distinct_tzs) != 1 and ignore_tz is False: + raise ValueError(f"Indices must have equal timezones; got {distinct_tzs}.") + + empty_idx = [len(i) == 0 for i in idxs] + if any(empty_idx): + return pd.DatetimeIndex([]) + + # If we land here, we have at least 2 indices, all are not empty. + + distinct_sod = set([i[0].time() for i in idxs]) + if len(distinct_sod) != 1 and ignore_start_of_day is False: + raise ValueError(f"Indices must have equal start-of-day; got {distinct_sod}.") + for i in range(len(idxs)): + if len(distinct_sod) != 1 and longer_or_shorter(idxs[i].freq, "D") == -1: + raise ValueError( + "Downsample all indices to daily-or-longer, or trim them so they have the same start-of-day, before attempting to calculate the intersection" + ) + + freq, name, tz = [], [], [] + for i in range(len(idxs)): + freq.append(idxs[i].freq) + name.append(idxs[i].name) + tz.append(idxs[i].tz) + + longest_freq = freq[0] + if ignore_freq is True and len(distinct_freqs) != 1: + # Find the longest frequency + longest_freq = longest(*freq) + # trim datetimeindex + for i in range(len(idxs)): + # if idxs[i].freq is not the same as longest freq, we trim idxs[i] + if idxs[i].freq != longest_freq: + idxs[i] = tools.trim.index(idxs[i], longest_freq) + + if ignore_tz is True and len(distinct_tzs) != 1: + # set timezone to none for all values + for i in range(len(idxs)): + idxs[i] = idxs[i].tz_localize(None) + + if ignore_start_of_day is True and len(distinct_sod) != 1: + # Save a copy of the original hours and minutes + start_of_day = [x[0].time() for x in idxs] + # Set the time components to midnight for each index in the list + idxs = [index.normalize() for index in idxs] + + # Calculation is cumbersome: pandas DatetimeIndex.intersection not working correctly on timezone-aware indices (#46702) + values = set(idxs[0]) + # intersection is not working on datetimeindex with different freq->we need to use mask + for i in idxs[1:]: + values = values.intersection(set(i)) + values = sorted(values) + + if len(values) == 0: + return tuple([pd.DatetimeIndex([]) for _i in idxs]) + + idxs_out = [] + for i in range(len(idxs)): + start = min(values) + # end = stamp(start, longest_freq._prefix) + end = max(values) + end = stamp(end, longest_freq) + + if ignore_start_of_day is True: + start = datetime.combine(pd.to_datetime(start).date(), start_of_day[i]) + end = datetime.combine(pd.to_datetime(end).date(), start_of_day[i]) + # inclusive = "left" + + idxs_out.append( + pd.date_range( + start=start, + end=end, + freq=freq[i], + name=name[i], + tz=tz[i], + inclusive="left", + ) + ) + + return tuple(idxs_out) + + def frames( - *frames: Union[pd.Series, pd.DataFrame] + *frames: Union[pd.Series, pd.DataFrame], + ignore_freq: bool = False, + ignore_tz: bool = False, + ignore_start_of_day: bool = False, ) -> List[Union[pd.Series, pd.DataFrame]]: """Intersect several dataframes and/or series. @@ -66,6 +200,15 @@ def frames( ---------- *frames : pd.Series and/or pd.DataFrame The frames to intersect. + ignore_freq: bool, optional (default: False) + If True, do the intersection even if the frequencies do not match; drop the + time periods that do not (fully) exist in either of the frames. + ignore_tz: bool, optional (default: False) + If True, ignore the timezones; perform the intersection using 'wall time'. + ignore_start_of_day: bool, optional (default: False) + If True, perform the intersection even if the frames have a different start-of-day. + The start-of-day of the original frames is preserved, even if the frequency is shorter + than daily. Returns ------- @@ -77,5 +220,10 @@ def frames( The indices must have equal frequency, timezone, start-of-day. Otherwise, an error is raised. If there is no overlap, empty frames are returned. """ - common_index = indices(*[fr.index for fr in frames]) - return [fr.loc[common_index] for fr in frames] + new_idxs = indices_flex( + *[fr.index for fr in frames], + ignore_freq=ignore_freq, + ignore_tz=ignore_tz, + ignore_start_of_day=ignore_start_of_day, + ) + return [fr.loc[idx] for idx, fr in zip(new_idxs, frames)] diff --git a/portfolyo/core/shared/concat.py b/portfolyo/tools2/concat.py similarity index 97% rename from portfolyo/core/shared/concat.py rename to portfolyo/tools2/concat.py index 5f105d2..bcb5dce 100644 --- a/portfolyo/core/shared/concat.py +++ b/portfolyo/tools2/concat.py @@ -5,11 +5,11 @@ import pandas as pd from portfolyo import tools -from ..pfstate import PfState -from ..pfline.enums import Structure +from ..core.pfstate import PfState +from ..core.pfline.enums import Structure -from ..pfline import PfLine, create -from .. import pfstate +from ..core.pfline import PfLine, create +from ..core import pfstate def general(pfl_or_pfs: Iterable[PfLine | PfState]) -> None: diff --git a/portfolyo/tools2/intersect.py b/portfolyo/tools2/intersect.py new file mode 100644 index 0000000..8b0664c --- /dev/null +++ b/portfolyo/tools2/intersect.py @@ -0,0 +1,47 @@ +from portfolyo.tools.intersect import indices_flex +from ..core.pfline import PfLine +from ..core.pfstate import PfState +from typing import List, Union + +import pandas as pd + + +def indexable( + *frames: Union[pd.Series, pd.DataFrame, PfLine, PfState], + ignore_freq: bool = False, + ignore_tz: bool = False, + ignore_start_of_day: bool = False, +) -> List[Union[pd.Series, pd.DataFrame, PfLine, PfState]]: + """Intersect several dataframes and/or series. + + Parameters + ---------- + *frames : pd.Series and/or pd.DataFrame and/or PfLines and/or PfStates + The frames to intersect. + ignore_freq: bool, optional (default: False) + If True, do the intersection even if the frequencies do not match; drop the + time periods that do not (fully) exist in either of the frames. + ignore_tz: bool, optional (default: False) + If True, ignore the timezones; perform the intersection using 'wall time'. + ignore_start_of_day: bool, optional (default: False) + If True, perform the intersection even if the frames have a different start-of-day. + The start-of-day of the original frames is preserved, even if the frequency is shorter + than daily. + + Returns + ------- + list of series and/or dataframes + As input, but trimmed to their intersection. + + Notes + ----- + The indices must have equal frequency, timezone, start-of-day. Otherwise, an error + is raised. If there is no overlap, empty frames are returned. + """ + new_idxs = indices_flex( + *[fr.index for fr in frames], + ignore_freq=ignore_freq, + ignore_tz=ignore_tz, + ignore_start_of_day=ignore_start_of_day, + ) + return [fr.loc[idx] for idx, fr in zip(new_idxs, frames)] diff --git a/portfolyo/tools2/plot.py b/portfolyo/tools2/plot.py new file mode 100644 index 0000000..59fcaa6 --- /dev/null +++ b/portfolyo/tools2/plot.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +from typing import Dict + +import matplotlib +import numpy as np +from matplotlib import pyplot as plt + +from portfolyo.core.shared.plot import defaultkwargs + +from .. import tools +from .. import visualize as vis + +from ..core.pfstate import PfState + + +def plot_pfstates(dic: Dict[str, PfState], freq: str = "MS") -> plt.Figure: + """Plot multiple PfState instances. + + Parameters + ---------- + dic : Dict[str, PfState] + Dictionary with PfState instances as values, and their names as the keys. + + Returns + ------- + plt.Figure + The figure object to which the instances were plotted. + """ + + gridspec = {"width_ratios": [0.3, 1, 1], "height_ratios": [4, 1] * len(dic)} + figsize = (15, 5 * len(dic)) + fig, axes = plt.subplots(len(dic) * 2, 3, gridspec_kw=gridspec, figsize=figsize) + axesgroups = axes.flatten().reshape((len(dic), 6)) + + # Share x axes. + sharex = axesgroups[:, (1, 2, 4)].flatten() + for ax1, ax2 in zip(sharex[1:], sharex[:-1]): + ax1.sharex(ax2) + # Share y axes. + sharey = axesgroups[:, 2] + for ax1, ax2 in zip(sharey[1:], sharey[:-1]): + ax1.sharey(ax2) + + # TODO: resample all to have same index (frequency and length). + + for i, ((pfname, pfs), axes) in enumerate(zip(dic.items(), axesgroups)): + # If freq is MS or longer: use categorical axes. Plot volumes in MWh. + # If freq is D or shorter: use time axes. Plot volumes in MW. + is_category = tools.freq.shortest(pfs.index.freq, "MS") == "MS" + + # Portfolio name. + axes[0].text( + 0, + 1, + pfname.replace(" ", "\n"), + fontsize=12, + fontweight="bold", + verticalalignment="top", + horizontalalignment="left", + ) + axes[0].axis("off") + + # Volumes. + if is_category: + s, kwargs = -1 * pfs.offtakevolume.q, defaultkwargs("q", is_category) + else: + s, kwargs = -1 * pfs.offtakevolume.w, defaultkwargs("w", is_category) + vis.plot_timeseries(axes[1], s, **kwargs) + + # Sourced fraction. + vis.plot_timeseries( + axes[2], pfs.sourcedfraction, **defaultkwargs("f", is_category) + ) + + # Empty. + axes[3].axis("off") + + # Procurement Price. + vis.plot_timeseries(axes[4], pfs.pnl_cost.p, **defaultkwargs("p", is_category)) + + # Empty. + axes[5].axis("off") + + # Tick formatting. + axes[2].yaxis.set_major_formatter(matplotlib.ticker.PercentFormatter(1.0)) + axes[1].yaxis.set_major_formatter( + matplotlib.ticker.FuncFormatter( + lambda x, p: "{:,.0f}".format(x).replace(",", " ") + ) + ) + + for a, ax in enumerate(axes): + if i == 0 and a in [1, 2]: + ax.xaxis.set_tick_params(labelbottom=False, labeltop=True, pad=25) + else: + ax.xaxis.set_tick_params(labelbottom=False, labeltop=False) + + if i == 0: + axes[1].set_title("Offtake Volume &\nprocurement price", y=1.27) + axes[2].set_title("Sourced fraction", y=1.27) + + return + draw_horizontal_lines(fig, axes) # draw horizontal lines between portfolios + + +def draw_horizontal_lines(fig, axes): + """Function to draw horizontal lines between multiple portfolios. + This function does not return anything, but tries to plot a 2D line after every 2 axes, eg. + after (0,2), (0,4),... beacuse each portfolio requires 2x4 axes in the fig (where rows=2, columns=4). + + Parameters + ---------- + fig : plt.subplots() + axes : plt.subplots() + """ + # rearange the axes for no overlap + fig.tight_layout() + + # Get the bounding boxes of the axes including text decorations + r = fig.canvas.get_renderer() + bboxes = np.array( + [ + ax.get_tightbbox(r).transformed(fig.transFigure.inverted()) + for ax in axes.flat + ], + matplotlib.transforms.Bbox, + ).reshape(axes.shape) + + """TO CORRECT: the horizontal line is not exactly in the middle of two graphs. + It is more inclined towards the second or next graph in the queue. + Each pftstate has 4x4 grid and this is plotted in the same graph, but as subgraphs. + """ + + # Get the minimum and maximum extent, get the coordinate half-way between those + ymax = ( + np.array(list(map(lambda b: b.y1, bboxes.flat))).reshape(axes.shape).max(axis=1) + ) + ymin = ( + np.array(list(map(lambda b: b.y0, bboxes.flat))).reshape(axes.shape).min(axis=1) + ) + ys = np.c_[ymax[2:-1:2], ymin[1:-2:2]].mean(axis=1) + ys = [ymax[0], *ys] + + # Draw a horizontal lines at those coordinates + for y in ys: + line = plt.Line2D([0, 1], [y, y], transform=fig.transFigure, color="black") + fig.add_artist(line) diff --git a/setup.cfg b/setup.cfg index 4e57090..4edd93f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,7 +12,7 @@ max-line-length = 120 ignore = E501, W503, E202, E226 [tool:pytest] -addopts = --cov=. +#addopts = --cov=. markers = only_on_pr: marks tests as slow (select with -m only_on_pr and deselect with -m "not only_on_pr") pythonpath = ./tests diff --git a/tests/core/shared/test_concat_error_cases.py b/tests/core/shared/test_concat_error_cases.py index a5eb551..4c8e8ff 100644 --- a/tests/core/shared/test_concat_error_cases.py +++ b/tests/core/shared/test_concat_error_cases.py @@ -7,7 +7,7 @@ from portfolyo import dev from portfolyo.core.pfline.enums import Kind from portfolyo.core.pfstate.pfstate import PfState -from portfolyo.core.shared import concat +from portfolyo.tools2 import concat def test_general(): diff --git a/tests/core/shared/test_concat_pfline.py b/tests/core/shared/test_concat_pfline.py index f066602..0a862dc 100644 --- a/tests/core/shared/test_concat_pfline.py +++ b/tests/core/shared/test_concat_pfline.py @@ -3,7 +3,7 @@ import pandas as pd import pytest from portfolyo import dev -from portfolyo.core.shared import concat +from portfolyo.tools2 import concat TESTCASES2 = [ # whole idx, freq, where diff --git a/tests/core/shared/test_concat_pfstate.py b/tests/core/shared/test_concat_pfstate.py index 3ce923e..8f2c2de 100644 --- a/tests/core/shared/test_concat_pfstate.py +++ b/tests/core/shared/test_concat_pfstate.py @@ -3,7 +3,7 @@ import pandas as pd import pytest from portfolyo import dev -from portfolyo.core.shared import concat +from portfolyo.tools2 import concat TESTCASES2 = [ # whole idx, freq, where diff --git a/tests/tools/test_intersect.py b/tests/tools/test_intersect.py index fbb83f4..fed0837 100644 --- a/tests/tools/test_intersect.py +++ b/tests/tools/test_intersect.py @@ -154,7 +154,7 @@ def test_intersect_nooverlap(indexorframe: str, tz: str, freq: str, starttime: s get_idx("2020-01-01", starttime, tz, freq, "2022-01-01"), get_idx("2023-01-01", starttime, tz, freq, "2025-01-01"), ] - do_test_intersect(indexorframe, idxs, None, "", tz, freq) + do_test_intersect(indexorframe, idxs, None, "", tz, freq, check_freq=False) def do_test_intersect( @@ -164,12 +164,21 @@ def do_test_intersect( expected_starttime: str = None, expected_tz: str = None, expected_freq: str = None, + **kwargs, ): if indexorframe == "idx": - do_test_fn = do_test_intersect_index + do_test_intersect_index( + idxs, expected_startdate, expected_starttime, expected_tz, expected_freq + ) else: - do_test_fn = do_test_intersect_frame - do_test_fn(idxs, expected_startdate, expected_starttime, expected_tz, expected_freq) + do_test_intersect_frame( + idxs, + expected_startdate, + expected_starttime, + expected_tz, + expected_freq, + **kwargs, + ) def do_test_intersect_index( @@ -200,17 +209,31 @@ def do_test_intersect_frame( expected_starttime: str = None, expected_tz: str = None, expected_freq: str = None, + ignore_freq: bool = False, + ignore_start_of_day: bool = False, + ignore_tz: bool = False, + **kwargs, ): frames = get_frames(idxs) # Error case. if type(expected_startdate) is type and issubclass(expected_startdate, Exception): with pytest.raises(expected_startdate): - tools.intersect.frames(*frames) + tools.intersect.frames( + *frames, + ignore_start_of_day=ignore_start_of_day, + ignore_tz=ignore_tz, + ignore_freq=ignore_freq, + ) return # Normal case. - result_frames = tools.intersect.frames(*frames) + result_frames = tools.intersect.frames( + *frames, + ignore_freq=ignore_freq, + ignore_start_of_day=ignore_start_of_day, + ignore_tz=ignore_tz, + ) expected_index = get_idx( expected_startdate, expected_starttime, expected_tz, expected_freq ) @@ -218,6 +241,6 @@ def do_test_intersect_frame( for result, expected in zip(result_frames, expected_frames): if isinstance(result, pd.Series): - testing.assert_series_equal(result, expected) + testing.assert_series_equal(result, expected, **kwargs) else: - testing.assert_frame_equal(result, expected) + testing.assert_frame_equal(result, expected, **kwargs) diff --git a/tests/tools/test_intersect_flex.py b/tests/tools/test_intersect_flex.py new file mode 100644 index 0000000..29e1281 --- /dev/null +++ b/tests/tools/test_intersect_flex.py @@ -0,0 +1,303 @@ +from typing import Iterable, Union + +import pandas as pd +import pytest + +from portfolyo import testing, tools + +COMMON_END = "2022-02-02" + +TESTCASES = [ # startdates, freq, expected_startdate + # One starts at first day of year. + (("2020-01-01", "2020-01-20"), "15T", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "15T", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "H", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "H", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "D", "2020-01-20"), + (("2020-01-01", "2020-01-20"), "D", "2020-01-20"), + (("2020-01-01", "2020-03-01"), "MS", "2020-03-01"), + (("2020-01-01", "2020-03-01"), "MS", "2020-03-01"), + (("2020-01-01", "2020-04-01"), "QS", "2020-04-01"), + (("2020-01-01", "2020-04-01"), "QS", "2020-04-01"), + (("2020-01-01", "2021-01-01"), "AS", "2021-01-01"), + (("2020-01-01", "2021-01-01"), "AS", "2021-01-01"), + # Both start in middle of year. + (("2020-04-21", "2020-06-20"), "15T", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "15T", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "H", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "H", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "D", "2020-06-20"), + (("2020-04-21", "2020-06-20"), "D", "2020-06-20"), +] + +COMMON_END_2 = "2023-01-01" +TESTCASES_2 = [ # startdates, freq, expected_dates + # One starts at first day of year. + (("2020-01-01", "2020-01-20"), ("15T", "H"), "2020-01-20"), + (("2020-01-01", "2020-01-20"), ("15T", "D"), "2020-01-20"), + (("2022-04-01", "2021-02-01"), ("H", "MS"), "2022-04-01"), + (("2020-01-01", "2020-04-01"), ("H", "QS"), "2020-04-01"), + (("2020-01-01", "2021-01-01"), ("D", "AS"), "2021-01-01"), + # Both start in middle of year. + (("2020-04-21", "2020-06-20"), ("15T", "H"), "2020-06-20"), + (("2020-04-21", "2020-06-20"), ("15T", "D"), "2020-06-20"), + (("2020-04-21", "2020-07-01"), ("H", "MS"), "2020-07-01"), + (("2020-04-21", "2020-07-01"), ("H", "QS"), "2020-07-01"), + (("2020-04-21", "2021-01-01"), ("D", "AS"), "2021-01-01"), +] + + +def get_idx( + startdate: str, + starttime: str, + tz: str, + freq: str, + enddate: str, +) -> pd.DatetimeIndex: + # Empty index. + if startdate is None: + return pd.DatetimeIndex([], freq=freq, tz=tz) + # Normal index. + ts_start = pd.Timestamp(f"{startdate} {starttime}", tz=tz) + ts_end = pd.Timestamp(f"{enddate} {starttime}", tz=tz) + return pd.date_range(ts_start, ts_end, freq=freq, inclusive="left") + + +@pytest.mark.parametrize("tz", [None, "Europe/Berlin", "Asia/Kolkata"]) +@pytest.mark.parametrize(("startdates", "freq", "expected_startdate"), TESTCASES) +@pytest.mark.parametrize("starttime", ["00:00", "06:00"]) +# @pytest.mark.parametrize("indexorframe", ["idx", "fr"]) +def test_intersect_flex_ignore_start_of_day( + # indexorframe: str, + startdates: Iterable[str], + starttime: str, + tz: str, + freq: str, + expected_startdate: str, +): + otherstarttime = "00:00" if starttime == "06:00" else "06:00" + idxs = [ + get_idx( + startdates[0], + starttime, + tz, + freq, + COMMON_END, + ), + get_idx( + startdates[1], + otherstarttime, + tz, + freq, + COMMON_END, + ), + ] + do_test_intersect( + "idx", + idxs, + ValueError if freq == "15T" or freq == "H" else expected_startdate, + expected_tz=tz, + expected_freq=freq, + expected_starttime=starttime, + expected_otherstarttime=otherstarttime, + expected_othertz=tz, + expected_otherfreq=freq, + enddate=COMMON_END, + ignore_start_of_day=True, + ) + + +@pytest.mark.parametrize("tz", [None, "Europe/Berlin", "Asia/Kolkata"]) +@pytest.mark.parametrize("starttime", ["00:00", "06:00"]) +@pytest.mark.parametrize(("startdates", "freq", "expected_startdate"), TESTCASES) +# @pytest.mark.parametrize("indexorframe", ["idx", "fr"]) +def test_intersect_flex_ignore_tz( + # indexorframe: str, + startdates: Iterable[str], + starttime: str, + tz: str, + freq: str, + expected_startdate: str, +): + othertz = None if tz == "Europe/Berlin" else "Europe/Berlin" + idxs = [ + get_idx(startdates[0], starttime, tz, freq, COMMON_END), + get_idx(startdates[1], starttime, othertz, freq, COMMON_END), + ] + do_test_intersect( + "idx", + idxs, + expected_startdate, + expected_tz=tz, + expected_freq=freq, + expected_starttime=starttime, + expected_otherstarttime=starttime, + expected_othertz=othertz, + expected_otherfreq=freq, + enddate=COMMON_END, + ignore_tz=True, + ) + + +@pytest.mark.parametrize("tz", [None, "Europe/Berlin", "Asia/Kolkata"]) +@pytest.mark.parametrize(("startdates", "freq", "expected_startdate"), TESTCASES_2) +@pytest.mark.parametrize("starttime", ["00:00", "06:00"]) +def test_intersect_flex_ignore_freq( + # indexorframe: str, + startdates: Iterable[str], + starttime: str, + tz: str, + freq: Iterable[str], + expected_startdate: str, +): + """Test if intersection of indices with distinct frequencies gives correct result.""" + + idxs = [ + get_idx(startdates[0], starttime, tz, freq[0], COMMON_END_2), + get_idx(startdates[1], starttime, tz, freq[1], COMMON_END_2), + ] + do_test_intersect( + "idx", + idxs, + expected_startdate, + expected_tz=tz, + expected_freq=freq[0], + expected_starttime=starttime, + expected_otherstarttime=starttime, + expected_othertz=tz, + expected_otherfreq=freq[1], + enddate=COMMON_END_2, + ignore_freq=True, + ) + + +@pytest.mark.parametrize("tz", [None, "Europe/Berlin", "Asia/Kolkata"]) +@pytest.mark.parametrize(("startdates", "freq", "expected_startdate"), TESTCASES_2) +@pytest.mark.parametrize("starttime", ["00:00", "06:00"]) +def test_ignore_all( # indexorframe: str, + startdates: Iterable[str], + starttime: str, + tz: str, + freq: Iterable[str], + expected_startdate: str, +): + otherstarttime = "00:00" if starttime == "06:00" else "06:00" + othertz = None if tz == "Europe/Berlin" else "Europe/Berlin" + idxs = [ + get_idx(startdates[0], starttime, tz, freq[0], COMMON_END_2), + get_idx(startdates[1], otherstarttime, othertz, freq[1], COMMON_END_2), + ] + do_test_intersect( + "idx", + idxs, + ( + ValueError + if freq[0] == "15T" or freq[0] == "H" or freq[1] == "15T" or freq[1] == "H" + else expected_startdate + ), + expected_tz=tz, + expected_freq=freq[0], + expected_starttime=starttime, + expected_otherstarttime=otherstarttime, + expected_othertz=othertz, + expected_otherfreq=freq[1], + enddate=COMMON_END_2, + ignore_freq=True, + ignore_start_of_day=True, + ignore_tz=True, + ) + + +def do_test_intersect( + indexorframe: str, + idxs: Iterable[pd.DatetimeIndex], + expected_startdate: Union[str, Exception], + expected_starttime: str = None, + expected_tz: str = None, + expected_freq: str = None, + expected_otherstarttime: str = None, + expected_othertz: str = None, + expected_otherfreq: str = None, + enddate: str = None, + ignore_start_of_day: bool = False, + ignore_tz: bool = False, + ignore_freq: bool = False, +): + if indexorframe == "idx": + do_test_intersect_index( + idxs, + expected_startdate, + expected_starttime, + expected_tz, + expected_freq, + expected_otherstarttime, + expected_othertz, + expected_otherfreq, + enddate, + ignore_start_of_day, + ignore_tz, + ignore_freq, + ) + + +def do_test_intersect_index( + idxs: Iterable[pd.DatetimeIndex], + expected_startdate: Union[str, Exception], + expected_starttime: str = None, + expected_tz: str = None, + expected_freq: str = None, + expected_otherstarttime: str = None, + expected_othertz: str = None, + expected_otherfreq: str = None, + enddate: str = None, + ignore_start_of_day: bool = False, + ignore_tz: bool = False, + ignore_freq: bool = False, +): + # Error case. + if isinstance(expected_startdate, type) and issubclass( + expected_startdate, Exception + ): + with pytest.raises(expected_startdate): + tools.intersect.indices_flex( + *idxs, + ignore_start_of_day=False, + ignore_tz=False, + ignore_freq=ignore_freq, + ) + return + # Normal case. + out_a, out_b = tools.intersect.indices_flex( + *idxs, + ignore_start_of_day=ignore_start_of_day, + ignore_tz=ignore_tz, + ignore_freq=ignore_freq, + ) + expected_a = get_idx( + expected_startdate, + expected_starttime, + expected_tz, + expected_freq, + enddate, + ) + expected_b = get_idx( + expected_startdate, + expected_otherstarttime, + expected_othertz, + expected_otherfreq, + enddate, + ) + testing.assert_index_equal(out_a, expected_a) + testing.assert_index_equal(out_b, expected_b) + + +def test_intersect_flex_dst(): + """Test if intersection keeps working if DST-boundary is right at end.""" + i1 = pd.date_range("2020", "2020-03-29", freq="D", tz="Europe/Berlin") + i2 = pd.date_range("2020", "2020-03-30", freq="D", tz="Europe/Berlin") + + expected = pd.date_range("2020", "2020-03-29", freq="D", tz="Europe/Berlin") + + result1, result2 = tools.intersect.indices_flex(i1, i2) + testing.assert_index_equal(result1, expected) + testing.assert_index_equal(result2, expected) diff --git a/tests/tools/test_intersect_flex_frame.py b/tests/tools/test_intersect_flex_frame.py new file mode 100644 index 0000000..5c42b05 --- /dev/null +++ b/tests/tools/test_intersect_flex_frame.py @@ -0,0 +1,175 @@ +import pandas as pd +import pytest + +from portfolyo import testing, tools + + +@pytest.mark.parametrize("types", ["series", "df"]) +@pytest.mark.parametrize("ignore_tz", [True, False]) +def test_frames_ignore_tz(types: str, ignore_tz: bool): + idx_a = pd.date_range( + "2020", "2022", freq="MS", inclusive="left", tz="Europe/Berlin" + ) + a = pd.Series(range(0, 24), idx_a) + + idx_b = pd.date_range("2020-02", "2021-09", freq="MS", inclusive="left") + b = pd.Series(range(0, 19), idx_b) + + exp_idx_a = pd.date_range( + "2020-02", "2021-09", freq="MS", inclusive="left", tz="Europe/Berlin" + ) + exp_idx_b = idx_b + exp_a = pd.Series(range(1, 20), exp_idx_a) + exp_b = pd.Series(range(0, 19), exp_idx_b) + + if types == "series": + if not ignore_tz: + with pytest.raises(ValueError): + _ = tools.intersect.frames(a, b, ignore_tz=ignore_tz) + return + result_a, result_b = tools.intersect.frames(a, b, ignore_tz=ignore_tz) + testing.assert_series_equal(result_a, exp_a) + testing.assert_series_equal(result_b, exp_b) + else: + a, b = pd.DataFrame({"col_a": a}), pd.DataFrame({"col_b": b}) + if not ignore_tz: + with pytest.raises(ValueError): + _ = tools.intersect.frames(a, b, ignore_tz=ignore_tz) + return + exp_a, exp_b = pd.DataFrame({"col_a": exp_a}), pd.DataFrame({"col_b": exp_b}) + result_a, result_b = tools.intersect.frames(a, b, ignore_tz=ignore_tz) + testing.assert_frame_equal(result_a, exp_a) + testing.assert_frame_equal(result_b, exp_b) + + +@pytest.mark.parametrize("types", ["series", "df"]) +@pytest.mark.parametrize("ignore_start_of_day", [True, False]) +def test_frames_ignore_start_of_day(types: str, ignore_start_of_day: bool): + idx_a = pd.date_range("2020 00:00", "2022 00:00", freq="MS", inclusive="left") + a = pd.Series(range(0, 24), idx_a) + + idx_b = pd.date_range("2020-02 06:00", "2021-09 06:00", freq="MS", inclusive="left") + b = pd.Series(range(0, 19), idx_b) + + exp_idx_a = pd.date_range( + "2020-02 00:00", "2021-09 00:00", freq="MS", inclusive="left" + ) + exp_idx_b = idx_b + exp_a = pd.Series(range(1, 20), exp_idx_a) + exp_b = pd.Series(range(0, 19), exp_idx_b) + if types == "series": + if not ignore_start_of_day: + with pytest.raises(ValueError): + _ = tools.intersect.frames( + a, b, ignore_start_of_day=ignore_start_of_day + ) + return + result_a, result_b = tools.intersect.frames( + a, b, ignore_start_of_day=ignore_start_of_day + ) + testing.assert_series_equal(result_a, exp_a) + testing.assert_series_equal(result_b, exp_b) + else: + a, b = pd.DataFrame({"col_a": a}), pd.DataFrame({"col_b": b}) + if not ignore_start_of_day: + with pytest.raises(ValueError): + _ = tools.intersect.frames( + a, b, ignore_start_of_day=ignore_start_of_day + ) + return + exp_a, exp_b = pd.DataFrame({"col_a": exp_a}), pd.DataFrame({"col_b": exp_b}) + result_a, result_b = tools.intersect.frames( + a, b, ignore_start_of_day=ignore_start_of_day + ) + testing.assert_frame_equal(result_a, exp_a) + testing.assert_frame_equal(result_b, exp_b) + + +@pytest.mark.parametrize("types", ["series", "df"]) +@pytest.mark.parametrize("ignore_freq", [True, False]) +def test_frames_ignore_freq(types: str, ignore_freq: bool): + idx_a = pd.date_range("2022-04-01", "2024-07-01", freq="QS", inclusive="left") + a = pd.Series(range(0, 9), idx_a) + + idx_b = pd.date_range("2021-01-01", "2024-01-01", freq="AS", inclusive="left") + b = pd.Series(range(0, 3), idx_b) + + exp_idx_a = pd.date_range("2023-01-01", "2024-01-01", freq="QS", inclusive="left") + exp_idx_b = pd.date_range("2023-01-01", "2024-01-01", freq="AS", inclusive="left") + exp_a = pd.Series(range(3, 7), exp_idx_a) + exp_b = pd.Series(range(2, 3), exp_idx_b) + if types == "series": + if not ignore_freq: + with pytest.raises(ValueError): + _ = tools.intersect.frames(a, b, ignore_freq=ignore_freq) + return + result_a, result_b = tools.intersect.frames(a, b, ignore_freq=ignore_freq) + testing.assert_series_equal(result_a, exp_a) + testing.assert_series_equal(result_b, exp_b) + else: + a, b = pd.DataFrame({"col_a": a}), pd.DataFrame({"col_b": b}) + if not ignore_freq: + with pytest.raises(ValueError): + _ = tools.intersect.frames(a, b, ignore_freq=ignore_freq) + return + exp_a, exp_b = pd.DataFrame({"col_a": exp_a}), pd.DataFrame({"col_b": exp_b}) + result_a, result_b = tools.intersect.frames(a, b, ignore_freq=ignore_freq) + testing.assert_frame_equal(result_a, exp_a) + testing.assert_frame_equal(result_b, exp_b) + + +@pytest.mark.parametrize("types", ["series", "df"]) +@pytest.mark.parametrize("ignore_all", [True, False]) +def test_frames_ignore_all(types: str, ignore_all: bool): + idx_a = pd.date_range( + "2022-04-01 00:00", + "2024-07-01 00:00", + freq="QS", + tz="Europe/Berlin", + inclusive="left", + ) + a = pd.Series(range(0, 9), idx_a) + + idx_b = pd.date_range( + "2021-01-01 06:00", "2024-01-01 06:00", freq="AS", inclusive="left" + ) + b = pd.Series(range(0, 3), idx_b) + + exp_idx_a = pd.date_range( + "2023-01-01 00:00", + "2024-01-01 00:00", + freq="QS", + tz="Europe/Berlin", + inclusive="left", + ) + exp_idx_b = pd.date_range( + "2023-01-01 06:00", "2024-01-01 06:00", freq="AS", inclusive="left" + ) + exp_a = pd.Series(range(3, 7), exp_idx_a) + exp_b = pd.Series(range(2, 3), exp_idx_b) + if types == "series": + if not ignore_all: + with pytest.raises(ValueError): + _ = tools.intersect.frames( + a, b, ignore_freq=False, ignore_start_of_day=False, ignore_tz=False + ) + return + result_a, result_b = tools.intersect.frames( + a, b, ignore_freq=True, ignore_start_of_day=True, ignore_tz=True + ) + testing.assert_series_equal(result_a, exp_a) + testing.assert_series_equal(result_b, exp_b) + else: + a, b = pd.DataFrame({"col_a": a}), pd.DataFrame({"col_b": b}) + if not ignore_all: + with pytest.raises(ValueError): + _ = tools.intersect.frames( + a, b, ignore_freq=False, ignore_start_of_day=False, ignore_tz=False + ) + return + exp_a, exp_b = pd.DataFrame({"col_a": exp_a}), pd.DataFrame({"col_b": exp_b}) + result_a, result_b = tools.intersect.frames( + a, b, ignore_freq=True, ignore_start_of_day=True, ignore_tz=True + ) + testing.assert_frame_equal(result_a, exp_a) + testing.assert_frame_equal(result_b, exp_b) diff --git a/tests/tools2/test_indexable.py b/tests/tools2/test_indexable.py new file mode 100644 index 0000000..6ca46ab --- /dev/null +++ b/tests/tools2/test_indexable.py @@ -0,0 +1,223 @@ +from typing import Union +import pandas as pd +import pytest + +import portfolyo as pf + + +def get_idx( + startdate: str, + starttime: str, + tz: str, + freq: str, + enddate: str, +) -> pd.DatetimeIndex: + # Empty index. + if startdate is None: + return pd.DatetimeIndex([], freq=freq, tz=tz) + # Normal index. + ts_start = pd.Timestamp(f"{startdate} {starttime}", tz=tz) + ts_end = pd.Timestamp(f"{enddate} {starttime}", tz=tz) + return pd.date_range(ts_start, ts_end, freq=freq, inclusive="left") + + +def create_obj( + series: pd.Series, name_obj: str +) -> Union[pd.DataFrame, pf.PfLine, pf.PfState]: + if name_obj == "pfline": + return pf.PfLine({"w": series}) + elif name_obj == "pfstate": + volume = pf.PfLine({"w": series}) + prices = pf.PfLine({"p": series}) + return pf.PfState(volume, prices) + else: + return pd.DataFrame({"col": series}) + + +def t_function(objtype: str): + if objtype == "series": + return pd.testing.assert_series_equal + elif objtype == "dataframe": + return pd.testing.assert_frame_equal + elif objtype == "pfline": + return pf.PfLine.__eq__ + else: + return pf.PfState.__eq__ + + +@pytest.mark.parametrize("first_obj", ["pfstate", "pfline", "series", "dataframe"]) +@pytest.mark.parametrize("second_obj", ["pfstate", "pfline", "series", "dataframe"]) +def test_intersect_freq_ignore( + first_obj: str, + second_obj: str, +): + """Test that intersection works properly on PfLines and/or PfStates with ignore_freq.""" + idx1 = get_idx("2022-04-01", "00:00", "Europe/Berlin", "QS", "2024-07-01") + s1 = pd.Series(range(len(idx1)), idx1) + + idx2 = get_idx("2021-01-01", "00:00", "Europe/Berlin", "MS", "2024-01-01") + s2 = pd.Series(range(len(idx2)), idx2) + + first = create_obj(s1, first_obj) if first_obj != "series" else s1 + second = create_obj(s2, second_obj) if second_obj != "series" else s2 + # Do intersection + intersect = pf.intersection(first, second, ignore_freq=True) + + # Expected results + expected_s1 = s1.iloc[:7] + expected_s2 = s2.iloc[15:48] + output_1 = ( + create_obj(expected_s1, first_obj) if first_obj != "series" else expected_s1 + ) + output_2 = ( + create_obj(expected_s2, second_obj) if second_obj != "series" else expected_s2 + ) + for a, b, objtype in zip([output_1, output_2], intersect, [first_obj, second_obj]): + fn = t_function(objtype) + fn(a, b) + + +@pytest.mark.parametrize("first_obj", ["pfstate", "pfline", "series", "dataframe"]) +@pytest.mark.parametrize("second_obj", ["pfstate", "pfline", "series", "dataframe"]) +def test_intersect_sod( + first_obj: str, + second_obj: str, +): + """Test that intersection works properly on PfLines and/or PfStates with ignore_sod.""" + idx1 = get_idx("2022-04-01", "00:00", "Europe/Berlin", "QS", "2024-07-01") + s1 = pd.Series(range(len(idx1)), idx1) + + idx2 = get_idx("2021-01-01", "06:00", "Europe/Berlin", "QS", "2024-01-01") + s2 = pd.Series(range(len(idx2)), idx2) + + first = create_obj(s1, first_obj) if first_obj != "series" else s1 + second = create_obj(s2, second_obj) if second_obj != "series" else s2 + # Do intersection + intersect = pf.intersection(first, second, ignore_start_of_day=True) + + # Expected results + expected_s1 = s1.iloc[:7] + expected_s2 = s2.iloc[5:12] + output_1 = ( + create_obj(expected_s1, first_obj) if first_obj != "series" else expected_s1 + ) + output_2 = ( + create_obj(expected_s2, second_obj) if second_obj != "series" else expected_s2 + ) + for a, b, objtype in zip([output_1, output_2], intersect, [first_obj, second_obj]): + fn = t_function(objtype) + fn(a, b) + + +@pytest.mark.parametrize("first_obj", ["pfstate", "pfline", "series", "dataframe"]) +@pytest.mark.parametrize("second_obj", ["pfstate", "pfline", "series", "dataframe"]) +def test_intersect_tz( + first_obj: str, + second_obj: str, +): + """Test that intersection works properly on PfLines and/or PfStates with ignore_tz.""" + idx1 = get_idx("2022-04-01", "00:00", "Europe/Berlin", "QS", "2024-07-01") + s1 = pd.Series(range(len(idx1)), idx1) + + idx2 = get_idx("2021-01-01", "00:00", None, "QS", "2024-01-01") + s2 = pd.Series(range(len(idx2)), idx2) + + first = create_obj(s1, first_obj) if first_obj != "series" else s1 + second = create_obj(s2, second_obj) if second_obj != "series" else s2 + # Do intersection + intersect = pf.intersection(first, second, ignore_tz=True) + + # Expected results + expected_s1 = s1.iloc[:7] + expected_s2 = s2.iloc[5:12] + output_1 = ( + create_obj(expected_s1, first_obj) if first_obj != "series" else expected_s1 + ) + output_2 = ( + create_obj(expected_s2, second_obj) if second_obj != "series" else expected_s2 + ) + for a, b, objtype in zip([output_1, output_2], intersect, [first_obj, second_obj]): + fn = t_function(objtype) + fn(a, b) + + +@pytest.mark.parametrize("first_obj", ["pfstate", "pfline", "series", "dataframe"]) +@pytest.mark.parametrize("second_obj", ["pfstate", "pfline", "series", "dataframe"]) +def test_intersect_ignore_all( + first_obj: str, + second_obj: str, +): + """Test that intersection works properly on PfLines and/or PfStates with ignore_all.""" + idx1 = get_idx("2022-04-01", "00:00", "Europe/Berlin", "QS", "2024-07-01") + s1 = pd.Series(range(len(idx1)), idx1) + + idx2 = get_idx("2021-01-01", "06:00", None, "MS", "2024-01-01") + s2 = pd.Series(range(len(idx2)), idx2) + + first = create_obj(s1, first_obj) if first_obj != "series" else s1 + second = create_obj(s2, second_obj) if second_obj != "series" else s2 + # Do intersection + intersect = pf.intersection( + first, second, ignore_freq=True, ignore_tz=True, ignore_start_of_day=True + ) + + # Expected results + expected_s1 = s1.iloc[:7] + expected_s2 = s2.iloc[15:48] + output_1 = ( + create_obj(expected_s1, first_obj) if first_obj != "series" else expected_s1 + ) + output_2 = ( + create_obj(expected_s2, second_obj) if second_obj != "series" else expected_s2 + ) + for a, b, objtype in zip([output_1, output_2], intersect, [first_obj, second_obj]): + fn = t_function(objtype) + fn(a, b) + + +@pytest.mark.parametrize("first_obj", ["pfstate", "pfline", "series", "dataframe"]) +@pytest.mark.parametrize("second_obj", ["pfstate", "pfline", "series", "dataframe"]) +@pytest.mark.parametrize("third_obj", ["pfstate", "pfline", "series", "dataframe"]) +def test_intersect_ignore_all_3obj( + first_obj: str, + second_obj: str, + third_obj: str, +): + """Test that intersection works properly on PfLines and/or PfStates with ignore_all.""" + idx1 = get_idx("2022-04-01", "00:00", "Europe/Berlin", "QS", "2024-07-01") + s1 = pd.Series(range(len(idx1)), idx1) + + idx2 = get_idx("2021-01-01", "06:00", None, "MS", "2024-01-01") + s2 = pd.Series(range(len(idx2)), idx2) + + idx3 = get_idx("2023-01-01", "00:00", "Asia/Kolkata", "AS", "2025-01-01") + s3 = pd.Series(range(len(idx3)), idx3) + + first = create_obj(s1, first_obj) if first_obj != "series" else s1 + second = create_obj(s2, second_obj) if second_obj != "series" else s2 + third = create_obj(s3, third_obj) if third_obj != "series" else s3 + + # Do intersection + intersect = pf.intersection( + first, second, third, ignore_freq=True, ignore_tz=True, ignore_start_of_day=True + ) + + # Expected results + expected_s1 = s1.iloc[3:7] + expected_s2 = s2.iloc[24:36] + expected_s3 = s3.iloc[:1] + output_1 = ( + create_obj(expected_s1, first_obj) if first_obj != "series" else expected_s1 + ) + output_2 = ( + create_obj(expected_s2, second_obj) if second_obj != "series" else expected_s2 + ) + output_3 = ( + create_obj(expected_s3, third_obj) if third_obj != "series" else expected_s3 + ) + + for a, b, objtype in zip( + [output_1, output_2, output_3], intersect, [first_obj, second_obj, third_obj] + ): + fn = t_function(objtype) + fn(a, b)