Skip to content

Commit 48295cd

Browse files
jbrockmendelclaude
andauthored
PERF: use lookup instead of hash_inner_join for merge with unique right keys (pandas-dev#64691)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 80743cd commit 48295cd

2 files changed

Lines changed: 10 additions & 7 deletions

File tree

doc/source/whatsnew/v3.1.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ Performance improvements
139139
- Performance improvement in :func:`bdate_range` and :func:`date_range` with ``freq="B"`` or ``freq="C"`` (business day frequencies) (:issue:`16463`)
140140
- Performance improvement in :func:`infer_freq` (:issue:`64463`)
141141
- Performance improvement in :func:`merge` and :meth:`DataFrame.join` for many-to-many joins with ``sort=False`` (:issue:`56564`)
142+
- Performance improvement in :func:`merge` for many-to-one joins with unique right keys (:issue:`38418`)
142143
- Performance improvement in :func:`merge` with ``how="cross"`` (:issue:`38082`)
143144
- Performance improvement in :func:`merge` with ``how="left"`` (:issue:`64370`)
144145
- Performance improvement in :func:`merge` with ``sort=False`` for single-key ``how="left"``/``how="right"`` joins when the opposite join key is sorted, unique, and range-like (:issue:`64146`)

pandas/core/reshape/merge.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3008,16 +3008,18 @@ def _factorize_keys(
30083008
if hash_join_available:
30093009
rlab = rizer.factorize(rk_data, mask=rk_mask)
30103010
if rizer.get_count() == len(rlab):
3011-
ridx, lidx = rizer.hash_inner_join(lk_data, lk_mask)
3011+
# GH#38418 Use lookup instead of hash_inner_join for unique right
3012+
# keys. lookup pre-allocates the result array and fills directly,
3013+
# avoiding dynamic vector resizing and scatter.
3014+
ridx = rizer.table.lookup(lk_data, lk_mask) # type: ignore[attr-defined]
30123015
if how == "inner":
3016+
mask = ridx != -1
3017+
lidx = mask.nonzero()[0].astype(np.intp, copy=False)
3018+
ridx = ridx[mask].astype(np.intp, copy=False)
30133019
return lidx, ridx, -1
30143020
# left-outer hash join with unique right side.
3015-
# Preserve original left-row order: one output row per left row,
3016-
# ridx=-1 for unmatched rows.
3017-
n_left = len(lk_data)
3018-
ridx_full = np.full(n_left, -1, dtype=np.intp)
3019-
ridx_full[lidx] = ridx
3020-
return np.arange(n_left, dtype=np.intp), ridx_full, -1
3021+
# One output row per left row, ridx=-1 for unmatched rows.
3022+
return np.arange(len(lk_data), dtype=np.intp), ridx, -1
30213023
else:
30223024
llab = rizer.factorize(lk_data, mask=lk_mask)
30233025
else:

0 commit comments

Comments
 (0)