pola-rs/polars · error · RuntimeError

sparkline data range/cols must all be adjacent

Error message

sparkline data range/cols must all be adjacent

What it means

RuntimeError raised by _inject_sparklines during write_excel: a sparkline's data columns must be adjacent in the worksheet, because an Excel sparkline charts one contiguous cell range. _adjacent_cols verifies the referenced columns form a consecutive block in the frame; if other columns sit between them, the range cannot be built.

Source

Thrown at py-polars/src/polars/io/spreadsheet/_write_utils.py:305

    ws: Worksheet,
    df: DataFrame,
    table_start: tuple[int, int],
    col: str,
    *,
    include_header: bool,
    params: Sequence[str] | dict[str, Any],
) -> None:
    """Inject sparklines into (previously-created) empty table columns."""
    from xlsxwriter.utility import xl_rowcol_to_cell

    m: dict[str, Any] = {}
    data_cols = params.get("columns") if isinstance(params, dict) else params
    if not data_cols:
        msg = "supplying 'columns' param value is mandatory for sparklines"
        raise ValueError(msg)
    elif not _adjacent_cols(df, data_cols, min_max=m):
        msg = "sparkline data range/cols must all be adjacent"
        raise RuntimeError(msg)

    spk_row, spk_col, _, _ = _xl_column_range(
        df, table_start, col, include_header=include_header, as_range=False
    )
    data_start_col = table_start[1] + m["min"]["idx"]
    data_end_col = table_start[1] + m["max"]["idx"]

    if not isinstance(params, dict):
        options = {}
    else:
        # strip polars-specific params before passing to xlsxwriter
        options = {
            name: val
            for name, val in params.items()
            if name not in ("columns", "insert_after", "insert_before")
        }
        if "negative_points" not in options:
            options["negative_points"] = options.get("type") in ("column", "win_loss")

View on GitHub (pinned to df599052da)

Solutions

  1. Reorder the frame so the sparkline's data columns are consecutive: df.select(['a', 'c', 'b', ...]) or sparklines over ['a','b','c'] instead.
  2. Or narrow the sparkline to a contiguous subset of the columns you care about.
  3. Or split into multiple sparklines, one per contiguous block.

Example fix

# before
pl.write_excel(df, sparklines={'trend': ['a', 'c']})  # df order: a, b, c

# after
pl.write_excel(df.select('a', 'c', 'b'), sparklines={'trend': ['a', 'c']})
Defensive patterns

Strategy: validation

Validate before calling

def adjacent(df, cols):
    idx = [df.columns.index(c) for c in cols]
    return max(idx) - min(idx) == len(idx) - 1

for name, spec in sparklines.items():
    cols = spec.get('columns') if isinstance(spec, dict) else spec
    if not adjacent(df, cols):
        df = df.select(*[c for c in df.columns if c not in cols], *cols)  # group them
pl.write_excel(df, sparklines=sparklines)

Try / catch

try:
    pl.write_excel(df, sparklines=sparklines)
except RuntimeError as e:
    if 'adjacent' in str(e):
        cols = next(iter(sparklines.values()))
        cols = cols.get('columns') if isinstance(cols, dict) else cols
        ordered = [c for c in df.columns if c not in cols] + list(cols)
        pl.write_excel(df.select(ordered), sparklines=sparklines)
    else:
        raise

Prevention

When it happens

Trigger: pl.write_excel(df, sparklines={'trend': ['a', 'c']}) when df columns are ordered a, b, c — 'b' sits inside the range. Also triggered by dict form with a non-contiguous 'columns' list.

Common situations: Pointing a sparkline at summary columns scattered across a wide export (e.g. monthly totals with metric-label columns interleaved); frames reordered by a group_by/with_columns pipeline so previously adjacent columns no longer are.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/c644b1653fdaf75c. Report an issue: GitHub.