pola-rs/polars · error · ValueError

cannot specify both `value` and `strategy`

Error message

cannot specify both `value` and `strategy`

What it means

Raised by Expr.fill_null when both a fill `value` and a fill `strategy` are passed. Polars validates arguments in the Python layer before dispatching to the Rust engine: filling is either an explicit literal/expression value or a strategy ('forward', 'backward', 'mean', 'median', 'min', 'max', 'zero', 'one'), never both, because the result would be ambiguous. The check happens eagerly at expression-construction time, before any data is touched.

Source

Thrown at py-polars/src/polars/expr/expr.py:3345

        │ 1    ┆ 4.0 │
        │ 2    ┆ 5.0 │
        │ null ┆ 6.0 │
        └──────┴─────┘
        >>> df.with_columns(pl.all().fill_null(pl.all().median()))
        shape: (3, 2)
        ┌─────┬─────┐
        │ a   ┆ b   │
        │ --- ┆ --- │
        │ f64 ┆ f64 │
        ╞═════╪═════╡
        │ 1.0 ┆ 4.0 │
        │ 2.0 ┆ 5.0 │
        │ 1.5 ┆ 6.0 │
        └─────┴─────┘
        """
        if value is not None and strategy is not None:
            msg = "cannot specify both `value` and `strategy`"
            raise ValueError(msg)
        elif value is None and strategy is None:
            msg = "must specify either a fill `value` or `strategy`"
            raise ValueError(msg)
        elif strategy not in ("forward", "backward") and limit is not None:
            msg = "can only specify `limit` when strategy is set to 'backward' or 'forward'"
            raise ValueError(msg)

        if value is not None:
            value_pyexpr = parse_into_expression(value, str_as_lit=True)
            return wrap_expr(self._pyexpr.fill_null(value_pyexpr))
        else:
            assert strategy is not None
            return wrap_expr(self._pyexpr.fill_null_with_strategy(strategy, limit))

    def fill_nan(self, value: int | float | Expr | None) -> Expr:
        """
        Fill floating point NaN value with a fill value.

View on GitHub (pinned to df599052da)

Solutions

  1. Remove one of the two arguments: keep `value` for an explicit fill, keep `strategy` for a computed fill.
  2. If you wanted 'fill with X where possible, else forward-fill', express it explicitly: pl.col('a').fill_null(0) or use coalesce of two fills.
  3. Audit the call site for kwargs built dynamically (e.g. **fill_kwargs) and make sure only one key survives.

Example fix

# before
pl.col('a').fill_null(0, strategy='forward')

# after
pl.col('a').fill_null(0)
# or
pl.col('a').fill_null(strategy='forward')
Defensive patterns

Strategy: validation

Validate before calling

def check_fill_args(value, strategy, limit=None):
    if value is not None and strategy is not None:
        raise ValueError('pass only one of value / strategy')
    if value is None and strategy is None:
        raise ValueError('pass at least one of value / strategy')
    if limit is not None and strategy not in ('forward', 'backward'):
        raise ValueError('limit requires strategy forward/backward')

check_fill_args(0, 'forward')  # fails fast before polars sees it

Prevention

When it happens

Trigger: Calls like pl.col('a').fill_null(0, strategy='forward') or df.fill_null(value=0, strategy='mean'). It fires at expression build time, even inside lazy queries that never run.

Common situations: Refactoring old code that used strategy= and adding a value= default; copy-pasting snippets that merge both styles; config-driven pipelines where a fallback value and a fallback strategy are both populated from settings.

Related errors


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