From 42b0fb87d1057e6c096b20715d43095fc3f50c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20L=C3=BCnenschlo=C3=9F?= <peter.luenenschloss@ufz.de> Date: Mon, 21 Aug 2023 20:37:38 +0200 Subject: [PATCH] Docu update multiplot --- CHANGELOG.md | 5 +- docs/cookbooks/MultivariateFlagging.rst | 9 +-- docs/cookbooks/ResidualOutlierDetection.rst | 20 +------ docs/documentation/GlobalKeywords.rst | 1 - saqc/funcs/tools.py | 13 ++++- saqc/lib/plotting.py | 63 +++++++++++++++------ 6 files changed, 65 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0c4d6d66..fff327b5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-3.0-or-later [List of commits](https://git.ufz.de/rdm-software/saqc/-/compare/v2.4.0...develop) ### Added - add multivariate plotting options to `plot` -- added `plot_kwargs` keyword to `plot` function +- added `plot_kwargs` keyword to `plot` function - added checks and unified error message for common inputs. - added command line `--version` option - `-ll` CLI option as a shorthand for `--log-level` @@ -20,6 +20,9 @@ SPDX-License-Identifier: GPL-3.0-or-later - pin pandas to versions >= 2.0 - parameter `fill_na` of `SaQC.flagUniLOF` and `SaQC.assignUniLOF` is now of type `bool` instead of one of `[None, "linear"]` +- in `plot` function: changed default color for single variables to `black` with `80% transparency` +- in `plot` function: added seperate legend for flags + ### Removed - removed deprecated `DictOfSeries.to_df` - removed plotting option with complete history (`history="complete"`) diff --git a/docs/cookbooks/MultivariateFlagging.rst b/docs/cookbooks/MultivariateFlagging.rst index a66e30ed2..e4e6b78ba 100644 --- a/docs/cookbooks/MultivariateFlagging.rst +++ b/docs/cookbooks/MultivariateFlagging.rst @@ -246,17 +246,14 @@ Check out the results for the year *2016* .. doctest:: exampleMV - >>> plt.plot(qc.data['sac254_raw']['2016'], alpha=.5, color='black', label='original') # doctest:+SKIP - >>> plt.plot(qc.data['sac254_corrected']['2016'], color='black', label='corrected') # doctest:+SKIP + >>> qc.plot(['sac254_raw','sac254_corrected'], xscope='2016', plot_kwargs={'color':['black', 'black'], 'alpha':[.5, 1], 'label':['original', 'corrrected']}) # doctest:+SKIP .. plot:: :context: :include-source: False - plt.figure(figsize=(16,9)) - plt.plot(qc.data['sac254_raw']['2016'], alpha=.5, color='black', label='original') - plt.plot(qc.data['sac254_corrected']['2016'], color='black', label='corrected') - plt.legend() + >>> qc.plot(['sac254_raw','sac254_corrected'], xscope='2016', plot_kwargs={'color':['black', 'black'], 'alpha':[.5, 1], 'label':['original', 'corrrected']}) + Multivariate Flagging Procedure ------------------------------- diff --git a/docs/cookbooks/ResidualOutlierDetection.rst b/docs/cookbooks/ResidualOutlierDetection.rst index d022ad7a5..0d60150d5 100644 --- a/docs/cookbooks/ResidualOutlierDetection.rst +++ b/docs/cookbooks/ResidualOutlierDetection.rst @@ -255,25 +255,11 @@ This function object, we can pass on to the :py:meth:`~saqc.SaQC.processGeneric` Visualisation ------------- -We can obtain those updated informations by generating a `pandas dataframe <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html>`_ -representation of it, with the :py:attr:`data <saqc.core.core.SaQC.data>` method: +To see all the results obtained so far, plotted in one figure window, we make use of the :py:meth:`~saqc.SaQC.plot` method. .. doctest:: exampleOD - >>> data = qc.data - -.. plot:: - :context: - :include-source: False - - data = qc.data - -To see all the results obtained so far, plotted in one figure window, we make use of the dataframes `plot <https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html>`_ method. - -.. doctest:: exampleOD - - >>> data.to_pandas().plot() - <Axes...> + >>> qc.plot(".", regex=True) # doctest: +SKIP .. plot:: :context: @@ -281,7 +267,7 @@ To see all the results obtained so far, plotted in one figure window, we make us :width: 80 % :class: center - data.to_pandas().plot() + qc.plot(".", regex=True) Residuals and Scores diff --git a/docs/documentation/GlobalKeywords.rst b/docs/documentation/GlobalKeywords.rst index 781dfbc6f..101f64c31 100644 --- a/docs/documentation/GlobalKeywords.rst +++ b/docs/documentation/GlobalKeywords.rst @@ -37,7 +37,6 @@ Example Data :context: close-figs :include-source: False - import matplotlib.pyplot as plt import pandas as pd import numpy as np import saqc diff --git a/saqc/funcs/tools.py b/saqc/funcs/tools.py index 599ac6bc9..7da335d54 100644 --- a/saqc/funcs/tools.py +++ b/saqc/funcs/tools.py @@ -306,9 +306,16 @@ class ToolsMixin: * ``"cycleskip"``: (int) start the cycle of shapes that are assigned any flag-type with a certain lag - defaults to ``0`` (no skip) plot_kwargs : - Keywords to modify data line appearance. The markers are set via the - `matplotlib.pyplot.plot <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html>`_ - method and can have the options listed there. + Keywords to modify the plot appearance. The plotting is delegated to + `matplotlib.pyplot.plot <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.scatter.html>`_, all options listed there are available. Additionally the following saqc specific configurations are possible: + + * ``"alpha"``: Either a scalar float in *[0,1]*, that determines all plots' transparencies, or + a list of floats, matching the number of variables to plot. + + * ``"linewidth"``: Either single float in *[0,1]*, that determines the thickness of all plotted, + or a list of floats, matching the number of variables to plot. + + Notes ----- diff --git a/saqc/lib/plotting.py b/saqc/lib/plotting.py index 5c5d60448..ab13e9ea8 100644 --- a/saqc/lib/plotting.py +++ b/saqc/lib/plotting.py @@ -35,7 +35,7 @@ MARKER_COL_CYCLE = [ ] # default color cycle for plot colors (many-in-one-plots) -PLOT_COL_CYCLE = MARKER_COL_CYCLE # itertools.cycle(MARKER_COL_CYCLE) +PLOT_COL_CYCLE = [(0, 0, 0)] + MARKER_COL_CYCLE # itertools.cycle(MARKER_COL_CYCLE) # default data plot configuration (color kwarg only effective for many-to-one-plots) PLOT_KWARGS = {"alpha": 0.8, "linewidth": 1, "color": PLOT_COL_CYCLE} @@ -248,14 +248,15 @@ def makeFig( mode, ) + # readability formattin fo the x-tick labels: + fig.autofmt_xdate() return fig -def _instantiateKwargContext( +def _instantiateAxesContext( plot_kwargs, scatter_kwargs, ax_kwargs, var_num, var_name, mode ): _scatter_mem = {} - _plot_kwargs = plot_kwargs.copy() _scatter_kwargs = scatter_kwargs.copy() _ax_kwargs = ax_kwargs.copy() _scatter_mem = {} @@ -286,7 +287,6 @@ def _instantiateKwargContext( _ax_kwargs["title"] = var_name if title is None else title return ( - _plot_kwargs, _scatter_kwargs, _ax_kwargs, _scatter_mem, @@ -379,6 +379,26 @@ def _configMarkers( return flags_i, _scatter_kwargs, _scatter_mem, marker_shape_cycle, marker_col_cycle +def _instantiatePlotContext(plot_kwargs, mode, var_name, var_num, plot_col_cycle): + _plot_kwargs = plot_kwargs.copy() + # get current plot color from plot color cycle + _plot_kwargs["color"] = next(plot_col_cycle) + # assign variable specific plot appearance + for plot_spec in ["alpha", "linewidth", "label"]: + spec = plot_kwargs.get(plot_spec, None) + if isinstance(spec, list): + _plot_kwargs[plot_spec] = spec[var_num] + elif isinstance(spec, dict): + _plot_kwargs[plot_spec] = spec.get(var_name, None) + + if mode == "oneplot": + _plot_kwargs["label"] = _plot_kwargs.get("label", None) or var_name + # when plotting in subplots, plot black line and label it as 'data' (if not opted otherwise) + else: + _plot_kwargs["label"] = _plot_kwargs.get("label", None) or "data" + return _plot_kwargs + + def _plotVarWithFlags( axes, dat_dict, @@ -410,25 +430,19 @@ def _plotVarWithFlags( # every time, axis target is fresh, reinstantiate the kwarg-contexts : if var_num == 0 or mode == "subplots": ( - _plot_kwargs, _scatter_kwargs, _ax_kwargs, _scatter_mem, marker_col_cycle, marker_shape_cycle, - ) = _instantiateKwargContext( + ) = _instantiateAxesContext( plot_kwargs, scatter_kwargs, ax_kwargs, var_num, var_name, mode ) ax.set(**_ax_kwargs) - # get current color from plot color cycle - _plot_kwargs["color"] = next(plot_col_cycle) - if mode == "oneplot": - _plot_kwargs["label"] = var_name - # when plotting in subplots, plot black line and label it as 'data' (if not opted otherwise) - else: - _plot_kwargs["label"] = _plot_kwargs.get("label", None) or "data" - + _plot_kwargs = _instantiatePlotContext( + plot_kwargs, mode, var_name, var_num, plot_col_cycle + ) # plot the data ax.plot(var_dat, **_plot_kwargs) @@ -482,12 +496,14 @@ def _plotVarWithFlags( _scatter_kwargs, ) - _rmDupesFromLegend(ax, dat_dict) - + _formatLegend(ax, dat_dict) + if mode == "subplots": + for ax in axes[1:]: + _formatLegend(ax, dat_dict) return -def _rmDupesFromLegend(ax, dat_dict): +def _formatLegend(ax, dat_dict): # the legend generated might contain dublucate entries, we remove those, since dubed entries are assigned all # the same marker color and shape: legend_h, legend_l = ax.get_legend_handles_labels() @@ -503,7 +519,18 @@ def _rmDupesFromLegend(ax, dat_dict): legend_f.append((legend_h[l[0]], l[1])) leg_l = [l[1] for l in legend_v] + [l[1] for l in legend_f] leg_h = [l[0] for l in legend_v] + [l[0] for l in legend_f] - ax.legend(leg_h, leg_l) + # if more than one variable is plotted, list plot line and flag marker shapes in seperate + # legends + h_types = np.array([isinstance(h, mpl.lines.Line2D) for h in leg_h]) + if sum(h_types) > 1: + lines_h = np.array(leg_h)[h_types] + lines_l = np.array(leg_l)[h_types] + flags_h = np.array(leg_h)[~h_types] + flags_l = np.array(leg_l)[~h_types] + ax.add_artist(plt.legend(flags_h, flags_l)) + ax.legend(lines_h, lines_l) + else: + ax.legend(leg_h, leg_l) return -- GitLab