Skip to content

plot_time_series

yohou.plotting.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 or categorical 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. Categorical columns are rendered as stepped line+marker traces with a labelled integer y-axis.

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 returns a FigureResampler, "widget" a FigureWidgetResampler, False disables resampling, and None reads from get_config.

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

Source code in src/yohou/plotting/exploration.py
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 or categorical
        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.
        Categorical columns are rendered as stepped line+marker traces with
        a labelled integer y-axis.
    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`` returns a
        ``FigureResampler``, ``"widget"`` a ``FigureWidgetResampler``,
        ``False`` disables resampling, and ``None`` reads from
        `get_config`.
    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


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

    View · Open in marimo

  • How to Apply Stationarity to Panel Data


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

    View · Open in marimo

  • How to Choose a Decomposition Strategy


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

    View · Open in marimo

  • How to Configure LocalPanelForecaster


    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 Forecast Panel Data with ColumnForecaster


    Apply a shared forecasting model across multiple series in a panel dataset using ColumnForecaster with the __ column separator convention.

    View · Open in marimo

  • How to Forecast with CatBoost


    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

See all 11 examples in the gallery