Skip to content

BUG: Fix+test dataframe tranpose with datetimeTZ #23730

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
4 changes: 3 additions & 1 deletion pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,9 @@ def _get_axes(N, K, index=index, columns=columns):
# if we don't have a dtype specified, then try to convert objects
# on the entire block; this is to convert if we have datetimelike's
# embedded in an object type
if dtype is None and is_object_dtype(values):
if dtype is None and is_object_dtype(values) and values.shape[0] == 1:
# only do this inference for single-column DataFrame, otherwise
# create_block_manager_from_blocks will raise a ValueError
values = maybe_infer_to_datetimelike(values)

return create_block_manager_from_blocks([values], [columns, index])
Expand Down
50 changes: 45 additions & 5 deletions pandas/core/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,22 @@
is_bool_dtype,
is_numeric_dtype,
is_datetime64_any_dtype,
is_timedelta64_dtype,
is_datetime64_dtype,
is_datetime64tz_dtype,
is_timedelta64_dtype,
is_list_like,
is_dict_like,
is_re_compilable,
is_period_arraylike,
is_object_dtype,
is_extension_array_dtype,
pandas_dtype)
from pandas.core.dtypes.cast import maybe_promote, maybe_upcast_putmask
from pandas.core.dtypes.cast import (
maybe_promote, maybe_upcast_putmask, maybe_infer_to_datetimelike)
from pandas.core.dtypes.inference import is_hashable
from pandas.core.dtypes.missing import isna, notna
from pandas.core.dtypes.generic import ABCSeries, ABCPanel, ABCDataFrame
from pandas.core.dtypes.generic import (
ABCSeries, ABCPanel, ABCDataFrame, ABCDatetimeIndex)

from pandas.core.base import PandasObject, SelectionMixin
from pandas.core.index import (Index, MultiIndex, ensure_index,
Expand Down Expand Up @@ -683,12 +686,20 @@ def transpose(self, *args, **kwargs):

new_axes = self._construct_axes_dict_from(self, [self._get_axis(x)
for x in axes_names])
new_values = self.values.transpose(axes_numbers)
values = self.values
if isinstance(values, ABCDatetimeIndex):
# we must case to numpy array otherwise transpose raises ValueError
values = np.array(values.astype(np.object)).reshape(self.shape)

new_values = values.transpose(axes_numbers)
if kwargs.pop('copy', None) or (len(args) and args[-1]):
new_values = new_values.copy()

nv.validate_transpose_for_generic(self, kwargs)
return self._constructor(new_values, **new_axes).__finalize__(self)
result = self._constructor(new_values, **new_axes)

result = maybe_restore_dtypes(result, self)
return result.__finalize__(self)

def swapaxes(self, axis1, axis2, copy=True):
"""
Expand Down Expand Up @@ -10753,6 +10764,35 @@ def logical_func(self, axis=0, bool_only=None, skipna=True, level=None,
return set_function_name(logical_func, name, cls)


def maybe_restore_dtypes(result, orig):
# GH#23730
if orig.ndim != 2:
return result

if orig.size == 0:
# ensure both orig.dtypes and result.dtypes have length >= 1
return result

if ((result.dtypes == np.object_).all() and
not (orig.dtypes == np.object_).any()):
# the transpose was lossy
if (orig.dtypes == orig.dtypes[0]).all():
if is_datetime64tz_dtype(orig.dtypes[0]):
tz = orig.dtypes[0].tz
for col in result.columns:
result[col] = maybe_infer_to_datetimelike(result[col])
if (is_datetime64_dtype(result[col]) and
isna(result[col]).all()):
# all-NaT gets inferred as tz-naive
result[col] = pd.DatetimeIndex(result[col], tz=tz)

else:
# TODO: consider doing something useful in this case?
pass

return result


# install the indexes
for _name, _indexer in indexing.get_indexers_list():
NDFrame._create_indexer(_name, _indexer)
8 changes: 6 additions & 2 deletions pandas/core/internals/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
find_common_type, infer_dtype_from_scalar, maybe_convert_objects,
maybe_promote)
from pandas.core.dtypes.common import (
_NS_DTYPE, is_datetimelike_v_numeric, is_extension_array_dtype,
is_extension_type, is_numeric_v_string_like, is_scalar)
_NS_DTYPE, is_datetime64tz_dtype, is_datetimelike_v_numeric,
is_extension_array_dtype, is_extension_type, is_numeric_v_string_like,
is_scalar)
import pandas.core.dtypes.concat as _concat
from pandas.core.dtypes.generic import ABCExtensionArray, ABCSeries
from pandas.core.dtypes.missing import isna
Expand Down Expand Up @@ -773,6 +774,9 @@ def _interleave(self):
dtype = dtype.subtype
elif is_extension_array_dtype(dtype):
dtype = 'object'
elif is_datetime64tz_dtype(dtype):
# TODO: avoid this conversion by allowing 2D DatetimeArray
dtype = 'object'

result = np.empty(self.shape, dtype=dtype)

Expand Down
27 changes: 27 additions & 0 deletions pandas/tests/arithmetic/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,19 @@ def box_df_fail(request):
return request.param


@pytest.fixture(params=[(pd.Index, False),
(pd.Series, False),
(pd.DataFrame, False),
(pd.DataFrame, True)],
ids=lambda x: x[0].__name__ + '-' + str(x[1]))
def box_with_transpose(request):
"""
Fixture similar to `box` but testing both transpose cases for DataFrame
"""
# GH#23620
return request.param


@pytest.fixture(params=[(pd.Index, False),
(pd.Series, False),
(pd.DataFrame, False),
Expand All @@ -178,6 +191,20 @@ def box_transpose_fail(request):
return request.param


@pytest.fixture(params=[(pd.Index, False),
(pd.Series, False),
(pd.DataFrame, False),
(pd.DataFrame, True),
(tm.to_array, False)],
ids=id_func)
def box_T_with_array(request):
"""
Like `box`, but specific to datetime64 for also testing pandas Array,
and both transpose cases for DataFrame
"""
return request.param


@pytest.fixture(params=[pd.Index, pd.Series, pd.DataFrame, tm.to_array],
ids=id_func)
def box_with_array(request):
Expand Down
Loading