Skip to content

Commit d581e3e

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 pandas-devgh-15524 (comment).
1 parent c4d71ce commit d581e3e

File tree

4 files changed

+67
-2
lines changed

4 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/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+
assert s.empty
849+
assert s.dtype == 'm8[ns]'
850+
851+
dtype = np.datetime64
852+
s = Series([], dtype=dtype)
853+
854+
assert s.empty
855+
assert 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

+10-2
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,14 @@ def astype_nansafe(arr, dtype, copy=True):
603603
# work around NumPy brokenness, #1987
604604
return lib.astype_intsafe(arr.ravel(), dtype).reshape(arr.shape)
605605

606+
# NumPy arrays don't handle generic timestamp dtypes well. Since
607+
# we only use the nanosecond frequency, interpret generic timestamp
608+
# dtypes as nanosecond frequency.
609+
if dtype.name == "datetime64":
610+
dtype = np.dtype('M8[ns]')
611+
elif dtype.name == "timedelta64":
612+
dtype = np.dtype('m8[ns]')
613+
606614
if copy:
607615
return arr.astype(dtype)
608616
return arr.view(dtype)
@@ -855,7 +863,7 @@ def maybe_cast_to_datetime(value, dtype, errors='raise'):
855863

856864
# force the dtype if needed
857865
if is_datetime64 and not is_dtype_equal(dtype, _NS_DTYPE):
858-
if dtype.name == 'datetime64[ns]':
866+
if dtype.name in ('datetime64', 'datetime64[ns]'):
859867
dtype = _NS_DTYPE
860868
else:
861869
raise TypeError("cannot convert datetimelike to "
@@ -869,7 +877,7 @@ def maybe_cast_to_datetime(value, dtype, errors='raise'):
869877
value = [value]
870878

871879
elif is_timedelta64 and not is_dtype_equal(dtype, _TD_DTYPE):
872-
if dtype.name == 'timedelta64[ns]':
880+
if dtype.name in ('timedelta64', 'timedelta64[ns]'):
873881
dtype = _TD_DTYPE
874882
else:
875883
raise TypeError("cannot convert timedeltalike to "

0 commit comments

Comments
 (0)