Skip to content

Commit eeb81f6

Browse files
sinhrksjreback
authored andcommitted
BUG: Concat with tz-aware and timedelta raises AttributeError
closes #12620 Author: sinhrks <[email protected]> Closes #12635 from sinhrks/tz_concat_object and squashes the following commits: 45d1ecd [sinhrks] BUG: Concat with tz-aware and timedelta raises AttributeError
1 parent b0d1e2a commit eeb81f6

File tree

4 files changed

+32
-23
lines changed

4 files changed

+32
-23
lines changed

doc/source/whatsnew/v0.18.1.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ Bug Fixes
128128

129129

130130

131-
131+
- Bug in ``concat`` raises ``AttributeError`` when input data contains tz-aware datetime and timedelta (:issue:`12620`)
132132

133133

134134

pandas/core/common.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2713,7 +2713,7 @@ def is_nonempty(x):
27132713
# these are mandated to handle empties as well
27142714
if 'datetime' in typs or 'datetimetz' in typs or 'timedelta' in typs:
27152715
from pandas.tseries.common import _concat_compat
2716-
return _concat_compat(to_concat, axis=axis)
2716+
return _concat_compat(to_concat, axis=axis, typs=typs)
27172717

27182718
elif 'sparse' in typs:
27192719
from pandas.sparse.array import _concat_compat

pandas/tools/tests/test_merge.py

+14
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,20 @@ def test_concat_tz_series(self):
11611161
result = pd.concat([first, second])
11621162
self.assertEqual(result[0].dtype, 'datetime64[ns, Europe/London]')
11631163

1164+
def test_concat_tz_series_with_datetimelike(self):
1165+
# GH 12620
1166+
# tz and timedelta
1167+
x = [pd.Timestamp('2011-01-01', tz='US/Eastern'),
1168+
pd.Timestamp('2011-02-01', tz='US/Eastern')]
1169+
y = [pd.Timedelta('1 day'), pd.Timedelta('2 day')]
1170+
result = concat([pd.Series(x), pd.Series(y)], ignore_index=True)
1171+
tm.assert_series_equal(result, pd.Series(x + y, dtype='object'))
1172+
1173+
# tz and period
1174+
y = [pd.Period('2011-03', freq='M'), pd.Period('2011-04', freq='M')]
1175+
result = concat([pd.Series(x), pd.Series(y)], ignore_index=True)
1176+
tm.assert_series_equal(result, pd.Series(x + y, dtype='object'))
1177+
11641178
def test_concat_period_series(self):
11651179
x = Series(pd.PeriodIndex(['2015-11-01', '2015-12-01'], freq='D'))
11661180
y = Series(pd.PeriodIndex(['2015-10-01', '2016-01-01'], freq='D'))

pandas/tseries/common.py

+16-21
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ class CombinedDatetimelikeProperties(DatetimeProperties, TimedeltaProperties):
238238
__doc__ = DatetimeProperties.__doc__
239239

240240

241-
def _concat_compat(to_concat, axis=0):
241+
def _concat_compat(to_concat, axis=0, typs=None):
242242
"""
243243
provide concatenation of an datetimelike array of arrays each of which is a
244244
single M8[ns], datetimet64[ns, tz] or m8[ns] dtype
@@ -272,38 +272,33 @@ def convert_to_pydatetime(x, axis):
272272

273273
return x
274274

275-
typs = get_dtype_kinds(to_concat)
275+
if typs is None:
276+
typs = get_dtype_kinds(to_concat)
276277

277-
# datetimetz
278-
if 'datetimetz' in typs:
279-
280-
# if to_concat have 'datetime' or 'object'
281-
# then we need to coerce to object
282-
if 'datetime' in typs or 'object' in typs:
283-
to_concat = [convert_to_pydatetime(x, axis) for x in to_concat]
284-
return np.concatenate(to_concat, axis=axis)
278+
# must be single dtype
279+
if len(typs) == 1:
285280

286-
# we require ALL of the same tz for datetimetz
287-
tzs = set([getattr(x, 'tz', None) for x in to_concat]) - set([None])
288-
if len(tzs) == 1:
289-
return DatetimeIndex(np.concatenate([x.tz_localize(None).asi8
290-
for x in to_concat]),
291-
tz=list(tzs)[0])
281+
if 'datetimetz' in typs:
282+
# datetime with no tz should be stored as "datetime" in typs,
283+
# thus no need to care
292284

293-
# single dtype
294-
if len(typs) == 1:
285+
# we require ALL of the same tz for datetimetz
286+
tzs = set([x.tz for x in to_concat])
287+
if len(tzs) == 1:
288+
return DatetimeIndex(np.concatenate([x.tz_localize(None).asi8
289+
for x in to_concat]),
290+
tz=list(tzs)[0])
295291

296-
if not len(typs - set(['datetime'])):
292+
elif 'datetime' in typs:
297293
new_values = np.concatenate([x.view(np.int64) for x in to_concat],
298294
axis=axis)
299295
return new_values.view(_NS_DTYPE)
300296

301-
elif not len(typs - set(['timedelta'])):
297+
elif 'timedelta' in typs:
302298
new_values = np.concatenate([x.view(np.int64) for x in to_concat],
303299
axis=axis)
304300
return new_values.view(_TD_DTYPE)
305301

306302
# need to coerce to object
307303
to_concat = [convert_to_pydatetime(x, axis) for x in to_concat]
308-
309304
return np.concatenate(to_concat, axis=axis)

0 commit comments

Comments
 (0)