Skip to content

Commit 3a34e07

Browse files
authored
REF: Move methods in core/reshape/util.py to where they are used (#59172)
* Move methods in core/reshape/util.py to where they are used * Remove unit tests
1 parent 9eaa4bc commit 3a34e07

File tree

5 files changed

+62
-112
lines changed

5 files changed

+62
-112
lines changed

pandas/core/indexes/multi.py

+57-1
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,6 @@ def from_product(
638638
(2, 'purple')],
639639
names=['number', 'color'])
640640
"""
641-
from pandas.core.reshape.util import cartesian_product
642641

643642
if not is_list_like(iterables):
644643
raise TypeError("Input must be a list / sequence of iterables.")
@@ -4105,3 +4104,60 @@ def _require_listlike(level, arr, arrname: str):
41054104
if not is_list_like(arr) or not is_list_like(arr[0]):
41064105
raise TypeError(f"{arrname} must be list of lists-like")
41074106
return level, arr
4107+
4108+
4109+
def cartesian_product(X: list[np.ndarray]) -> list[np.ndarray]:
4110+
"""
4111+
Numpy version of itertools.product.
4112+
Sometimes faster (for large inputs)...
4113+
4114+
Parameters
4115+
----------
4116+
X : list-like of list-likes
4117+
4118+
Returns
4119+
-------
4120+
product : list of ndarrays
4121+
4122+
Examples
4123+
--------
4124+
>>> cartesian_product([list("ABC"), [1, 2]])
4125+
[array(['A', 'A', 'B', 'B', 'C', 'C'], dtype='<U1'), array([1, 2, 1, 2, 1, 2])]
4126+
4127+
See Also
4128+
--------
4129+
itertools.product : Cartesian product of input iterables. Equivalent to
4130+
nested for-loops.
4131+
"""
4132+
msg = "Input must be a list-like of list-likes"
4133+
if not is_list_like(X):
4134+
raise TypeError(msg)
4135+
for x in X:
4136+
if not is_list_like(x):
4137+
raise TypeError(msg)
4138+
4139+
if len(X) == 0:
4140+
return []
4141+
4142+
lenX = np.fromiter((len(x) for x in X), dtype=np.intp)
4143+
cumprodX = np.cumprod(lenX)
4144+
4145+
if np.any(cumprodX < 0):
4146+
raise ValueError("Product space too large to allocate arrays!")
4147+
4148+
a = np.roll(cumprodX, 1)
4149+
a[0] = 1
4150+
4151+
if cumprodX[-1] != 0:
4152+
b = cumprodX[-1] / cumprodX
4153+
else:
4154+
# if any factor is empty, the cartesian product is empty
4155+
b = np.zeros_like(cumprodX)
4156+
4157+
return [
4158+
np.tile(
4159+
np.repeat(x, b[i]),
4160+
np.prod(a[i]),
4161+
)
4162+
for i, x in enumerate(X)
4163+
]

pandas/core/reshape/melt.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import pandas.core.algorithms as algos
1616
from pandas.core.indexes.api import MultiIndex
1717
from pandas.core.reshape.concat import concat
18-
from pandas.core.reshape.util import tile_compat
1918
from pandas.core.tools.numeric import to_numeric
2019

2120
if TYPE_CHECKING:
@@ -266,7 +265,8 @@ def melt(
266265
result = frame._constructor(mdata, columns=mcolumns)
267266

268267
if not ignore_index:
269-
result.index = tile_compat(frame.index, num_cols_adjusted)
268+
taker = np.tile(np.arange(len(frame)), num_cols_adjusted)
269+
result.index = frame.index.take(taker)
270270

271271
return result
272272

pandas/core/reshape/pivot.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
get_objs_combined_axis,
3232
)
3333
from pandas.core.reshape.concat import concat
34-
from pandas.core.reshape.util import cartesian_product
3534
from pandas.core.series import Series
3635

3736
if TYPE_CHECKING:
@@ -358,15 +357,11 @@ def __internal_pivot_table(
358357

359358
if not dropna:
360359
if isinstance(table.index, MultiIndex):
361-
m = MultiIndex.from_arrays(
362-
cartesian_product(table.index.levels), names=table.index.names
363-
)
360+
m = MultiIndex.from_product(table.index.levels, names=table.index.names)
364361
table = table.reindex(m, axis=0, fill_value=fill_value)
365362

366363
if isinstance(table.columns, MultiIndex):
367-
m = MultiIndex.from_arrays(
368-
cartesian_product(table.columns.levels), names=table.columns.names
369-
)
364+
m = MultiIndex.from_product(table.columns.levels, names=table.columns.names)
370365
table = table.reindex(m, axis=1, fill_value=fill_value)
371366

372367
if sort is True and isinstance(table, ABCDataFrame):

pandas/core/reshape/util.py

-85
This file was deleted.

pandas/tests/reshape/test_util.py renamed to pandas/tests/indexes/multi/test_util.py

+1-17
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
date_range,
77
)
88
import pandas._testing as tm
9-
from pandas.core.reshape.util import cartesian_product
9+
from pandas.core.indexes.multi import cartesian_product
1010

1111

1212
class TestCartesianProduct:
@@ -28,22 +28,6 @@ def test_datetimeindex(self):
2828
tm.assert_index_equal(result1, expected1)
2929
tm.assert_index_equal(result2, expected2)
3030

31-
def test_tzaware_retained(self):
32-
x = date_range("2000-01-01", periods=2, tz="US/Pacific")
33-
y = np.array([3, 4])
34-
result1, result2 = cartesian_product([x, y])
35-
36-
expected = x.repeat(2)
37-
tm.assert_index_equal(result1, expected)
38-
39-
def test_tzaware_retained_categorical(self):
40-
x = date_range("2000-01-01", periods=2, tz="US/Pacific").astype("category")
41-
y = np.array([3, 4])
42-
result1, result2 = cartesian_product([x, y])
43-
44-
expected = x.repeat(2)
45-
tm.assert_index_equal(result1, expected)
46-
4731
@pytest.mark.parametrize("x, y", [[[], []], [[0, 1], []], [[], ["a", "b", "c"]]])
4832
def test_empty(self, x, y):
4933
# product of empty factors

0 commit comments

Comments
 (0)