@@ -1019,10 +1019,14 @@ def _maybe_add_join_keys(
10191019 take_left , take_right = None , None
10201020
10211021 if name in result :
1022- if left_indexer is not None and right_indexer is not None :
1022+ if left_indexer is not None or right_indexer is not None :
10231023 if name in self .left :
10241024 if left_has_missing is None :
1025- left_has_missing = (left_indexer == - 1 ).any ()
1025+ left_has_missing = (
1026+ False
1027+ if left_indexer is None
1028+ else (left_indexer == - 1 ).any ()
1029+ )
10261030
10271031 if left_has_missing :
10281032 take_right = self .right_join_keys [i ]
@@ -1032,21 +1036,27 @@ def _maybe_add_join_keys(
10321036
10331037 elif name in self .right :
10341038 if right_has_missing is None :
1035- right_has_missing = (right_indexer == - 1 ).any ()
1039+ right_has_missing = (
1040+ False
1041+ if right_indexer is None
1042+ else (right_indexer == - 1 ).any ()
1043+ )
10361044
10371045 if right_has_missing :
10381046 take_left = self .left_join_keys [i ]
10391047
10401048 if result [name ].dtype != self .right [name ].dtype :
10411049 take_right = self .right [name ]._values
10421050
1043- elif left_indexer is not None :
1051+ else :
10441052 take_left = self .left_join_keys [i ]
10451053 take_right = self .right_join_keys [i ]
10461054
10471055 if take_left is not None or take_right is not None :
10481056 if take_left is None :
10491057 lvals = result [name ]._values
1058+ elif left_indexer is None :
1059+ lvals = take_left
10501060 else :
10511061 # TODO: can we pin down take_left's type earlier?
10521062 take_left = extract_array (take_left , extract_numpy = True )
@@ -1055,6 +1065,8 @@ def _maybe_add_join_keys(
10551065
10561066 if take_right is None :
10571067 rvals = result [name ]._values
1068+ elif right_indexer is None :
1069+ rvals = take_right
10581070 else :
10591071 # TODO: can we pin down take_right's type earlier?
10601072 taker = extract_array (take_right , extract_numpy = True )
@@ -1063,16 +1075,17 @@ def _maybe_add_join_keys(
10631075
10641076 # if we have an all missing left_indexer
10651077 # make sure to just use the right values or vice-versa
1066- mask_left = left_indexer == - 1
1067- # error: Item "bool" of "Union[Any, bool]" has no attribute "all"
1068- if mask_left .all (): # type: ignore[union-attr]
1078+ if left_indexer is not None and (left_indexer == - 1 ).all ():
10691079 key_col = Index (rvals )
10701080 result_dtype = rvals .dtype
10711081 elif right_indexer is not None and (right_indexer == - 1 ).all ():
10721082 key_col = Index (lvals )
10731083 result_dtype = lvals .dtype
10741084 else :
1075- key_col = Index (lvals ).where (~ mask_left , rvals )
1085+ key_col = Index (lvals )
1086+ if left_indexer is not None :
1087+ mask_left = left_indexer == - 1
1088+ key_col = key_col .where (~ mask_left , rvals )
10761089 result_dtype = find_common_type ([lvals .dtype , rvals .dtype ])
10771090 if (
10781091 lvals .dtype .kind == "M"
@@ -1103,7 +1116,9 @@ def _maybe_add_join_keys(
11031116 else :
11041117 result .insert (i , name or f"key_{ i } " , key_col )
11051118
1106- def _get_join_indexers (self ) -> tuple [npt .NDArray [np .intp ], npt .NDArray [np .intp ]]:
1119+ def _get_join_indexers (
1120+ self ,
1121+ ) -> tuple [npt .NDArray [np .intp ] | None , npt .NDArray [np .intp ] | None ]:
11071122 """return the join indexers"""
11081123 # make mypy happy
11091124 assert self .how != "asof"
@@ -1143,6 +1158,8 @@ def _get_join_info(
11431158 left_indexer ,
11441159 how = "right" ,
11451160 )
1161+ elif right_indexer is None :
1162+ join_index = right_ax .copy ()
11461163 else :
11471164 join_index = right_ax .take (right_indexer )
11481165 elif self .left_index :
@@ -1162,10 +1179,13 @@ def _get_join_info(
11621179 right_indexer ,
11631180 how = "left" ,
11641181 )
1182+ elif left_indexer is None :
1183+ join_index = left_ax .copy ()
11651184 else :
11661185 join_index = left_ax .take (left_indexer )
11671186 else :
1168- join_index = default_index (len (left_indexer ))
1187+ n = len (left_ax ) if left_indexer is None else len (left_indexer )
1188+ join_index = default_index (n )
11691189
11701190 return join_index , left_indexer , right_indexer
11711191
@@ -1174,17 +1194,20 @@ def _create_join_index(
11741194 self ,
11751195 index : Index ,
11761196 other_index : Index ,
1177- indexer : npt .NDArray [np .intp ],
1197+ indexer : npt .NDArray [np .intp ] | None ,
11781198 how : JoinHow = "left" ,
11791199 ) -> Index :
11801200 """
11811201 Create a join index by rearranging one index to match another
11821202
11831203 Parameters
11841204 ----------
1185- index : Index being rearranged
1186- other_index : Index used to supply values not found in index
1187- indexer : np.ndarray[np.intp] how to rearrange index
1205+ index : Index
1206+ index being rearranged
1207+ other_index : Index
1208+ used to supply values not found in index
1209+ indexer : np.ndarray[np.intp] or None
1210+ how to rearrange index
11881211 how : str
11891212 Replacement is only necessary if indexer based on other_index.
11901213
@@ -1202,6 +1225,8 @@ def _create_join_index(
12021225 if np .any (mask ):
12031226 fill_value = na_value_for_dtype (index .dtype , compat = False )
12041227 index = index .append (Index ([fill_value ]))
1228+ if indexer is None :
1229+ return index .copy ()
12051230 return index .take (indexer )
12061231
12071232 @final
@@ -1660,7 +1685,7 @@ def get_join_indexers(
16601685 right_keys : list [ArrayLike ],
16611686 sort : bool = False ,
16621687 how : JoinHow = "inner" ,
1663- ) -> tuple [npt .NDArray [np .intp ], npt .NDArray [np .intp ]]:
1688+ ) -> tuple [npt .NDArray [np .intp ] | None , npt .NDArray [np .intp ] | None ]:
16641689 """
16651690
16661691 Parameters
@@ -1672,9 +1697,9 @@ def get_join_indexers(
16721697
16731698 Returns
16741699 -------
1675- np.ndarray[np.intp]
1700+ np.ndarray[np.intp] or None
16761701 Indexer into the left_keys.
1677- np.ndarray[np.intp]
1702+ np.ndarray[np.intp] or None
16781703 Indexer into the right_keys.
16791704 """
16801705 assert len (left_keys ) == len (
@@ -1695,37 +1720,77 @@ def get_join_indexers(
16951720 elif not sort and how in ["left" , "outer" ]:
16961721 return _get_no_sort_one_missing_indexer (left_n , False )
16971722
1698- # get left & right join labels and num. of levels at each location
1699- mapped = (
1700- _factorize_keys (left_keys [n ], right_keys [n ], sort = sort )
1701- for n in range (len (left_keys ))
1702- )
1703- zipped = zip (* mapped )
1704- llab , rlab , shape = (list (x ) for x in zipped )
1723+ lkey : ArrayLike
1724+ rkey : ArrayLike
1725+ if len (left_keys ) > 1 :
1726+ # get left & right join labels and num. of levels at each location
1727+ mapped = (
1728+ _factorize_keys (left_keys [n ], right_keys [n ], sort = sort )
1729+ for n in range (len (left_keys ))
1730+ )
1731+ zipped = zip (* mapped )
1732+ llab , rlab , shape = (list (x ) for x in zipped )
17051733
1706- # get flat i8 keys from label lists
1707- lkey , rkey = _get_join_keys (llab , rlab , tuple (shape ), sort )
1734+ # get flat i8 keys from label lists
1735+ lkey , rkey = _get_join_keys (llab , rlab , tuple (shape ), sort )
1736+ else :
1737+ lkey = left_keys [0 ]
1738+ rkey = right_keys [0 ]
17081739
1709- # factorize keys to a dense i8 space
1710- # `count` is the num. of unique keys
1711- # set(lkey) | set(rkey) == range(count)
1740+ left = Index (lkey )
1741+ right = Index (rkey )
17121742
1713- lkey , rkey , count = _factorize_keys (lkey , rkey , sort = sort )
1714- # preserve left frame order if how == 'left' and sort == False
1715- kwargs = {}
1716- if how in ("inner" , "left" , "right" ):
1717- kwargs ["sort" ] = sort
1718- join_func = {
1719- "inner" : libjoin .inner_join ,
1720- "left" : libjoin .left_outer_join ,
1721- "right" : lambda x , y , count , ** kwargs : libjoin .left_outer_join (
1722- y , x , count , ** kwargs
1723- )[::- 1 ],
1724- "outer" : libjoin .full_outer_join ,
1725- }[how ]
1726-
1727- # error: Cannot call function of unknown type
1728- return join_func (lkey , rkey , count , ** kwargs ) # type: ignore[operator]
1743+ if (
1744+ left .is_monotonic_increasing
1745+ and right .is_monotonic_increasing
1746+ and (left .is_unique or right .is_unique )
1747+ ):
1748+ _ , lidx , ridx = left .join (right , how = how , return_indexers = True , sort = sort )
1749+ else :
1750+ lidx , ridx = get_join_indexers_non_unique (
1751+ left ._values , right ._values , sort , how
1752+ )
1753+
1754+ if lidx is not None and is_range_indexer (lidx , len (left )):
1755+ lidx = None
1756+ if ridx is not None and is_range_indexer (ridx , len (right )):
1757+ ridx = None
1758+ return lidx , ridx
1759+
1760+
1761+ def get_join_indexers_non_unique (
1762+ left : ArrayLike ,
1763+ right : ArrayLike ,
1764+ sort : bool = False ,
1765+ how : JoinHow = "inner" ,
1766+ ) -> tuple [npt .NDArray [np .intp ], npt .NDArray [np .intp ]]:
1767+ """
1768+ Get join indexers for left and right.
1769+
1770+ Parameters
1771+ ----------
1772+ left : ArrayLike
1773+ right : ArrayLike
1774+ sort : bool, default False
1775+ how : {'inner', 'outer', 'left', 'right'}, default 'inner'
1776+
1777+ Returns
1778+ -------
1779+ np.ndarray[np.intp]
1780+ Indexer into left.
1781+ np.ndarray[np.intp]
1782+ Indexer into right.
1783+ """
1784+ lkey , rkey , count = _factorize_keys (left , right , sort = sort )
1785+ if how == "left" :
1786+ lidx , ridx = libjoin .left_outer_join (lkey , rkey , count , sort = sort )
1787+ elif how == "right" :
1788+ ridx , lidx = libjoin .left_outer_join (rkey , lkey , count , sort = sort )
1789+ elif how == "inner" :
1790+ lidx , ridx = libjoin .inner_join (lkey , rkey , count , sort = sort )
1791+ elif how == "outer" :
1792+ lidx , ridx = libjoin .full_outer_join (lkey , rkey , count )
1793+ return lidx , ridx
17291794
17301795
17311796def restore_dropped_levels_multijoin (
@@ -1860,7 +1925,10 @@ def get_result(self, copy: bool | None = True) -> DataFrame:
18601925 left_indexer = cast ("npt.NDArray[np.intp]" , left_indexer )
18611926 right_indexer = cast ("npt.NDArray[np.intp]" , right_indexer )
18621927 left_join_indexer = libjoin .ffill_indexer (left_indexer )
1863- right_join_indexer = libjoin .ffill_indexer (right_indexer )
1928+ if right_indexer is None :
1929+ right_join_indexer = None
1930+ else :
1931+ right_join_indexer = libjoin .ffill_indexer (right_indexer )
18641932 elif self .fill_method is None :
18651933 left_join_indexer = left_indexer
18661934 right_join_indexer = right_indexer
0 commit comments