Source code for metobs_toolkit.modeltimeseries

import logging
from typing import Union
import pandas as pd
import numpy as np

from matplotlib.pyplot import Axes

from metobs_toolkit.backend_collection.df_helpers import to_timedelta
import metobs_toolkit.backend_collection.printing_collection as printing
from metobs_toolkit.obstypes import Obstype
import metobs_toolkit.plot_collection.timeseries_plotting as plotting


logger = logging.getLogger("<metobs_toolkit>")


[docs] class ModelTimeSeries: """Class for model-based timeseries at one location. Parameters ---------- site : object The site object representing the location. datarecords : np.ndarray Array of data records. timestamps : np.ndarray Array of timestamps corresponding to the data records. obstype : Obstype The observation type. datadtype : type, optional Data type for the records, by default np.float32. timezone : str, optional Timezone for the timestamps, by default "UTC". modelname : str, optional Name of the model, by default None. modelvariable : str, optional Name of the model variable, by default None. """
[docs] def __init__( self, site, datarecords: np.ndarray, timestamps: np.ndarray, obstype: Obstype, datadtype: type = np.float32, timezone: str = "UTC", modelname: str = None, modelvariable: str = None, ): self.site = site self.obstype = obstype # Data data = pd.Series( data=pd.to_numeric(datarecords, errors="coerce").astype(datadtype), index=pd.DatetimeIndex(data=timestamps, tz=timezone, name="datetime"), name=obstype.name, ) self.series = data # model metadata self.modelname = modelname self.modelvariable = modelvariable
# ------------------------------------------ # Specials # ------------------------------------------ def __eq__(self, other) -> bool: """Check equality with another ModelTimeSeries object.""" if not isinstance(other, ModelTimeSeries): return False return ( self.site == other.site and self.obstype == other.obstype and self.series.equals(other.series) and self.modelname == other.modelname and self.modelvariable == other.modelvariable ) @property def df(self) -> pd.DataFrame: """Return all records as a DataFrame.""" # get all records df = ( self.series.to_frame() .rename(columns={self.obstype.name: "value", self.stationname: "value"}) .assign(model=self.modelname) ) return df @property def stationname(self) -> str: """Return the name of the station this SensorData belongs to.""" return self.site.stationname @property def tz(self) -> str: """Return the timezone of the stored timestamps.""" return self.series.index.tz @property def start_datetime(self) -> pd.Timestamp: """Return the start datetime of the series.""" return self.series.index.min() @property def end_datetime(self) -> pd.Timestamp: """Return the end datetime of the series.""" return self.series.index.max() @property def freq(self) -> pd.Timedelta: """Return the frequency of the series.""" freq = pd.infer_freq(self.series.index) if freq is None: raise ValueError("Frequency could not be computed.") # note: sometimes 'h' is returned, and this gives issues, so add a 1 in front return to_timedelta(freq) def _get_info_core(self, nident_root=1) -> dict: infostr = "" infostr += printing.print_fmt_line( f"Origin {self.modelname} -> variable/band: {self.modelvariable}", nident_root, ) infostr += printing.print_fmt_line( f"From {self.start_datetime} --> {self.end_datetime}", nident_root ) infostr += printing.print_fmt_line( f"Assumed frequency: {self.freq}", nident_root ) infostr += printing.print_fmt_line( f"Number of records: {self.series.shape[0]}", nident_root ) infostr += printing.print_fmt_line( f"Units are converted from {self.obstype.model_unit} --> {self.obstype.std_unit}", nident_root, ) return infostr
[docs] def get_info(self, printout: bool = True) -> Union[None, str]: """ Print or return information about the ModelTimeSeries. Parameters ---------- printout : bool, optional If True, print the information. If False, return as string. Default is True. Returns ------- None or str None if printout is True, otherwise the information string. """ logger.debug(f"{self.__class__.__name__}.get_info called for {self}") infostr = "" infostr += printing.print_fmt_title("General info of ModelTimeSeries") infostr += printing.print_fmt_line( f"{self.obstype.name} model data at location of {self.stationname}" ) infostr += self._get_info_core(nident_root=1) if printout: print(infostr) else: return infostr
[docs] def make_plot( self, linecolor: str = None, ax: Union[Axes, None] = None, figkwargs: dict = {}, title: Union[str, None] = None, ) -> Axes: """ Create a plot of the model time series. Parameters ---------- linecolor : str, optional Color of the line, by default None. ax : Axes, optional Matplotlib Axes to plot on, by default None. figkwargs : dict, optional Additional keyword arguments for figure creation, by default {}. title : str or None, optional Title for the plot, by default None. Returns ------- Axes The matplotlib Axes with the plot. """ logger.debug(f"{self.__class__.__name__}.make_plot called for {self}") # define figure if ax is None: ax = plotting.create_axes(**figkwargs) # Define a color if linecolor is None: # create a new color color = plotting.create_categorical_color_map(["dummy"])["dummy"] else: color = linecolor legendname = f"{self.modelname}:{self.modelvariable}@{self.stationname}" ax = plotting.add_lines_to_axes( ax=ax, series=self.series, legend_label=legendname, linestyle="--", color=color, ) # Add Styling attributes # Set title: if title is None: plotting.set_title( ax, f"{self.obstype.name} data for station {self.stationname}" ) else: plotting.set_title(ax, title) # Set ylabel plotting.set_ylabel(ax, self.obstype._get_plot_y_label()) # Set xlabel plotting.set_xlabel(ax, f"Timestamps (in {self.tz})") # Add legend plotting.set_legend(ax) return ax