Skip to content

feat: add nulls_equal to join - #3817

Open
mkzung wants to merge 1 commit into
narwhals-dev:mainfrom
mkzung:feat/join-nulls-equal
Open

feat: add nulls_equal to join#3817
mkzung wants to merge 1 commit into
narwhals-dev:mainfrom
mkzung:feat/join-nulls-equal

Conversation

@mkzung

@mkzung mkzung commented Jul 24, 2026

Copy link
Copy Markdown

Description

Adds a nulls_equal parameter to DataFrame.join and LazyFrame.join, matching polars.
It defaults to False (null keys never match, the current behaviour); with nulls_equal=True
a null key matches another null key.

df.join(other, on="key", how="inner", nulls_equal=True)

Each backend needs a different mechanism, since their default null handling differs:

  • polars: passthrough (nulls_equal, or join_nulls before 1.24).
  • pandas / dask: their merge already matches nulls, so the existing dropna on the
    keys is skipped when nulls_equal=True.
  • duckdb: the join predicate becomes IS NOT DISTINCT FROM ((a = b) OR (a IS NULL AND b IS NULL)).
  • spark-like: Column.eqNullSafe.
  • ibis: Column.identical_to.
  • pyarrow: pyarrow joins drop null keys, so it joins on a null-safe encoding (an
    is_null flag plus the value cast to string with null filled). The flag keeps a real
    "" distinct from a null, so it is collision-free.

nulls_equal applies to every strategy except cross (which has no keys).

What type of PR is this? (check all applicable)

  • 💾 Refactor
  • ✨ Feature
  • 🐛 Bug Fix
  • 🔧 Optimization
  • 📝 Documentation
  • ✅ Test
  • 🐳 Other

Related issues

AI assistance

  • No AI tools were used for this PR.
  • AI tools were used.

Checklist

  • Code follows style guide (ruff)
  • Tests added
  • Documented the changes

Closes narwhals-dev#3615. Adds a nulls_equal parameter to DataFrame.join and LazyFrame.join,
defaulting to False (current behaviour). With nulls_equal=True a null key matches
another null key, matching polars.

Each backend uses a different mechanism: polars passes it through (join_nulls before
1.24); pandas and dask skip the key dropna since their merge already matches nulls;
duckdb uses an IS NOT DISTINCT FROM predicate; spark-like uses eqNullSafe; ibis uses
identical_to; pyarrow joins on a null-safe encoding since its join drops null keys.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enh]: Add nulls_equal parameter to join

1 participant