pola-rs/polars · error

expected `left_on` to be str or Expr, got {qualified_type_na

Error message

expected `left_on` to be str or Expr, got {qualified_type_name(left_on)!r}

What it means

In DataFrame.join_asof, when `on` is None you must specify `left_on` separately, and it must be a single str or pl.Expr. This guard fires for lists, tuples, pl.Series, numpy values — and also for None itself when `on` was forgotten entirely (the message then reports NoneType), since left_on=None alongside on=None leaves the join without any key.

Source

Thrown at py-polars/src/polars/dataframe/frame.py:8225

        │ Germany     ┆ 2018-08-01 ┆ 82.66      ┆ 4696 │
        │ Germany     ┆ 2019-01-01 ┆ 83.12      ┆ 4696 │
        │ Netherlands ┆ 2016-03-01 ┆ 17.11      ┆ 784  │
        │ Netherlands ┆ 2018-08-01 ┆ 17.32      ┆ 910  │
        │ Netherlands ┆ 2019-01-01 ┆ 17.4       ┆ 910  │
        └─────────────┴────────────┴────────────┴──────┘
        """
        require_same_type(self, other)

        if on is not None:
            if not isinstance(on, (str, pl.Expr)):
                msg = (
                    f"expected `on` to be str or Expr, got {qualified_type_name(on)!r}"
                )
                raise TypeError(msg)
        else:
            if not isinstance(left_on, (str, pl.Expr)):
                msg = f"expected `left_on` to be str or Expr, got {qualified_type_name(left_on)!r}"
                raise TypeError(msg)
            elif not isinstance(right_on, (str, pl.Expr)):
                msg = f"expected `right_on` to be str or Expr, got {qualified_type_name(right_on)!r}"
                raise TypeError(msg)

        from polars.lazyframe.opt_flags import QueryOptFlags

        return (
            self.lazy()
            .join_asof(
                other.lazy(),
                left_on=left_on,
                right_on=right_on,
                on=on,
                by_left=by_left,
                by_right=by_right,
                by=by,
                strategy=strategy,
                suffix=suffix,

View on GitHub (pinned to df599052da)

Solutions

  1. Provide both single-value keys: df.join_asof(other, left_on='ts_left', right_on='ts_right')
  2. If the key column has the same name on both sides, use on='ts' instead of left_on/right_on
  3. Add extra (non-time) match keys via by=/by_left=/by_right= rather than putting them in left_on
  4. Ensure every value is a plain column name string or pl.col(...) — not a Series, array, or tuple

Example fix

# before
out = df.join_asof(other, left_on=['ts_left', 'id'])

# after
out = (
    df.sort('ts_left')
      .join_asof(other.sort('ts_right'), left_on='ts_left', right_on='ts_right', by='id')
)
Defensive patterns

Strategy: type-guard

Validate before calling

from polars.expr import Expr

if on is None and not isinstance(left_on, (str, Expr)):
    raise TypeError(f'left_on must be a single str or Expr, got {type(left_on)!r}; did you forget `on`?')

Type guard

from polars.expr import Expr

def is_single_asof_key(v: object) -> bool:
    return isinstance(v, (str, Expr))

Prevention

When it happens

Trigger: df.join_asof(other, left_on=['ts', 'id']); df.join_asof(other, left_on=pl.Series(...)); df.join_asof(other, right_on='ts_right') with both on and left_on left as None; left_on=('ts',) tuple.

Common situations: Using differently named timestamp columns per side and forgetting one of the pair; assuming multi-column left_on works like DataFrame.join's; omitting all key parameters when copying a call template.

Related errors


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