Skip to content

Commit 9d9ed9c

Browse files
phoflmroeschke
authored andcommitted
BUG: DataFrame.pivot not respecting None as column name (pandas-dev#48293)
* BUG: DataFrame.pivot not respecting None as column name * Add gh ref * Fix test * Add comment * Update doc/source/whatsnew/v1.6.0.rst Co-authored-by: Matthew Roeschke <[email protected]> Co-authored-by: Matthew Roeschke <[email protected]>
1 parent f983159 commit 9d9ed9c

File tree

5 files changed

+80
-14
lines changed

5 files changed

+80
-14
lines changed

doc/source/whatsnew/v1.6.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ Groupby/resample/rolling
196196

197197
Reshaping
198198
^^^^^^^^^
199+
- Bug in :meth:`DataFrame.pivot` not respecting ``None`` as column name (:issue:`48293`)
199200
- Bug in :func:`join` when ``left_on`` or ``right_on`` is or includes a :class:`CategoricalIndex` incorrectly raising ``AttributeError`` (:issue:`48464`)
200201
-
201202

pandas/core/frame.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -8583,7 +8583,9 @@ def groupby(
85838583
@Substitution("")
85848584
@Appender(_shared_docs["pivot"])
85858585
@deprecate_nonkeyword_arguments(version=None, allowed_args=["self"])
8586-
def pivot(self, index=None, columns=None, values=None) -> DataFrame:
8586+
def pivot(
8587+
self, index=lib.NoDefault, columns=lib.NoDefault, values=lib.NoDefault
8588+
) -> DataFrame:
85878589
from pandas.core.reshape.pivot import pivot
85888590

85898591
return pivot(self, index=index, columns=columns, values=values)

pandas/core/reshape/pivot.py

+22-9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import numpy as np
1212

13+
from pandas._libs import lib
1314
from pandas._typing import (
1415
AggFuncType,
1516
AggFuncTypeBase,
@@ -482,30 +483,37 @@ def _convert_by(by):
482483
@deprecate_nonkeyword_arguments(version=None, allowed_args=["data"])
483484
def pivot(
484485
data: DataFrame,
485-
index: IndexLabel | None = None,
486-
columns: IndexLabel | None = None,
487-
values: IndexLabel | None = None,
486+
index: IndexLabel | lib.NoDefault = lib.NoDefault,
487+
columns: IndexLabel | lib.NoDefault = lib.NoDefault,
488+
values: IndexLabel | lib.NoDefault = lib.NoDefault,
488489
) -> DataFrame:
489-
if columns is None:
490+
if columns is lib.NoDefault:
490491
raise TypeError("pivot() missing 1 required argument: 'columns'")
491492

492493
columns_listlike = com.convert_to_list_like(columns)
493494

495+
# If columns is None we will create a MultiIndex level with None as name
496+
# which might cause duplicated names because None is the default for
497+
# level names
498+
data.index.names = [
499+
name if name is not None else lib.NoDefault for name in data.index.names
500+
]
501+
494502
indexed: DataFrame | Series
495-
if values is None:
496-
if index is not None:
503+
if values is lib.NoDefault:
504+
if index is not lib.NoDefault:
497505
cols = com.convert_to_list_like(index)
498506
else:
499507
cols = []
500508

501-
append = index is None
509+
append = index is lib.NoDefault
502510
# error: Unsupported operand types for + ("List[Any]" and "ExtensionArray")
503511
# error: Unsupported left operand type for + ("ExtensionArray")
504512
indexed = data.set_index(
505513
cols + columns_listlike, append=append # type: ignore[operator]
506514
)
507515
else:
508-
if index is None:
516+
if index is lib.NoDefault:
509517
if isinstance(data.index, MultiIndex):
510518
# GH 23955
511519
index_list = [
@@ -531,7 +539,12 @@ def pivot(
531539
# error: Argument 1 to "unstack" of "DataFrame" has incompatible type "Union
532540
# [List[Any], ExtensionArray, ndarray[Any, Any], Index, Series]"; expected
533541
# "Hashable"
534-
return indexed.unstack(columns_listlike) # type: ignore[arg-type]
542+
result = indexed.unstack(columns_listlike) # type: ignore[arg-type]
543+
result.index.names = [
544+
name if name is not lib.NoDefault else None for name in result.index.names
545+
]
546+
547+
return result
535548

536549

537550
def crosstab(

pandas/tests/reshape/test_pivot.py

+49-1
Original file line numberDiff line numberDiff line change
@@ -2341,7 +2341,7 @@ def test_pivot_index_list_values_none_immutable_args(self):
23412341
)
23422342
index = ["lev1", "lev2"]
23432343
columns = ["lev3"]
2344-
result = df.pivot(index=index, columns=columns, values=None)
2344+
result = df.pivot(index=index, columns=columns)
23452345

23462346
expected = DataFrame(
23472347
np.array(
@@ -2365,3 +2365,51 @@ def test_pivot_index_list_values_none_immutable_args(self):
23652365

23662366
assert index == ["lev1", "lev2"]
23672367
assert columns == ["lev3"]
2368+
2369+
def test_pivot_columns_not_given(self):
2370+
# GH#48293
2371+
df = DataFrame({"a": [1], "b": 1})
2372+
with pytest.raises(TypeError, match="missing 1 required argument"):
2373+
df.pivot()
2374+
2375+
def test_pivot_columns_is_none(self):
2376+
# GH#48293
2377+
df = DataFrame({None: [1], "b": 2, "c": 3})
2378+
result = df.pivot(columns=None)
2379+
expected = DataFrame({("b", 1): [2], ("c", 1): 3})
2380+
tm.assert_frame_equal(result, expected)
2381+
2382+
result = df.pivot(columns=None, index="b")
2383+
expected = DataFrame({("c", 1): 3}, index=Index([2], name="b"))
2384+
tm.assert_frame_equal(result, expected)
2385+
2386+
result = df.pivot(columns=None, index="b", values="c")
2387+
expected = DataFrame({1: 3}, index=Index([2], name="b"))
2388+
tm.assert_frame_equal(result, expected)
2389+
2390+
def test_pivot_index_is_none(self):
2391+
# GH#48293
2392+
df = DataFrame({None: [1], "b": 2, "c": 3})
2393+
2394+
result = df.pivot(columns="b", index=None)
2395+
expected = DataFrame({("c", 2): 3}, index=[1])
2396+
expected.columns.names = [None, "b"]
2397+
tm.assert_frame_equal(result, expected)
2398+
2399+
result = df.pivot(columns="b", index=None, values="c")
2400+
expected = DataFrame(3, index=[1], columns=Index([2], name="b"))
2401+
tm.assert_frame_equal(result, expected)
2402+
2403+
def test_pivot_values_is_none(self):
2404+
# GH#48293
2405+
df = DataFrame({None: [1], "b": 2, "c": 3})
2406+
2407+
result = df.pivot(columns="b", index="c", values=None)
2408+
expected = DataFrame(
2409+
1, index=Index([3], name="c"), columns=Index([2], name="b")
2410+
)
2411+
tm.assert_frame_equal(result, expected)
2412+
2413+
result = df.pivot(columns="b", values=None)
2414+
expected = DataFrame(1, index=[0], columns=Index([2], name="b"))
2415+
tm.assert_frame_equal(result, expected)

pandas/tests/reshape/test_pivot_multilevel.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import numpy as np
22
import pytest
33

4+
from pandas._libs import lib
5+
46
import pandas as pd
57
from pandas import (
68
Index,
@@ -33,7 +35,7 @@
3335
(
3436
["lev4"],
3537
"lev3",
36-
None,
38+
lib.NoDefault,
3739
[
3840
[1.0, np.nan, 1.0, np.nan, 0.0, np.nan],
3941
[np.nan, 1.0, np.nan, 1.0, np.nan, 1.0],
@@ -70,7 +72,7 @@
7072
(
7173
["lev1", "lev2"],
7274
"lev3",
73-
None,
75+
lib.NoDefault,
7476
[[1, 2, 0, 1], [3, 4, 2, 3], [5, 6, 4, 5], [7, 8, 6, 7]],
7577
MultiIndex.from_tuples(
7678
[("lev4", 1), ("lev4", 2), ("values", 1), ("values", 2)],
@@ -243,7 +245,7 @@ def test_pivot_df_multiindex_index_none():
243245
)
244246
df = df.set_index(["index_1", "index_2"])
245247

246-
result = df.pivot(index=None, columns="label", values="value")
248+
result = df.pivot(columns="label", values="value")
247249
expected = pd.DataFrame(
248250
[[1.0, np.nan], [np.nan, 2.0], [3.0, np.nan], [np.nan, 4.0]],
249251
index=df.index,

0 commit comments

Comments
 (0)