Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ MultiIndex
- :meth:`MultiIndex.insert` would not insert NA value correctly at unified location of index -1 (:issue:`59003`)
- :func:`MultiIndex.get_level_values` accessing a :class:`DatetimeIndex` does not carry the frequency attribute along (:issue:`58327`, :issue:`57949`)
- Bug in :class:`DataFrame` arithmetic operations in case of unaligned MultiIndex columns (:issue:`60498`)
-
- Bug in :meth:`MultiIndex.from_tuples` causing wrong output with input of type tuples having NaN values (:issue:`60695`)

I/O
^^^
Expand Down
3 changes: 2 additions & 1 deletion pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Sequence,
)
from functools import wraps
from itertools import zip_longest
from sys import getsizeof
from typing import (
TYPE_CHECKING,
Expand Down Expand Up @@ -588,7 +589,7 @@ def from_tuples(
elif isinstance(tuples, list):
arrays = list(lib.to_object_array_tuples(tuples).T)
else:
arrs = zip(*tuples)
arrs = zip_longest(*tuples, fillvalue=np.nan)
arrays = cast(list[Sequence[Hashable]], arrs)

return cls.from_arrays(arrays, sortorder=sortorder, names=names)
Expand Down
22 changes: 22 additions & 0 deletions pandas/tests/indexes/multi/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,28 @@ def test_from_tuples_with_tuple_label():
tm.assert_frame_equal(expected, result)


@pytest.mark.parametrize(
"keys, expected",
(
((("l1",), ("l1", "l2")), (("l1", np.nan), ("l1", "l2"))),
(
(
(
"l1",
"l2",
),
("l1",),
),
(("l1", "l2"), ("l1", np.nan)),
),
),
)
def test_from_tuples_with_various_tuple_lengths(keys, expected):
# GH 60695
idx = MultiIndex.from_tuples(keys)
assert tuple(idx) == expected


# ----------------------------------------------------------------------------
# from_product
# ----------------------------------------------------------------------------
Expand Down
4 changes: 3 additions & 1 deletion pandas/tests/series/methods/test_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,9 @@ def test_map_dict_with_tuple_keys():
df = DataFrame({"a": [(1,), (2,), (3, 4), (5, 6)]})
label_mappings = {(1,): "A", (2,): "B", (3, 4): "A", (5, 6): "B"}

df["labels"] = df["a"].map(label_mappings)
# GH 60695
df["labels"] = df["a"].apply(lambda x: label_mappings.get(x, None))

df["expected_labels"] = Series(["A", "B", "A", "B"], index=df.index)
# All labels should be filled now
tm.assert_series_equal(df["labels"], df["expected_labels"], check_names=False)
Expand Down
15 changes: 15 additions & 0 deletions pandas/tests/series/test_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,12 @@ def test_constructor_dict_of_tuples(self):
expected = Series([3, 6], index=MultiIndex.from_tuples([(1, 2), (None, 5)]))
tm.assert_series_equal(result, expected)

# GH 60695
data = {(1,): 3, (4, 5): 6}
result = Series(data).sort_values()
expected = Series([3, 6], index=MultiIndex.from_tuples([(1, None), (4, 5)]))
tm.assert_series_equal(result, expected)

# https://github.com/pandas-dev/pandas/issues/22698
@pytest.mark.filterwarnings("ignore:elementwise comparison:FutureWarning")
def test_fromDict(self, using_infer_string):
Expand Down Expand Up @@ -1878,6 +1884,15 @@ def test_constructor_dict_multiindex(self):
result = result.reindex(index=expected.index)
tm.assert_series_equal(result, expected)

# GH 60695
d = {("a",): 0.0, ("a", "b"): 1.0}
_d = sorted(d.items())
result = Series(d)
expected = Series(
[x[1] for x in _d], index=MultiIndex.from_tuples([x[0] for x in _d])
)
tm.assert_series_equal(result, expected)

def test_constructor_dict_multiindex_reindex_flat(self):
# construction involves reindexing with a MultiIndex corner case
data = {("i", "i"): 0, ("i", "j"): 1, ("j", "i"): 2, "j": np.nan}
Expand Down
Loading