diff --git a/RELEASE.rst b/RELEASE.rst index 26bb6453adfa1..9e133ed324694 100755 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -77,6 +77,8 @@ pandas 0.11.0 correctly - astype on datetimes to object are now handled (as well as NaT conversions to np.nan) + - all timedelta like objects will be correctly assigned to ``timedelta64`` + with mixed ``NaN`` and/or ``NaT`` allowed - arguments to DataFrame.clip were inconsistent to numpy and Series clipping (GH2747_) @@ -108,6 +110,16 @@ pandas 0.11.0 - Bug showing up in applymap where some object type columns are converted (GH2909_) had an incorrect default in convert_objects + - TimeDeltas + + - Series ops with a Timestamp on the rhs was throwing an exception (GH2898_) + added tests for Series ops with datetimes,timedeltas,Timestamps, and datelike + Series on both lhs and rhs + - Series will now set its dtype automatically to ``timedelta64[ns]`` + if all passed objects are timedelta objects + - Support null checking on timedelta64, representing (and formatting) with NaT + - Support setitem with np.nan value, converts to NaT + .. _GH622: https://github.com/pydata/pandas/issues/622 .. _GH797: https://github.com/pydata/pandas/issues/797 .. _GH2681: https://github.com/pydata/pandas/issues/2681 @@ -121,6 +133,7 @@ pandas 0.11.0 .. _GH2845: https://github.com/pydata/pandas/issues/2845 .. _GH2867: https://github.com/pydata/pandas/issues/2867 .. _GH2807: https://github.com/pydata/pandas/issues/2807 +.. _GH2898: https://github.com/pydata/pandas/issues/2898 .. _GH2909: https://github.com/pydata/pandas/issues/2909 pandas 0.10.1 diff --git a/doc/source/dsintro.rst b/doc/source/dsintro.rst index 1ce40ea74a6bb..0957ed8f0e073 100644 --- a/doc/source/dsintro.rst +++ b/doc/source/dsintro.rst @@ -912,6 +912,8 @@ method: panel.to_frame() +.. _dsintro.panel4d: + Panel4D (Experimental) ---------------------- diff --git a/doc/source/timeseries.rst b/doc/source/timeseries.rst index e7044371e7fdb..d627212c6ae9c 100644 --- a/doc/source/timeseries.rst +++ b/doc/source/timeseries.rst @@ -917,3 +917,47 @@ TimeSeries, aligning the data on the UTC timestamps: result = eastern + berlin result result.index + +.. _timeseries.timedeltas: + +Time Deltas +----------- + +Timedeltas are differences in times, expressed in difference units, e.g. days,hours,minutes,seconds. +They can be both positive and negative. + +.. ipython:: python + + from datetime import datetime, timedelta + s = Series(date_range('2012-1-1', periods=3, freq='D')) + td = Series([ timedelta(days=i) for i in range(3) ]) + df = DataFrame(dict(A = s, B = td)) + df + df['C'] = df['A'] + df['B'] + df + df.dtypes + + s - s.max() + s - datetime(2011,1,1,3,5) + s + timedelta(minutes=5) + +Series of timedeltas with ``NaT`` values are supported + +.. ipython:: python + + y = s - s.shift() + y +The can be set to ``NaT`` using ``np.nan`` analagously to datetimes + +.. ipython:: python + + y[1] = np.nan + y + +Operands can also appear in a reversed order (a singluar object operated with a Series) + +.. ipython:: python + + s.max() - s + datetime(2011,1,1,3,5) - s + timedelta(minutes=5) + s diff --git a/doc/source/v0.10.0.txt b/doc/source/v0.10.0.txt index 4bf77968c14eb..c220d2cbba81d 100644 --- a/doc/source/v0.10.0.txt +++ b/doc/source/v0.10.0.txt @@ -330,7 +330,7 @@ N Dimensional Panels (Experimental) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Adding experimental support for Panel4D and factory functions to create n-dimensional named panels. -:ref:`Docs ` for NDim. Here is a taste of what to expect. +:ref:`Docs ` for NDim. Here is a taste of what to expect. .. ipython:: python diff --git a/doc/source/v0.11.0.txt b/doc/source/v0.11.0.txt index 1716f2d4d1413..c799463d7935c 100644 --- a/doc/source/v0.11.0.txt +++ b/doc/source/v0.11.0.txt @@ -140,11 +140,9 @@ Astype conversion on ``datetime64[ns]`` to ``object``, implicity converts ``NaT` s.dtype -New features +Enhancements ~~~~~~~~~~~~ -**Enhancements** - - In ``HDFStore``, provide dotted attribute access to ``get`` from stores (e.g. store.df == store['df']) @@ -178,7 +176,37 @@ New features price. This just obtains the data from Options.get_near_stock_price instead of Options.get_xxx_data(). -**Bug Fixes** +Bug Fixes +~~~~~~~~~ + + - Timedeltas are now fully operational (closes GH2898_) + + .. ipython:: python + + from datetime import datetime, timedelta + s = Series(date_range('2012-1-1', periods=3, freq='D')) + td = Series([ timedelta(days=i) for i in range(3) ]) + df = DataFrame(dict(A = s, B = td)) + df + s - s.max() + s - datetime(2011,1,1,3,5) + s + timedelta(minutes=5) + df['C'] = df['A'] + df['B'] + df + df.dtypes + + # timedelta are representas as ``NaT`` + y = s - s.shift() + y + + # can be set via ``np.nan`` + y[1] = np.nan + y + + # works on lhs too + s.max() - s + datetime(2011,1,1,3,5) - s + timedelta(minutes=5) + s See the `full release notes `__ or issue tracker @@ -187,4 +215,5 @@ on GitHub for a complete list. .. _GH2809: https://github.com/pydata/pandas/issues/2809 .. _GH2810: https://github.com/pydata/pandas/issues/2810 .. _GH2837: https://github.com/pydata/pandas/issues/2837 +.. _GH2898: https://github.com/pydata/pandas/issues/2898 diff --git a/pandas/core/common.py b/pandas/core/common.py index 4f2852f42f985..4e6215969e7ec 100644 --- a/pandas/core/common.py +++ b/pandas/core/common.py @@ -148,9 +148,9 @@ def _isnull_ndarraylike(obj): elif values.dtype == np.dtype('M8[ns]'): # this is the NaT pattern result = values.view('i8') == tslib.iNaT - elif issubclass(values.dtype.type, np.timedelta64): - # -np.isfinite(values.view('i8')) - result = np.ones(values.shape, dtype=bool) + elif values.dtype == np.dtype('m8[ns]'): + # this is the NaT pattern + result = values.view('i8') == tslib.iNaT else: # -np.isfinite(obj) result = np.isnan(obj) @@ -902,35 +902,50 @@ def _possibly_convert_platform(values): return values +def _possibly_cast_to_timedelta(value): + """ try to cast to timedelta64 w/o coercion """ + new_value = tslib.array_to_timedelta64(value.astype(object), coerce=False) + if new_value.dtype == 'i8': + value = np.array(new_value,dtype='timedelta64[ns]') + return value + def _possibly_cast_to_datetime(value, dtype, coerce = False): """ try to cast the array/value to a datetimelike dtype, converting float nan to iNaT """ if isinstance(dtype, basestring): dtype = np.dtype(dtype) - if dtype is not None and is_datetime64_dtype(dtype): - if np.isscalar(value): - if value == tslib.iNaT or isnull(value): - value = tslib.iNaT - else: - value = np.array(value) + if dtype is not None: + is_datetime64 = is_datetime64_dtype(dtype) + is_timedelta64 = is_timedelta64_dtype(dtype) - # have a scalar array-like (e.g. NaT) - if value.ndim == 0: - value = tslib.iNaT + if is_datetime64 or is_timedelta64: - # we have an array of datetime & nulls - elif np.prod(value.shape): - try: - value = tslib.array_to_datetime(value, coerce = coerce) - except: - pass + if np.isscalar(value): + if value == tslib.iNaT or isnull(value): + value = tslib.iNaT + else: + value = np.array(value) + + # have a scalar array-like (e.g. NaT) + if value.ndim == 0: + value = tslib.iNaT + + # we have an array of datetime or timedeltas & nulls + elif np.prod(value.shape) and value.dtype != dtype: + try: + if is_datetime64: + value = tslib.array_to_datetime(value, coerce = coerce) + elif is_timedelta64: + value = _possibly_cast_to_timedelta(value) + except: + pass elif dtype is None: # we might have a array (or single object) that is datetime like, and no dtype is passed # don't change the value unless we find a datetime set v = value - if not (is_list_like(v) or hasattr(v,'len')): + if not is_list_like(v): v = [ v ] if len(v): inferred_type = lib.infer_dtype(v) @@ -939,6 +954,8 @@ def _possibly_cast_to_datetime(value, dtype, coerce = False): value = tslib.array_to_datetime(np.array(v)) except: pass + elif inferred_type == 'timedelta': + value = _possibly_cast_to_timedelta(value) return value @@ -1281,6 +1298,16 @@ def is_datetime64_dtype(arr_or_dtype): return issubclass(tipo, np.datetime64) +def is_timedelta64_dtype(arr_or_dtype): + if isinstance(arr_or_dtype, np.dtype): + tipo = arr_or_dtype.type + elif isinstance(arr_or_dtype, type): + tipo = np.dtype(arr_or_dtype).type + else: + tipo = arr_or_dtype.dtype.type + return issubclass(tipo, np.timedelta64) + + def is_float_dtype(arr_or_dtype): if isinstance(arr_or_dtype, np.dtype): tipo = arr_or_dtype.type @@ -1290,8 +1317,7 @@ def is_float_dtype(arr_or_dtype): def is_list_like(arg): - return hasattr(arg, '__iter__') and not isinstance(arg, basestring) - + return hasattr(arg, '__iter__') and not isinstance(arg, basestring) or hasattr(arg,'len') def _is_sequence(x): try: diff --git a/pandas/core/format.py b/pandas/core/format.py index a14393374e41d..b4a6ac59719b5 100644 --- a/pandas/core/format.py +++ b/pandas/core/format.py @@ -1012,6 +1012,8 @@ def format_array(values, formatter, float_format=None, na_rep='NaN', fmt_klass = IntArrayFormatter elif com.is_datetime64_dtype(values.dtype): fmt_klass = Datetime64Formatter + elif com.is_timedelta64_dtype(values.dtype): + fmt_klass = Timedelta64Formatter else: fmt_klass = GenericArrayFormatter @@ -1170,7 +1172,6 @@ def get_result(self): fmt_values = [formatter(x) for x in self.values] return _make_fixed_width(fmt_values, self.justify) - def _format_datetime64(x, tz=None): if isnull(x): return 'NaT' @@ -1179,6 +1180,24 @@ def _format_datetime64(x, tz=None): return stamp._repr_base +class Timedelta64Formatter(Datetime64Formatter): + + def get_result(self): + if self.formatter: + formatter = self.formatter + else: + + formatter = _format_timedelta64 + + fmt_values = [formatter(x) for x in self.values] + return _make_fixed_width(fmt_values, self.justify) + +def _format_timedelta64(x): + if isnull(x): + return 'NaT' + + return lib.repr_timedelta64(x) + def _make_fixed_width(strings, justify='right', minimum=None): if len(strings) == 0: return strings diff --git a/pandas/core/series.py b/pandas/core/series.py index 5570f81e9ce15..041e708d7a907 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -17,7 +17,7 @@ from pandas.core.common import (isnull, notnull, _is_bool_indexer, _default_index, _maybe_promote, _maybe_upcast, _asarray_tuplesafe, is_integer_dtype, - _infer_dtype_from_scalar) + _infer_dtype_from_scalar, is_list_like) from pandas.core.index import (Index, MultiIndex, InvalidIndexError, _ensure_index, _handle_legacy_indexes) from pandas.core.indexing import _SeriesIndexer, _check_bool_indexer @@ -81,18 +81,51 @@ def wrapper(self, other): lvalues, rvalues = self, other - if com.is_datetime64_dtype(self): + is_timedelta = com.is_timedelta64_dtype(self) + is_datetime = com.is_datetime64_dtype(self) + + if is_datetime or is_timedelta: + + # convert the argument to an ndarray + def convert_to_array(values): + if not is_list_like(values): + values = np.array([values]) + inferred_type = lib.infer_dtype(values) + if inferred_type in set(['datetime64','datetime','date','time']): + if isinstance(values, pa.Array) and com.is_datetime64_dtype(values): + pass + else: + values = tslib.array_to_datetime(values) + else: + values = pa.array(values) + return values - if not isinstance(rvalues, pa.Array): - rvalues = pa.array([rvalues]) + # swap the valuesor com.is_timedelta64_dtype(self): + if is_timedelta: + lvalues, rvalues = rvalues, lvalues + lvalues = convert_to_array(lvalues) + is_timedelta = False + + rvalues = convert_to_array(rvalues) # rhs is either a timedelta or a series/ndarray - if lib.is_timedelta_array(rvalues): - rvalues = pa.array([np.timedelta64(v) for v in rvalues], - dtype='timedelta64[ns]') + if lib.is_timedelta_or_timedelta64_array(rvalues): + + # need to convert timedelta to ns here + # safest to convert it to an object arrany to process + rvalues = tslib.array_to_timedelta64(rvalues.astype(object)) dtype = 'M8[ns]' elif com.is_datetime64_dtype(rvalues): dtype = 'timedelta64[ns]' + + # we may have to convert to object unfortunately here + mask = isnull(lvalues) | isnull(rvalues) + if mask.any(): + def wrap_results(x): + x = pa.array(x,dtype='timedelta64[ns]') + np.putmask(x,mask,tslib.iNaT) + return x + else: raise ValueError('cannot operate on a series with out a rhs ' 'of a series/ndarray of type datetime64[ns] ' @@ -105,7 +138,6 @@ def wrapper(self, other): lvalues = lvalues.values rvalues = rvalues.values - if self.index.equals(other.index): name = _maybe_match_name(self, other) return Series(wrap_results(na_op(lvalues, rvalues)), @@ -123,12 +155,14 @@ def wrapper(self, other): arr = na_op(lvalues, rvalues) name = _maybe_match_name(self, other) - return Series(arr, index=join_idx, name=name,dtype=dtype) + return Series(wrap_results(arr), index=join_idx, name=name,dtype=dtype) elif isinstance(other, DataFrame): return NotImplemented else: # scalars - return Series(na_op(lvalues.values, rvalues), + if hasattr(lvalues,'values'): + lvalues = lvalues.values + return Series(wrap_results(na_op(lvalues, rvalues)), index=self.index, name=self.name, dtype=dtype) return wrapper @@ -690,6 +724,18 @@ def __setitem__(self, key, value): if 'unorderable' in str(e): # pragma: no cover raise IndexError(key) # Could not hash item + except ValueError: + + # reassign a null value to iNaT + if com.is_timedelta64_dtype(self.dtype): + if isnull(value): + value = tslib.iNaT + + try: + self.index._engine.set_value(self, key, value) + return + except (TypeError): + pass if _is_bool_indexer(key): key = _check_bool_indexer(self.index, key) diff --git a/pandas/index.pyx b/pandas/index.pyx index f3d1773401eb3..c13f1f506f5ed 100644 --- a/pandas/index.pyx +++ b/pandas/index.pyx @@ -482,7 +482,9 @@ cdef class DatetimeEngine(Int64Engine): cpdef convert_scalar(ndarray arr, object value): if arr.descr.type_num == NPY_DATETIME: - if isinstance(value, Timestamp): + if isinstance(value,np.ndarray): + pass + elif isinstance(value, Timestamp): return value.value elif value is None or value != value: return iNaT diff --git a/pandas/lib.pyx b/pandas/lib.pyx index bf40587c7a953..1fd579553f094 100644 --- a/pandas/lib.pyx +++ b/pandas/lib.pyx @@ -32,7 +32,7 @@ from datetime cimport * from tslib cimport convert_to_tsobject import tslib -from tslib import NaT, Timestamp +from tslib import NaT, Timestamp, repr_timedelta64 cdef int64_t NPY_NAT = util.get_nat() @@ -160,6 +160,9 @@ def time64_to_datetime(ndarray[int64_t, ndim=1] arr): return result +cdef inline int64_t get_timedelta64_value(val): + return val.view('i8') + #---------------------------------------------------------------------- # isnull / notnull related @@ -174,6 +177,8 @@ cpdef checknull(object val): return get_datetime64_value(val) == NPY_NAT elif val is NaT: return True + elif util.is_timedelta64_object(val): + return get_timedelta64_value(val) == NPY_NAT elif is_array(val): return False else: @@ -186,6 +191,8 @@ cpdef checknull_old(object val): return get_datetime64_value(val) == NPY_NAT elif val is NaT: return True + elif util.is_timedelta64_object(val): + return get_timedelta64_value(val) == NPY_NAT elif is_array(val): return False else: diff --git a/pandas/src/inference.pyx b/pandas/src/inference.pyx index 082ebf4f5b3c2..2f84dd416100e 100644 --- a/pandas/src/inference.pyx +++ b/pandas/src/inference.pyx @@ -92,6 +92,10 @@ def infer_dtype(object _values): if is_unicode_array(values): return 'unicode' + elif is_timedelta(val): + if is_timedelta_or_timedelta64_array(values): + return 'timedelta' + for i in range(n): val = util.get_value_1d(values, i) if util.is_integer_object(val): @@ -265,6 +269,10 @@ def is_datetime64_array(ndarray values): return False return True +def is_timedelta(object o): + import datetime + return isinstance(o,datetime.timedelta) or isinstance(o,np.timedelta64) + def is_timedelta_array(ndarray values): import datetime cdef int i, n = len(values) @@ -275,6 +283,24 @@ def is_timedelta_array(ndarray values): return False return True +def is_timedelta64_array(ndarray values): + cdef int i, n = len(values) + if n == 0: + return False + for i in range(n): + if not isinstance(values[i],np.timedelta64): + return False + return True + +def is_timedelta_or_timedelta64_array(ndarray values): + import datetime + cdef int i, n = len(values) + if n == 0: + return False + for i in range(n): + if not (isinstance(values[i],datetime.timedelta) or isinstance(values[i],np.timedelta64)): + return False + return True def is_date_array(ndarray[object] values): cdef int i, n = len(values) diff --git a/pandas/src/numpy.pxd b/pandas/src/numpy.pxd index b005a716e7d5f..f61c8762a1e50 100644 --- a/pandas/src/numpy.pxd +++ b/pandas/src/numpy.pxd @@ -82,6 +82,7 @@ cdef extern from "numpy/arrayobject.h": NPY_COMPLEX512 NPY_DATETIME + NPY_TIMEDELTA NPY_INTP diff --git a/pandas/src/numpy_helper.h b/pandas/src/numpy_helper.h index 493eecc378b4a..d5485e74b4927 100644 --- a/pandas/src/numpy_helper.h +++ b/pandas/src/numpy_helper.h @@ -54,7 +54,6 @@ get_datetime64_value(PyObject* obj) { } - PANDAS_INLINE int is_integer_object(PyObject* obj) { return (!PyBool_Check(obj)) && PyArray_IsIntegerScalar(obj); @@ -85,6 +84,11 @@ is_datetime64_object(PyObject *obj) { return PyArray_IsScalar(obj, Datetime); } +PANDAS_INLINE int +is_timedelta64_object(PyObject *obj) { + return PyArray_IsScalar(obj, Timedelta); +} + PANDAS_INLINE int assign_value_1d(PyArrayObject* ap, Py_ssize_t _i, PyObject* v) { npy_intp i = (npy_intp) _i; diff --git a/pandas/src/util.pxd b/pandas/src/util.pxd index b4eb903fdfc17..7a30f018e623e 100644 --- a/pandas/src/util.pxd +++ b/pandas/src/util.pxd @@ -12,6 +12,7 @@ cdef extern from "numpy_helper.h": inline int is_bool_object(object) inline int is_string_object(object) inline int is_datetime64_object(object) + inline int is_timedelta64_object(object) inline int assign_value_1d(ndarray, Py_ssize_t, object) except -1 inline cnp.int64_t get_nat() inline object get_value_1d(ndarray, Py_ssize_t) diff --git a/pandas/tests/test_format.py b/pandas/tests/test_format.py index eecf30872a80d..4f91b88291a3d 100644 --- a/pandas/tests/test_format.py +++ b/pandas/tests/test_format.py @@ -1202,10 +1202,29 @@ def test_float_trim_zeros(self): self.assert_('+10' in line) def test_timedelta64(self): + + from pandas import date_range + from datetime import datetime + Series(np.array([1100, 20], dtype='timedelta64[s]')).to_string() # check this works # GH2146 + # adding NaTs + s = Series(date_range('2012-1-1', periods=3, freq='D')) + y = s-s.shift(1) + result = y.to_string() + self.assertTrue('1 days, 00:00:00' in result) + self.assertTrue('NaT' in result) + self.assertTrue('timedelta64[ns]' in result) + + # with frac seconds + s = Series(date_range('2012-1-1', periods=3, freq='D')) + y = s-datetime(2012,1,1,microsecond=150) + result = y.to_string() + self.assertTrue('00:00:00.000150' in result) + self.assertTrue('timedelta64[ns]' in result) + def test_mixed_datetime64(self): df = DataFrame({'A': [1, 2], 'B': ['2012-01-01', '2012-01-02']}) diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py index a0dbe760bd405..2de7102dabc74 100755 --- a/pandas/tests/test_frame.py +++ b/pandas/tests/test_frame.py @@ -2859,6 +2859,32 @@ def test_constructor_for_list_with_dtypes(self): expected.sort() assert_series_equal(result, expected) + def test_timedeltas(self): + + from pandas import date_range + df = DataFrame(dict(A = Series(date_range('2012-1-1', periods=3, freq='D')), + B = Series([ timedelta(days=i) for i in range(3) ]))) + result = df.get_dtype_counts() + expected = Series({'datetime64[ns]': 1, 'timedelta64[ns]' : 1 }) + result.sort() + expected.sort() + assert_series_equal(result, expected) + + df['C'] = df['A'] + df['B'] + expected = Series({'datetime64[ns]': 2, 'timedelta64[ns]' : 1 }) + result = df.get_dtype_counts() + result.sort() + expected.sort() + assert_series_equal(result, expected) + + # mixed int types + df['D'] = 1 + expected = Series({'datetime64[ns]': 2, 'timedelta64[ns]' : 1, 'int64' : 1 }) + result = df.get_dtype_counts() + result.sort() + expected.sort() + assert_series_equal(result, expected) + def test_new_empty_index(self): df1 = DataFrame(randn(0, 3)) df2 = DataFrame(randn(0, 3)) diff --git a/pandas/tests/test_series.py b/pandas/tests/test_series.py index 42b0fa3827281..891099c385008 100644 --- a/pandas/tests/test_series.py +++ b/pandas/tests/test_series.py @@ -443,8 +443,10 @@ def test_constructor_dtype_datetime64(self): s = Series(tslib.iNaT, dtype='M8[ns]', index=range(5)) self.assert_(isnull(s).all() == True) - s = Series(tslib.NaT, dtype='M8[ns]', index=range(5)) - self.assert_(isnull(s).all() == True) + #### in theory this should be all nulls, but since + #### we are not specifying a dtype is ambiguous + s = Series(tslib.iNaT, index=range(5)) + self.assert_(isnull(s).all() == False) s = Series(nan, dtype='M8[ns]', index=range(5)) self.assert_(isnull(s).all() == True) @@ -1667,12 +1669,126 @@ def test_operators_empty_int_corner(self): # it works! _ = s1 * s2 - def test_operators_datetime64(self): + def test_constructor_dtype_timedelta64(self): + + td = Series([ timedelta(days=i) for i in range(3) ]) + self.assert_(td.dtype=='timedelta64[ns]') + + # mixed with NaT + from pandas import tslib + td = Series([ timedelta(days=i) for i in range(3) ] + [ tslib.NaT ], dtype='m8[ns]' ) + self.assert_(td.dtype=='timedelta64[ns]') + + td = Series([ timedelta(days=i) for i in range(3) ] + [ tslib.iNaT ], dtype='m8[ns]' ) + self.assert_(td.dtype=='timedelta64[ns]') + + td = Series([ timedelta(days=i) for i in range(3) ] + [ np.nan ], dtype='m8[ns]' ) + self.assert_(td.dtype=='timedelta64[ns]') + + # this is an invalid casting + self.assertRaises(Exception, Series, [ timedelta(days=i) for i in range(3) ] + [ 'foo' ], dtype='m8[ns]' ) + + # leave as object here + td = Series([ timedelta(days=i) for i in range(3) ] + [ 'foo' ]) + self.assert_(td.dtype=='object') + + def test_operators_timedelta64(self): + + # invalid ops + self.assertRaises(Exception, self.objSeries.__add__, 1) + self.assertRaises(Exception, self.objSeries.__add__, np.array(1,dtype=np.int64)) + self.assertRaises(Exception, self.objSeries.__sub__, 1) + self.assertRaises(Exception, self.objSeries.__sub__, np.array(1,dtype=np.int64)) + + # seriese ops v1 = date_range('2012-1-1', periods=3, freq='D') v2 = date_range('2012-1-2', periods=3, freq='D') rs = Series(v2) - Series(v1) xp = Series(1e9 * 3600 * 24, rs.index).astype('timedelta64[ns]') assert_series_equal(rs, xp) + self.assert_(rs.dtype=='timedelta64[ns]') + + df = DataFrame(dict(A = v1)) + td = Series([ timedelta(days=i) for i in range(3) ]) + self.assert_(td.dtype=='timedelta64[ns]') + + # series on the rhs + result = df['A'] - df['A'].shift() + self.assert_(result.dtype=='timedelta64[ns]') + + result = df['A'] + td + self.assert_(result.dtype=='M8[ns]') + + # scalar Timestamp on rhs + maxa = df['A'].max() + self.assert_(isinstance(maxa,Timestamp)) + + resultb = df['A']- df['A'].max() + self.assert_(resultb.dtype=='timedelta64[ns]') + + # timestamp on lhs + result = resultb + df['A'] + expected = Series([Timestamp('20111230'),Timestamp('20120101'),Timestamp('20120103')]) + assert_series_equal(result,expected) + + # datetimes on rhs + result = df['A'] - datetime(2001,1,1) + self.assert_(result.dtype=='timedelta64[ns]') + + result = df['A'] + datetime(2001,1,1) + self.assert_(result.dtype=='timedelta64[ns]') + + td = datetime(2001,1,1,3,4) + resulta = df['A'] - td + self.assert_(resulta.dtype=='timedelta64[ns]') + + resultb = df['A'] + td + self.assert_(resultb.dtype=='timedelta64[ns]') + + # timedelta on lhs + result = resultb + td + self.assert_(resultb.dtype=='timedelta64[ns]') + + # timedeltas on rhs + td = timedelta(days=1) + resulta = df['A'] + td + resultb = resulta - td + assert_series_equal(resultb,df['A']) + + td = timedelta(minutes=5,seconds=3) + resulta = df['A'] + td + resultb = resulta - td + self.assert_(resultb.dtype=='M8[ns]') + + def test_timedelta64_nan(self): + + from pandas import tslib + td = Series([ timedelta(days=i) for i in range(10) ]) + + # nan ops on timedeltas + td1 = td.copy() + td1[0] = np.nan + self.assert_(isnull(td1[0]) == True) + self.assert_(td1[0].view('i8') == tslib.iNaT) + td1[0] = td[0] + self.assert_(isnull(td1[0]) == False) + + td1[1] = tslib.iNaT + self.assert_(isnull(td1[1]) == True) + self.assert_(td1[1].view('i8') == tslib.iNaT) + td1[1] = td[1] + self.assert_(isnull(td1[1]) == False) + + td1[2] = tslib.NaT + self.assert_(isnull(td1[2]) == True) + self.assert_(td1[2].view('i8') == tslib.iNaT) + td1[2] = td[2] + self.assert_(isnull(td1[2]) == False) + + #### boolean setting + #### this doesn't work, not sure numpy even supports it + #result = td[(td>np.timedelta64(timedelta(days=3))) & (td