Skip to content

Commit c71e898

Browse files
committed
fix for pandas-dev#60695 fix Series constructor dropping key levels when keys have varying entry counts
1 parent 0f77b11 commit c71e898

File tree

2 files changed

+27
-41
lines changed

2 files changed

+27
-41
lines changed

pandas/core/indexes/multi.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -545,25 +545,28 @@ def from_tuples(
545545
if not is_list_like(tuples):
546546
raise TypeError("Input must be a list / sequence of tuple-likes.")
547547

548-
if is_iterator(tuples):
549-
tuples = list(tuples)
550-
551-
tuples = cast(Collection[tuple[Hashable, ...]], tuples)
552-
553-
if len(tuples) == 0:
548+
# Handle empty tuples case first
549+
if isinstance(tuples, (list, tuple)) and len(tuples) == 0:
554550
if names is None:
555551
raise TypeError("Cannot infer number of levels from empty list")
556552
names_seq = cast(Sequence[Hashable], names)
557553
arrays: List[ArrayLike] = [[]] * len(names_seq)
558554
return cls.from_arrays(arrays, sortorder=sortorder, names=names)
559555

556+
# Convert iterator to list
557+
if is_iterator(tuples):
558+
tuples = list(tuples)
559+
560+
tuples = cast(Collection[tuple[Hashable, ...]], tuples)
561+
562+
# Handle numpy array or Index
560563
if isinstance(tuples, (np.ndarray, Index)):
561564
if isinstance(tuples, Index):
562565
tuples = np.asarray(tuples._values)
563566
arrays = list(lib.tuples_to_object_array(tuples).T)
564567
return cls.from_arrays(arrays, sortorder=sortorder, names=names)
565568

566-
# Convert to list and process
569+
# Convert to list and normalize
567570
tuples_list = list(tuples)
568571
max_length = max(len(t) if isinstance(t, tuple) else 1 for t in tuples_list)
569572

pandas/tests/indexes/multi/test_constructors.py

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -862,62 +862,45 @@ def test_dtype_representation(using_infer_string):
862862
def test_from_tuples_different_lengths_gh60695():
863863
"""
864864
Test that MultiIndex.from_tuples properly handles tuples of different lengths.
865-
865+
866866
GH#60695
867867
"""
868868
# Test case 1: Basic string tuples
869869
tuples = [("l1",), ("l1", "l2")]
870870
result = MultiIndex.from_tuples(tuples)
871871
expected = MultiIndex.from_tuples([("l1", np.nan), ("l1", "l2")])
872-
tm.assert_index_equal(result, expected)
872+
tm.assert_index_equal(result, expected, exact=True)
873873

874874
# Test case 2: Series with tuple keys
875875
s = pd.Series({("l1",): "v1", ("l1", "l2"): "v2"})
876876
expected = pd.Series(
877877
["v1", "v2"],
878878
index=MultiIndex.from_tuples([("l1", np.nan), ("l1", "l2")])
879879
)
880-
tm.assert_series_equal(s, expected)
881-
882-
# Test case 3: Numeric tuples
883-
tuples = [(1,), (1, 2)]
884-
result = MultiIndex.from_tuples(tuples)
885-
expected = MultiIndex.from_tuples([(1, np.nan), (1, 2)])
886-
tm.assert_index_equal(result, expected)
887-
888-
# Test case 4: Mixed types
889-
tuples = [(1, "a"), (1,), (2, "b", "c")]
890-
result = MultiIndex.from_tuples(tuples)
891-
expected = MultiIndex.from_tuples([
892-
(1, "a", np.nan),
893-
(1, np.nan, np.nan),
894-
(2, "b", "c")
895-
])
896-
tm.assert_index_equal(result, expected)
880+
tm.assert_series_equal(s, expected, check_index_type=True)
897881

898-
# Test case 5: Empty input with names
882+
# Test case 3: Empty input with names
899883
empty_idx = MultiIndex.from_tuples([], names=["a", "b"])
884+
assert isinstance(empty_idx, MultiIndex)
900885
assert empty_idx.names == ["a", "b"]
901886
assert len(empty_idx) == 0
902887

903-
# Test case 6: Empty input without names
888+
# Test case 4: Empty input without names
904889
with pytest.raises(TypeError, match="Cannot infer number of levels"):
905890
MultiIndex.from_tuples([])
906891

907-
# Test case 7: None values
892+
# Test case 5: None values
908893
tuples = [(1, None), (1, 2)]
909894
result = MultiIndex.from_tuples(tuples)
910895
expected = MultiIndex.from_tuples([(1, np.nan), (1, 2)])
911-
tm.assert_index_equal(result, expected)
896+
tm.assert_index_equal(result, expected, exact=True)
912897

913-
# Test case 8: DataFrame with tuple index
914-
df = pd.DataFrame(
915-
{"col": ["v1", "v2"]},
916-
index=MultiIndex.from_tuples([("l1",), ("l1", "l2")])
917-
)
918-
expected_index = MultiIndex.from_tuples([("l1", np.nan), ("l1", "l2")])
919-
expected_df = pd.DataFrame(
920-
{"col": ["v1", "v2"]},
921-
index=expected_index
922-
)
923-
tm.assert_frame_equal(df, expected_df)
898+
# Test case 6: Mixed types
899+
tuples = [(1, "a"), (1,), (2, "b", "c")]
900+
result = MultiIndex.from_tuples(tuples)
901+
expected = MultiIndex.from_tuples([
902+
(1, "a", np.nan),
903+
(1, np.nan, np.nan),
904+
(2, "b", "c")
905+
])
906+
tm.assert_index_equal(result, expected, exact=True)

0 commit comments

Comments
 (0)