Skip to content

plot_time_series

yohou.plotting.exploration.plot_time_series(df, *, columns=None, groups=None, facet_by='member', facet_n_cols=2, color_palette=None, show_legend=True, title=None, x_label=None, y_label=None, width=None, height=None, connect_gaps=False, resampler=None, line_width=2.0, line_dash='solid', line_opacity=1.0)

Plot basic line plots for one or more time series.

Parameters

Name Type Description Default
df DataFrame

Input DataFrame with 'time' column and numeric columns to plot.

required
columns str | list[str] | None

Column(s) to plot. If None, plots all numeric columns except 'time'. If str, plots single column. If list, plots multiple columns.

None
groups list[str] | None

Panel group prefixes to plot. Creates separate subplots per group. If None and panel data is detected, plots all groups.

None
facet_by Literal['group', 'member'] | None

Faceting axis for panel data. "group" creates one subplot per group, "member" one per member. None disables faceting. Ignored for non-panel data.

"member"
facet_n_cols int

Number of columns in facet grid when using panel groups.

2
color_palette list[str] | None

Custom color palette as hex codes. If None, uses yohou palette.

None
show_legend bool

Whether to show legend when plotting multiple columns.

True
title str | None

Plot title.

None
x_label str | None

X-axis label. Defaults to "time".

None
y_label str | None

Y-axis label.

None
width int | None

Plot width in pixels.

None
height int | None

Plot height in pixels.

None
connect_gaps bool

Whether to connect gaps in the data with lines.

False
resampler bool | Literal['widget'] | None

Enable plotly-resampler for large datasets. True or "widget" creates a FigureWidgetResampler; False or None uses a plain go.Figure.

None
line_width float

Width of the line traces in pixels.

2.0
line_dash str

Dash style for lines. One of "solid", "dash", "dot", "dashdot".

"solid"
line_opacity float

Opacity of the line traces (0.0 to 1.0).

1.0

Returns

Type Description
Figure

Plotly figure object.

Raises

Type Description
TypeError

If df is not a Polars DataFrame.

ValueError

If DataFrame is empty, missing 'time' column, or specified columns don't exist.

Examples

>>> import polars as pl
>>> from yohou.plotting import plot_time_series
>>> # Create sample data
>>> df = pl.DataFrame({
...     "time": pl.date_range(pl.date(2020, 1, 1), pl.date(2020, 12, 31), "1mo", eager=True),
...     "y": [100, 120, 115, 130, 140, 135, 150, 160, 155, 170, 180, 175],
... })
>>> # Plot single column
>>> fig = plot_time_series(df, columns="y")
>>> len(fig.data)
1
>>> # Multiple columns
>>> df = df.with_columns((pl.col("y") * 1.1).alias("y2"))
>>> fig = plot_time_series(df, columns=["y", "y2"])
>>> len(fig.data)
2

See Also

plot_rolling_statistics : Plot rolling window statistics.

Source Code

Show/Hide source
def plot_time_series(
    df: pl.DataFrame,
    *,
    columns: str | list[str] | None = None,
    groups: list[str] | None = None,
    facet_by: Literal["group", "member"] | None = "member",
    facet_n_cols: int = 2,
    color_palette: list[str] | None = None,
    show_legend: bool = True,
    title: str | None = None,
    x_label: str | None = None,
    y_label: str | None = None,
    width: int | None = None,
    height: int | None = None,
    connect_gaps: bool = False,
    resampler: bool | Literal["widget"] | None = None,
    line_width: float = 2.0,
    line_dash: str = "solid",
    line_opacity: float = 1.0,
) -> go.Figure:
    """
    Plot basic line plots for one or more time series.

    Parameters
    ----------
    df : pl.DataFrame
        Input DataFrame with 'time' column and numeric columns to plot.
    columns : str | list[str] | None, default=None
        Column(s) to plot. If None, plots all numeric columns except 'time'.
        If str, plots single column. If list, plots multiple columns.
    groups : list[str] | None, default=None
        Panel group prefixes to plot. Creates separate subplots per group.
        If None and panel data is detected, plots all groups.
    facet_by : Literal["group", "member"] | None, default="member"
        Faceting axis for panel data.  ``"group"`` creates one subplot per
        group, ``"member"`` one per member.  ``None`` disables faceting.
        Ignored for non-panel data.
    facet_n_cols : int, default=2
        Number of columns in facet grid when using panel groups.
    color_palette : list[str] | None, default=None
        Custom color palette as hex codes. If None, uses yohou palette.
    show_legend : bool, default=True
        Whether to show legend when plotting multiple columns.
    title : str | None, default=None
        Plot title.
    x_label : str | None, default=None
        X-axis label. Defaults to "time".
    y_label : str | None, default=None
        Y-axis label.
    width : int | None, default=None
        Plot width in pixels.
    height : int | None, default=None
        Plot height in pixels.
    connect_gaps : bool, default=False
        Whether to connect gaps in the data with lines.
    resampler : bool | Literal["widget"] | None, default=None
        Enable plotly-resampler for large datasets.  ``True`` or
        ``"widget"`` creates a ``FigureWidgetResampler``; ``False`` or
        ``None`` uses a plain ``go.Figure``.
    line_width : float, default=2.0
        Width of the line traces in pixels.
    line_dash : str, default="solid"
        Dash style for lines. One of ``"solid"``, ``"dash"``, ``"dot"``,
        ``"dashdot"``.
    line_opacity : float, default=1.0
        Opacity of the line traces (0.0 to 1.0).

    Returns
    -------
    go.Figure
        Plotly figure object.

    Raises
    ------
    TypeError
        If df is not a Polars DataFrame.
    ValueError
        If DataFrame is empty, missing 'time' column, or specified columns don't exist.

    Examples
    --------
    >>> import polars as pl
    >>> from yohou.plotting import plot_time_series

    >>> # Create sample data
    >>> df = pl.DataFrame({
    ...     "time": pl.date_range(pl.date(2020, 1, 1), pl.date(2020, 12, 31), "1mo", eager=True),
    ...     "y": [100, 120, 115, 130, 140, 135, 150, 160, 155, 170, 180, 175],
    ... })

    >>> # Plot single column
    >>> fig = plot_time_series(df, columns="y")
    >>> len(fig.data)
    1

    >>> # Multiple columns
    >>> df = df.with_columns((pl.col("y") * 1.1).alias("y2"))
    >>> fig = plot_time_series(df, columns=["y", "y2"])
    >>> len(fig.data)
    2

    See Also
    --------
    [`plot_rolling_statistics`][yohou.plotting.plot_rolling_statistics] : Plot rolling window statistics.
    """
    # Validate inputs
    validate_plotting_data(df)
    validate_plotting_params(width=width, height=height)

    if groups is None and columns is None and _auto_detect_panel(df):
        groups = []

    if groups is not None and _auto_detect_panel(df, groups):
        # Pre-compute palette for consistent colouring across the overlaid dimension
        pn_cols = resolve_panel_columns(df, groups, columns if groups is not None else None)
        grouped, all_members = _group_panel_columns(pn_cols)
        effective_facet_by = facet_by or "member"
        n_overlay = len(list(grouped.keys())) if effective_facet_by == "member" else len(all_members)
        color_palette = resolve_color_palette(color_palette, n_overlay)
        legend_tracker = LegendTracker(show_legend=show_legend)

        def _render_ts(ctx: RenderContext) -> None:
            """Render one trace into the faceted figure."""
            color = color_palette[ctx.entity_idx]
            _show = legend_tracker.should_show(ctx.display_name)
            series = ctx.sub_df[ctx.display_name]
            if is_categorical_dtype(series.dtype):
                sorted_cats, cat_to_int = build_category_map(series)
                y_vals = [cat_to_int.get(str(v), -1) for v in series.to_list()]
                ctx.fig.add_trace(
                    go.Scatter(
                        x=ctx.sub_df["time"],
                        y=y_vals,
                        mode="lines+markers",
                        name=ctx.display_name,
                        line={"color": color, "width": line_width, "dash": line_dash, "shape": "hv"},
                        marker={"size": 5},
                        opacity=line_opacity,
                        showlegend=_show,
                        legendgroup=ctx.display_name,
                        text=[str(v) for v in series.to_list()],
                        hovertemplate=f"<b>{ctx.display_name}</b><br>%{{x}}<br>Class: %{{text}}<extra></extra>",
                    ),
                    row=ctx.row,
                    col=ctx.col,
                )
                ctx.fig.update_yaxes(
                    tickvals=list(range(len(sorted_cats))),
                    ticktext=sorted_cats,
                    row=ctx.row,
                    col=ctx.col,
                )
            else:
                ctx.fig.add_trace(
                    go.Scatter(
                        x=ctx.sub_df["time"],
                        y=series,
                        mode="lines",
                        name=ctx.display_name,
                        line={"color": color, "width": line_width, "dash": line_dash},
                        opacity=line_opacity,
                        connectgaps=connect_gaps,
                        showlegend=_show,
                        legendgroup=ctx.display_name,
                        hovertemplate=f"<b>{ctx.display_name}</b><br>%{{x}}<br>%{{y:.2f}}<extra></extra>",
                    ),
                    row=ctx.row,
                    col=ctx.col,
                )

        fig = facet_figure(
            df,
            _render_ts,
            groups=groups,
            columns=columns if groups is not None else None,
            facet_by=effective_facet_by,
            facet_n_cols=facet_n_cols,
            title=title,
            x_label=x_label or "Time",
            y_label=y_label,
            width=width,
            height=height,
            resampler=resampler,
        )
        fig.update_layout(showlegend=show_legend)

        return fig

    # Non-panel case: use column-mode facet_figure (one subplot per column)
    plot_columns = validate_plotting_data(df, columns=columns, exclude=["time"], include_categorical=True)
    _colors = resolve_color_palette(color_palette, len(plot_columns))
    _col_colors = dict(zip(plot_columns, _colors, strict=False))

    # Pre-compute category maps for any categorical columns
    _cat_maps: dict[str, tuple[list[str], dict[str, int]]] = {}
    for _c in plot_columns:
        if is_categorical_dtype(df[_c].dtype):
            _cat_maps[_c] = build_category_map(df[_c])

    def _render_ts(ctx: RenderContext) -> None:
        """Render one trace into a column-faceted subplot."""
        col_name = ctx.display_name
        color = _col_colors[col_name]
        if col_name in _cat_maps:
            sorted_cats, cat_to_int = _cat_maps[col_name]
            series = ctx.sub_df[col_name]
            y_vals = [cat_to_int.get(str(v), -1) for v in series.to_list()]
            ctx.fig.add_trace(
                go.Scatter(
                    x=ctx.sub_df["time"],
                    y=y_vals,
                    mode="lines+markers",
                    name=col_name,
                    line={"color": color, "width": line_width, "dash": line_dash, "shape": "hv"},
                    marker={"size": 5},
                    opacity=line_opacity,
                    text=[str(v) for v in series.to_list()],
                    hovertemplate=f"<b>{col_name}</b><br>%{{x}}<br>Class: %{{text}}<extra></extra>",
                ),
                row=ctx.row,
                col=ctx.col,
            )
            ctx.fig.update_yaxes(
                tickvals=list(range(len(sorted_cats))),
                ticktext=sorted_cats,
                row=ctx.row,
                col=ctx.col,
            )
        else:
            ctx.fig.add_trace(
                go.Scatter(
                    x=ctx.sub_df["time"],
                    y=ctx.sub_df[col_name],
                    mode="lines",
                    name=col_name,
                    line={"color": color, "width": line_width, "dash": line_dash},
                    opacity=line_opacity,
                    connectgaps=connect_gaps,
                    hovertemplate=f"<b>{col_name}</b><br>%{{x}}<br>%{{y:.2f}}<extra></extra>",
                ),
                row=ctx.row,
                col=ctx.col,
            )

    fig = facet_figure(
        df,
        _render_ts,
        columns=plot_columns,
        facet_n_cols=facet_n_cols,
        title=title,
        x_label=x_label or "Time",
        y_label=y_label,
        width=width,
        height=height,
        resampler=resampler,
    )
    fig.update_layout(showlegend=show_legend)

    return fig

Tutorials

The following example notebooks use this component:

  • How to Aggregate Scorer Results


    Evaluation-Search

    Demonstrate all scorer aggregation strategies (stepwise, vintagewise, componentwise, groupwise, coveragewise, all) on panel data with weighted group aggregation.

    View · Open in marimo

  • How to Forecast with CatBoost


    Forecasting-Models

    Plug CatBoostRegressor into PointReductionForecaster as a drop-in sklearn estimator, compare gradient-boosted versus Ridge linear baseline, and demonstrate the direct reduction strategy with tree-based models.

    View · Open in marimo

  • How to Choose a Decomposition Strategy


    Forecasting-Models

    Build 2- and 3-component DecompositionPipeline forecasters chaining trend, seasonality, and residual models with target pre-transformation.

    View · Open in marimo

  • How to Use Lagged Forecasts as Features


    Forecasting-Models

    Compare ForecastedFeatureForecaster strategies (actual, predicted, rewind) and split ratio tuning for chaining feature and target forecasters.

    View · Open in marimo

  • How to Configure LocalPanelForecaster


    Panel-Data

    Wrap any forecaster with LocalPanelForecaster for fully independent per-group clones, parallel fitting via n_jobs, and selective group operations.

    View · Open in marimo

  • How to Run Panel Cross-Validation


    Panel-Data

    Time series cross-validation on panel data with GridSearchCV, selective group observation, rewind operations, and groupwise performance comparison.

    View · Open in marimo

  • How to Apply Stationarity to Panel Data


    Panel-Data

    Apply per-group stationarity transforms on panel data with SeasonalDifferencing, DecompositionPipeline (polynomial trend + pattern seasonality), and residuals.

    View · Open in marimo