From 19acec962d01e17ebcaa14b571200b8aafe9ec87 Mon Sep 17 00:00:00 2001 From: leufen1 <l.leufen@fz-juelich.de> Date: Fri, 14 Jan 2022 11:22:43 +0100 Subject: [PATCH] added some doc strings, removed outdated lines, updated some tests --- .../data_handler/data_handler_with_filter.py | 34 +++-- mlair/helpers/filter.py | 117 ++++++++---------- mlair/plotting/data_insight_plotting.py | 30 ++--- test/test_helpers/test_filter.py | 23 ++-- 4 files changed, 93 insertions(+), 111 deletions(-) diff --git a/mlair/data_handler/data_handler_with_filter.py b/mlair/data_handler/data_handler_with_filter.py index 448db592..761c7c8d 100644 --- a/mlair/data_handler/data_handler_with_filter.py +++ b/mlair/data_handler/data_handler_with_filter.py @@ -190,7 +190,7 @@ class DataHandlerFirFilterSingleStation(DataHandlerFilterSingleStation): def apply_filter(self): """Apply FIR filter only on inputs.""" fir = FIRFilter(self.input_data.astype("float32"), self.fs, self.filter_order, self.filter_cutoff_freq, - self.filter_window_type, self.target_dim, self.time_dim, station_name=self.station[0], + self.filter_window_type, self.target_dim, self.time_dim, display_name=self.station[0], minimum_length=self.window_history_size, offset=self.window_history_offset, plot_path=self.plot_path) self.fir_coeff = fir.filter_coefficients filter_data = fir.filtered_data @@ -326,12 +326,15 @@ class DataHandlerClimateFirFilterSingleStation(DataHandlerFirFilterSingleStation apriori_type is None or `zeros`, and a climatology of the residuum is used for `residuum_stats`. :param apriori_diurnal: use diurnal anomalies of each hour as addition to the apriori information type chosen by parameter apriori_type. This is only applicable for hourly resolution data. + :param apriori_sel_opts: specify some parameters to select a subset of data before calculating the apriori + information. Use this parameter for example, if apriori shall only calculated on a shorter time period than + available in given data. :param extend_length_opts: use this parameter to use future data in the filter calculation. This parameter does not - affect the size of the history samples as this is handled by the window_history_offset parameter. Example: set + affect the size of the history samples as this is handled by the window_history_size parameter. Example: set extend_length_opts=7*24 to use the observation of the next 7 days to calculate the filtered components. Which - data are finally used for the input samples is not affected by these 7 days. In case the window_history_offset - parameter exceeds the `extend_length_opts`, it has also an influence on the filter component calculation as it - will return also climatological estimates. + data are finally used for the input samples is not affected by these 7 days. In case the range of history sample + exceeds the horizon of extend_length_opts, the history sample will also include data from climatological + estimates. """ DEFAULT_EXTEND_LENGTH_OPTS = 0 _hash = DataHandlerFirFilterSingleStation._hash + ["apriori_type", "apriori_sel_opts", "apriori_diurnal", @@ -339,14 +342,13 @@ class DataHandlerClimateFirFilterSingleStation(DataHandlerFirFilterSingleStation _store_attributes = DataHandlerFirFilterSingleStation.store_attributes() + ["apriori"] def __init__(self, *args, apriori=None, apriori_type=None, apriori_diurnal=False, apriori_sel_opts=None, - name_affix=None, extend_length_opts=DEFAULT_EXTEND_LENGTH_OPTS, **kwargs): + extend_length_opts=DEFAULT_EXTEND_LENGTH_OPTS, **kwargs): self.apriori_type = apriori_type self.climate_filter_coeff = None # coefficents of the used FIR filter self.apriori = apriori # exogenous apriori information or None to calculate from data (endogenous) self.apriori_diurnal = apriori_diurnal self.all_apriori = None # collection of all apriori information self.apriori_sel_opts = apriori_sel_opts # ensure to separate exogenous and endogenous information - self.plot_name_affix = name_affix self.extend_length_opts = extend_length_opts super().__init__(*args, **kwargs) @@ -362,7 +364,7 @@ class DataHandlerClimateFirFilterSingleStation(DataHandlerFirFilterSingleStation apriori_diurnal=self.apriori_diurnal, sel_opts=self.apriori_sel_opts, plot_path=self.plot_path, minimum_length=self.window_history_size, new_dim=self.window_dim, - station_name=self.station[0], extend_length_opts=self.extend_length_opts, + display_name=self.station[0], extend_length_opts=self.extend_length_opts, offset=self.window_history_end) self.climate_filter_coeff = climate_filter.filter_coefficients @@ -431,21 +433,15 @@ class DataHandlerClimateFirFilterSingleStation(DataHandlerFirFilterSingleStation def make_history_window(self, dim_name_of_inputs: str, window: int, dim_name_of_shift: str) -> None: """ - Create a xr.DataArray containing history data. - - Shift the data window+1 times and return a xarray which has a new dimension 'window' containing the shifted - data. This is used to represent history in the data. Results are stored in history attribute. + Create a xr.DataArray containing history data. As 'input_data' already consists of a dimension 'window', this + method only shifts the data along 'window' dimension x times where x is given by 'window_history_offset'. + Results are stored in history attribute. :param dim_name_of_inputs: Name of dimension which contains the input variables - :param window: number of time steps to look back in history - Note: window will be treated as negative value. This should be in agreement with looking back on - a time line. Nonetheless positive values are allowed but they are converted to its negative - expression + :param window: this parameter is not used in the inherited method :param dim_name_of_shift: Dimension along shift will be applied """ - # self.history = self.input_data.__deepcopy__() - #todo stopped here. maybe there is nothing to do anymore? - data = self.input_data #todo trim data to be only in range slice(None, self.window_history_offset) ?? + data = self.input_data sampling = {"daily": "D", "hourly": "h"}.get(to_list(self.sampling)[0]) data.coords[dim_name_of_shift] = data.coords[dim_name_of_shift] - np.timedelta64(self.window_history_offset, sampling) diff --git a/mlair/helpers/filter.py b/mlair/helpers/filter.py index 3e93ebc6..03065402 100644 --- a/mlair/helpers/filter.py +++ b/mlair/helpers/filter.py @@ -2,8 +2,6 @@ import gc import warnings from typing import Union, Callable, Tuple, Dict, Any import logging -import os -import time import datetime import numpy as np @@ -19,7 +17,7 @@ from mlair.helpers import to_list, TimeTrackingWrapper, TimeTracking class FIRFilter: from mlair.plotting.data_insight_plotting import PlotFirFilter - def __init__(self, data, fs, order, cutoff, window, var_dim, time_dim, station_name=None, minimum_length=None, offset=0, plot_path=None): + def __init__(self, data, fs, order, cutoff, window, var_dim, time_dim, display_name=None, minimum_length=None, offset=0, plot_path=None): self._filtered = [] self._h = [] self.data = data @@ -29,14 +27,14 @@ class FIRFilter: self.window = window self.var_dim = var_dim self.time_dim = time_dim - self.station_name = station_name + self.display_name = display_name self.minimum_length = minimum_length self.offset = offset self.plot_path = plot_path self.run() def run(self): - logging.info(f"{self.station_name}: start {self.__class__.__name__}") + logging.info(f"{self.display_name}: start {self.__class__.__name__}") filtered = [] h = [] input_data = self.data.__deepcopy__() @@ -50,7 +48,7 @@ class FIRFilter: for i in range(len(self.order)): # apply filter fi, hi = self.fir_filter(input_data, self.fs, self.cutoff[i], self.order[i], time_dim=self.time_dim, - var_dim=self.var_dim, window=self.window, station_name=self.station_name) + var_dim=self.var_dim, window=self.window, display_name=self.display_name) filtered.append(fi) h.append(hi) @@ -69,7 +67,7 @@ class FIRFilter: # visualize if self.plot_path is not None: try: - self.PlotFirFilter(self.plot_path, plot_data, self.station_name) # not working when t0 != 0 + self.PlotFirFilter(self.plot_path, plot_data, self.display_name) # not working when t0 != 0 except Exception as e: logging.info(f"Could not plot climate fir filter due to following reason:\n{e}") @@ -106,7 +104,7 @@ class FIRFilter: @TimeTrackingWrapper def fir_filter(self, data, fs, cutoff_high, order, sampling="1d", time_dim="datetime", var_dim="variables", window: Union[str, Tuple] = "hamming", - minimum_length=None, new_dim="window", plot_dates=None, station_name=None): + minimum_length=None, new_dim="window", plot_dates=None, display_name=None): # calculate FIR filter coefficients h = self._calculate_filter_coefficients(window, order, cutoff_high, fs) @@ -121,7 +119,7 @@ class FIRFilter: filtered = xr.concat(coll, var_dim) # create result array with same shape like input data, gaps are filled by nans - filtered = self._create_full_filter_result_array(data, filtered, time_dim, station_name) + filtered = self._create_full_filter_result_array(data, filtered, time_dim, display_name) return filtered, h @staticmethod @@ -145,7 +143,7 @@ class FIRFilter: @staticmethod def _create_full_filter_result_array(template_array: xr.DataArray, result_array: xr.DataArray, new_dim: str, - station_name: str = None) -> xr.DataArray: + display_name: str = None) -> xr.DataArray: """ Create result filter array with same shape line given template data (should be the original input data before filtering the data). All gaps are filled by nans. @@ -153,9 +151,9 @@ class FIRFilter: :param template_array: this array is used as template for shape and ordering of dims :param result_array: array with data that are filled into template :param new_dim: new dimension which is shifted/appended to/at the end (if present or not) - :param station_name: string that is attached to logging (default None) + :param display_name: string that is attached to logging (default None) """ - logging.debug(f"{station_name}: create res_full") + logging.debug(f"{display_name}: create res_full") new_coords = {**{k: template_array.coords[k].values for k in template_array.coords if k != new_dim}, new_dim: result_array.coords[new_dim]} dims = [*template_array.dims, new_dim] if new_dim not in template_array.dims else template_array.dims @@ -168,7 +166,7 @@ class ClimateFIRFilter(FIRFilter): def __init__(self, data, fs, order, cutoff, window, time_dim, var_dim, apriori=None, apriori_type=None, apriori_diurnal=False, sel_opts=None, plot_path=None, - minimum_length=None, new_dim=None, station_name=None, extend_length_opts: int = 0, + minimum_length=None, new_dim=None, display_name=None, extend_length_opts: int = 0, offset: Union[dict, int] = 0): """ :param data: data to filter @@ -185,6 +183,9 @@ class ClimateFIRFilter(FIRFilter): residua is used ("residuum_stats"). :param apriori_diurnal: Use diurnal cycle as additional apriori information (only applicable for hourly resoluted data). The mean anomaly of each hour is added to the apriori_type information. + :param sel_opts: specify some parameters to select a subset of data before calculating the apriori information. + Use this parameter for example, if apriori shall only calculated on a shorter time period than available in + given data. :param extend_length_opts: shift information switch between historical data and apriori estimation by the given values (default None). Must either be a dictionary with keys available in var_dim or a single value that is applied to all data. This parameter has only influence on which information is available at t0 for the @@ -202,7 +203,7 @@ class ClimateFIRFilter(FIRFilter): self.new_dim = new_dim self.plot_data = [] self.extend_length_opts = extend_length_opts - super().__init__(data, fs, order, cutoff, window, var_dim, time_dim, station_name=station_name, + super().__init__(data, fs, order, cutoff, window, var_dim, time_dim, display_name=display_name, minimum_length=minimum_length, plot_path=plot_path, offset=offset) def run(self): @@ -211,17 +212,17 @@ class ClimateFIRFilter(FIRFilter): if self.sel_opts is not None: self.sel_opts = self.sel_opts if isinstance(self.sel_opts, dict) else {self.time_dim: self.sel_opts} sampling = {1: "1d", 24: "1H"}.get(int(self.fs)) - logging.debug(f"{self.station_name}: create diurnal_anomalies") + logging.debug(f"{self.display_name}: create diurnal_anomalies") if self.apriori_diurnal is True and sampling == "1H": diurnal_anomalies = self.create_seasonal_hourly_mean(self.data, self.time_dim, sel_opts=self.sel_opts, sampling=sampling, as_anomaly=True) else: diurnal_anomalies = 0 - logging.debug(f"{self.station_name}: create monthly apriori") + logging.debug(f"{self.display_name}: create monthly apriori") if self._apriori is None: self._apriori = self.create_monthly_mean(self.data, self.time_dim, sel_opts=self.sel_opts, sampling=sampling) + diurnal_anomalies - logging.debug(f"{self.station_name}: apriori shape = {self._apriori.shape}") + logging.debug(f"{self.display_name}: apriori shape = {self._apriori.shape}") apriori_list = to_list(self._apriori) input_data = self.data.__deepcopy__() @@ -232,19 +233,19 @@ class ClimateFIRFilter(FIRFilter): new_dim = self._create_tmp_dimension(input_data) if self.new_dim is None else self.new_dim for i in range(len(self.order)): - logging.info(f"{self.station_name}: start filter for order {self.order[i]}") + logging.info(f"{self.display_name}: start filter for order {self.order[i]}") # calculate climatological filter next_order = self._next_order(self.order, 0, i, self.window) fi, input_data, hi, apriori, plot_data = self.clim_filter(input_data, self.fs, self.cutoff[i], self.order[i], - apriori=apriori_list[i], sel_opts=self.sel_opts, - sampling=sampling, time_dim=self.time_dim, - window=self.window, var_dim=self.var_dim, - minimum_length=self.minimum_length, new_dim=new_dim, - plot_dates=plot_dates, station_name=self.station_name, - extend_opts=self.extend_length_opts, - offset=self.offset, next_order=next_order) - - logging.info(f"{self.station_name}: finished clim_filter calculation") + apriori=apriori_list[i], sel_opts=self.sel_opts, + sampling=sampling, time_dim=self.time_dim, + window=self.window, var_dim=self.var_dim, + minimum_length=self.minimum_length, new_dim=new_dim, + plot_dates=plot_dates, display_name=self.display_name, + extend_opts=self.extend_length_opts, + offset=self.offset, next_order=next_order) + + logging.info(f"{self.display_name}: finished clim_filter calculation") if self.minimum_length is None: filtered.append(fi.sel({new_dim: slice(None, self.offset)})) else: @@ -255,7 +256,7 @@ class ClimateFIRFilter(FIRFilter): plot_dates = {e["viz_date"] for e in plot_data} # calculate residuum - logging.info(f"{self.station_name}: calculate residuum") + logging.info(f"{self.display_name}: calculate residuum") coord_range = range(fi.coords[new_dim].values.min(), fi.coords[new_dim].values.max() + 1) if new_dim in input_data.coords: input_data = input_data.sel({new_dim: coord_range}) - fi @@ -264,14 +265,14 @@ class ClimateFIRFilter(FIRFilter): # create new apriori information for next iteration if no further apriori is provided if len(apriori_list) < len(self.order): - logging.info(f"{self.station_name}: create diurnal_anomalies") + logging.info(f"{self.display_name}: create diurnal_anomalies") if self.apriori_diurnal is True and sampling == "1H": diurnal_anomalies = self.create_seasonal_hourly_mean(input_data.sel({new_dim: 0}, drop=True), self.time_dim, sel_opts=self.sel_opts, sampling=sampling, as_anomaly=True) else: diurnal_anomalies = 0 - logging.info(f"{self.station_name}: create monthly apriori") + logging.info(f"{self.display_name}: create monthly apriori") if self.apriori_type is None or self.apriori_type == "zeros": # zero version apriori_list.append(xr.zeros_like(apriori_list[i]) + diurnal_anomalies) elif self.apriori_type == "residuum_stats": # calculate monthly statistic on residuum @@ -295,7 +296,7 @@ class ClimateFIRFilter(FIRFilter): # visualize if self.plot_path is not None: try: - self.PlotClimateFirFilter(self.plot_path, self.plot_data, sampling, self.station_name) + self.PlotClimateFirFilter(self.plot_path, self.plot_data, sampling, self.display_name) except Exception as e: logging.info(f"Could not plot climate fir filter due to following reason:\n{e}") @@ -460,7 +461,7 @@ class ClimateFIRFilter(FIRFilter): @staticmethod def extend_apriori(data: xr.DataArray, apriori: xr.DataArray, time_dim: str, sampling: str = "1d", - station_name: str = None) -> xr.DataArray: + display_name: str = None) -> xr.DataArray: """ Extend time range of apriori information to span a longer period as data (or at least of equal length). This method may not working properly if length of apriori contains data from less then one year. @@ -471,7 +472,7 @@ class ClimateFIRFilter(FIRFilter): apriori data. :param time_dim: name of temporal dimension :param sampling: sampling of data (e.g. "1m", "1d", default "1d") - :param station_name: name to use for logging message (default None) + :param display_name: name to use for logging message (default None) :returns: array which adjusted temporal coverage derived from apriori """ dates = data.coords[time_dim].values @@ -479,7 +480,7 @@ class ClimateFIRFilter(FIRFilter): # apriori starts after data if dates[0] < apriori.coords[time_dim].values[0]: - logging.debug(f"{station_name}: apriori starts after data") + logging.debug(f"{display_name}: apriori starts after data") # add difference in full years date_diff = abs(dates[0] - apriori.coords[time_dim].values[0]).astype("timedelta64[D]") @@ -500,7 +501,7 @@ class ClimateFIRFilter(FIRFilter): # apriori ends before data if dates[-1] + np.timedelta64(365, "D") > apriori.coords[time_dim].values[-1]: - logging.debug(f"{station_name}: apriori ends before data") + logging.debug(f"{display_name}: apriori ends before data") # add difference in full years + 1 year (because apriori is used as future estimate) date_diff = abs(dates[-1] - apriori.coords[time_dim].values[-1]).astype("timedelta64[D]") @@ -572,42 +573,32 @@ class ClimateFIRFilter(FIRFilter): plot_data = [] offset = 0 if offset is None else offset extend_length_opts = 0 if extend_length_opts is None else extend_length_opts - for viz_date in set(plot_dates).intersection(filtered.coords[time_dim].values): + for t0 in set(plot_dates).intersection(filtered.coords[time_dim].values): try: td_type = {"1d": "D", "1H": "h"}.get(sampling) - # t0 = viz_date + np.timedelta64(int(offset), td_type) - t0 = viz_date # offset should be irrelevant for visualization - t_minus = viz_date + np.timedelta64(int(-extend_length_history), td_type) + t_minus = t0 + np.timedelta64(int(-extend_length_history), td_type) t_plus = t0 + np.timedelta64(int(extend_length_future + 1), td_type) - # t_plus = t0 + np.timedelta64(int(extend_length_future + extend_length_opts), td_type) if new_dim not in data.coords: - # tmp_filter_data = self._shift_data(data.sel({time_dim: slice(t_minus, t_plus)}), - # range(int(-extend_length_history), - # int(extend_length_future + extend_length_opts)), - # time_dim, - # new_dim).sel({time_dim: viz_date}) tmp_filter_data = self._shift_data(data.sel({time_dim: slice(t_minus, t_plus)}), range(int(-extend_length_history), int(extend_length_future + 1)), time_dim, - new_dim).sel({time_dim: viz_date}) + new_dim).sel({time_dim: t0}) else: tmp_filter_data = None valid_start = int(filtered.window.min()) + int((len(h) + 1) / 2) valid_end = min(extend_length_opts + offset + 1, int(filtered.window.max()) - int((len(h) + 1) / 2)) valid_range = range(valid_start, valid_end) plot_data.append({"t0": t0, - "viz_date": viz_date, "var": variable_name, - "filter_input": filter_input_data.sel({time_dim: viz_date}), + "filter_input": filter_input_data.sel({time_dim: t0}), "filter_input_nc": tmp_filter_data, "valid_range": valid_range, "time_range": data.sel( {time_dim: slice(t_minus, t_plus - np.timedelta64(1, td_type))}).coords[ time_dim].values, "h": h, - "new_dim": new_dim, - "offset": offset}) + "new_dim": new_dim}) except: pass return plot_data @@ -667,7 +658,7 @@ class ClimateFIRFilter(FIRFilter): @staticmethod def _create_full_filter_result_array(template_array: xr.DataArray, result_array: xr.DataArray, new_dim: str, - station_name: str = None) -> xr.DataArray: + display_name: str = None) -> xr.DataArray: """ Create result filter array with same shape line given template data (should be the original input data before filtering the data). All gaps are filled by nans. @@ -675,9 +666,9 @@ class ClimateFIRFilter(FIRFilter): :param template_array: this array is used as template for shape and ordering of dims :param result_array: array with data that are filled into template :param new_dim: new dimension which is shifted/appended to/at the end (if present or not) - :param station_name: string that is attached to logging (default None) + :param display_name: string that is attached to logging (default None) """ - logging.debug(f"{station_name}: create res_full") + logging.debug(f"{display_name}: create res_full") new_coords = {**{k: template_array.coords[k].values for k in template_array.coords if k != new_dim}, new_dim: result_array.coords[new_dim]} dims = [*template_array.dims, new_dim] if new_dim not in template_array.dims else template_array.dims @@ -687,16 +678,16 @@ class ClimateFIRFilter(FIRFilter): @TimeTrackingWrapper def clim_filter(self, data, fs, cutoff_high, order, apriori=None, sel_opts=None, sampling="1d", time_dim="datetime", var_dim="variables", window: Union[str, Tuple] = "hamming", - minimum_length=0, next_order=0, new_dim="window", plot_dates=None, station_name=None, + minimum_length=0, next_order=0, new_dim="window", plot_dates=None, display_name=None, extend_opts: int = 0, offset: int = 0): - logging.debug(f"{station_name}: extend apriori") + logging.debug(f"{display_name}: extend apriori") # calculate apriori information from data if not given and extend its range if not sufficient long enough if apriori is None: apriori = self.create_monthly_mean(data, time_dim, sel_opts=sel_opts, sampling=sampling) apriori = apriori.astype(data.dtype) - apriori = self.extend_apriori(data, apriori, time_dim, sampling, station_name=station_name) + apriori = self.extend_apriori(data, apriori, time_dim, sampling, display_name=display_name) # calculate FIR filter coefficients h = self._calculate_filter_coefficients(window, order, cutoff_high, fs) @@ -717,13 +708,13 @@ class ClimateFIRFilter(FIRFilter): coll_input = [] for var in reversed(data.coords[var_dim].values): - logging.info(f"{station_name} ({var}): sel data") + logging.info(f"{display_name} ({var}): sel data") _start, _end = self._get_year_interval(data, time_dim) filt_coll = [] filt_input_coll = [] for _year in range(_start, _end + 1): - logging.debug(f"{station_name} ({var}): year={_year}") + logging.debug(f"{display_name} ({var}): year={_year}") # select observations and apriori data time_slice = self._create_time_range_extend( @@ -746,8 +737,8 @@ class ClimateFIRFilter(FIRFilter): continue # apply filter - logging.debug(f"{station_name} ({var}): start filter convolve") - with TimeTracking(name=f"{station_name} ({var}): filter convolve", logging_level=logging.DEBUG): + logging.debug(f"{display_name} ({var}): start filter convolve") + with TimeTracking(name=f"{display_name} ({var}): filter convolve", logging_level=logging.DEBUG): filt = xr.apply_ufunc(fir_filter_convolve, filter_input_data, input_core_dims=[[new_dim]], output_core_dims=[[new_dim]], vectorize=True, kwargs={"h": h}, output_dtypes=[d.dtype]) @@ -774,13 +765,13 @@ class ClimateFIRFilter(FIRFilter): gc.collect() # concat all variables - logging.debug(f"{station_name}: concat all variables") + logging.debug(f"{display_name}: concat all variables") res = xr.concat(coll, var_dim) res_input = xr.concat(coll_input, var_dim) # create result array with same shape like input data, gaps are filled by nans - res_full = self._create_full_filter_result_array(data, res, new_dim, station_name) - res_input_full = self._create_full_filter_result_array(data, res_input, new_dim, station_name) + res_full = self._create_full_filter_result_array(data, res, new_dim, display_name) + res_input_full = self._create_full_filter_result_array(data, res_input, new_dim, display_name) return res_full, res_input_full, h, apriori, plot_data @staticmethod diff --git a/mlair/plotting/data_insight_plotting.py b/mlair/plotting/data_insight_plotting.py index 2048f4c2..526ee867 100644 --- a/mlair/plotting/data_insight_plotting.py +++ b/mlair/plotting/data_insight_plotting.py @@ -977,42 +977,37 @@ class PlotClimateFirFilter(AbstractPlotClass): # pragma: no cover for p_d in plot_data: var = p_d.get("var") t0 = p_d.get("t0") - viz_date = p_d.get("viz_date") filter_input = p_d.get("filter_input") filter_input_nc = p_d.get("filter_input_nc") valid_range = p_d.get("valid_range") time_range = p_d.get("time_range") - offset = p_d.get("offset") if new_dim is None: new_dim = p_d.get("new_dim") else: assert new_dim == p_d.get("new_dim") h = p_d.get("h") plot_dict_var = plot_dict.get(var, {}) - plot_dict_t0 = plot_dict_var.get(viz_date, {}) + plot_dict_t0 = plot_dict_var.get(t0, {}) plot_dict_order = {"filter_input": filter_input, "filter_input_nc": filter_input_nc, "valid_range": valid_range, "time_range": time_range, - "order": len(h), "h": h, - "t0": t0, - "offset": offset} + "order": len(h), "h": h} plot_dict_t0[i] = plot_dict_order - plot_dict_var[viz_date] = plot_dict_t0 + plot_dict_var[t0] = plot_dict_t0 plot_dict[var] = plot_dict_var self.variables_list = list(plot_dict.keys()) return plot_dict, new_dim def _plot(self, plot_dict, sampling, new_dim="window"): td_type = {"1d": "D", "1H": "h"}.get(sampling) - for var, viz_date_dict in plot_dict.items(): - for iviz_date, viz_date in enumerate(viz_date_dict.keys()): - viz_data = viz_date_dict[viz_date] + for var, vis_dict in plot_dict.items(): + for it0, t0 in enumerate(vis_dict.keys()): + vis_data = vis_dict[t0] residuum_true = None try: - for ifilter in sorted(viz_data.keys()): - data = viz_data[ifilter] - t0 = data["t0"] + for ifilter in sorted(vis_data.keys()): + data = vis_data[ifilter] filter_input = data["filter_input"] filter_input_nc = data["filter_input_nc"] if residuum_true is None else residuum_true.sel( {new_dim: filter_input.coords[new_dim]}) @@ -1020,11 +1015,10 @@ class PlotClimateFirFilter(AbstractPlotClass): # pragma: no cover time_axis = data["time_range"] filter_order = data["order"] h = data["h"] - offset = data["offset"] fig, ax = plt.subplots() # plot backgrounds - self._plot_valid_area(ax, viz_date, valid_range, td_type) + self._plot_valid_area(ax, t0, valid_range, td_type) self._plot_t0(ax, t0) # original data @@ -1048,12 +1042,12 @@ class PlotClimateFirFilter(AbstractPlotClass): # pragma: no cover plt.legend() fig.autofmt_xdate() plt.tight_layout() - self.plot_name = f"climFIR_{self._name}_{str(var)}_{iviz_date}_{ifilter}" + self.plot_name = f"climFIR_{self._name}_{str(var)}_{it0}_{ifilter}" self._save() # plot residuum fig, ax = plt.subplots() - self._plot_valid_area(ax, viz_date, valid_range, td_type) + self._plot_valid_area(ax, t0, valid_range, td_type) self._plot_t0(ax, t0) self._plot_series(ax, time_axis, residuum_true.values.flatten(), style="ideal") self._plot_series(ax, time_axis, residuum_estimated.values.flatten(), style="clim") @@ -1063,7 +1057,7 @@ class PlotClimateFirFilter(AbstractPlotClass): # pragma: no cover fig.autofmt_xdate() plt.tight_layout() - self.plot_name = f"climFIR_{self._name}_{str(var)}_{iviz_date}_{ifilter}_residuum" + self.plot_name = f"climFIR_{self._name}_{str(var)}_{it0}_{ifilter}_residuum" self._save() except Exception as e: logging.info(f"Could not create plot because of:\n{sys.exc_info()[0]}\n{sys.exc_info()[1]}\n{sys.exc_info()[2]}") diff --git a/test/test_helpers/test_filter.py b/test/test_helpers/test_filter.py index 6f72eda6..3c362911 100644 --- a/test/test_helpers/test_filter.py +++ b/test/test_helpers/test_filter.py @@ -132,7 +132,7 @@ class TestClimateFIRFilter: res = obj._next_order([128, 64, 43], None, 0, "hamming") assert res == 64 res = obj._next_order([43], None, 0, "hamming") - assert res is None + assert res == 0 def test_next_order_with_kzf(self): obj = object.__new__(ClimateFIRFilter) @@ -315,28 +315,29 @@ class TestClimateFIRFilter: def test_clim_filter(self, xr_array_long_with_var, time_dim, var_dim): obj = object.__new__(ClimateFIRFilter) filter_order = 10*24+1 - res = obj.clim_filter(xr_array_long_with_var, 24, 0.05, 10*24+1, sampling="1H", time_dim=time_dim, var_dim=var_dim) + res = obj.clim_filter(xr_array_long_with_var, 24, 0.05, filter_order, sampling="1H", time_dim=time_dim, + var_dim=var_dim, minimum_length=24) assert len(res) == 5 # check filter data properties - assert res[0].shape == (*xr_array_long_with_var.shape, filter_order + 1) + assert res[0].shape == (*xr_array_long_with_var.shape, 24 + 2) assert res[0].dims == (*xr_array_long_with_var.dims, "window") # check filter properties assert np.testing.assert_almost_equal( - res[1], obj._calculate_filter_coefficients("hamming", filter_order, 0.05, 24)) is None + res[2], obj._calculate_filter_coefficients("hamming", filter_order, 0.05, 24)) is None # check apriori apriori = obj.create_monthly_mean(xr_array_long_with_var, time_dim, sampling="1H") apriori = apriori.astype(xr_array_long_with_var.dtype) apriori = obj.extend_apriori(xr_array_long_with_var, apriori, time_dim, "1H") - assert xr.testing.assert_equal(res[2], apriori) is None + assert xr.testing.assert_equal(res[3], apriori) is None # check plot data format - assert isinstance(res[3], list) - assert isinstance(res[3][0], dict) + assert isinstance(res[4], list) + assert isinstance(res[4][0], dict) keys = {"t0", "var", "filter_input", "filter_input_nc", "valid_range", "time_range", "h", "new_dim"} - assert len(keys.symmetric_difference(res[3][0].keys())) == 0 + assert len(keys.symmetric_difference(res[4][0].keys())) == 0 def test_clim_filter_kwargs(self, xr_array_long_with_var, time_dim, var_dim): obj = object.__new__(ClimateFIRFilter) @@ -352,9 +353,9 @@ class TestClimateFIRFilter: assert res[0].shape == (*xr_array_long_with_var.shape, 1000 + 2) assert res[0].dims == (*xr_array_long_with_var.dims, "total_new_dim") assert np.testing.assert_almost_equal( - res[1], obj._calculate_filter_coefficients(("kaiser", 5), filter_order, 0.05, 24)) is None - assert xr.testing.assert_equal(res[2], apriori) is None - assert len(res[3]) == len(res[0].coords[var_dim]) + res[2], obj._calculate_filter_coefficients(("kaiser", 5), filter_order, 0.05, 24)) is None + assert xr.testing.assert_equal(res[3], apriori) is None + assert len(res[4]) == len(res[0].coords[var_dim]) class TestFirFilterConvolve: -- GitLab