Skip to content

Commit e9b9ca5

Browse files
committed
Merge pull request pandas-dev#2899 from jreback/timedelta_issue
BUG: Series ops with a rhs of a Timestamp raising exception (pandas-dev#2898)
2 parents d7ab5be + 542d668 commit e9b9ca5

18 files changed

+493
-48
lines changed

RELEASE.rst

+13
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ pandas 0.11.0
7777
correctly
7878
- astype on datetimes to object are now handled (as well as NaT
7979
conversions to np.nan)
80+
- all timedelta like objects will be correctly assigned to ``timedelta64``
81+
with mixed ``NaN`` and/or ``NaT`` allowed
8082

8183
- arguments to DataFrame.clip were inconsistent to numpy and Series clipping
8284
(GH2747_)
@@ -108,6 +110,16 @@ pandas 0.11.0
108110
- Bug showing up in applymap where some object type columns are converted (GH2909_)
109111
had an incorrect default in convert_objects
110112

113+
- TimeDeltas
114+
115+
- Series ops with a Timestamp on the rhs was throwing an exception (GH2898_)
116+
added tests for Series ops with datetimes,timedeltas,Timestamps, and datelike
117+
Series on both lhs and rhs
118+
- Series will now set its dtype automatically to ``timedelta64[ns]``
119+
if all passed objects are timedelta objects
120+
- Support null checking on timedelta64, representing (and formatting) with NaT
121+
- Support setitem with np.nan value, converts to NaT
122+
111123
.. _GH622: https://github.com/pydata/pandas/issues/622
112124
.. _GH797: https://github.com/pydata/pandas/issues/797
113125
.. _GH2681: https://github.com/pydata/pandas/issues/2681
@@ -121,6 +133,7 @@ pandas 0.11.0
121133
.. _GH2845: https://github.com/pydata/pandas/issues/2845
122134
.. _GH2867: https://github.com/pydata/pandas/issues/2867
123135
.. _GH2807: https://github.com/pydata/pandas/issues/2807
136+
.. _GH2898: https://github.com/pydata/pandas/issues/2898
124137
.. _GH2909: https://github.com/pydata/pandas/issues/2909
125138

126139
pandas 0.10.1

doc/source/dsintro.rst

+2
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,8 @@ method:
912912
panel.to_frame()
913913
914914
915+
.. _dsintro.panel4d:
916+
915917
Panel4D (Experimental)
916918
----------------------
917919

doc/source/timeseries.rst

+44
Original file line numberDiff line numberDiff line change
@@ -917,3 +917,47 @@ TimeSeries, aligning the data on the UTC timestamps:
917917
result = eastern + berlin
918918
result
919919
result.index
920+
921+
.. _timeseries.timedeltas:
922+
923+
Time Deltas
924+
-----------
925+
926+
Timedeltas are differences in times, expressed in difference units, e.g. days,hours,minutes,seconds.
927+
They can be both positive and negative.
928+
929+
.. ipython:: python
930+
931+
from datetime import datetime, timedelta
932+
s = Series(date_range('2012-1-1', periods=3, freq='D'))
933+
td = Series([ timedelta(days=i) for i in range(3) ])
934+
df = DataFrame(dict(A = s, B = td))
935+
df
936+
df['C'] = df['A'] + df['B']
937+
df
938+
df.dtypes
939+
940+
s - s.max()
941+
s - datetime(2011,1,1,3,5)
942+
s + timedelta(minutes=5)
943+
944+
Series of timedeltas with ``NaT`` values are supported
945+
946+
.. ipython:: python
947+
948+
y = s - s.shift()
949+
y
950+
The can be set to ``NaT`` using ``np.nan`` analagously to datetimes
951+
952+
.. ipython:: python
953+
954+
y[1] = np.nan
955+
y
956+
957+
Operands can also appear in a reversed order (a singluar object operated with a Series)
958+
959+
.. ipython:: python
960+
961+
s.max() - s
962+
datetime(2011,1,1,3,5) - s
963+
timedelta(minutes=5) + s

doc/source/v0.10.0.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ N Dimensional Panels (Experimental)
330330
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
331331

332332
Adding experimental support for Panel4D and factory functions to create n-dimensional named panels.
333-
:ref:`Docs <dsintro-panel4d>` for NDim. Here is a taste of what to expect.
333+
:ref:`Docs <dsintro.panel4d>` for NDim. Here is a taste of what to expect.
334334

335335
.. ipython:: python
336336

doc/source/v0.11.0.txt

+33-4
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,9 @@ Astype conversion on ``datetime64[ns]`` to ``object``, implicity converts ``NaT`
140140
s.dtype
141141

142142

143-
New features
143+
Enhancements
144144
~~~~~~~~~~~~
145145

146-
**Enhancements**
147-
148146
- In ``HDFStore``, provide dotted attribute access to ``get`` from stores
149147
(e.g. store.df == store['df'])
150148

@@ -178,7 +176,37 @@ New features
178176
price. This just obtains the data from Options.get_near_stock_price
179177
instead of Options.get_xxx_data().
180178

181-
**Bug Fixes**
179+
Bug Fixes
180+
~~~~~~~~~
181+
182+
- Timedeltas are now fully operational (closes GH2898_)
183+
184+
.. ipython:: python
185+
186+
from datetime import datetime, timedelta
187+
s = Series(date_range('2012-1-1', periods=3, freq='D'))
188+
td = Series([ timedelta(days=i) for i in range(3) ])
189+
df = DataFrame(dict(A = s, B = td))
190+
df
191+
s - s.max()
192+
s - datetime(2011,1,1,3,5)
193+
s + timedelta(minutes=5)
194+
df['C'] = df['A'] + df['B']
195+
df
196+
df.dtypes
197+
198+
# timedelta are representas as ``NaT``
199+
y = s - s.shift()
200+
y
201+
202+
# can be set via ``np.nan``
203+
y[1] = np.nan
204+
y
205+
206+
# works on lhs too
207+
s.max() - s
208+
datetime(2011,1,1,3,5) - s
209+
timedelta(minutes=5) + s
182210

183211
See the `full release notes
184212
<https://github.com/pydata/pandas/blob/master/RELEASE.rst>`__ or issue tracker
@@ -187,4 +215,5 @@ on GitHub for a complete list.
187215
.. _GH2809: https://github.com/pydata/pandas/issues/2809
188216
.. _GH2810: https://github.com/pydata/pandas/issues/2810
189217
.. _GH2837: https://github.com/pydata/pandas/issues/2837
218+
.. _GH2898: https://github.com/pydata/pandas/issues/2898
190219

pandas/core/common.py

+47-21
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ def _isnull_ndarraylike(obj):
148148
elif values.dtype == np.dtype('M8[ns]'):
149149
# this is the NaT pattern
150150
result = values.view('i8') == tslib.iNaT
151-
elif issubclass(values.dtype.type, np.timedelta64):
152-
# -np.isfinite(values.view('i8'))
153-
result = np.ones(values.shape, dtype=bool)
151+
elif values.dtype == np.dtype('m8[ns]'):
152+
# this is the NaT pattern
153+
result = values.view('i8') == tslib.iNaT
154154
else:
155155
# -np.isfinite(obj)
156156
result = np.isnan(obj)
@@ -902,35 +902,50 @@ def _possibly_convert_platform(values):
902902
return values
903903

904904

905+
def _possibly_cast_to_timedelta(value):
906+
""" try to cast to timedelta64 w/o coercion """
907+
new_value = tslib.array_to_timedelta64(value.astype(object), coerce=False)
908+
if new_value.dtype == 'i8':
909+
value = np.array(new_value,dtype='timedelta64[ns]')
910+
return value
911+
905912
def _possibly_cast_to_datetime(value, dtype, coerce = False):
906913
""" try to cast the array/value to a datetimelike dtype, converting float nan to iNaT """
907914

908915
if isinstance(dtype, basestring):
909916
dtype = np.dtype(dtype)
910917

911-
if dtype is not None and is_datetime64_dtype(dtype):
912-
if np.isscalar(value):
913-
if value == tslib.iNaT or isnull(value):
914-
value = tslib.iNaT
915-
else:
916-
value = np.array(value)
918+
if dtype is not None:
919+
is_datetime64 = is_datetime64_dtype(dtype)
920+
is_timedelta64 = is_timedelta64_dtype(dtype)
917921

918-
# have a scalar array-like (e.g. NaT)
919-
if value.ndim == 0:
920-
value = tslib.iNaT
922+
if is_datetime64 or is_timedelta64:
921923

922-
# we have an array of datetime & nulls
923-
elif np.prod(value.shape):
924-
try:
925-
value = tslib.array_to_datetime(value, coerce = coerce)
926-
except:
927-
pass
924+
if np.isscalar(value):
925+
if value == tslib.iNaT or isnull(value):
926+
value = tslib.iNaT
927+
else:
928+
value = np.array(value)
929+
930+
# have a scalar array-like (e.g. NaT)
931+
if value.ndim == 0:
932+
value = tslib.iNaT
933+
934+
# we have an array of datetime or timedeltas & nulls
935+
elif np.prod(value.shape) and value.dtype != dtype:
936+
try:
937+
if is_datetime64:
938+
value = tslib.array_to_datetime(value, coerce = coerce)
939+
elif is_timedelta64:
940+
value = _possibly_cast_to_timedelta(value)
941+
except:
942+
pass
928943

929944
elif dtype is None:
930945
# we might have a array (or single object) that is datetime like, and no dtype is passed
931946
# don't change the value unless we find a datetime set
932947
v = value
933-
if not (is_list_like(v) or hasattr(v,'len')):
948+
if not is_list_like(v):
934949
v = [ v ]
935950
if len(v):
936951
inferred_type = lib.infer_dtype(v)
@@ -939,6 +954,8 @@ def _possibly_cast_to_datetime(value, dtype, coerce = False):
939954
value = tslib.array_to_datetime(np.array(v))
940955
except:
941956
pass
957+
elif inferred_type == 'timedelta':
958+
value = _possibly_cast_to_timedelta(value)
942959

943960
return value
944961

@@ -1281,6 +1298,16 @@ def is_datetime64_dtype(arr_or_dtype):
12811298
return issubclass(tipo, np.datetime64)
12821299

12831300

1301+
def is_timedelta64_dtype(arr_or_dtype):
1302+
if isinstance(arr_or_dtype, np.dtype):
1303+
tipo = arr_or_dtype.type
1304+
elif isinstance(arr_or_dtype, type):
1305+
tipo = np.dtype(arr_or_dtype).type
1306+
else:
1307+
tipo = arr_or_dtype.dtype.type
1308+
return issubclass(tipo, np.timedelta64)
1309+
1310+
12841311
def is_float_dtype(arr_or_dtype):
12851312
if isinstance(arr_or_dtype, np.dtype):
12861313
tipo = arr_or_dtype.type
@@ -1290,8 +1317,7 @@ def is_float_dtype(arr_or_dtype):
12901317

12911318

12921319
def is_list_like(arg):
1293-
return hasattr(arg, '__iter__') and not isinstance(arg, basestring)
1294-
1320+
return hasattr(arg, '__iter__') and not isinstance(arg, basestring) or hasattr(arg,'len')
12951321

12961322
def _is_sequence(x):
12971323
try:

pandas/core/format.py

+20-1
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,8 @@ def format_array(values, formatter, float_format=None, na_rep='NaN',
10121012
fmt_klass = IntArrayFormatter
10131013
elif com.is_datetime64_dtype(values.dtype):
10141014
fmt_klass = Datetime64Formatter
1015+
elif com.is_timedelta64_dtype(values.dtype):
1016+
fmt_klass = Timedelta64Formatter
10151017
else:
10161018
fmt_klass = GenericArrayFormatter
10171019

@@ -1170,7 +1172,6 @@ def get_result(self):
11701172
fmt_values = [formatter(x) for x in self.values]
11711173
return _make_fixed_width(fmt_values, self.justify)
11721174

1173-
11741175
def _format_datetime64(x, tz=None):
11751176
if isnull(x):
11761177
return 'NaT'
@@ -1179,6 +1180,24 @@ def _format_datetime64(x, tz=None):
11791180
return stamp._repr_base
11801181

11811182

1183+
class Timedelta64Formatter(Datetime64Formatter):
1184+
1185+
def get_result(self):
1186+
if self.formatter:
1187+
formatter = self.formatter
1188+
else:
1189+
1190+
formatter = _format_timedelta64
1191+
1192+
fmt_values = [formatter(x) for x in self.values]
1193+
return _make_fixed_width(fmt_values, self.justify)
1194+
1195+
def _format_timedelta64(x):
1196+
if isnull(x):
1197+
return 'NaT'
1198+
1199+
return lib.repr_timedelta64(x)
1200+
11821201
def _make_fixed_width(strings, justify='right', minimum=None):
11831202
if len(strings) == 0:
11841203
return strings

0 commit comments

Comments
 (0)