Skip to content

Commit 3ec69e8

Browse files
committed
ENH: Handle generic timestamp dtypes with Series
We only use the nanosecond frequency, so generic timestamp frequencies should be interpreted with the nanosecond frequency. xref gh-15524 (comment).
1 parent c4d71ce commit 3ec69e8

File tree

5 files changed

+67
-2
lines changed

5 files changed

+67
-2
lines changed

doc/source/whatsnew/v0.20.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ Other Enhancements
320320
^^^^^^^^^^^^^^^^^^
321321

322322
- Integration with the ``feather-format``, including a new top-level ``pd.read_feather()`` and ``DataFrame.to_feather()`` method, see :ref:`here <io.feather>`.
323+
- The ``Series`` constructor will now accept timestamp dtypes that do not specify frequency like ``np.datetime64`` (:issue:`15524`)
323324
- ``Series.str.replace()`` now accepts a callable, as replacement, which is passed to ``re.sub`` (:issue:`15055`)
324325
- ``Series.str.replace()`` now accepts a compiled regular expression as a pattern (:issue:`15446`)
325326

pandas/core/internals.py

+8
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,14 @@ def _astype(self, dtype, copy=False, errors='raise', values=None,
492492
return self.copy()
493493
return self
494494

495+
# numpy arrays don't handle generic timestamp dtypes well. Since
496+
# we only use the nanosecond frequency, interpret generic timestamp
497+
# dtypes as nanosecond frequency.
498+
if dtype.name == "datetime64":
499+
dtype = np.dtype('M8[ns]')
500+
elif dtype.name == "timedelta64":
501+
dtype = np.dtype('m8[ns]')
502+
495503
if klass is None:
496504
if dtype == np.object_:
497505
klass = ObjectBlock

pandas/tests/series/test_constructors.py

+24
Original file line numberDiff line numberDiff line change
@@ -839,3 +839,27 @@ def test_constructor_cast_object(self):
839839
s = Series(date_range('1/1/2000', periods=10), dtype=object)
840840
exp = Series(date_range('1/1/2000', periods=10))
841841
tm.assert_series_equal(s, exp)
842+
843+
def test_constructor_generic_timestamp(self):
844+
# see gh-15524
845+
dtype = np.timedelta64
846+
s = Series([], dtype=dtype)
847+
848+
self.assertTrue(s.empty)
849+
self.assertEqual(s.dtype, 'm8[ns]')
850+
851+
dtype = np.datetime64
852+
s = Series([], dtype=dtype)
853+
854+
self.assertTrue(s.empty)
855+
self.assertEqual(s.dtype, 'M8[ns]')
856+
857+
# These timestamps have the wrong frequencies,
858+
# so an Exception should be raised now.
859+
msg = "cannot convert timedeltalike"
860+
with tm.assertRaisesRegexp(TypeError, msg):
861+
Series([], dtype='m8[ps]')
862+
863+
msg = "cannot convert datetimelike"
864+
with tm.assertRaisesRegexp(TypeError, msg):
865+
Series([], dtype='M8[ps]')

pandas/tests/series/test_dtypes.py

+32
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,38 @@ def test_astype_dict(self):
153153
self.assertRaises(KeyError, s.astype, {'abc': str, 'def': str})
154154
self.assertRaises(KeyError, s.astype, {0: str})
155155

156+
def test_astype_generic_timestamp(self):
157+
# see gh-15524
158+
data = [1]
159+
160+
s = Series(data)
161+
dtype = np.datetime64
162+
result = s.astype(dtype)
163+
expected = Series(data, dtype=dtype)
164+
assert_series_equal(result, expected)
165+
166+
s = Series(data)
167+
dtype = np.timedelta64
168+
result = s.astype(dtype)
169+
expected = Series(data, dtype=dtype)
170+
assert_series_equal(result, expected)
171+
172+
def test_astype_empty_constructor_equality(self):
173+
# see gh-15524
174+
175+
for dtype in np.typecodes['All']:
176+
if dtype not in ('S', 'V'): # poor support (if any) currently
177+
init_empty = Series([], dtype=dtype)
178+
astype_empty = Series([]).astype(dtype)
179+
180+
try:
181+
assert_series_equal(init_empty, astype_empty)
182+
except AssertionError as e:
183+
name = np.dtype(dtype).name
184+
msg = "{dtype} failed: ".format(dtype=name) + str(e)
185+
186+
raise AssertionError(msg)
187+
156188
def test_complexx(self):
157189
# GH4819
158190
# complex access for ndarray compat

pandas/types/cast.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,7 @@ def maybe_cast_to_datetime(value, dtype, errors='raise'):
855855

856856
# force the dtype if needed
857857
if is_datetime64 and not is_dtype_equal(dtype, _NS_DTYPE):
858-
if dtype.name == 'datetime64[ns]':
858+
if dtype.name in ('datetime64', 'datetime64[ns]'):
859859
dtype = _NS_DTYPE
860860
else:
861861
raise TypeError("cannot convert datetimelike to "
@@ -869,7 +869,7 @@ def maybe_cast_to_datetime(value, dtype, errors='raise'):
869869
value = [value]
870870

871871
elif is_timedelta64 and not is_dtype_equal(dtype, _TD_DTYPE):
872-
if dtype.name == 'timedelta64[ns]':
872+
if dtype.name in ('timedelta64', 'timedelta64[ns]'):
873873
dtype = _TD_DTYPE
874874
else:
875875
raise TypeError("cannot convert timedeltalike to "

0 commit comments

Comments
 (0)