Skip to content

Commit e460135

Browse files
committed
This fix enables you to preserve the datetime precision when using the melt method. It fixes the issues raised in #55254.
1 parent d01669f commit e460135

File tree

3 files changed

+61
-3
lines changed

3 files changed

+61
-3
lines changed

doc/source/whatsnew/v2.2.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,8 @@ Bug fixes
250250
- Bug in :class:`AbstractHolidayCalendar` where timezone data was not propagated when computing holiday observances (:issue:`54580`)
251251
- Bug in :class:`pandas.core.window.Rolling` where duplicate datetimelike indexes are treated as consecutive rather than equal with ``closed='left'`` and ``closed='neither'`` (:issue:`20712`)
252252
- Bug in :meth:`DataFrame.apply` where passing ``raw=True`` ignored ``args`` passed to the applied function (:issue:`55009`)
253+
- Bug in :meth:`pandas.DataFrame.melt` where it would not preserve the datetime (:issue:`55254`)
253254
- Bug in :meth:`pandas.read_excel` with a ODS file without cached formatted cell for float values (:issue:`55219`)
254-
255255
Categorical
256256
^^^^^^^^^^^
257257
- :meth:`Categorical.isin` raising ``InvalidIndexError`` for categorical containing overlapping :class:`Interval` values (:issue:`34974`)

pandas/core/reshape/melt.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@
99

1010
from pandas.core.dtypes.common import is_list_like
1111
from pandas.core.dtypes.concat import concat_compat
12+
from pandas.core.dtypes.dtypes import (
13+
CategoricalDtype,
14+
DatetimeTZDtype,
15+
ExtensionDtype,
16+
IntervalDtype,
17+
PeriodDtype,
18+
SparseDtype,
19+
)
1220
from pandas.core.dtypes.missing import notna
1321

1422
import pandas.core.algorithms as algos
@@ -120,6 +128,14 @@ def melt(
120128
K -= len(id_vars)
121129

122130
mdata: dict[Hashable, AnyArrayLike] = {}
131+
pd_dtypes = (
132+
CategoricalDtype,
133+
DatetimeTZDtype,
134+
ExtensionDtype,
135+
IntervalDtype,
136+
PeriodDtype,
137+
SparseDtype,
138+
)
123139
for col in id_vars:
124140
id_data = frame.pop(col)
125141
if not isinstance(id_data.dtype, np.dtype):
@@ -133,8 +149,9 @@ def melt(
133149
mdata[col] = np.tile(id_data._values, K)
134150

135151
mcolumns = id_vars + var_name + [value_name]
136-
137-
if frame.shape[1] > 0:
152+
if frame.shape[1] > 0 and not any(
153+
isinstance(dt, pd_dtypes) for dt in frame.dtypes.values
154+
):
138155
mdata[value_name] = concat(
139156
[frame.iloc[:, i] for i in range(frame.shape[1])]
140157
).values

pandas/tests/reshape/test_melt.py

+41
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,47 @@ def test_melt_ea_columns(self):
459459
)
460460
tm.assert_frame_equal(result, expected)
461461

462+
def test_melt_preserves_datetime(self):
463+
df = DataFrame(
464+
data=[
465+
{
466+
"type": "A0",
467+
"start_date": pd.Timestamp("2023/03/01", tz="Asia/Tokyo"),
468+
"end_date": pd.Timestamp("2023/03/10", tz="Asia/Tokyo"),
469+
},
470+
{
471+
"type": "A1",
472+
"start_date": pd.Timestamp("2023/03/01", tz="Asia/Tokyo"),
473+
"end_date": pd.Timestamp("2023/03/11", tz="Asia/Tokyo"),
474+
},
475+
],
476+
index=["aaaa", "bbbb"],
477+
)
478+
result = df.melt(
479+
id_vars=["type"],
480+
value_vars=["start_date", "end_date"],
481+
var_name="start/end",
482+
value_name="date",
483+
)
484+
expected = DataFrame(
485+
{
486+
"type": {0: "A0", 1: "A1", 2: "A0", 3: "A1"},
487+
"start/end": {
488+
0: "start_date",
489+
1: "start_date",
490+
2: "end_date",
491+
3: "end_date",
492+
},
493+
"date": {
494+
0: pd.Timestamp("2023-03-01 00:00:00+0900", tz="Asia/Tokyo"),
495+
1: pd.Timestamp("2023-03-01 00:00:00+0900", tz="Asia/Tokyo"),
496+
2: pd.Timestamp("2023-03-10 00:00:00+0900", tz="Asia/Tokyo"),
497+
3: pd.Timestamp("2023-03-11 00:00:00+0900", tz="Asia/Tokyo"),
498+
},
499+
}
500+
)
501+
tm.assert_frame_equal(result, expected)
502+
462503

463504
class TestLreshape:
464505
def test_pairs(self):

0 commit comments

Comments
 (0)