diff --git a/doc/source/whatsnew/v0.24.0.rst b/doc/source/whatsnew/v0.24.0.rst index 15476c3bc2e13..e6a08362a70f7 100644 --- a/doc/source/whatsnew/v0.24.0.rst +++ b/doc/source/whatsnew/v0.24.0.rst @@ -1545,6 +1545,7 @@ Reshaping - Bug in :meth:`DataFrame.append` with a :class:`Series` with a dateutil timezone would raise a ``TypeError`` (:issue:`23682`) - Bug in ``Series`` construction when passing no data and ``dtype=str`` (:issue:`22477`) - Bug in :func:`cut` with ``bins`` as an overlapping ``IntervalIndex`` where multiple bins were returned per item instead of raising a ``ValueError`` (:issue:`23980`) +- Bug in :func:`pandas.concat` when joining ``Series`` datetimetz with ``Series`` category would lose timezone (:issue:`23816`) - Bug in :meth:`DataFrame.join` when joining on partial MultiIndex would drop names (:issue:`20452`). .. _whatsnew_0240.bug_fixes.sparse: diff --git a/pandas/core/dtypes/concat.py b/pandas/core/dtypes/concat.py index 58f1bcbfa74c0..0df0c01dbd47a 100644 --- a/pandas/core/dtypes/concat.py +++ b/pandas/core/dtypes/concat.py @@ -191,15 +191,6 @@ def _concat_categorical(to_concat, axis=0): A single array, preserving the combined dtypes """ - def _concat_asobject(to_concat): - to_concat = [x.get_values() if is_categorical_dtype(x.dtype) - else np.asarray(x).ravel() for x in to_concat] - res = _concat_compat(to_concat) - if axis == 1: - return res.reshape(1, len(res)) - else: - return res - # we could have object blocks and categoricals here # if we only have a single categoricals then combine everything # else its a non-compat categorical @@ -214,7 +205,14 @@ def _concat_asobject(to_concat): if all(first.is_dtype_equal(other) for other in to_concat[1:]): return union_categoricals(categoricals) - return _concat_asobject(to_concat) + # extract the categoricals & coerce to object if needed + to_concat = [x.get_values() if is_categorical_dtype(x.dtype) + else np.asarray(x).ravel() if not is_datetime64tz_dtype(x) + else np.asarray(x.astype(object)) for x in to_concat] + result = _concat_compat(to_concat) + if axis == 1: + result = result.reshape(1, len(result)) + return result def union_categoricals(to_union, sort_categories=False, ignore_order=False): diff --git a/pandas/tests/reshape/test_concat.py b/pandas/tests/reshape/test_concat.py index 4113fb7f0f11e..bb002f151b455 100644 --- a/pandas/tests/reshape/test_concat.py +++ b/pandas/tests/reshape/test_concat.py @@ -2552,3 +2552,16 @@ def test_concat_series_name_npscalar_tuple(s1name, s2name): result = pd.concat([s1, s2]) expected = pd.Series({'a': 1, 'b': 2, 'c': 5, 'd': 6}) tm.assert_series_equal(result, expected) + + +def test_concat_categorical_tz(): + # GH-23816 + a = pd.Series(pd.date_range('2017-01-01', periods=2, tz='US/Pacific')) + b = pd.Series(['a', 'b'], dtype='category') + result = pd.concat([a, b], ignore_index=True) + expected = pd.Series([ + pd.Timestamp('2017-01-01', tz="US/Pacific"), + pd.Timestamp('2017-01-02', tz="US/Pacific"), + 'a', 'b' + ]) + tm.assert_series_equal(result, expected)