pola-rs/polars · error · PolarsError::InvalidOperation

`unique` operation not supported for dtype `{}`

Error message

`unique` operation not supported for dtype `{}`

What it means

agg_n_unique is the group_by aggregation behind n_unique()/approx_n_unique style operations. Before counting distinct values it takes the physical representation; if that representation contains objects (DataType::Object, arbitrary Rust T: PolarsObject values), it panics because hashing/arbitrary object equality is not supported on this path.

Source

Thrown at crates/polars-core/src/frame/group_by/aggregations/dispatch.rs:289

                            return None;
                        }
                        let v = mask.sliced_unchecked(first as usize, len as usize);
                        let tz = v.trailing_zeros() as IdxSize;
                        if tz == len { None } else { Some(len - tz - 1) }
                    })
                    .collect_ca(PlSmallStr::EMPTY)
            },
        };

        out.into_series()
    }

    #[doc(hidden)]
    pub unsafe fn agg_n_unique(&self, groups: &GroupsType) -> Series {
        let values = self.to_physical_repr();
        let dtype = values.dtype();
        let values = if dtype.contains_objects() {
            panic!("{}", polars_err!(opq = unique, dtype));
        } else if let Some(ca) = values.try_str() {
            ca.as_binary().into_column()
        } else if dtype.is_nested() {
            encode_rows_unordered(&[values.into_owned().into_column()])
                .unwrap()
                .into_column()
        } else {
            values.into_owned().into_column()
        };

        // Keep the Column for the sort-fallback path. Big groups go through
        // `Series::n_unique`, bypassing the amortized hashset.
        let col = values.clone();
        let values = values.rechunk_to_arrow(CompatLevel::newest());
        let values = values.as_ref();
        let state = amortized_unique_from_dtype(values.dtype());

        struct CloneWrapper(Box<dyn AmortizedUnique>);

View on GitHub (pinned to 68506541d2)

Solutions

  1. Cast or serialize the object column to Utf8/Binary before the aggregation: pl.col("obj").cast(pl.String).n_unique()
  2. Replace the object column with a supported dtype (struct, list, enum/categorical) at ingestion time
  3. Drop the object column from the n_unique aggregation and compute uniqueness on a typed key instead

Example fix

# before
df.group_by("k").agg(pl.col("obj").n_unique())

# after
df.group_by("k").agg(pl.col("obj").cast(pl.String).n_unique())
Defensive patterns

Strategy: validation

Validate before calling

fn is_object_col(s: &Series) -> bool {
    s.dtype().contains_objects()
}

Prevention

When it happens

Trigger: A group_by aggregation that needs unique counts over a column whose dtype is Object, e.g. df.group_by(["k"]).agg(pl.col("obj").n_unique()) in Python where "obj" holds Python objects, or the Rust equivalent with a PolarsObject column.

Common situations: polars-python users who created object columns (e.g. via pl.Series with mixed Python objects, or UDFs returning objects); Rust users with custom PolarsObject types; Arrow extension-type data imported as Object.

Related errors


AI-assisted analysis of pola-rs/polars@68506541d2 (2026-08-19). Data as JSON: /api/errors/4b636fe64717e24d. Report an issue: GitHub.