1"""
2SQL-style merge routines
3"""
4
5from __future__ import annotations
6
7from collections.abc import (
8 Hashable,
9 Sequence,
10)
11import datetime
12from functools import partial
13import types
14from typing import (
15 TYPE_CHECKING,
16 Literal,
17 cast,
18 final,
19)
20import uuid
21import warnings
22
23import numpy as np
24
25from pandas._libs import (
26 Timedelta,
27 hashtable as libhashtable,
28 join as libjoin,
29 lib,
30)
31from pandas._libs.lib import is_range_indexer
32from pandas._typing import (
33 AnyArrayLike,
34 ArrayLike,
35 IndexLabel,
36 JoinHow,
37 MergeHow,
38 Shape,
39 Suffixes,
40 npt,
41)
42from pandas.errors import MergeError
43from pandas.util._decorators import (
44 cache_readonly,
45 set_module,
46)
47from pandas.util._exceptions import find_stack_level
48
49from pandas.core.dtypes.base import ExtensionDtype
50from pandas.core.dtypes.cast import find_common_type
51from pandas.core.dtypes.common import (
52 ensure_int64,
53 ensure_object,
54 is_bool,
55 is_bool_dtype,
56 is_float_dtype,
57 is_integer,
58 is_integer_dtype,
59 is_list_like,
60 is_number,
61 is_numeric_dtype,
62 is_object_dtype,
63 is_string_dtype,
64 needs_i8_conversion,
65)
66from pandas.core.dtypes.dtypes import (
67 CategoricalDtype,
68 DatetimeTZDtype,
69)
70from pandas.core.dtypes.generic import (
71 ABCDataFrame,
72 ABCSeries,
73)
74from pandas.core.dtypes.missing import (
75 isna,
76 na_value_for_dtype,
77)
78
79from pandas import (
80 ArrowDtype,
81 Categorical,
82 Index,
83 MultiIndex,
84 Series,
85)
86import pandas.core.algorithms as algos
87from pandas.core.arrays import (
88 ArrowExtensionArray,
89 BaseMaskedArray,
90 ExtensionArray,
91)
92from pandas.core.arrays.string_ import StringDtype
93import pandas.core.common as com
94from pandas.core.construction import (
95 ensure_wrapped_if_datetimelike,
96 extract_array,
97)
98from pandas.core.indexes.api import default_index
99from pandas.core.sorting import (
100 get_group_index,
101 is_int64_overflow_possible,
102)
103
104if TYPE_CHECKING:
105 from pandas import DataFrame
106 from pandas.core import groupby
107 from pandas.core.arrays import DatetimeArray
108 from pandas.core.indexes.frozen import FrozenList
109
110_factorizers = {
111 np.int64: libhashtable.Int64Factorizer,
112 np.longlong: libhashtable.Int64Factorizer,
113 np.int32: libhashtable.Int32Factorizer,
114 np.int16: libhashtable.Int16Factorizer,
115 np.int8: libhashtable.Int8Factorizer,
116 np.uint64: libhashtable.UInt64Factorizer,
117 np.uint32: libhashtable.UInt32Factorizer,
118 np.uint16: libhashtable.UInt16Factorizer,
119 np.uint8: libhashtable.UInt8Factorizer,
120 np.bool_: libhashtable.UInt8Factorizer,
121 np.float64: libhashtable.Float64Factorizer,
122 np.float32: libhashtable.Float32Factorizer,
123 np.complex64: libhashtable.Complex64Factorizer,
124 np.complex128: libhashtable.Complex128Factorizer,
125 np.object_: libhashtable.ObjectFactorizer,
126}
127
128# See https://github.com/pandas-dev/pandas/issues/52451
129if np.intc is not np.int32:
130 if np.dtype(np.intc).itemsize == 4:
131 _factorizers[np.intc] = libhashtable.Int32Factorizer
132 else:
133 _factorizers[np.intc] = libhashtable.Int64Factorizer
134
135if np.uintc is not np.uint32:
136 if np.dtype(np.uintc).itemsize == 4:
137 _factorizers[np.uintc] = libhashtable.UInt32Factorizer
138 else:
139 _factorizers[np.uintc] = libhashtable.UInt64Factorizer
140
141
142_known = (np.ndarray, ExtensionArray, Index, ABCSeries)
143
144
145@set_module("pandas")
146def merge(
147 left: DataFrame | Series,
148 right: DataFrame | Series,
149 how: MergeHow = "inner",
150 on: IndexLabel | AnyArrayLike | None = None,
151 left_on: IndexLabel | AnyArrayLike | None = None,
152 right_on: IndexLabel | AnyArrayLike | None = None,
153 left_index: bool = False,
154 right_index: bool = False,
155 sort: bool = False,
156 suffixes: Suffixes = ("_x", "_y"),
157 copy: bool | lib.NoDefault = lib.no_default,
158 indicator: str | bool = False,
159 validate: str | None = None,
160) -> DataFrame:
161 """
162 Merge DataFrame or named Series objects with a database-style join.
163
164 A named Series object is treated as a DataFrame with a single named column.
165
166 The join is done on columns or indexes. If joining columns on
167 columns, the DataFrame indexes *will be ignored*. Otherwise if joining indexes
168 on indexes or indexes on a column or columns, the index will be passed on.
169 When performing a cross merge, no column specifications to merge on are
170 allowed.
171
172 .. warning::
173
174 If both key columns contain rows where the key is a null value, those
175 rows will be matched against each other. This is different from usual SQL
176 join behaviour and can lead to unexpected results.
177
178 Parameters
179 ----------
180 left : DataFrame or named Series
181 First pandas object to merge.
182 right : DataFrame or named Series
183 Second pandas object to merge.
184 how : {'left', 'right', 'outer', 'inner', 'cross', 'left_anti', 'right_anti},
185 default 'inner'
186 Type of merge to be performed.
187
188 * left: use only keys from left frame, similar to a SQL left outer join;
189 preserve key order.
190 * right: use only keys from right frame, similar to a SQL right outer join;
191 preserve key order.
192 * outer: use union of keys from both frames, similar to a SQL full outer
193 join; sort keys lexicographically.
194 * inner: use intersection of keys from both frames, similar to a SQL inner
195 join; preserve the order of the left keys.
196 * cross: creates the cartesian product from both frames, preserves the order
197 of the left keys.
198 * left_anti: use only keys from left frame that are not in right frame, similar
199 to SQL left anti join; preserve key order.
200 * right_anti: use only keys from right frame that are not in left frame, similar
201 to SQL right anti join; preserve key order.
202 on : Hashable or a sequence of the previous
203 Column or index level names to join on. These must be found in both
204 DataFrames. If `on` is None and not merging on indexes then this defaults
205 to the intersection of the columns in both DataFrames.
206 left_on : Hashable or a sequence of the previous, or array-like
207 Column or index level names to join on in the left DataFrame. Can also
208 be an array or list of arrays of the length of the left DataFrame.
209 These arrays are treated as if they are columns.
210 right_on : Hashable or a sequence of the previous, or array-like
211 Column or index level names to join on in the right DataFrame. Can also
212 be an array or list of arrays of the length of the right DataFrame.
213 These arrays are treated as if they are columns.
214 left_index : bool, default False
215 Use the index from the left DataFrame as the join key(s). If it is a
216 MultiIndex, the number of keys in the other DataFrame (either the index
217 or a number of columns) must match the number of levels.
218 right_index : bool, default False
219 Use the index from the right DataFrame as the join key. Same caveats as
220 left_index.
221 sort : bool, default False
222 Sort the join keys lexicographically in the result DataFrame. If False,
223 the order of the join keys depends on the join type (how keyword).
224 suffixes : list-like, default is ("_x", "_y")
225 A length-2 sequence where each element is optionally a string
226 indicating the suffix to add to overlapping column names in
227 `left` and `right` respectively. Pass a value of `None` instead
228 of a string to indicate that the column name from `left` or
229 `right` should be left as-is, with no suffix. At least one of the
230 values must not be None.
231 copy : bool, default False
232 This keyword is now ignored; changing its value will have no
233 impact on the method.
234
235 .. deprecated:: 3.0.0
236
237 This keyword is ignored and will be removed in pandas 4.0. Since
238 pandas 3.0, this method always returns a new object using a lazy
239 copy mechanism that defers copies until necessary
240 (Copy-on-Write). See the `user guide on Copy-on-Write
241 <https://pandas.pydata.org/docs/dev/user_guide/copy_on_write.html>`__
242 for more details.
243
244 indicator : bool or str, default False
245 If True, adds a column to the output DataFrame called "_merge" with
246 information on the source of each row. The column can be given a different
247 name by providing a string argument. The column will have a Categorical
248 type with the value of "left_only" for observations whose merge key only
249 appears in the left DataFrame, "right_only" for observations
250 whose merge key only appears in the right DataFrame, and "both"
251 if the observation's merge key is found in both DataFrames.
252
253 validate : str, optional
254 If specified, checks if merge is of specified type.
255
256 * "one_to_one" or "1:1": check if merge keys are unique in both
257 left and right datasets.
258 * "one_to_many" or "1:m": check if merge keys are unique in left
259 dataset.
260 * "many_to_one" or "m:1": check if merge keys are unique in right
261 dataset.
262 * "many_to_many" or "m:m": allowed, but does not result in checks.
263
264 Returns
265 -------
266 DataFrame
267 A DataFrame of the two merged objects.
268
269 See Also
270 --------
271 merge_ordered : Merge with optional filling/interpolation.
272 merge_asof : Merge on nearest keys.
273 DataFrame.join : Similar method using indices.
274
275 Examples
276 --------
277 >>> df1 = pd.DataFrame(
278 ... {"lkey": ["foo", "bar", "baz", "foo"], "value": [1, 2, 3, 5]}
279 ... )
280 >>> df2 = pd.DataFrame(
281 ... {"rkey": ["foo", "bar", "baz", "foo"], "value": [5, 6, 7, 8]}
282 ... )
283 >>> df1
284 lkey value
285 0 foo 1
286 1 bar 2
287 2 baz 3
288 3 foo 5
289 >>> df2
290 rkey value
291 0 foo 5
292 1 bar 6
293 2 baz 7
294 3 foo 8
295
296 Merge df1 and df2 on the lkey and rkey columns. The value columns have
297 the default suffixes, _x and _y, appended.
298
299 >>> df1.merge(df2, left_on="lkey", right_on="rkey")
300 lkey value_x rkey value_y
301 0 foo 1 foo 5
302 1 foo 1 foo 8
303 2 bar 2 bar 6
304 3 baz 3 baz 7
305 4 foo 5 foo 5
306 5 foo 5 foo 8
307
308 Merge DataFrames df1 and df2 with specified left and right suffixes
309 appended to any overlapping columns.
310
311 >>> df1.merge(df2, left_on="lkey", right_on="rkey", suffixes=("_left", "_right"))
312 lkey value_left rkey value_right
313 0 foo 1 foo 5
314 1 foo 1 foo 8
315 2 bar 2 bar 6
316 3 baz 3 baz 7
317 4 foo 5 foo 5
318 5 foo 5 foo 8
319
320 Merge DataFrames df1 and df2, but raise an exception if the DataFrames have
321 any overlapping columns.
322
323 >>> df1.merge(df2, left_on="lkey", right_on="rkey", suffixes=(False, False))
324 Traceback (most recent call last):
325 ...
326 ValueError: columns overlap but no suffix specified:
327 Index(['value'], dtype='str')
328
329 >>> df1 = pd.DataFrame({"a": ["foo", "bar"], "b": [1, 2]})
330 >>> df2 = pd.DataFrame({"a": ["foo", "baz"], "c": [3, 4]})
331 >>> df1
332 a b
333 0 foo 1
334 1 bar 2
335 >>> df2
336 a c
337 0 foo 3
338 1 baz 4
339
340 >>> df1.merge(df2, how="inner", on="a")
341 a b c
342 0 foo 1 3
343
344 >>> df1.merge(df2, how="left", on="a")
345 a b c
346 0 foo 1 3.0
347 1 bar 2 NaN
348
349 >>> df1 = pd.DataFrame({"left": ["foo", "bar"]})
350 >>> df2 = pd.DataFrame({"right": [7, 8]})
351 >>> df1
352 left
353 0 foo
354 1 bar
355 >>> df2
356 right
357 0 7
358 1 8
359
360 >>> df1.merge(df2, how="cross")
361 left right
362 0 foo 7
363 1 foo 8
364 2 bar 7
365 3 bar 8
366 """
367 left_df = _validate_operand(left)
368 left._check_copy_deprecation(copy)
369 right_df = _validate_operand(right)
370 if how == "cross":
371 return _cross_merge(
372 left_df,
373 right_df,
374 on=on,
375 left_on=left_on,
376 right_on=right_on,
377 left_index=left_index,
378 right_index=right_index,
379 sort=sort,
380 suffixes=suffixes,
381 indicator=indicator,
382 validate=validate,
383 )
384 else:
385 op = _MergeOperation(
386 left_df,
387 right_df,
388 how=how,
389 on=on,
390 left_on=left_on,
391 right_on=right_on,
392 left_index=left_index,
393 right_index=right_index,
394 sort=sort,
395 suffixes=suffixes,
396 indicator=indicator,
397 validate=validate,
398 )
399 return op.get_result()
400
401
402def _cross_merge(
403 left: DataFrame,
404 right: DataFrame,
405 on: IndexLabel | AnyArrayLike | None = None,
406 left_on: IndexLabel | AnyArrayLike | None = None,
407 right_on: IndexLabel | AnyArrayLike | None = None,
408 left_index: bool = False,
409 right_index: bool = False,
410 sort: bool = False,
411 suffixes: Suffixes = ("_x", "_y"),
412 indicator: str | bool = False,
413 validate: str | None = None,
414) -> DataFrame:
415 """
416 See merge.__doc__ with how='cross'
417 """
418
419 if (
420 left_index
421 or right_index
422 or right_on is not None
423 or left_on is not None
424 or on is not None
425 ):
426 raise MergeError(
427 "Can not pass on, right_on, left_on or set right_index=True or "
428 "left_index=True"
429 )
430
431 cross_col = f"_cross_{uuid.uuid4()}"
432 left = left.assign(**{cross_col: 1})
433 right = right.assign(**{cross_col: 1})
434
435 left_on = right_on = [cross_col]
436
437 res = merge(
438 left,
439 right,
440 how="inner",
441 on=on,
442 left_on=left_on,
443 right_on=right_on,
444 left_index=left_index,
445 right_index=right_index,
446 sort=sort,
447 suffixes=suffixes,
448 indicator=indicator,
449 validate=validate,
450 )
451 del res[cross_col]
452 return res
453
454
455def _groupby_and_merge(
456 by, left: DataFrame | Series, right: DataFrame | Series, merge_pieces
457):
458 """
459 groupby & merge; we are always performing a left-by type operation
460
461 Parameters
462 ----------
463 by: field to group
464 left: DataFrame
465 right: DataFrame
466 merge_pieces: function for merging
467 """
468 pieces = []
469 if not isinstance(by, (list, tuple)):
470 by = [by]
471
472 lby = left.groupby(by, sort=False)
473 rby: groupby.DataFrameGroupBy | groupby.SeriesGroupBy | None = None
474
475 # if we can groupby the rhs
476 # then we can get vastly better perf
477 if all(item in right.columns for item in by):
478 rby = right.groupby(by, sort=False)
479
480 for key, lhs in lby._grouper.get_iterator(lby._selected_obj):
481 if rby is None:
482 rhs = right
483 else:
484 try:
485 rhs = right.take(rby.indices[key])
486 except KeyError:
487 # key doesn't exist in left
488 lcols = lhs.columns.tolist()
489 cols = lcols + [r for r in right.columns if r not in set(lcols)]
490 merged = lhs.reindex(columns=cols)
491 merged.index = range(len(merged))
492 pieces.append(merged)
493 continue
494
495 merged = merge_pieces(lhs, rhs)
496
497 # make sure join keys are in the merged
498 # TODO, should merge_pieces do this?
499 merged[by] = key
500
501 pieces.append(merged)
502
503 # preserve the original order
504 # if we have a missing piece this can be reset
505 from pandas.core.reshape.concat import concat
506
507 result = concat(pieces, ignore_index=True)
508 result = result.reindex(columns=pieces[0].columns)
509 return result, lby
510
511
512@set_module("pandas")
513def merge_ordered(
514 left: DataFrame | Series,
515 right: DataFrame | Series,
516 on: IndexLabel | None = None,
517 left_on: IndexLabel | None = None,
518 right_on: IndexLabel | None = None,
519 left_by=None,
520 right_by=None,
521 fill_method: str | None = None,
522 suffixes: Suffixes = ("_x", "_y"),
523 how: JoinHow = "outer",
524) -> DataFrame:
525 """
526 Perform a merge for ordered data with optional filling/interpolation.
527
528 Designed for ordered data like time series data. Optionally
529 perform group-wise merge (see examples).
530
531 Parameters
532 ----------
533 left : DataFrame or named Series
534 First pandas object to merge.
535 right : DataFrame or named Series
536 Second pandas object to merge.
537 on : Hashable or a sequence of the previous
538 Field names to join on. Must be found in both DataFrames.
539 left_on : Hashable or a sequence of the previous, or array-like
540 Field names to join on in left DataFrame. Can be a vector or list of
541 vectors of the length of the DataFrame to use a particular vector as
542 the join key instead of columns.
543 right_on : Hashable or a sequence of the previous, or array-like
544 Field names to join on in right DataFrame or vector/list of vectors per
545 left_on docs.
546 left_by : column name or list of column names
547 Group left DataFrame by group columns and merge piece by piece with
548 right DataFrame. Must be None if either left or right are a Series.
549 right_by : column name or list of column names
550 Group right DataFrame by group columns and merge piece by piece with
551 left DataFrame. Must be None if either left or right are a Series.
552 fill_method : {'ffill', None}, default None
553 Interpolation method for data.
554 suffixes : list-like, default is ("_x", "_y")
555 A length-2 sequence where each element is optionally a string
556 indicating the suffix to add to overlapping column names in
557 `left` and `right` respectively. Pass a value of `None` instead
558 of a string to indicate that the column name from `left` or
559 `right` should be left as-is, with no suffix. At least one of the
560 values must not be None.
561
562 how : {'left', 'right', 'outer', 'inner'}, default 'outer'
563 * left: use only keys from left frame (SQL: left outer join)
564 * right: use only keys from right frame (SQL: right outer join)
565 * outer: use union of keys from both frames (SQL: full outer join)
566 * inner: use intersection of keys from both frames (SQL: inner join).
567
568 Returns
569 -------
570 DataFrame
571 The merged DataFrame output type will be the same as
572 'left', if it is a subclass of DataFrame.
573
574 See Also
575 --------
576 merge : Merge with a database-style join.
577 merge_asof : Merge on nearest keys.
578
579 Examples
580 --------
581 >>> from pandas import merge_ordered
582 >>> df1 = pd.DataFrame(
583 ... {
584 ... "key": ["a", "c", "e", "a", "c", "e"],
585 ... "lvalue": [1, 2, 3, 1, 2, 3],
586 ... "group": ["a", "a", "a", "b", "b", "b"],
587 ... }
588 ... )
589 >>> df1
590 key lvalue group
591 0 a 1 a
592 1 c 2 a
593 2 e 3 a
594 3 a 1 b
595 4 c 2 b
596 5 e 3 b
597
598 >>> df2 = pd.DataFrame({"key": ["b", "c", "d"], "rvalue": [1, 2, 3]})
599 >>> df2
600 key rvalue
601 0 b 1
602 1 c 2
603 2 d 3
604
605 >>> merge_ordered(df1, df2, fill_method="ffill", left_by="group")
606 key lvalue group rvalue
607 0 a 1 a NaN
608 1 b 1 a 1.0
609 2 c 2 a 2.0
610 3 d 2 a 3.0
611 4 e 3 a 3.0
612 5 a 1 b NaN
613 6 b 1 b 1.0
614 7 c 2 b 2.0
615 8 d 2 b 3.0
616 9 e 3 b 3.0
617 """
618
619 def _merger(x, y) -> DataFrame:
620 # perform the ordered merge operation
621 op = _OrderedMerge(
622 x,
623 y,
624 on=on,
625 left_on=left_on,
626 right_on=right_on,
627 suffixes=suffixes,
628 fill_method=fill_method,
629 how=how,
630 )
631 return op.get_result()
632
633 if left_by is not None and right_by is not None:
634 raise ValueError("Can only group either left or right frames")
635 if left_by is not None:
636 if isinstance(left_by, str):
637 left_by = [left_by]
638 check = set(left_by).difference(left.columns)
639 if len(check) != 0:
640 raise KeyError(f"{check} not found in left columns")
641 result, _ = _groupby_and_merge(left_by, left, right, lambda x, y: _merger(x, y))
642 elif right_by is not None:
643 if isinstance(right_by, str):
644 right_by = [right_by]
645 check = set(right_by).difference(right.columns)
646 if len(check) != 0:
647 raise KeyError(f"{check} not found in right columns")
648 result, _ = _groupby_and_merge(
649 right_by, right, left, lambda x, y: _merger(y, x)
650 )
651 else:
652 result = _merger(left, right)
653 return result
654
655
656@set_module("pandas")
657def merge_asof(
658 left: DataFrame | Series,
659 right: DataFrame | Series,
660 on: IndexLabel | None = None,
661 left_on: IndexLabel | None = None,
662 right_on: IndexLabel | None = None,
663 left_index: bool = False,
664 right_index: bool = False,
665 by=None,
666 left_by=None,
667 right_by=None,
668 suffixes: Suffixes = ("_x", "_y"),
669 tolerance: int | datetime.timedelta | None = None,
670 allow_exact_matches: bool = True,
671 direction: str = "backward",
672) -> DataFrame:
673 """
674 Perform a merge by key distance.
675
676 This is similar to a left-join except that we match on nearest
677 key rather than equal keys. Both DataFrames must be first sorted by
678 the merge key in ascending order before calling this function.
679 Sorting by any additional 'by' grouping columns is not required.
680
681 For each row in the left DataFrame:
682
683 - A "backward" search selects the last row in the right DataFrame whose
684 'on' key is less than or equal to the left's key.
685
686 - A "forward" search selects the first row in the right DataFrame whose
687 'on' key is greater than or equal to the left's key.
688
689 - A "nearest" search selects the row in the right DataFrame whose 'on'
690 key is closest in absolute distance to the left's key.
691
692 Optionally match on equivalent keys with 'by' before searching with 'on'.
693
694 Parameters
695 ----------
696 left : DataFrame or named Series
697 First pandas object to merge.
698 right : DataFrame or named Series
699 Second pandas object to merge.
700 on : label
701 Field name to join on. Must be found in both DataFrames.
702 The data MUST be in ascending order. Furthermore this must be
703 a numeric column, such as datetimelike, integer, or float. ``on``
704 or ``left_on`` / ``right_on`` must be given.
705 left_on : label
706 Field name to join on in left DataFrame. If specified, sort the left
707 DataFrame by this column in ascending order before merging.
708 right_on : label
709 Field name to join on in right DataFrame. If specified, sort the right
710 DataFrame by this column in ascending order before merging.
711 left_index : bool
712 Use the index of the left DataFrame as the join key.
713 right_index : bool
714 Use the index of the right DataFrame as the join key.
715 by : column name or list of column names
716 Match on these columns before performing merge operation. It is not required
717 to sort by these columns.
718 left_by : column name
719 Field names to match on in the left DataFrame.
720 right_by : column name
721 Field names to match on in the right DataFrame.
722 suffixes : 2-length sequence (tuple, list, ...)
723 Suffix to apply to overlapping column names in the left and right
724 side, respectively.
725 tolerance : int or timedelta, optional, default None
726 Select asof tolerance within this range; must be compatible
727 with the merge index.
728 allow_exact_matches : bool, default True
729
730 - If True, allow matching with the same 'on' value
731 (i.e. less-than-or-equal-to / greater-than-or-equal-to)
732 - If False, don't match the same 'on' value
733 (i.e., strictly less-than / strictly greater-than).
734
735 direction : 'backward' (default), 'forward', or 'nearest'
736 Whether to search for prior, subsequent, or closest matches.
737
738 Returns
739 -------
740 DataFrame
741 A DataFrame of the two merged objects, containing all rows from the
742 left DataFrame and the nearest matches from the right DataFrame.
743
744 See Also
745 --------
746 merge : Merge with a database-style join.
747 merge_ordered : Merge with optional filling/interpolation.
748
749 Examples
750 --------
751 >>> left = pd.DataFrame({"a": [1, 5, 10], "left_val": ["a", "b", "c"]})
752 >>> left
753 a left_val
754 0 1 a
755 1 5 b
756 2 10 c
757
758 >>> right = pd.DataFrame({"a": [1, 2, 3, 6, 7], "right_val": [1, 2, 3, 6, 7]})
759 >>> right
760 a right_val
761 0 1 1
762 1 2 2
763 2 3 3
764 3 6 6
765 4 7 7
766
767 >>> pd.merge_asof(left, right, on="a")
768 a left_val right_val
769 0 1 a 1
770 1 5 b 3
771 2 10 c 7
772
773 >>> pd.merge_asof(left, right, on="a", allow_exact_matches=False)
774 a left_val right_val
775 0 1 a NaN
776 1 5 b 3.0
777 2 10 c 7.0
778
779 >>> pd.merge_asof(left, right, on="a", direction="forward")
780 a left_val right_val
781 0 1 a 1.0
782 1 5 b 6.0
783 2 10 c NaN
784
785 >>> pd.merge_asof(left, right, on="a", direction="nearest")
786 a left_val right_val
787 0 1 a 1
788 1 5 b 6
789 2 10 c 7
790
791 We can use indexed DataFrames as well.
792
793 >>> left = pd.DataFrame({"left_val": ["a", "b", "c"]}, index=[1, 5, 10])
794 >>> left
795 left_val
796 1 a
797 5 b
798 10 c
799
800 >>> right = pd.DataFrame({"right_val": [1, 2, 3, 6, 7]}, index=[1, 2, 3, 6, 7])
801 >>> right
802 right_val
803 1 1
804 2 2
805 3 3
806 6 6
807 7 7
808
809 >>> pd.merge_asof(left, right, left_index=True, right_index=True)
810 left_val right_val
811 1 a 1
812 5 b 3
813 10 c 7
814
815 Here is a real-world times-series example
816
817 >>> quotes = pd.DataFrame(
818 ... {
819 ... "time": [
820 ... pd.Timestamp("2016-05-25 13:30:00.023"),
821 ... pd.Timestamp("2016-05-25 13:30:00.023"),
822 ... pd.Timestamp("2016-05-25 13:30:00.030"),
823 ... pd.Timestamp("2016-05-25 13:30:00.041"),
824 ... pd.Timestamp("2016-05-25 13:30:00.048"),
825 ... pd.Timestamp("2016-05-25 13:30:00.049"),
826 ... pd.Timestamp("2016-05-25 13:30:00.072"),
827 ... pd.Timestamp("2016-05-25 13:30:00.075"),
828 ... ],
829 ... "ticker": [
830 ... "GOOG",
831 ... "MSFT",
832 ... "MSFT",
833 ... "MSFT",
834 ... "GOOG",
835 ... "AAPL",
836 ... "GOOG",
837 ... "MSFT",
838 ... ],
839 ... "bid": [720.50, 51.95, 51.97, 51.99, 720.50, 97.99, 720.50, 52.01],
840 ... "ask": [720.93, 51.96, 51.98, 52.00, 720.93, 98.01, 720.88, 52.03],
841 ... }
842 ... )
843 >>> quotes
844 time ticker bid ask
845 0 2016-05-25 13:30:00.023 GOOG 720.50 720.93
846 1 2016-05-25 13:30:00.023 MSFT 51.95 51.96
847 2 2016-05-25 13:30:00.030 MSFT 51.97 51.98
848 3 2016-05-25 13:30:00.041 MSFT 51.99 52.00
849 4 2016-05-25 13:30:00.048 GOOG 720.50 720.93
850 5 2016-05-25 13:30:00.049 AAPL 97.99 98.01
851 6 2016-05-25 13:30:00.072 GOOG 720.50 720.88
852 7 2016-05-25 13:30:00.075 MSFT 52.01 52.03
853
854 >>> trades = pd.DataFrame(
855 ... {
856 ... "time": [
857 ... pd.Timestamp("2016-05-25 13:30:00.023"),
858 ... pd.Timestamp("2016-05-25 13:30:00.038"),
859 ... pd.Timestamp("2016-05-25 13:30:00.048"),
860 ... pd.Timestamp("2016-05-25 13:30:00.048"),
861 ... pd.Timestamp("2016-05-25 13:30:00.048"),
862 ... ],
863 ... "ticker": ["MSFT", "MSFT", "GOOG", "GOOG", "AAPL"],
864 ... "price": [51.95, 51.95, 720.77, 720.92, 98.0],
865 ... "quantity": [75, 155, 100, 100, 100],
866 ... }
867 ... )
868 >>> trades
869 time ticker price quantity
870 0 2016-05-25 13:30:00.023 MSFT 51.95 75
871 1 2016-05-25 13:30:00.038 MSFT 51.95 155
872 2 2016-05-25 13:30:00.048 GOOG 720.77 100
873 3 2016-05-25 13:30:00.048 GOOG 720.92 100
874 4 2016-05-25 13:30:00.048 AAPL 98.00 100
875
876 By default we are taking the asof of the quotes
877
878 >>> pd.merge_asof(trades, quotes, on="time", by="ticker")
879 time ticker price quantity bid ask
880 0 2016-05-25 13:30:00.023 MSFT 51.95 75 51.95 51.96
881 1 2016-05-25 13:30:00.038 MSFT 51.95 155 51.97 51.98
882 2 2016-05-25 13:30:00.048 GOOG 720.77 100 720.50 720.93
883 3 2016-05-25 13:30:00.048 GOOG 720.92 100 720.50 720.93
884 4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
885
886 We only asof within 2ms between the quote time and the trade time
887
888 >>> pd.merge_asof(
889 ... trades, quotes, on="time", by="ticker", tolerance=pd.Timedelta("2ms")
890 ... )
891 time ticker price quantity bid ask
892 0 2016-05-25 13:30:00.023 MSFT 51.95 75 51.95 51.96
893 1 2016-05-25 13:30:00.038 MSFT 51.95 155 NaN NaN
894 2 2016-05-25 13:30:00.048 GOOG 720.77 100 720.50 720.93
895 3 2016-05-25 13:30:00.048 GOOG 720.92 100 720.50 720.93
896 4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
897
898 We only asof within 10ms between the quote time and the trade time
899 and we exclude exact matches on time. However *prior* data will
900 propagate forward
901
902 >>> pd.merge_asof(
903 ... trades,
904 ... quotes,
905 ... on="time",
906 ... by="ticker",
907 ... tolerance=pd.Timedelta("10ms"),
908 ... allow_exact_matches=False,
909 ... )
910 time ticker price quantity bid ask
911 0 2016-05-25 13:30:00.023 MSFT 51.95 75 NaN NaN
912 1 2016-05-25 13:30:00.038 MSFT 51.95 155 51.97 51.98
913 2 2016-05-25 13:30:00.048 GOOG 720.77 100 NaN NaN
914 3 2016-05-25 13:30:00.048 GOOG 720.92 100 NaN NaN
915 4 2016-05-25 13:30:00.048 AAPL 98.00 100 NaN NaN
916 """
917 op = _AsOfMerge(
918 left,
919 right,
920 on=on,
921 left_on=left_on,
922 right_on=right_on,
923 left_index=left_index,
924 right_index=right_index,
925 by=by,
926 left_by=left_by,
927 right_by=right_by,
928 suffixes=suffixes,
929 how="asof",
930 tolerance=tolerance,
931 allow_exact_matches=allow_exact_matches,
932 direction=direction,
933 )
934 return op.get_result()
935
936
937# TODO: transformations??
938class _MergeOperation:
939 """
940 Perform a database (SQL) merge operation between two DataFrame or Series
941 objects using either columns as keys or their row indexes
942 """
943
944 _merge_type = "merge"
945 how: JoinHow | Literal["asof"]
946 on: IndexLabel | None
947 # left_on/right_on may be None when passed, but in validate_specification
948 # get replaced with non-None.
949 left_on: Sequence[Hashable | AnyArrayLike]
950 right_on: Sequence[Hashable | AnyArrayLike]
951 left_index: bool
952 right_index: bool
953 sort: bool
954 suffixes: Suffixes
955 indicator: str | bool
956 validate: str | None
957 join_names: list[Hashable]
958 right_join_keys: list[ArrayLike]
959 left_join_keys: list[ArrayLike]
960
961 def __init__(
962 self,
963 left: DataFrame | Series,
964 right: DataFrame | Series,
965 how: JoinHow | Literal["left_anti", "right_anti", "asof"] = "inner",
966 on: IndexLabel | AnyArrayLike | None = None,
967 left_on: IndexLabel | AnyArrayLike | None = None,
968 right_on: IndexLabel | AnyArrayLike | None = None,
969 left_index: bool = False,
970 right_index: bool = False,
971 sort: bool = True,
972 suffixes: Suffixes = ("_x", "_y"),
973 indicator: str | bool = False,
974 validate: str | None = None,
975 ) -> None:
976 _left = _validate_operand(left)
977 _right = _validate_operand(right)
978 self.left = self.orig_left = _left
979 self.right = self.orig_right = _right
980 self.how, self.anti_join = self._validate_how(how)
981
982 self.on = com.maybe_make_list(on)
983
984 self.suffixes = suffixes
985 self.sort = sort or how == "outer"
986
987 self.left_index = left_index
988 self.right_index = right_index
989
990 self.indicator = indicator
991
992 if not is_bool(left_index):
993 raise ValueError(
994 f"left_index parameter must be of type bool, not {type(left_index)}"
995 )
996 if not is_bool(right_index):
997 raise ValueError(
998 f"right_index parameter must be of type bool, not {type(right_index)}"
999 )
1000
1001 # GH 40993: raise when merging between different levels; enforced in 2.0
1002 if _left.columns.nlevels != _right.columns.nlevels:
1003 msg = (
1004 "Not allowed to merge between different levels. "
1005 f"({_left.columns.nlevels} levels on the left, "
1006 f"{_right.columns.nlevels} on the right)"
1007 )
1008 raise MergeError(msg)
1009
1010 self.left_on, self.right_on = self._validate_left_right_on(left_on, right_on)
1011
1012 (
1013 self.left_join_keys,
1014 self.right_join_keys,
1015 self.join_names,
1016 left_drop,
1017 right_drop,
1018 ) = self._get_merge_keys()
1019
1020 if left_drop:
1021 self.left = self.left._drop_labels_or_levels(left_drop)
1022
1023 if right_drop:
1024 self.right = self.right._drop_labels_or_levels(right_drop)
1025
1026 self._maybe_require_matching_dtypes(self.left_join_keys, self.right_join_keys)
1027 self._validate_tolerance(self.left_join_keys)
1028
1029 # validate the merge keys dtypes. We may need to coerce
1030 # to avoid incompatible dtypes
1031 self._maybe_coerce_merge_keys()
1032
1033 # If argument passed to validate,
1034 # check if columns specified as unique
1035 # are in fact unique.
1036 if validate is not None:
1037 self._validate_validate_kwd(validate)
1038
1039 @final
1040 def _validate_how(
1041 self, how: JoinHow | Literal["left_anti", "right_anti", "asof"]
1042 ) -> tuple[JoinHow | Literal["asof"], bool]:
1043 """
1044 Validate the 'how' parameter and return the actual join type and whether
1045 this is an anti join.
1046 """
1047 # GH 59435: raise when "how" is not a valid Merge type
1048 merge_type = {
1049 "left",
1050 "right",
1051 "inner",
1052 "outer",
1053 "left_anti",
1054 "right_anti",
1055 "cross",
1056 "asof",
1057 }
1058 if how not in merge_type:
1059 raise ValueError(
1060 f"'{how}' is not a valid Merge type: "
1061 f"left, right, inner, outer, left_anti, right_anti, cross, asof"
1062 )
1063 anti_join = False
1064 if how in {"left_anti", "right_anti"}:
1065 how = how.split("_")[0] # type: ignore[assignment]
1066 anti_join = True
1067 how = cast(JoinHow | Literal["asof"], how)
1068 return how, anti_join
1069
1070 def _maybe_require_matching_dtypes(
1071 self, left_join_keys: list[ArrayLike], right_join_keys: list[ArrayLike]
1072 ) -> None:
1073 # Overridden by AsOfMerge
1074 pass
1075
1076 def _validate_tolerance(self, left_join_keys: list[ArrayLike]) -> None:
1077 # Overridden by AsOfMerge
1078 pass
1079
1080 @final
1081 def _reindex_and_concat(
1082 self,
1083 join_index: Index,
1084 left_indexer: npt.NDArray[np.intp] | None,
1085 right_indexer: npt.NDArray[np.intp] | None,
1086 ) -> DataFrame:
1087 """
1088 reindex along index and concat along columns.
1089 """
1090 # Take views so we do not alter the originals
1091 left = self.left[:]
1092 right = self.right[:]
1093
1094 llabels, rlabels = _items_overlap_with_suffix(
1095 self.left._info_axis, self.right._info_axis, self.suffixes
1096 )
1097
1098 if left_indexer is not None and not is_range_indexer(left_indexer, len(left)):
1099 # Pinning the index here (and in the right code just below) is not
1100 # necessary, but makes the `.take` more performant if we have e.g.
1101 # a MultiIndex for left.index.
1102 lmgr = left._mgr.reindex_indexer(
1103 join_index,
1104 left_indexer,
1105 axis=1,
1106 only_slice=True,
1107 allow_dups=True,
1108 use_na_proxy=True,
1109 )
1110 left = left._constructor_from_mgr(lmgr, axes=lmgr.axes)
1111 left.index = join_index
1112
1113 if right_indexer is not None and not is_range_indexer(
1114 right_indexer, len(right)
1115 ):
1116 rmgr = right._mgr.reindex_indexer(
1117 join_index,
1118 right_indexer,
1119 axis=1,
1120 only_slice=True,
1121 allow_dups=True,
1122 use_na_proxy=True,
1123 )
1124 right = right._constructor_from_mgr(rmgr, axes=rmgr.axes)
1125 right.index = join_index
1126
1127 from pandas import concat
1128
1129 left.columns = llabels
1130 right.columns = rlabels
1131 result = concat([left, right], axis=1)
1132 return result
1133
1134 def get_result(self) -> DataFrame:
1135 """
1136 Execute the merge.
1137 """
1138 if self.indicator:
1139 self.left, self.right = self._indicator_pre_merge(self.left, self.right)
1140
1141 join_index, left_indexer, right_indexer = self._get_join_info()
1142
1143 result = self._reindex_and_concat(join_index, left_indexer, right_indexer)
1144
1145 if self.indicator:
1146 result = self._indicator_post_merge(result)
1147
1148 self._maybe_add_join_keys(result, left_indexer, right_indexer)
1149
1150 self._maybe_restore_index_levels(result)
1151
1152 return result.__finalize__(
1153 types.SimpleNamespace(
1154 input_objs=[self.left, self.right], left=self.left, right=self.right
1155 ),
1156 method="merge",
1157 )
1158
1159 @final
1160 @cache_readonly
1161 def _indicator_name(self) -> str | None:
1162 if isinstance(self.indicator, str):
1163 return self.indicator
1164 elif isinstance(self.indicator, bool):
1165 return "_merge" if self.indicator else None
1166 else:
1167 raise ValueError(
1168 "indicator option can only accept boolean or string arguments"
1169 )
1170
1171 @final
1172 def _indicator_pre_merge(
1173 self, left: DataFrame, right: DataFrame
1174 ) -> tuple[DataFrame, DataFrame]:
1175 """
1176 Add one indicator column to each of the left and right inputs.
1177
1178 These columns are used to produce another column in the output of the
1179 merge, indicating for each row of the output whether it was produced
1180 using the left, right or both inputs.
1181 """
1182 columns = left.columns.union(right.columns)
1183
1184 for i in ["_left_indicator", "_right_indicator"]:
1185 if i in columns:
1186 raise ValueError(
1187 "Cannot use `indicator=True` option when "
1188 f"data contains a column named {i}"
1189 )
1190 if self._indicator_name in columns:
1191 raise ValueError(
1192 "Cannot use name of an existing column for indicator column"
1193 )
1194
1195 left = left.copy(deep=False)
1196 right = right.copy(deep=False)
1197
1198 left["_left_indicator"] = 1
1199 left["_left_indicator"] = left["_left_indicator"].astype("int8")
1200
1201 right["_right_indicator"] = 2
1202 right["_right_indicator"] = right["_right_indicator"].astype("int8")
1203
1204 return left, right
1205
1206 @final
1207 def _indicator_post_merge(self, result: DataFrame) -> DataFrame:
1208 """
1209 Add an indicator column to the merge result.
1210
1211 This column indicates for each row of the output whether it was produced using
1212 the left, right or both inputs.
1213 """
1214 result["_left_indicator"] = result["_left_indicator"].fillna(0)
1215 result["_right_indicator"] = result["_right_indicator"].fillna(0)
1216
1217 result[self._indicator_name] = Categorical(
1218 (result["_left_indicator"] + result["_right_indicator"]),
1219 categories=[1, 2, 3],
1220 )
1221 result[self._indicator_name] = result[
1222 self._indicator_name
1223 ].cat.rename_categories(["left_only", "right_only", "both"])
1224
1225 result = result.drop(labels=["_left_indicator", "_right_indicator"], axis=1)
1226 return result
1227
1228 @final
1229 def _maybe_restore_index_levels(self, result: DataFrame) -> None:
1230 """
1231 Restore index levels specified as `on` parameters
1232
1233 Here we check for cases where `self.left_on` and `self.right_on` pairs
1234 each reference an index level in their respective DataFrames. The
1235 joined columns corresponding to these pairs are then restored to the
1236 index of `result`.
1237
1238 **Note:** This method has side effects. It modifies `result` in-place
1239
1240 Parameters
1241 ----------
1242 result: DataFrame
1243 merge result
1244
1245 Returns
1246 -------
1247 None
1248 """
1249 names_to_restore = []
1250 for name, left_key, right_key in zip(
1251 self.join_names, self.left_on, self.right_on, strict=True
1252 ):
1253 if (
1254 # Argument 1 to "_is_level_reference" of "NDFrame" has incompatible
1255 # type "Union[Hashable, ExtensionArray, Index, Series]"; expected
1256 # "Hashable"
1257 self.orig_left._is_level_reference(left_key) # type: ignore[arg-type]
1258 # Argument 1 to "_is_level_reference" of "NDFrame" has incompatible
1259 # type "Union[Hashable, ExtensionArray, Index, Series]"; expected
1260 # "Hashable"
1261 and self.orig_right._is_level_reference(
1262 right_key # type: ignore[arg-type]
1263 )
1264 and left_key == right_key
1265 and name not in result.index.names
1266 ):
1267 names_to_restore.append(name)
1268
1269 if names_to_restore:
1270 result.set_index(names_to_restore, inplace=True)
1271
1272 @final
1273 def _maybe_add_join_keys(
1274 self,
1275 result: DataFrame,
1276 left_indexer: npt.NDArray[np.intp] | None,
1277 right_indexer: npt.NDArray[np.intp] | None,
1278 ) -> None:
1279 left_has_missing = None
1280 right_has_missing = None
1281
1282 assert all(isinstance(x, _known) for x in self.left_join_keys)
1283
1284 keys = zip(self.join_names, self.left_on, self.right_on, strict=True)
1285 for i, (name, lname, rname) in enumerate(keys):
1286 if not _should_fill(lname, rname):
1287 continue
1288
1289 take_left, take_right = None, None
1290
1291 if name in result:
1292 if left_indexer is not None or right_indexer is not None:
1293 if name in self.left:
1294 if left_has_missing is None:
1295 left_has_missing = (
1296 False
1297 if left_indexer is None
1298 else (left_indexer == -1).any()
1299 )
1300
1301 if left_has_missing:
1302 take_right = self.right_join_keys[i]
1303
1304 if result[name].dtype != self.left[name].dtype:
1305 take_left = self.left[name]._values
1306
1307 elif name in self.right:
1308 if right_has_missing is None:
1309 right_has_missing = (
1310 False
1311 if right_indexer is None
1312 else (right_indexer == -1).any()
1313 )
1314
1315 if right_has_missing:
1316 take_left = self.left_join_keys[i]
1317
1318 if result[name].dtype != self.right[name].dtype:
1319 take_right = self.right[name]._values
1320
1321 else:
1322 take_left = self.left_join_keys[i]
1323 take_right = self.right_join_keys[i]
1324
1325 if take_left is not None or take_right is not None:
1326 if take_left is None:
1327 lvals = result[name]._values
1328 elif left_indexer is None:
1329 lvals = take_left
1330 else:
1331 # TODO: can we pin down take_left's type earlier?
1332 take_left = extract_array(take_left, extract_numpy=True)
1333 lfill = na_value_for_dtype(take_left.dtype)
1334 lvals = algos.take_nd(take_left, left_indexer, fill_value=lfill)
1335
1336 if take_right is None:
1337 rvals = result[name]._values
1338 elif right_indexer is None:
1339 rvals = take_right
1340 else:
1341 # TODO: can we pin down take_right's type earlier?
1342 taker = extract_array(take_right, extract_numpy=True)
1343 rfill = na_value_for_dtype(taker.dtype)
1344 rvals = algos.take_nd(taker, right_indexer, fill_value=rfill)
1345
1346 # if we have an all missing left_indexer
1347 # make sure to just use the right values or vice-versa
1348 if left_indexer is not None and (left_indexer == -1).all():
1349 key_col = Index(rvals, dtype=rvals.dtype, copy=False)
1350 result_dtype = rvals.dtype
1351 elif right_indexer is not None and (right_indexer == -1).all():
1352 key_col = Index(lvals, dtype=lvals.dtype, copy=False)
1353 result_dtype = lvals.dtype
1354 else:
1355 key_col = Index(lvals, dtype=lvals.dtype, copy=False)
1356 if left_indexer is not None:
1357 mask_left = left_indexer == -1
1358 key_col = key_col.where(~mask_left, rvals)
1359 result_dtype = find_common_type([lvals.dtype, rvals.dtype])
1360 if (
1361 lvals.dtype.kind == "M"
1362 and rvals.dtype.kind == "M"
1363 and result_dtype.kind == "O"
1364 ):
1365 # TODO(non-nano) Workaround for common_type not dealing
1366 # with different resolutions
1367 result_dtype = key_col.dtype
1368
1369 if result._is_label_reference(name):
1370 result[name] = result._constructor_sliced(
1371 key_col, dtype=result_dtype, index=result.index
1372 )
1373 elif result._is_level_reference(name):
1374 if isinstance(result.index, MultiIndex):
1375 key_col.name = name
1376 idx_list = [
1377 (
1378 result.index.get_level_values(level_name)
1379 if level_name != name
1380 else key_col
1381 )
1382 for level_name in result.index.names
1383 ]
1384
1385 result.set_index(idx_list, inplace=True)
1386 else:
1387 result.index = Index(key_col, name=name)
1388 else:
1389 result.insert(i, name or f"key_{i}", key_col)
1390
1391 def _get_join_indexers(
1392 self,
1393 ) -> tuple[npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
1394 """return the join indexers"""
1395 # make mypy happy
1396 assert self.how != "asof"
1397 return get_join_indexers(
1398 self.left_join_keys, self.right_join_keys, sort=self.sort, how=self.how
1399 )
1400
1401 @final
1402 def _get_join_info(
1403 self,
1404 ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
1405 left_ax = self.left.index
1406 right_ax = self.right.index
1407
1408 if self.left_index and self.right_index and self.how != "asof":
1409 join_index, left_indexer, right_indexer = left_ax.join(
1410 right_ax, how=self.how, return_indexers=True, sort=self.sort
1411 )
1412
1413 elif self.right_index and self.how == "left":
1414 join_index, left_indexer, right_indexer = _left_join_on_index(
1415 left_ax, right_ax, self.left_join_keys, sort=self.sort
1416 )
1417
1418 elif self.left_index and self.how == "right":
1419 join_index, right_indexer, left_indexer = _left_join_on_index(
1420 right_ax, left_ax, self.right_join_keys, sort=self.sort
1421 )
1422 else:
1423 (left_indexer, right_indexer) = self._get_join_indexers()
1424
1425 if self.right_index:
1426 if len(self.left) > 0:
1427 join_index = self._create_join_index(
1428 left_ax,
1429 right_ax,
1430 left_indexer,
1431 how="right",
1432 )
1433 elif right_indexer is None:
1434 join_index = right_ax.copy()
1435 else:
1436 join_index = right_ax.take(right_indexer)
1437 elif self.left_index:
1438 if self.how == "asof":
1439 # GH#33463 asof should always behave like a left merge
1440 join_index = self._create_join_index(
1441 left_ax,
1442 right_ax,
1443 left_indexer,
1444 how="left",
1445 )
1446
1447 elif len(self.right) > 0:
1448 join_index = self._create_join_index(
1449 right_ax,
1450 left_ax,
1451 right_indexer,
1452 how="left",
1453 )
1454 elif left_indexer is None:
1455 join_index = left_ax.copy()
1456 else:
1457 join_index = left_ax.take(left_indexer)
1458 else:
1459 n = len(left_ax) if left_indexer is None else len(left_indexer)
1460 join_index = default_index(n)
1461
1462 if self.anti_join:
1463 join_index, left_indexer, right_indexer = self._handle_anti_join(
1464 join_index, left_indexer, right_indexer
1465 )
1466
1467 return join_index, left_indexer, right_indexer
1468
1469 @final
1470 def _create_join_index(
1471 self,
1472 index: Index,
1473 other_index: Index,
1474 indexer: npt.NDArray[np.intp] | None,
1475 how: JoinHow = "left",
1476 ) -> Index:
1477 """
1478 Create a join index by rearranging one index to match another
1479
1480 Parameters
1481 ----------
1482 index : Index
1483 index being rearranged
1484 other_index : Index
1485 used to supply values not found in index
1486 indexer : np.ndarray[np.intp] or None
1487 how to rearrange index
1488 how : str
1489 Replacement is only necessary if indexer based on other_index.
1490
1491 Returns
1492 -------
1493 Index
1494 """
1495 if self.how in (how, "outer") and not isinstance(other_index, MultiIndex):
1496 # if final index requires values in other_index but not target
1497 # index, indexer may hold missing (-1) values, causing Index.take
1498 # to take the final value in target index. So, we set the last
1499 # element to be the desired fill value. We do not use allow_fill
1500 # and fill_value because it throws a ValueError on integer indices
1501 mask = indexer == -1
1502 if np.any(mask):
1503 fill_value = na_value_for_dtype(index.dtype, compat=False)
1504 if not index._can_hold_na:
1505 new_index = Index([fill_value])
1506 else:
1507 new_index = Index([fill_value], dtype=index.dtype)
1508 index = index.append(new_index)
1509 if indexer is None:
1510 return index.copy()
1511 return index.take(indexer)
1512
1513 @final
1514 def _handle_anti_join(
1515 self,
1516 join_index: Index,
1517 left_indexer: npt.NDArray[np.intp] | None,
1518 right_indexer: npt.NDArray[np.intp] | None,
1519 ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
1520 """
1521 Handle anti join by returning the correct join index and indexers
1522
1523 Parameters
1524 ----------
1525 join_index : Index
1526 join index
1527 left_indexer : np.ndarray[np.intp] or None
1528 left indexer
1529 right_indexer : np.ndarray[np.intp] or None
1530 right indexer
1531
1532 Returns
1533 -------
1534 Index, np.ndarray[np.intp] or None, np.ndarray[np.intp] or None
1535 """
1536 # Make sure indexers are not None
1537 if left_indexer is None:
1538 left_indexer = np.arange(len(self.left))
1539 if right_indexer is None:
1540 right_indexer = np.arange(len(self.right))
1541
1542 assert self.how in {"left", "right"}
1543 if self.how == "left":
1544 # Filter to rows where left keys are not in right keys
1545 filt = right_indexer == -1
1546 else:
1547 # Filter to rows where right keys are not in left keys
1548 filt = left_indexer == -1
1549 join_index = join_index[filt]
1550 left_indexer = left_indexer[filt]
1551 right_indexer = right_indexer[filt]
1552
1553 return join_index, left_indexer, right_indexer
1554
1555 @final
1556 def _get_merge_keys(
1557 self,
1558 ) -> tuple[
1559 list[ArrayLike],
1560 list[ArrayLike],
1561 list[Hashable],
1562 list[Hashable],
1563 list[Hashable],
1564 ]:
1565 """
1566 Returns
1567 -------
1568 left_keys, right_keys, join_names, left_drop, right_drop
1569 """
1570 left_keys: list[ArrayLike] = []
1571 right_keys: list[ArrayLike] = []
1572 join_names: list[Hashable] = []
1573 right_drop: list[Hashable] = []
1574 left_drop: list[Hashable] = []
1575
1576 left, right = self.left, self.right
1577
1578 is_lkey = lambda x: isinstance(x, _known) and len(x) == len(left)
1579 is_rkey = lambda x: isinstance(x, _known) and len(x) == len(right)
1580
1581 # Note that pd.merge_asof() has separate 'on' and 'by' parameters. A
1582 # user could, for example, request 'left_index' and 'left_by'. In a
1583 # regular pd.merge(), users cannot specify both 'left_index' and
1584 # 'left_on'. (Instead, users have a MultiIndex). That means the
1585 # self.left_on in this function is always empty in a pd.merge(), but
1586 # a pd.merge_asof(left_index=True, left_by=...) will result in a
1587 # self.left_on array with a None in the middle of it. This requires
1588 # a work-around as designated in the code below.
1589 # See _validate_left_right_on() for where this happens.
1590
1591 # ugh, spaghetti re #733
1592 if _any(self.left_on) and _any(self.right_on):
1593 for lk, rk in zip(self.left_on, self.right_on, strict=True):
1594 lk = extract_array(lk, extract_numpy=True)
1595 rk = extract_array(rk, extract_numpy=True)
1596 if is_lkey(lk):
1597 lk = cast(ArrayLike, lk)
1598 left_keys.append(lk)
1599 if is_rkey(rk):
1600 rk = cast(ArrayLike, rk)
1601 right_keys.append(rk)
1602 join_names.append(None) # what to do?
1603 else:
1604 # Then we're either Hashable or a wrong-length arraylike,
1605 # the latter of which will raise
1606 rk = cast(Hashable, rk)
1607 if rk is not None:
1608 right_keys.append(right._get_label_or_level_values(rk))
1609 join_names.append(rk)
1610 else:
1611 # work-around for merge_asof(right_index=True)
1612 right_keys.append(right.index._values)
1613 join_names.append(right.index.name)
1614 else:
1615 if not is_rkey(rk):
1616 # Then we're either Hashable or a wrong-length arraylike,
1617 # the latter of which will raise
1618 rk = cast(Hashable, rk)
1619 if rk is not None:
1620 right_keys.append(right._get_label_or_level_values(rk))
1621 else:
1622 # work-around for merge_asof(right_index=True)
1623 right_keys.append(right.index._values)
1624 if lk is not None and lk == rk: # FIXME: what about other NAs?
1625 right_drop.append(rk)
1626 else:
1627 rk = cast(ArrayLike, rk)
1628 right_keys.append(rk)
1629 if lk is not None:
1630 # Then we're either Hashable or a wrong-length arraylike,
1631 # the latter of which will raise
1632 lk = cast(Hashable, lk)
1633 left_keys.append(left._get_label_or_level_values(lk))
1634 join_names.append(lk)
1635 else:
1636 # work-around for merge_asof(left_index=True)
1637 left_keys.append(left.index._values)
1638 join_names.append(left.index.name)
1639 elif _any(self.left_on):
1640 for k in self.left_on:
1641 if is_lkey(k):
1642 k = extract_array(k, extract_numpy=True)
1643 k = cast(ArrayLike, k)
1644 left_keys.append(k)
1645 join_names.append(None)
1646 else:
1647 # Then we're either Hashable or a wrong-length arraylike,
1648 # the latter of which will raise
1649 k = cast(Hashable, k)
1650 left_keys.append(left._get_label_or_level_values(k))
1651 join_names.append(k)
1652 if isinstance(self.right.index, MultiIndex):
1653 right_keys = [
1654 lev._values.take(lev_codes)
1655 for lev, lev_codes in zip(
1656 self.right.index.levels, self.right.index.codes, strict=True
1657 )
1658 ]
1659 else:
1660 right_keys = [self.right.index._values]
1661 elif _any(self.right_on):
1662 for k in self.right_on:
1663 k = extract_array(k, extract_numpy=True)
1664 if is_rkey(k):
1665 k = cast(ArrayLike, k)
1666 right_keys.append(k)
1667 join_names.append(None)
1668 else:
1669 # Then we're either Hashable or a wrong-length arraylike,
1670 # the latter of which will raise
1671 k = cast(Hashable, k)
1672 right_keys.append(right._get_label_or_level_values(k))
1673 join_names.append(k)
1674 if isinstance(self.left.index, MultiIndex):
1675 left_keys = [
1676 lev._values.take(lev_codes)
1677 for lev, lev_codes in zip(
1678 self.left.index.levels, self.left.index.codes, strict=True
1679 )
1680 ]
1681 else:
1682 left_keys = [self.left.index._values]
1683
1684 return left_keys, right_keys, join_names, left_drop, right_drop
1685
1686 @final
1687 def _maybe_coerce_merge_keys(self) -> None:
1688 # we have valid merges but we may have to further
1689 # coerce these if they are originally incompatible types
1690 #
1691 # for example if these are categorical, but are not dtype_equal
1692 # or if we have object and integer dtypes
1693
1694 for lk, rk, name in zip(
1695 self.left_join_keys, self.right_join_keys, self.join_names, strict=True
1696 ):
1697 if (len(lk) and not len(rk)) or (not len(lk) and len(rk)):
1698 continue
1699
1700 lk = extract_array(lk, extract_numpy=True)
1701 rk = extract_array(rk, extract_numpy=True)
1702
1703 lk_is_cat = isinstance(lk.dtype, CategoricalDtype)
1704 rk_is_cat = isinstance(rk.dtype, CategoricalDtype)
1705 lk_is_object_or_string = is_object_dtype(lk.dtype) or is_string_dtype(
1706 lk.dtype
1707 )
1708 rk_is_object_or_string = is_object_dtype(rk.dtype) or is_string_dtype(
1709 rk.dtype
1710 )
1711
1712 # if either left or right is a categorical
1713 # then the must match exactly in categories & ordered
1714 if lk_is_cat and rk_is_cat:
1715 lk = cast(Categorical, lk)
1716 rk = cast(Categorical, rk)
1717 if lk._categories_match_up_to_permutation(rk):
1718 continue
1719
1720 elif lk_is_cat or rk_is_cat:
1721 pass
1722
1723 elif lk.dtype == rk.dtype:
1724 continue
1725
1726 msg = (
1727 f"You are trying to merge on {lk.dtype} and {rk.dtype} columns "
1728 f"for key '{name}'. If you wish to proceed you should use pd.concat"
1729 )
1730
1731 # if we are numeric, then allow differing
1732 # kinds to proceed, eg. int64 and int8, int and float
1733 # further if we are object, but we infer to
1734 # the same, then proceed
1735 if is_numeric_dtype(lk.dtype) and is_numeric_dtype(rk.dtype):
1736 if lk.dtype.kind == rk.dtype.kind:
1737 continue
1738
1739 if isinstance(lk.dtype, ExtensionDtype) and not isinstance(
1740 rk.dtype, ExtensionDtype
1741 ):
1742 ct = find_common_type([lk.dtype, rk.dtype])
1743 if isinstance(ct, ExtensionDtype):
1744 com_cls = ct.construct_array_type()
1745 rk = com_cls._from_sequence(rk, dtype=ct, copy=False)
1746 else:
1747 rk = rk.astype(ct)
1748 elif isinstance(rk.dtype, ExtensionDtype):
1749 ct = find_common_type([lk.dtype, rk.dtype])
1750 if isinstance(ct, ExtensionDtype):
1751 com_cls = ct.construct_array_type()
1752 lk = com_cls._from_sequence(lk, dtype=ct, copy=False)
1753 else:
1754 lk = lk.astype(ct)
1755
1756 # check whether ints and floats
1757 if is_integer_dtype(rk.dtype) and is_float_dtype(lk.dtype):
1758 # GH 47391 numpy > 1.24 will raise a RuntimeError for nan -> int
1759 with np.errstate(invalid="ignore"):
1760 # error: Argument 1 to "astype" of "ndarray" has incompatible
1761 # type "Union[ExtensionDtype, Any, dtype[Any]]"; expected
1762 # "Union[dtype[Any], Type[Any], _SupportsDType[dtype[Any]]]"
1763 casted = lk.astype(rk.dtype) # type: ignore[arg-type]
1764
1765 mask = ~np.isnan(lk)
1766 match = lk == casted
1767 # error: Item "ExtensionArray" of
1768 # "ExtensionArray | Any" has no attribute "all"
1769 if not match[mask].all(): # type: ignore[union-attr]
1770 warnings.warn(
1771 "You are merging on int and float "
1772 "columns where the float values "
1773 "are not equal to their int representation.",
1774 UserWarning,
1775 stacklevel=find_stack_level(),
1776 )
1777 continue
1778
1779 if is_float_dtype(rk.dtype) and is_integer_dtype(lk.dtype):
1780 # GH 47391 numpy > 1.24 will raise a RuntimeError for nan -> int
1781 with np.errstate(invalid="ignore"):
1782 # error: Argument 1 to "astype" of "ndarray" has incompatible
1783 # type "Union[ExtensionDtype, Any, dtype[Any]]"; expected
1784 # "Union[dtype[Any], Type[Any], _SupportsDType[dtype[Any]]]"
1785 casted = rk.astype(lk.dtype) # type: ignore[arg-type]
1786
1787 mask = ~np.isnan(rk)
1788 match = rk == casted
1789 # error: Item "ExtensionArray" of
1790 # "ExtensionArray | Any" has no attribute "all"
1791 if not match[mask].all(): # type: ignore[union-attr]
1792 warnings.warn(
1793 "You are merging on int and float "
1794 "columns where the float values "
1795 "are not equal to their int representation.",
1796 UserWarning,
1797 stacklevel=find_stack_level(),
1798 )
1799 continue
1800
1801 # let's infer and see if we are ok
1802 if lib.infer_dtype(lk, skipna=False) == lib.infer_dtype(
1803 rk, skipna=False
1804 ):
1805 continue
1806
1807 # Check if we are trying to merge on obviously
1808 # incompatible dtypes GH 9780, GH 15800
1809
1810 # bool values are coerced to object
1811 elif (lk_is_object_or_string and is_bool_dtype(rk.dtype)) or (
1812 is_bool_dtype(lk.dtype) and rk_is_object_or_string
1813 ):
1814 pass
1815
1816 # object values are allowed to be merged
1817 elif (lk_is_object_or_string and is_numeric_dtype(rk.dtype)) or (
1818 is_numeric_dtype(lk.dtype) and rk_is_object_or_string
1819 ):
1820 inferred_left = lib.infer_dtype(lk, skipna=False)
1821 inferred_right = lib.infer_dtype(rk, skipna=False)
1822 bool_types = ["integer", "mixed-integer", "boolean", "empty"]
1823 string_types = ["string", "unicode", "mixed", "bytes", "empty"]
1824
1825 # inferred bool
1826 if inferred_left in bool_types and inferred_right in bool_types:
1827 pass
1828
1829 # unless we are merging non-string-like with string-like
1830 elif (
1831 inferred_left in string_types and inferred_right not in string_types
1832 ) or (
1833 inferred_right in string_types and inferred_left not in string_types
1834 ):
1835 raise ValueError(msg)
1836
1837 # datetimelikes must match exactly
1838 elif needs_i8_conversion(lk.dtype) and not needs_i8_conversion(rk.dtype):
1839 raise ValueError(msg)
1840 elif not needs_i8_conversion(lk.dtype) and needs_i8_conversion(rk.dtype):
1841 raise ValueError(msg)
1842 elif isinstance(lk.dtype, DatetimeTZDtype) and not isinstance(
1843 rk.dtype, DatetimeTZDtype
1844 ):
1845 raise ValueError(msg)
1846 elif not isinstance(lk.dtype, DatetimeTZDtype) and isinstance(
1847 rk.dtype, DatetimeTZDtype
1848 ):
1849 raise ValueError(msg)
1850 elif (
1851 isinstance(lk.dtype, DatetimeTZDtype)
1852 and isinstance(rk.dtype, DatetimeTZDtype)
1853 ) or (lk.dtype.kind == "M" and rk.dtype.kind == "M"):
1854 # allows datetime with different resolutions
1855 continue
1856 # datetime and timedelta not allowed
1857 elif lk.dtype.kind == "M" and rk.dtype.kind == "m":
1858 raise ValueError(msg)
1859 elif lk.dtype.kind == "m" and rk.dtype.kind == "M":
1860 raise ValueError(msg)
1861
1862 elif is_object_dtype(lk.dtype) and is_object_dtype(rk.dtype):
1863 continue
1864
1865 # Houston, we have a problem!
1866 # let's coerce to object if the dtypes aren't
1867 # categorical, otherwise coerce to the category
1868 # dtype. If we coerced categories to object,
1869 # then we would lose type information on some
1870 # columns, and end up trying to merge
1871 # incompatible dtypes. See GH 16900.
1872 if name in self.left.columns:
1873 typ = cast(Categorical, lk).categories.dtype if lk_is_cat else object
1874 self.left = self.left.copy(deep=False)
1875 self.left[name] = self.left[name].astype(typ)
1876 if name in self.right.columns:
1877 typ = cast(Categorical, rk).categories.dtype if rk_is_cat else object
1878 self.right = self.right.copy(deep=False)
1879 self.right[name] = self.right[name].astype(typ)
1880
1881 def _validate_left_right_on(self, left_on, right_on):
1882 left_on = com.maybe_make_list(left_on)
1883 right_on = com.maybe_make_list(right_on)
1884
1885 # Hm, any way to make this logic less complicated??
1886 if self.on is None and left_on is None and right_on is None:
1887 if self.left_index and self.right_index:
1888 left_on, right_on = (), ()
1889 elif self.left_index:
1890 raise MergeError("Must pass right_on or right_index=True")
1891 elif self.right_index:
1892 raise MergeError("Must pass left_on or left_index=True")
1893 else:
1894 # use the common columns
1895 left_cols = self.left.columns
1896 right_cols = self.right.columns
1897 common_cols = left_cols.intersection(right_cols)
1898 if len(common_cols) == 0:
1899 raise MergeError(
1900 "No common columns to perform merge on. "
1901 f"Merge options: left_on={left_on}, "
1902 f"right_on={right_on}, "
1903 f"left_index={self.left_index}, "
1904 f"right_index={self.right_index}"
1905 )
1906 if (
1907 not left_cols.join(common_cols, how="inner").is_unique
1908 or not right_cols.join(common_cols, how="inner").is_unique
1909 ):
1910 raise MergeError(f"Data columns not unique: {common_cols!r}")
1911 left_on = right_on = common_cols
1912 elif self.on is not None:
1913 if left_on is not None or right_on is not None:
1914 raise MergeError(
1915 'Can only pass argument "on" OR "left_on" '
1916 'and "right_on", not a combination of both.'
1917 )
1918 if self.left_index or self.right_index:
1919 raise MergeError(
1920 'Can only pass argument "on" OR "left_index" '
1921 'and "right_index", not a combination of both.'
1922 )
1923 left_on = right_on = self.on
1924 elif left_on is not None:
1925 if self.left_index:
1926 raise MergeError(
1927 'Can only pass argument "left_on" OR "left_index" not both.'
1928 )
1929 if not self.right_index and right_on is None:
1930 raise MergeError('Must pass "right_on" OR "right_index".')
1931 if self.right_index and right_on is not None:
1932 raise MergeError(
1933 'Can only pass argument "right_on" OR "right_index" not both.'
1934 )
1935 n = len(left_on)
1936 if self.right_index:
1937 if len(left_on) != self.right.index.nlevels:
1938 raise ValueError(
1939 "len(left_on) must equal the number "
1940 'of levels in the index of "right"'
1941 )
1942 right_on = [None] * n
1943 elif right_on is not None:
1944 if self.right_index:
1945 raise MergeError(
1946 'Can only pass argument "right_on" OR "right_index" not both.'
1947 )
1948 if not self.left_index and left_on is None:
1949 raise MergeError('Must pass "left_on" OR "left_index".')
1950 n = len(right_on)
1951 if self.left_index:
1952 if len(right_on) != self.left.index.nlevels:
1953 raise ValueError(
1954 "len(right_on) must equal the number "
1955 'of levels in the index of "left"'
1956 )
1957 left_on = [None] * n
1958 if len(right_on) != len(left_on):
1959 raise ValueError("len(right_on) must equal len(left_on)")
1960
1961 return left_on, right_on
1962
1963 @final
1964 def _validate_validate_kwd(self, validate: str) -> None:
1965 # Check uniqueness of each
1966 if self.left_index:
1967 left_join_index = self.orig_left.index
1968 left_unique = left_join_index.is_unique
1969 else:
1970 left_join_index = MultiIndex.from_arrays(self.left_join_keys)
1971 left_unique = left_join_index.is_unique
1972
1973 if self.right_index:
1974 right_join_index = self.orig_right.index
1975 right_unique = self.orig_right.index.is_unique
1976 else:
1977 right_join_index = MultiIndex.from_arrays(self.right_join_keys)
1978 right_unique = right_join_index.is_unique
1979
1980 def left_error_msg(x: Index) -> str:
1981 name = self.left_on if not self.left_index else lib.no_default
1982 msg = x[x.duplicated()][:5].to_frame(name=name).to_string(index=False)
1983 return f"\nDuplicates in left:\n {msg} ..."
1984
1985 def right_error_msg(x: Index) -> str:
1986 name = self.right_on if not self.right_index else lib.no_default
1987 msg = x[x.duplicated()][:5].to_frame(name=name).to_string(index=False)
1988 return f"\nDuplicates in right:\n {msg} ..."
1989
1990 # Check data integrity
1991 if validate in ["one_to_one", "1:1"]:
1992 if not left_unique and not right_unique:
1993 raise MergeError(
1994 "Merge keys are not unique in either left "
1995 "or right dataset; not a one-to-one merge."
1996 f"{left_error_msg(left_join_index)}"
1997 f"{right_error_msg(right_join_index)}"
1998 )
1999 if not left_unique:
2000 raise MergeError(
2001 "Merge keys are not unique in left dataset; not a one-to-one merge"
2002 f"{left_error_msg(left_join_index)}"
2003 )
2004 if not right_unique:
2005 raise MergeError(
2006 "Merge keys are not unique in right dataset; not a one-to-one merge"
2007 f"{right_error_msg(right_join_index)}"
2008 )
2009
2010 elif validate in ["one_to_many", "1:m"]:
2011 if not left_unique:
2012 raise MergeError(
2013 "Merge keys are not unique in left dataset; not a one-to-many merge"
2014 f"{left_error_msg(left_join_index)}"
2015 )
2016
2017 elif validate in ["many_to_one", "m:1"]:
2018 if not right_unique:
2019 raise MergeError(
2020 "Merge keys are not unique in right dataset; "
2021 "not a many-to-one merge\n"
2022 f"{right_error_msg(right_join_index)}"
2023 )
2024
2025 elif validate in ["many_to_many", "m:m"]:
2026 pass
2027
2028 else:
2029 raise ValueError(
2030 f'"{validate}" is not a valid argument. '
2031 "Valid arguments are:\n"
2032 '- "1:1"\n'
2033 '- "1:m"\n'
2034 '- "m:1"\n'
2035 '- "m:m"\n'
2036 '- "one_to_one"\n'
2037 '- "one_to_many"\n'
2038 '- "many_to_one"\n'
2039 '- "many_to_many"'
2040 )
2041
2042
2043def get_join_indexers(
2044 left_keys: list[ArrayLike],
2045 right_keys: list[ArrayLike],
2046 sort: bool = False,
2047 how: JoinHow = "inner",
2048) -> tuple[npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]:
2049 """
2050
2051 Parameters
2052 ----------
2053 left_keys : list[ndarray, ExtensionArray, Index, Series]
2054 right_keys : list[ndarray, ExtensionArray, Index, Series]
2055 sort : bool, default False
2056 how : {'inner', 'outer', 'left', 'right'}, default 'inner'
2057
2058 Returns
2059 -------
2060 np.ndarray[np.intp] or None
2061 Indexer into the left_keys.
2062 np.ndarray[np.intp] or None
2063 Indexer into the right_keys.
2064 """
2065 assert len(left_keys) == len(right_keys), (
2066 "left_keys and right_keys must be the same length"
2067 )
2068
2069 # fast-path for empty left/right
2070 left_n = len(left_keys[0])
2071 right_n = len(right_keys[0])
2072 if left_n == 0:
2073 if how in ["left", "inner"]:
2074 return _get_empty_indexer()
2075 elif not sort and how in ["right", "outer"]:
2076 return _get_no_sort_one_missing_indexer(right_n, True)
2077 elif right_n == 0:
2078 if how in ["right", "inner"]:
2079 return _get_empty_indexer()
2080 elif not sort and how in ["left", "outer"]:
2081 return _get_no_sort_one_missing_indexer(left_n, False)
2082
2083 lkey: ArrayLike
2084 rkey: ArrayLike
2085 if len(left_keys) > 1:
2086 # get left & right join labels and num. of levels at each location
2087 mapped = (
2088 _factorize_keys(left_keys[n], right_keys[n], sort=sort)
2089 for n in range(len(left_keys))
2090 )
2091 zipped = zip(*mapped, strict=True)
2092 llab, rlab, shape = (list(x) for x in zipped)
2093
2094 # get flat i8 keys from label lists
2095 lkey, rkey = _get_join_keys(llab, rlab, tuple(shape), sort)
2096 else:
2097 lkey = left_keys[0]
2098 rkey = right_keys[0]
2099
2100 left = Index(lkey, copy=False)
2101 right = Index(rkey, copy=False)
2102
2103 if (
2104 left.is_monotonic_increasing
2105 and right.is_monotonic_increasing
2106 and (left.is_unique or right.is_unique)
2107 ):
2108 _, lidx, ridx = left.join(right, how=how, return_indexers=True, sort=sort)
2109 else:
2110 lidx, ridx = get_join_indexers_non_unique(
2111 left._values, right._values, sort, how
2112 )
2113
2114 if lidx is not None and is_range_indexer(lidx, len(left)):
2115 lidx = None
2116 if ridx is not None and is_range_indexer(ridx, len(right)):
2117 ridx = None
2118 return lidx, ridx
2119
2120
2121def get_join_indexers_non_unique(
2122 left: ArrayLike,
2123 right: ArrayLike,
2124 sort: bool = False,
2125 how: JoinHow = "inner",
2126) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
2127 """
2128 Get join indexers for left and right.
2129
2130 Parameters
2131 ----------
2132 left : ArrayLike
2133 right : ArrayLike
2134 sort : bool, default False
2135 how : {'inner', 'outer', 'left', 'right'}, default 'inner'
2136
2137 Returns
2138 -------
2139 np.ndarray[np.intp]
2140 Indexer into left.
2141 np.ndarray[np.intp]
2142 Indexer into right.
2143 """
2144 lkey, rkey, count = _factorize_keys(left, right, sort=sort, how=how)
2145 if count == -1:
2146 # hash join
2147 return lkey, rkey
2148 if how == "left":
2149 lidx, ridx = libjoin.left_outer_join(lkey, rkey, count, sort=sort)
2150 elif how == "right":
2151 ridx, lidx = libjoin.left_outer_join(rkey, lkey, count, sort=sort)
2152 elif how == "inner":
2153 lidx, ridx = libjoin.inner_join(lkey, rkey, count, sort=sort)
2154 elif how == "outer":
2155 lidx, ridx = libjoin.full_outer_join(lkey, rkey, count)
2156 return lidx, ridx
2157
2158
2159def restore_dropped_levels_multijoin(
2160 left: MultiIndex,
2161 right: MultiIndex,
2162 dropped_level_names,
2163 join_index: Index,
2164 lindexer: npt.NDArray[np.intp],
2165 rindexer: npt.NDArray[np.intp],
2166) -> tuple[FrozenList, FrozenList, FrozenList]:
2167 """
2168 *this is an internal non-public method*
2169
2170 Returns the levels, labels and names of a multi-index to multi-index join.
2171 Depending on the type of join, this method restores the appropriate
2172 dropped levels of the joined multi-index.
2173 The method relies on lindexer, rindexer which hold the index positions of
2174 left and right, where a join was feasible
2175
2176 Parameters
2177 ----------
2178 left : MultiIndex
2179 left index
2180 right : MultiIndex
2181 right index
2182 dropped_level_names : str array
2183 list of non-common level names
2184 join_index : Index
2185 the index of the join between the
2186 common levels of left and right
2187 lindexer : np.ndarray[np.intp]
2188 left indexer
2189 rindexer : np.ndarray[np.intp]
2190 right indexer
2191
2192 Returns
2193 -------
2194 levels : list of Index
2195 levels of combined multiindexes
2196 labels : np.ndarray[np.intp]
2197 labels of combined multiindexes
2198 names : List[Hashable]
2199 names of combined multiindex levels
2200
2201 """
2202
2203 def _convert_to_multiindex(index: Index) -> MultiIndex:
2204 if isinstance(index, MultiIndex):
2205 return index
2206 else:
2207 return MultiIndex.from_arrays([index._values], names=[index.name])
2208
2209 # For multi-multi joins with one overlapping level,
2210 # the returned index if of type Index
2211 # Assure that join_index is of type MultiIndex
2212 # so that dropped levels can be appended
2213 join_index = _convert_to_multiindex(join_index)
2214
2215 join_levels = join_index.levels
2216 join_codes = join_index.codes
2217 join_names = join_index.names
2218
2219 # Iterate through the levels that must be restored
2220 for dropped_level_name in dropped_level_names:
2221 if dropped_level_name in left.names:
2222 idx = left
2223 indexer = lindexer
2224 else:
2225 idx = right
2226 indexer = rindexer
2227
2228 # The index of the level name to be restored
2229 name_idx = idx.names.index(dropped_level_name)
2230
2231 restore_levels = idx.levels[name_idx]
2232 # Inject -1 in the codes list where a join was not possible
2233 # IOW indexer[i]=-1
2234 codes = idx.codes[name_idx]
2235 if indexer is None:
2236 restore_codes = codes
2237 else:
2238 restore_codes = algos.take_nd(codes, indexer, fill_value=-1)
2239
2240 # Use + operator: FrozenList.__add__ returns FrozenList, unpacking returns list
2241 join_levels = join_levels + [restore_levels] # noqa: RUF005
2242 join_codes = join_codes + [restore_codes] # noqa: RUF005
2243 join_names = join_names + [dropped_level_name] # noqa: RUF005
2244
2245 return join_levels, join_codes, join_names
2246
2247
2248class _OrderedMerge(_MergeOperation):
2249 _merge_type = "ordered_merge"
2250
2251 def __init__(
2252 self,
2253 left: DataFrame | Series,
2254 right: DataFrame | Series,
2255 on: IndexLabel | None = None,
2256 left_on: IndexLabel | None = None,
2257 right_on: IndexLabel | None = None,
2258 left_index: bool = False,
2259 right_index: bool = False,
2260 suffixes: Suffixes = ("_x", "_y"),
2261 fill_method: str | None = None,
2262 how: JoinHow | Literal["asof"] = "outer",
2263 ) -> None:
2264 self.fill_method = fill_method
2265 _MergeOperation.__init__(
2266 self,
2267 left,
2268 right,
2269 on=on,
2270 left_on=left_on,
2271 left_index=left_index,
2272 right_index=right_index,
2273 right_on=right_on,
2274 how=how,
2275 suffixes=suffixes,
2276 sort=True, # factorize sorts
2277 )
2278
2279 def get_result(self) -> DataFrame:
2280 join_index, left_indexer, right_indexer = self._get_join_info()
2281
2282 left_join_indexer: npt.NDArray[np.intp] | None
2283 right_join_indexer: npt.NDArray[np.intp] | None
2284
2285 if self.fill_method == "ffill":
2286 if left_indexer is None:
2287 left_join_indexer = None
2288 else:
2289 left_join_indexer = libjoin.ffill_indexer(left_indexer)
2290 if right_indexer is None:
2291 right_join_indexer = None
2292 else:
2293 right_join_indexer = libjoin.ffill_indexer(right_indexer)
2294 elif self.fill_method is None:
2295 left_join_indexer = left_indexer
2296 right_join_indexer = right_indexer
2297 else:
2298 raise ValueError("fill_method must be 'ffill' or None")
2299
2300 result = self._reindex_and_concat(
2301 join_index, left_join_indexer, right_join_indexer
2302 )
2303 self._maybe_add_join_keys(result, left_indexer, right_indexer)
2304
2305 return result
2306
2307
2308def _asof_by_function(direction: str):
2309 name = f"asof_join_{direction}_on_X_by_Y"
2310 return getattr(libjoin, name, None)
2311
2312
2313class _AsOfMerge(_OrderedMerge):
2314 _merge_type = "asof_merge"
2315
2316 def __init__(
2317 self,
2318 left: DataFrame | Series,
2319 right: DataFrame | Series,
2320 on: IndexLabel | None = None,
2321 left_on: IndexLabel | None = None,
2322 right_on: IndexLabel | None = None,
2323 left_index: bool = False,
2324 right_index: bool = False,
2325 by=None,
2326 left_by=None,
2327 right_by=None,
2328 suffixes: Suffixes = ("_x", "_y"),
2329 how: Literal["asof"] = "asof",
2330 tolerance=None,
2331 allow_exact_matches: bool = True,
2332 direction: str = "backward",
2333 ) -> None:
2334 self.by = by
2335 self.left_by = left_by
2336 self.right_by = right_by
2337 self.tolerance = tolerance
2338 self.allow_exact_matches = allow_exact_matches
2339 self.direction = direction
2340
2341 # check 'direction' is valid
2342 if self.direction not in ["backward", "forward", "nearest"]:
2343 raise MergeError(f"direction invalid: {self.direction}")
2344
2345 # validate allow_exact_matches
2346 if not is_bool(self.allow_exact_matches):
2347 msg = (
2348 "allow_exact_matches must be boolean, "
2349 f"passed {self.allow_exact_matches}"
2350 )
2351 raise MergeError(msg)
2352
2353 _OrderedMerge.__init__(
2354 self,
2355 left,
2356 right,
2357 on=on,
2358 left_on=left_on,
2359 right_on=right_on,
2360 left_index=left_index,
2361 right_index=right_index,
2362 how=how,
2363 suffixes=suffixes,
2364 fill_method=None,
2365 )
2366
2367 def _validate_left_right_on(self, left_on, right_on):
2368 left_on, right_on = super()._validate_left_right_on(left_on, right_on)
2369
2370 # we only allow on to be a single item for on
2371 if len(left_on) != 1 and not self.left_index:
2372 raise MergeError("can only asof on a key for left")
2373
2374 if len(right_on) != 1 and not self.right_index:
2375 raise MergeError("can only asof on a key for right")
2376
2377 if self.left_index and isinstance(self.left.index, MultiIndex):
2378 raise MergeError("left can only have one index")
2379
2380 if self.right_index and isinstance(self.right.index, MultiIndex):
2381 raise MergeError("right can only have one index")
2382
2383 # set 'by' columns
2384 if self.by is not None:
2385 if self.left_by is not None or self.right_by is not None:
2386 raise MergeError("Can only pass by OR left_by and right_by")
2387 self.left_by = self.right_by = self.by
2388 if self.left_by is None and self.right_by is not None:
2389 raise MergeError("missing left_by")
2390 if self.left_by is not None and self.right_by is None:
2391 raise MergeError("missing right_by")
2392
2393 # GH#29130 Check that merge keys do not have dtype object
2394 if not self.left_index:
2395 left_on_0 = left_on[0]
2396 if isinstance(left_on_0, _known):
2397 lo_dtype = left_on_0.dtype
2398 else:
2399 lo_dtype = (
2400 self.left._get_label_or_level_values(left_on_0).dtype
2401 if left_on_0 in self.left.columns
2402 else self.left.index.get_level_values(left_on_0)
2403 )
2404 else:
2405 lo_dtype = self.left.index.dtype
2406
2407 if not self.right_index:
2408 right_on_0 = right_on[0]
2409 if isinstance(right_on_0, _known):
2410 ro_dtype = right_on_0.dtype
2411 else:
2412 ro_dtype = (
2413 self.right._get_label_or_level_values(right_on_0).dtype
2414 if right_on_0 in self.right.columns
2415 else self.right.index.get_level_values(right_on_0)
2416 )
2417 else:
2418 ro_dtype = self.right.index.dtype
2419
2420 if (
2421 is_object_dtype(lo_dtype)
2422 or is_object_dtype(ro_dtype)
2423 or is_string_dtype(lo_dtype)
2424 or is_string_dtype(ro_dtype)
2425 ):
2426 raise MergeError(
2427 f"Incompatible merge dtype, {lo_dtype!r} and "
2428 f"{ro_dtype!r}, both sides must have numeric dtype"
2429 )
2430
2431 # add 'by' to our key-list so we can have it in the
2432 # output as a key
2433 if self.left_by is not None:
2434 if not is_list_like(self.left_by):
2435 self.left_by = [self.left_by]
2436 if not is_list_like(self.right_by):
2437 self.right_by = [self.right_by]
2438
2439 if len(self.left_by) != len(self.right_by):
2440 raise MergeError("left_by and right_by must be the same length")
2441
2442 left_on = self.left_by + list(left_on)
2443 right_on = self.right_by + list(right_on)
2444
2445 return left_on, right_on
2446
2447 def _maybe_require_matching_dtypes(
2448 self, left_join_keys: list[ArrayLike], right_join_keys: list[ArrayLike]
2449 ) -> None:
2450 # TODO: why do we do this for AsOfMerge but not the others?
2451
2452 def _check_dtype_match(left: ArrayLike, right: ArrayLike, i: int) -> None:
2453 if left.dtype != right.dtype:
2454 if isinstance(left.dtype, CategoricalDtype) and isinstance(
2455 right.dtype, CategoricalDtype
2456 ):
2457 # The generic error message is confusing for categoricals.
2458 #
2459 # In this function, the join keys include both the original
2460 # ones of the merge_asof() call, and also the keys passed
2461 # to its by= argument. Unordered but equal categories
2462 # are not supported for the former, but will fail
2463 # later with a ValueError, so we don't *need* to check
2464 # for them here.
2465 msg = (
2466 f"incompatible merge keys [{i}] {left.dtype!r} and "
2467 f"{right.dtype!r}, both sides category, but not equal ones"
2468 )
2469 else:
2470 msg = (
2471 f"incompatible merge keys [{i}] {left.dtype!r} and "
2472 f"{right.dtype!r}, must be the same type"
2473 )
2474 raise MergeError(msg)
2475
2476 # validate index types are the same
2477 for i, (lk, rk) in enumerate(zip(left_join_keys, right_join_keys, strict=True)):
2478 _check_dtype_match(lk, rk, i)
2479
2480 if self.left_index:
2481 lt = self.left.index._values
2482 else:
2483 lt = left_join_keys[-1]
2484
2485 if self.right_index:
2486 rt = self.right.index._values
2487 else:
2488 rt = right_join_keys[-1]
2489
2490 _check_dtype_match(lt, rt, 0)
2491
2492 def _validate_tolerance(self, left_join_keys: list[ArrayLike]) -> None:
2493 # validate tolerance; datetime.timedelta or Timedelta if we have a DTI
2494 if self.tolerance is not None:
2495 if self.left_index:
2496 lt = self.left.index._values
2497 else:
2498 lt = left_join_keys[-1]
2499
2500 msg = (
2501 f"incompatible tolerance {self.tolerance}, must be compat "
2502 f"with type {lt.dtype!r}"
2503 )
2504
2505 if needs_i8_conversion(lt.dtype) or (
2506 isinstance(lt, ArrowExtensionArray) and lt.dtype.kind in "mM"
2507 ):
2508 if not isinstance(self.tolerance, datetime.timedelta):
2509 raise MergeError(msg)
2510 if self.tolerance < Timedelta(0):
2511 raise MergeError("tolerance must be positive")
2512
2513 elif is_integer_dtype(lt.dtype):
2514 if not is_integer(self.tolerance):
2515 raise MergeError(msg)
2516 if self.tolerance < 0:
2517 raise MergeError("tolerance must be positive")
2518
2519 elif is_float_dtype(lt.dtype):
2520 if not is_number(self.tolerance):
2521 raise MergeError(msg)
2522 # error: Unsupported operand types for > ("int" and "Number")
2523 if self.tolerance < 0: # type: ignore[operator]
2524 raise MergeError("tolerance must be positive")
2525
2526 else:
2527 raise MergeError("key must be integer, timestamp or float")
2528
2529 def _convert_values_for_libjoin(
2530 self, values: AnyArrayLike, side: str
2531 ) -> np.ndarray:
2532 # we require sortedness and non-null values in the join keys
2533 if not Index(values, copy=False).is_monotonic_increasing:
2534 if isna(values).any():
2535 raise ValueError(f"Merge keys contain null values on {side} side")
2536 raise ValueError(f"{side} keys must be sorted")
2537
2538 if isinstance(values, ArrowExtensionArray):
2539 values = values._maybe_convert_datelike_array()
2540
2541 if needs_i8_conversion(values.dtype):
2542 values = values.view("i8")
2543
2544 elif isinstance(values, BaseMaskedArray):
2545 # we've verified above that no nulls exist
2546 values = values._data
2547 elif isinstance(values, ExtensionArray):
2548 values = values.to_numpy()
2549
2550 # error: Incompatible return value type (got "Union[ExtensionArray,
2551 # Any, ndarray[Any, Any], ndarray[Any, dtype[Any]], Index, Series]",
2552 # expected "ndarray[Any, Any]")
2553 return values # type: ignore[return-value]
2554
2555 def _get_join_indexers(self) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
2556 """return the join indexers"""
2557
2558 # values to compare
2559 left_values = (
2560 self.left.index._values if self.left_index else self.left_join_keys[-1]
2561 )
2562 right_values = (
2563 self.right.index._values if self.right_index else self.right_join_keys[-1]
2564 )
2565
2566 # _maybe_require_matching_dtypes already checked for dtype matching
2567 assert left_values.dtype == right_values.dtype
2568
2569 tolerance = self.tolerance
2570 if tolerance is not None:
2571 # TODO: can we reuse a tolerance-conversion function from
2572 # e.g. TimedeltaIndex?
2573 if needs_i8_conversion(left_values.dtype) or (
2574 isinstance(left_values, ArrowExtensionArray)
2575 and left_values.dtype.kind in "mM"
2576 ):
2577 tolerance = Timedelta(tolerance)
2578 # TODO: we have no test cases with PeriodDtype here; probably
2579 # need to adjust tolerance for that case.
2580 if left_values.dtype.kind in "mM":
2581 # Make sure the i8 representation for tolerance
2582 # matches that for left_values/right_values.
2583 if isinstance(left_values, ArrowExtensionArray):
2584 unit = left_values.dtype.pyarrow_dtype.unit
2585 else:
2586 unit = ensure_wrapped_if_datetimelike(left_values).unit
2587 tolerance = tolerance.as_unit(unit)
2588
2589 tolerance = tolerance._value
2590
2591 # initial type conversion as needed
2592 left_values = self._convert_values_for_libjoin(left_values, "left")
2593 right_values = self._convert_values_for_libjoin(right_values, "right")
2594
2595 # a "by" parameter requires special handling
2596 if self.left_by is not None:
2597 # remove 'on' parameter from values if one existed
2598 if self.left_index and self.right_index:
2599 left_join_keys = self.left_join_keys
2600 right_join_keys = self.right_join_keys
2601 else:
2602 left_join_keys = self.left_join_keys[0:-1]
2603 right_join_keys = self.right_join_keys[0:-1]
2604
2605 mapped = [
2606 _factorize_keys(
2607 left_join_keys[n],
2608 right_join_keys[n],
2609 sort=False,
2610 )
2611 for n in range(len(left_join_keys))
2612 ]
2613
2614 if len(left_join_keys) == 1:
2615 left_by_values = mapped[0][0]
2616 right_by_values = mapped[0][1]
2617 else:
2618 arrs = [np.concatenate(m[:2]) for m in mapped]
2619 shape = tuple(m[2] for m in mapped)
2620 group_index = get_group_index(
2621 arrs, shape=shape, sort=False, xnull=False
2622 )
2623 left_len = len(left_join_keys[0])
2624 left_by_values = group_index[:left_len]
2625 right_by_values = group_index[left_len:]
2626
2627 left_by_values = ensure_int64(left_by_values)
2628 right_by_values = ensure_int64(right_by_values)
2629
2630 # choose appropriate function by type
2631 func = _asof_by_function(self.direction)
2632 return func(
2633 left_values,
2634 right_values,
2635 left_by_values,
2636 right_by_values,
2637 self.allow_exact_matches,
2638 tolerance,
2639 )
2640 else:
2641 # choose appropriate function by type
2642 func = _asof_by_function(self.direction)
2643 return func(
2644 left_values,
2645 right_values,
2646 None,
2647 None,
2648 self.allow_exact_matches,
2649 tolerance,
2650 False,
2651 )
2652
2653
2654def _get_multiindex_indexer(
2655 join_keys: list[ArrayLike], index: MultiIndex, sort: bool
2656) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
2657 # left & right join labels and num. of levels at each location
2658 mapped = (
2659 _factorize_keys(index.levels[n]._values, join_keys[n], sort=sort)
2660 for n in range(index.nlevels)
2661 )
2662 zipped = zip(*mapped, strict=True)
2663 rcodes, lcodes, shape = (list(x) for x in zipped)
2664 if sort:
2665 rcodes = list(map(np.take, rcodes, index.codes))
2666 else:
2667 i8copy = lambda a: a.astype("i8", subok=False)
2668 rcodes = list(map(i8copy, index.codes))
2669
2670 # fix right labels if there were any nulls
2671 for i, join_key in enumerate(join_keys):
2672 mask = index.codes[i] == -1
2673 if mask.any():
2674 # check if there already was any nulls at this location
2675 # if there was, it is factorized to `shape[i] - 1`
2676 a = join_key[lcodes[i] == shape[i] - 1]
2677 if a.size == 0 or not a[0] != a[0]:
2678 shape[i] += 1
2679
2680 rcodes[i][mask] = shape[i] - 1
2681
2682 # get flat i8 join keys
2683 lkey, rkey = _get_join_keys(lcodes, rcodes, tuple(shape), sort)
2684 return lkey, rkey
2685
2686
2687def _get_empty_indexer() -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
2688 """Return empty join indexers."""
2689 return (
2690 np.array([], dtype=np.intp),
2691 np.array([], dtype=np.intp),
2692 )
2693
2694
2695def _get_no_sort_one_missing_indexer(
2696 n: int, left_missing: bool
2697) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
2698 """
2699 Return join indexers where all of one side is selected without sorting
2700 and none of the other side is selected.
2701
2702 Parameters
2703 ----------
2704 n : int
2705 Length of indexers to create.
2706 left_missing : bool
2707 If True, the left indexer will contain only -1's.
2708 If False, the right indexer will contain only -1's.
2709
2710 Returns
2711 -------
2712 np.ndarray[np.intp]
2713 Left indexer
2714 np.ndarray[np.intp]
2715 Right indexer
2716 """
2717 idx = np.arange(n, dtype=np.intp)
2718 idx_missing = np.full(shape=n, fill_value=-1, dtype=np.intp)
2719 if left_missing:
2720 return idx_missing, idx
2721 return idx, idx_missing
2722
2723
2724def _left_join_on_index(
2725 left_ax: Index, right_ax: Index, join_keys: list[ArrayLike], sort: bool = False
2726) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp]]:
2727 if isinstance(right_ax, MultiIndex):
2728 lkey, rkey = _get_multiindex_indexer(join_keys, right_ax, sort=sort)
2729 else:
2730 # error: Incompatible types in assignment (expression has type
2731 # "Union[Union[ExtensionArray, ndarray[Any, Any]], Index, Series]",
2732 # variable has type "ndarray[Any, dtype[signedinteger[Any]]]")
2733 lkey = join_keys[0] # type: ignore[assignment]
2734 # error: Incompatible types in assignment (expression has type "Index",
2735 # variable has type "ndarray[Any, dtype[signedinteger[Any]]]")
2736 rkey = right_ax._values # type: ignore[assignment]
2737
2738 left_key, right_key, count = _factorize_keys(lkey, rkey, sort=sort)
2739 left_indexer, right_indexer = libjoin.left_outer_join(
2740 left_key, right_key, count, sort=sort
2741 )
2742
2743 if sort or len(left_ax) != len(left_indexer):
2744 # if asked to sort or there are 1-to-many matches
2745 join_index = left_ax.take(left_indexer)
2746 return join_index, left_indexer, right_indexer
2747
2748 # left frame preserves order & length of its index
2749 return left_ax, None, right_indexer
2750
2751
2752def _factorize_keys(
2753 lk: ArrayLike,
2754 rk: ArrayLike,
2755 sort: bool = True,
2756 how: str | None = None,
2757) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp], int]:
2758 """
2759 Encode left and right keys as enumerated types.
2760
2761 This is used to get the join indexers to be used when merging DataFrames.
2762
2763 Parameters
2764 ----------
2765 lk : ndarray, ExtensionArray
2766 Left key.
2767 rk : ndarray, ExtensionArray
2768 Right key.
2769 sort : bool, defaults to True
2770 If True, the encoding is done such that the unique elements in the
2771 keys are sorted.
2772 how: str, optional
2773 Used to determine if we can use hash-join. If not given, then just factorize
2774 keys.
2775
2776 Returns
2777 -------
2778 np.ndarray[np.intp]
2779 Left (resp. right if called with `key='right'`) labels, as enumerated type.
2780 np.ndarray[np.intp]
2781 Right (resp. left if called with `key='right'`) labels, as enumerated type.
2782 int
2783 Number of unique elements in union of left and right labels. -1 if we used
2784 a hash-join.
2785
2786 See Also
2787 --------
2788 merge : Merge DataFrame or named Series objects
2789 with a database-style join.
2790 algorithms.factorize : Encode the object as an enumerated type
2791 or categorical variable.
2792
2793 Examples
2794 --------
2795 >>> lk = np.array(["a", "c", "b"])
2796 >>> rk = np.array(["a", "c"])
2797
2798 Here, the unique values are `'a', 'b', 'c'`. With the default
2799 `sort=True`, the encoding will be `{0: 'a', 1: 'b', 2: 'c'}`:
2800
2801 >>> pd.core.reshape.merge._factorize_keys(lk, rk)
2802 (array([0, 2, 1]), array([0, 2]), 3)
2803
2804 With the `sort=False`, the encoding will correspond to the order
2805 in which the unique elements first appear: `{0: 'a', 1: 'c', 2: 'b'}`:
2806
2807 >>> pd.core.reshape.merge._factorize_keys(lk, rk, sort=False)
2808 (array([0, 1, 2]), array([0, 1]), 3)
2809 """
2810 # TODO: if either is a RangeIndex, we can likely factorize more efficiently?
2811
2812 if (
2813 isinstance(lk.dtype, DatetimeTZDtype) and isinstance(rk.dtype, DatetimeTZDtype)
2814 ) or (lib.is_np_dtype(lk.dtype, "M") and lib.is_np_dtype(rk.dtype, "M")):
2815 # Extract the ndarray (UTC-localized) values
2816 # Note: we dont need the dtypes to match, as these can still be compared
2817 lk, rk = cast("DatetimeArray", lk)._ensure_matching_resos(rk)
2818 lk = cast("DatetimeArray", lk)._ndarray
2819 rk = cast("DatetimeArray", rk)._ndarray
2820
2821 elif (
2822 isinstance(lk.dtype, CategoricalDtype)
2823 and isinstance(rk.dtype, CategoricalDtype)
2824 and lk.dtype == rk.dtype
2825 ):
2826 assert isinstance(lk, Categorical)
2827 assert isinstance(rk, Categorical)
2828 # Cast rk to encoding so we can compare codes with lk
2829
2830 rk = lk._encode_with_my_categories(rk)
2831
2832 lk = ensure_int64(lk.codes)
2833 rk = ensure_int64(rk.codes)
2834
2835 elif isinstance(lk, ExtensionArray) and lk.dtype == rk.dtype:
2836 if isinstance(lk.dtype, ArrowDtype) or (
2837 isinstance(lk.dtype, StringDtype) and lk.dtype.storage == "pyarrow"
2838 ):
2839 import pyarrow as pa
2840
2841 from pandas.compat.pyarrow import _safe_fill_null
2842
2843 len_lk = len(lk)
2844 lk = lk._pa_array # type: ignore[attr-defined]
2845 rk = rk._pa_array # type: ignore[union-attr]
2846 dc = (
2847 pa.chunked_array(lk.chunks + rk.chunks) # type: ignore[union-attr]
2848 .combine_chunks()
2849 .dictionary_encode()
2850 )
2851
2852 llab, rlab, count = (
2853 _safe_fill_null(dc.indices[slice(len_lk)], -1)
2854 .to_numpy()
2855 .astype(np.intp, copy=False),
2856 _safe_fill_null(dc.indices[slice(len_lk, None)], -1)
2857 .to_numpy()
2858 .astype(np.intp, copy=False),
2859 len(dc.dictionary),
2860 )
2861
2862 if sort:
2863 uniques = dc.dictionary.to_numpy(zero_copy_only=False)
2864 llab, rlab = _sort_labels(uniques, llab, rlab)
2865
2866 if dc.null_count > 0:
2867 lmask = llab == -1
2868 lany = lmask.any()
2869 rmask = rlab == -1
2870 rany = rmask.any()
2871 if lany:
2872 np.putmask(llab, lmask, count)
2873 if rany:
2874 np.putmask(rlab, rmask, count)
2875 count += 1
2876 return llab, rlab, count
2877
2878 if not isinstance(lk, BaseMaskedArray) and not (
2879 # exclude arrow dtypes that would get cast to object
2880 isinstance(lk.dtype, ArrowDtype)
2881 and (
2882 is_numeric_dtype(lk.dtype.numpy_dtype)
2883 or (is_string_dtype(lk.dtype) and not sort)
2884 )
2885 ):
2886 lk, _ = lk._values_for_factorize()
2887
2888 # error: Item "ndarray" of "Union[Any, ndarray]" has no attribute
2889 # "_values_for_factorize"
2890 rk, _ = rk._values_for_factorize() # type: ignore[union-attr]
2891
2892 if needs_i8_conversion(lk.dtype) and lk.dtype == rk.dtype:
2893 # GH#23917 TODO: Needs tests for non-matching dtypes
2894 # GH#23917 TODO: needs tests for case where lk is integer-dtype
2895 # and rk is datetime-dtype
2896 lk = np.asarray(lk, dtype=np.int64)
2897 rk = np.asarray(rk, dtype=np.int64)
2898
2899 klass, lk, rk = _convert_arrays_and_get_rizer_klass(lk, rk)
2900
2901 rizer = klass(
2902 max(len(lk), len(rk)),
2903 uses_mask=isinstance(rk, (BaseMaskedArray, ArrowExtensionArray)),
2904 )
2905
2906 if isinstance(lk, BaseMaskedArray):
2907 assert isinstance(rk, BaseMaskedArray)
2908 lk_data, lk_mask = lk._data, lk._mask
2909 rk_data, rk_mask = rk._data, rk._mask
2910 elif isinstance(lk, ArrowExtensionArray):
2911 assert isinstance(rk, ArrowExtensionArray)
2912 # we can only get here with numeric dtypes
2913 # TODO: Remove when we have a Factorizer for Arrow
2914 lk_data = lk.to_numpy(na_value=1, dtype=lk.dtype.numpy_dtype)
2915 rk_data = rk.to_numpy(na_value=1, dtype=lk.dtype.numpy_dtype)
2916 lk_mask, rk_mask = lk.isna(), rk.isna()
2917 else:
2918 # Argument 1 to "factorize" of "ObjectFactorizer" has incompatible type
2919 # "Union[ndarray[Any, dtype[signedinteger[_64Bit]]],
2920 # ndarray[Any, dtype[object_]]]"; expected "ndarray[Any, dtype[object_]]"
2921 lk_data, rk_data = lk, rk # type: ignore[assignment]
2922 lk_mask, rk_mask = None, None
2923
2924 hash_join_available = how == "inner" and not sort and lk.dtype.kind in "iufb"
2925 if hash_join_available:
2926 rlab = rizer.factorize(rk_data, mask=rk_mask)
2927 if rizer.get_count() == len(rlab):
2928 ridx, lidx = rizer.hash_inner_join(lk_data, lk_mask)
2929 return lidx, ridx, -1
2930 else:
2931 llab = rizer.factorize(lk_data, mask=lk_mask)
2932 else:
2933 llab = rizer.factorize(lk_data, mask=lk_mask)
2934 rlab = rizer.factorize(rk_data, mask=rk_mask)
2935
2936 assert llab.dtype == np.dtype(np.intp), llab.dtype
2937 assert rlab.dtype == np.dtype(np.intp), rlab.dtype
2938
2939 count = rizer.get_count()
2940
2941 if sort:
2942 uniques = rizer.uniques.to_array()
2943 llab, rlab = _sort_labels(uniques, llab, rlab)
2944
2945 # NA group
2946 lmask = llab == -1
2947 lany = lmask.any()
2948 rmask = rlab == -1
2949 rany = rmask.any()
2950
2951 if lany or rany:
2952 if lany:
2953 np.putmask(llab, lmask, count)
2954 if rany:
2955 np.putmask(rlab, rmask, count)
2956 count += 1
2957
2958 return llab, rlab, count
2959
2960
2961def _convert_arrays_and_get_rizer_klass(
2962 lk: ArrayLike, rk: ArrayLike
2963) -> tuple[type[libhashtable.Factorizer], ArrayLike, ArrayLike]:
2964 klass: type[libhashtable.Factorizer]
2965 if is_numeric_dtype(lk.dtype):
2966 if lk.dtype != rk.dtype:
2967 dtype = find_common_type([lk.dtype, rk.dtype])
2968 if isinstance(dtype, ExtensionDtype):
2969 cls = dtype.construct_array_type()
2970 if not isinstance(lk, ExtensionArray):
2971 lk = cls._from_sequence(lk, dtype=dtype, copy=False)
2972 else:
2973 lk = lk.astype(dtype, copy=False)
2974
2975 if not isinstance(rk, ExtensionArray):
2976 rk = cls._from_sequence(rk, dtype=dtype, copy=False)
2977 else:
2978 rk = rk.astype(dtype, copy=False)
2979 else:
2980 lk = lk.astype(dtype, copy=False)
2981 rk = rk.astype(dtype, copy=False)
2982 if isinstance(lk, BaseMaskedArray):
2983 klass = _factorizers[lk.dtype.type]
2984 elif isinstance(lk.dtype, ArrowDtype):
2985 klass = _factorizers[lk.dtype.numpy_dtype.type]
2986 else:
2987 klass = _factorizers[lk.dtype.type]
2988
2989 else:
2990 klass = libhashtable.ObjectFactorizer
2991 lk = ensure_object(lk)
2992 rk = ensure_object(rk)
2993 return klass, lk, rk
2994
2995
2996def _sort_labels(
2997 uniques: np.ndarray, left: npt.NDArray[np.intp], right: npt.NDArray[np.intp]
2998) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
2999 llength = len(left)
3000 labels = np.concatenate([left, right])
3001
3002 _, new_labels = algos.safe_sort(uniques, labels, use_na_sentinel=True)
3003 new_left, new_right = new_labels[:llength], new_labels[llength:]
3004
3005 return new_left, new_right
3006
3007
3008def _get_join_keys(
3009 llab: list[npt.NDArray[np.int64 | np.intp]],
3010 rlab: list[npt.NDArray[np.int64 | np.intp]],
3011 shape: Shape,
3012 sort: bool,
3013) -> tuple[npt.NDArray[np.int64], npt.NDArray[np.int64]]:
3014 # how many levels can be done without overflow
3015 nlev = next(
3016 lev
3017 for lev in range(len(shape), 0, -1)
3018 if not is_int64_overflow_possible(shape[:lev])
3019 )
3020
3021 # get keys for the first `nlev` levels
3022 stride = np.prod(shape[1:nlev], dtype="i8")
3023 lkey = stride * llab[0].astype("i8", subok=False, copy=False)
3024 rkey = stride * rlab[0].astype("i8", subok=False, copy=False)
3025
3026 for i in range(1, nlev):
3027 with np.errstate(divide="ignore"):
3028 stride //= shape[i]
3029 lkey += llab[i] * stride
3030 rkey += rlab[i] * stride
3031
3032 if nlev == len(shape): # all done!
3033 return lkey, rkey
3034
3035 # densify current keys to avoid overflow
3036 lkey, rkey, count = _factorize_keys(lkey, rkey, sort=sort)
3037
3038 llab = [lkey, *llab[nlev:]]
3039 rlab = [rkey, *rlab[nlev:]]
3040 shape = (count, *shape[nlev:])
3041
3042 return _get_join_keys(llab, rlab, shape, sort)
3043
3044
3045def _should_fill(lname, rname) -> bool:
3046 if not isinstance(lname, str) or not isinstance(rname, str):
3047 return True
3048 return lname == rname
3049
3050
3051def _any(x) -> bool:
3052 return x is not None and com.any_not_none(*x)
3053
3054
3055def _validate_operand(obj: DataFrame | Series) -> DataFrame:
3056 if isinstance(obj, ABCDataFrame):
3057 return obj
3058 elif isinstance(obj, ABCSeries):
3059 if obj.name is None:
3060 raise ValueError("Cannot merge a Series without a name")
3061 return obj.to_frame()
3062 else:
3063 raise TypeError(
3064 f"Can only merge Series or DataFrame objects, a {type(obj)} was passed"
3065 )
3066
3067
3068def _items_overlap_with_suffix(
3069 left: Index, right: Index, suffixes: Suffixes
3070) -> tuple[Index, Index]:
3071 """
3072 Suffixes type validation.
3073
3074 If two indices overlap, add suffixes to overlapping entries.
3075
3076 If corresponding suffix is empty, the entry is simply converted to string.
3077
3078 """
3079 if not is_list_like(suffixes, allow_sets=False) or isinstance(suffixes, dict):
3080 raise TypeError(
3081 f"Passing 'suffixes' as a {type(suffixes)}, is not supported. "
3082 "Provide 'suffixes' as a tuple instead."
3083 )
3084
3085 to_rename = left.intersection(right)
3086 if len(to_rename) == 0:
3087 return left, right
3088
3089 lsuffix, rsuffix = suffixes
3090
3091 if not lsuffix and not rsuffix:
3092 raise ValueError(f"columns overlap but no suffix specified: {to_rename}")
3093
3094 def renamer(x, suffix: str | None):
3095 """
3096 Rename the left and right indices.
3097
3098 If there is overlap, and suffix is not None, add
3099 suffix, otherwise, leave it as-is.
3100
3101 Parameters
3102 ----------
3103 x : original column name
3104 suffix : str or None
3105
3106 Returns
3107 -------
3108 x : renamed column name
3109 """
3110 if x in to_rename and suffix is not None:
3111 return f"{x}{suffix}"
3112 return x
3113
3114 lrenamer = partial(renamer, suffix=lsuffix)
3115 rrenamer = partial(renamer, suffix=rsuffix)
3116
3117 llabels = left._transform_index(lrenamer)
3118 rlabels = right._transform_index(rrenamer)
3119
3120 dups = []
3121 if not llabels.is_unique:
3122 # Only warn when duplicates are caused because of suffixes, already duplicated
3123 # columns in origin should not warn
3124 dups.extend(llabels[(llabels.duplicated()) & (~left.duplicated())].tolist())
3125 if not rlabels.is_unique:
3126 dups.extend(rlabels[(rlabels.duplicated()) & (~right.duplicated())].tolist())
3127 # Suffix addition creates duplicate to pre-existing column name
3128 dups.extend(llabels.intersection(right.difference(to_rename)).tolist())
3129 dups.extend(rlabels.intersection(left.difference(to_rename)).tolist())
3130 if dups:
3131 raise MergeError(
3132 f"Passing 'suffixes' which cause duplicate columns {set(dups)} is "
3133 "not allowed.",
3134 )
3135
3136 return llabels, rlabels