Source code for tseda.visualization.series_visualizer

"""Plotly-based visualizers for raw and segmented time-series data."""

import plotly.express as px
from pandas import Series, DataFrame
from typing import Optional
from plotly.graph_objects import Figure
import numpy as np
import pandas as pd
import statsmodels.api as sm

[docs] class SeriesVisualizer: """Interactive scatter and LOWESS-smoothed visualizations for a single time series."""
[docs] def __init__(self, series: Series, title: str = "Signal Visualization") -> None: """Convert the input series to a two-column DataFrame for plotting. Args: series: Timestamp-indexed numeric series. title: Figure title used by visualizer methods. """ self._df: DataFrame = series.to_frame().reset_index() self._df.columns = ["date", "signal"] self._title: str = title
[docs] def getVisualization(self) -> Figure: """Create an interactive scatter plot coloured by signal value. Returns: Plotly figure object for the raw signal visualization. """ # Create the figure and add a scatter trace fig = px.scatter(self._df, x="date", y="signal", color="signal") return fig
[docs] def calc_epoch(self, ts: np.datetime64 = 10) -> float: """Convert a datetime64 value to fractional years since the Unix epoch. Args: ts: Datetime64 scalar to convert. Returns: Floating-point year representation. """ return pd.to_datetime(ts).timestamp()/(3600*24*365.25)
[docs] def calc_dates(self, ts: np.datetime64) -> np.ndarray: """Format a datetime64 value as an ISO 8601 date string. Args: ts: Datetime64 scalar to format. Returns: Date string in ``YYYY-MM-DD`` format. """ return pd.to_datetime(ts).strftime("%Y-%m-%d")
[docs] def LowessVisualizer(self, frac: float = 0.05) -> Figure: """Return a LOWESS-smoothed line plot. Args: frac: Fraction of the data used when estimating each y-value (see ``statsmodels.nonparametric.lowess``). Returns: Plotly line figure of the smoothed signal. """ data = self._df["signal"].values get_epochs = np.vectorize(self.calc_epoch) epochs = get_epochs(self._df["date"].values) get_dates = np.vectorize(self.calc_dates) date_vals = get_dates(self._df["date"].values) lowess = sm.nonparametric.lowess smoothed = lowess(data, epochs, frac=frac) pdata = {"date": date_vals, "signal" : smoothed[:,1]} df_lowess = pd.DataFrame.from_dict(pdata) fig = px.line(df_lowess, x='date', y='signal', title='Smooth Line Plot with LOWESS') return fig
[docs] class SegmentedSeriesVisualizer(SeriesVisualizer): """Visualizer for change-point segmented time-series DataFrames."""
[docs] def __init__(self, df: pd.DataFrame, title: str = "Signal Visualization") -> None: """Store the pre-segmented DataFrame for plotting. Args: df: DataFrame with at least ``date``, ``signal``, and ``segment`` columns. title: Figure title. """ self._df: DataFrame = df self._title: str = title
[docs] def getVisualization(self) -> Figure: """Create a segmented line plot coloured by segment label. Returns: Plotly figure object showing segment-wise signal trajectories. """ # Create the line plot with the 'color' attribute fig = px.line( self._df, x="date", y="signal", color="segment", # This attribute automatically colors lines by country title="Segmented Change Point Representation" ) # Customize the plot appearance (optional) fig.update_traces(mode='lines+markers') # Add markers to the lines # Return the figure return fig