Skip to content

Commit 9fc8636

Browse files
committed
Merge pull request #4534 from jreback/td
BUG/API: Fix operating with timedelta64/pd.offsets on rhs of a datelike series/index
2 parents b2ad044 + b7e80a5 commit 9fc8636

File tree

9 files changed

+682
-278
lines changed

9 files changed

+682
-278
lines changed

doc/source/release.rst

+9
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ pandas 0.13
5353
- Add ``rename`` and ``set_names`` methods to ``Index`` as well as
5454
``set_names``, ``set_levels``, ``set_labels`` to ``MultiIndex``.
5555
(:issue:`4039`)
56+
- A Series of dtype ``timedelta64[ns]`` can now be divided/multiplied
57+
by an integer series (:issue`4521`)
58+
- A Series of dtype ``timedelta64[ns]`` can now be divided by another
59+
``timedelta64[ns]`` object to yield a ``float64`` dtyped Series. This
60+
is frequency conversion.
5661

5762
**API Changes**
5863

@@ -166,6 +171,10 @@ pandas 0.13
166171
- Fixed issue where individual ``names``, ``levels`` and ``labels`` could be
167172
set on ``MultiIndex`` without validation (:issue:`3714`, :issue:`4039`)
168173
- Fixed (:issue:`3334`) in pivot_table. Margins did not compute if values is the index.
174+
- Fix bug in having a rhs of ``np.timedelta64`` or ``np.offsets.DateOffset`` when operating
175+
with datetimes (:issue:`4532`)
176+
- Fix arithmetic with series/datetimeindex and ``np.timedelta64`` not working the same (:issue:`4134`)
177+
and buggy timedelta in numpy 1.6 (:issue:`4135`)
169178

170179
pandas 0.12
171180
===========

doc/source/timeseries.rst

+58-30
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ Take care, ``to_datetime`` may not act as you expect on mixed data:
170170

171171
.. ipython:: python
172172
173-
pd.to_datetime([1, '1'])
173+
to_datetime([1, '1'])
174174
175175
.. _timeseries.daterange:
176176

@@ -297,7 +297,7 @@ the year or year and month as strings:
297297
298298
ts['2011-6']
299299
300-
This type of slicing will work on a DataFrame with a ``DateTimeIndex`` as well. Since the
300+
This type of slicing will work on a DataFrame with a ``DateTimeIndex`` as well. Since the
301301
partial string selection is a form of label slicing, the endpoints **will be** included. This
302302
would include matching times on an included date. Here's an example:
303303

@@ -1112,7 +1112,8 @@ Time Deltas
11121112
-----------
11131113

11141114
Timedeltas are differences in times, expressed in difference units, e.g. days,hours,minutes,seconds.
1115-
They can be both positive and negative.
1115+
They can be both positive and negative. :ref:`DateOffsets<timeseries.offsets>` that are absolute in nature
1116+
(``Day, Hour, Minute, Second, Milli, Micro, Nano``) can be used as ``timedeltas``.
11161117

11171118
.. ipython:: python
11181119
@@ -1128,41 +1129,16 @@ They can be both positive and negative.
11281129
s - s.max()
11291130
s - datetime(2011,1,1,3,5)
11301131
s + timedelta(minutes=5)
1132+
s + Minute(5)
1133+
s + Minute(5) + Milli(5)
11311134
11321135
Getting scalar results from a ``timedelta64[ns]`` series
11331136

1134-
.. ipython:: python
1135-
:suppress:
1136-
1137-
from distutils.version import LooseVersion
1138-
11391137
.. ipython:: python
11401138
11411139
y = s - s[0]
11421140
y
11431141
1144-
.. code-block:: python
1145-
1146-
if LooseVersion(np.__version__) <= '1.6.2':
1147-
y.apply(lambda x: x.item().total_seconds())
1148-
y.apply(lambda x: x.item().days)
1149-
else:
1150-
y.apply(lambda x: x / np.timedelta64(1, 's'))
1151-
y.apply(lambda x: x / np.timedelta64(1, 'D'))
1152-
1153-
.. note::
1154-
1155-
As you can see from the conditional statement above, these operations are
1156-
different in numpy 1.6.2 and in numpy >= 1.7. The ``timedelta64[ns]`` scalar
1157-
type in 1.6.2 is much like a ``datetime.timedelta``, while in 1.7 it is a
1158-
nanosecond based integer. A future version of pandas will make this
1159-
transparent.
1160-
1161-
.. note::
1162-
1163-
In numpy >= 1.7 dividing a ``timedelta64`` array by another ``timedelta64``
1164-
array will yield an array with dtype ``np.float64``.
1165-
11661142
Series of timedeltas with ``NaT`` values are supported
11671143

11681144
.. ipython:: python
@@ -1218,3 +1194,55 @@ issues). ``idxmin, idxmax`` are supported as well.
12181194
12191195
df.min().idxmax()
12201196
df.min(axis=1).idxmin()
1197+
1198+
.. _timeseries.timedeltas_convert:
1199+
1200+
Time Deltas & Conversions
1201+
-------------------------
1202+
1203+
.. versionadded:: 0.13
1204+
1205+
Timedeltas can be converted to other 'frequencies' by dividing by another timedelta.
1206+
These operations yield ``float64`` dtyped Series.
1207+
1208+
.. ipython:: python
1209+
1210+
td = Series(date_range('20130101',periods=4))-Series(date_range('20121201',periods=4))
1211+
td[2] += np.timedelta64(timedelta(minutes=5,seconds=3))
1212+
td[3] = np.nan
1213+
td
1214+
1215+
# to days
1216+
td / np.timedelta64(1,'D')
1217+
1218+
# to seconds
1219+
td / np.timedelta64(1,'s')
1220+
1221+
Dividing or multiplying a ``timedelta64[ns]`` Series by an integer or integer Series
1222+
yields another ``timedelta64[ns]`` dtypes Series.
1223+
1224+
.. ipython:: python
1225+
1226+
td * -1
1227+
td * Series([1,2,3,4])
1228+
1229+
Numpy < 1.7 Compatibility
1230+
~~~~~~~~~~~~~~~~~~~~~~~~~
1231+
1232+
Numpy < 1.7 has a broken ``timedelta64`` type that does not correctly work
1233+
for arithmetic. Pandas bypasses this, but for frequency conversion as above,
1234+
you need to create the divisor yourself. The ``np.timetimedelta64`` type only
1235+
has 1 argument, the number of **micro** seconds.
1236+
1237+
The following are equivalent statements in the two versions of numpy.
1238+
1239+
.. code-block:: python
1240+
1241+
from distutils.version import LooseVersion
1242+
if LooseVersion(np.__version__) <= '1.6.2':
1243+
y / np.timedelta(86400*int(1e6))
1244+
y / np.timedelta(int(1e6))
1245+
else:
1246+
y / np.timedelta64(1,'D')
1247+
y / np.timedelta64(1,'s')
1248+

doc/source/v0.13.0.txt

+34
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,40 @@ Enhancements
100100
- Added a more informative error message when plot arguments contain
101101
overlapping color and style arguments (:issue:`4402`)
102102

103+
- ``timedelta64[ns]`` operations
104+
105+
- A Series of dtype ``timedelta64[ns]`` can now be divided by another
106+
``timedelta64[ns]`` object to yield a ``float64`` dtyped Series. This
107+
is frequency conversion. See :ref:`here<timeseries.timedeltas_convert>` for the docs.
108+
109+
.. ipython:: python
110+
111+
from datetime import timedelta
112+
td = Series(date_range('20130101',periods=4))-Series(date_range('20121201',periods=4))
113+
td[2] += np.timedelta64(timedelta(minutes=5,seconds=3))
114+
td[3] = np.nan
115+
td
116+
117+
# to days
118+
td / np.timedelta64(1,'D')
119+
120+
# to seconds
121+
td / np.timedelta64(1,'s')
122+
123+
- Dividing or multiplying a ``timedelta64[ns]`` Series by an integer or integer Series
124+
125+
.. ipython:: python
126+
127+
td * -1
128+
td * Series([1,2,3,4])
129+
130+
- Absolute ``DateOffset`` objects can act equivalenty to ``timedeltas``
131+
132+
.. ipython:: python
133+
134+
from pandas import offsets
135+
td + offsets.Minute(5) + offsets.Milli(5)
136+
103137
Bug Fixes
104138
~~~~~~~~~
105139

pandas/core/common.py

+47-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
import pandas.algos as algos
1212
import pandas.lib as lib
1313
import pandas.tslib as tslib
14-
14+
from distutils.version import LooseVersion
1515
from pandas import compat
1616
from pandas.compat import StringIO, BytesIO, range, long, u, zip, map
17+
from datetime import timedelta
18+
1719
from pandas.core.config import get_option
1820
from pandas.core import array as pa
1921

@@ -33,6 +35,10 @@ class PandasError(Exception):
3335
class AmbiguousIndexError(PandasError, KeyError):
3436
pass
3537

38+
# versioning
39+
_np_version = np.version.short_version
40+
_np_version_under1p6 = LooseVersion(_np_version) < '1.6'
41+
_np_version_under1p7 = LooseVersion(_np_version) < '1.7'
3642

3743
_POSSIBLY_CAST_DTYPES = set([ np.dtype(t) for t in ['M8[ns]','m8[ns]','O','int8','uint8','int16','uint16','int32','uint32','int64','uint64'] ])
3844
_NS_DTYPE = np.dtype('M8[ns]')
@@ -1144,7 +1150,45 @@ def _possibly_convert_platform(values):
11441150
def _possibly_cast_to_timedelta(value, coerce=True):
11451151
""" try to cast to timedelta64, if already a timedeltalike, then make
11461152
sure that we are [ns] (as numpy 1.6.2 is very buggy in this regards,
1147-
don't force the conversion unless coerce is True """
1153+
don't force the conversion unless coerce is True
1154+
1155+
if coerce='compat' force a compatibilty coercerion (to timedeltas) if needeed
1156+
"""
1157+
1158+
# coercion compatability
1159+
if coerce == 'compat' and _np_version_under1p7:
1160+
1161+
def convert(td, type):
1162+
1163+
# we have an array with a non-object dtype
1164+
if hasattr(td,'item'):
1165+
td = td.astype(np.int64).item()
1166+
if td == tslib.iNaT:
1167+
return td
1168+
if dtype == 'm8[us]':
1169+
td *= 1000
1170+
return td
1171+
1172+
if td == tslib.compat_NaT:
1173+
return tslib.iNaT
1174+
1175+
# convert td value to a nanosecond value
1176+
d = td.days
1177+
s = td.seconds
1178+
us = td.microseconds
1179+
1180+
if dtype == 'object' or dtype == 'm8[ns]':
1181+
td = 1000*us + (s + d * 24 * 3600) * 10 ** 9
1182+
else:
1183+
raise ValueError("invalid conversion of dtype in np < 1.7 [%s]" % dtype)
1184+
1185+
return td
1186+
1187+
# < 1.7 coercion
1188+
if not is_list_like(value):
1189+
value = np.array([ value ])
1190+
dtype = value.dtype
1191+
return np.array([ convert(v,dtype) for v in value ], dtype='m8[ns]')
11481192

11491193
# deal with numpy not being able to handle certain timedelta operations
11501194
if isinstance(value,np.ndarray) and value.dtype.kind == 'm':
@@ -1154,6 +1198,7 @@ def _possibly_cast_to_timedelta(value, coerce=True):
11541198

11551199
# we don't have a timedelta, but we want to try to convert to one (but don't force it)
11561200
if coerce:
1201+
11571202
new_value = tslib.array_to_timedelta64(value.astype(object), coerce=False)
11581203
if new_value.dtype == 'i8':
11591204
value = np.array(new_value,dtype='timedelta64[ns]')

0 commit comments

Comments
 (0)