Repository: pandas-dev/pandas

Issue:
Inconsistency in return value of mean, median, and quantile on timedelta64[ns] series
If I have a Series of `timedelta64[ns]` (the result of a `diff()` on a Timestamp Series in my case), doing mean, median, and quantile operations on that series returns inconsistent values. Example:

```
In [68]: s
Out[68]: 
0   00:00:14.020705
1   00:00:14.020705
2   00:00:14.020705
3   00:00:28.041410
4   00:00:28.041410
5   00:00:42.062116
6   00:00:42.062116
7   00:00:42.062116
8   00:00:56.082821
9   00:02:20.207052
dtype: timedelta64[ns]

In [69]: s.mean()
Out[69]: 42062115618.0

In [70]: type(s.mean())
Out[70]: numpy.float64

In [71]: s.median()
Out[71]: 35051763015.0

In [72]: type(s.median())
Out[72]: float

In [73]: s.quantile(.95)
Out[73]: numpy.timedelta64(102351148003,'ns')

In [74]: type(s.quantile(.95))
Out[74]: numpy.timedelta64
```

As you can see, `mean` and `median` return a float (although the printed Out is a little different), but `quantile` returns a single `timedelta64[ns]`.

This is with versions pandas==0.12.0 and numpy==1.7.1



Discussion:
by definition `mean` cannot return a `timedelta64[ns]` type (I suppose there are cases it _could_, but the general case it cannot). You are meaning an integer value that might not be an integer after that. 

`median` for the same reason (though you could make a case where it should just return the closests, but right now it doesn't do that)

in 0.13/master this is supported

```
In [73]: s = Series([Timestamp('20130101') + timedelta(seconds=i*i) for i in range(10) ])

In [74]: s
Out[74]: 
0   2013-01-01 00:00:00
1   2013-01-01 00:00:01
2   2013-01-01 00:00:04
3   2013-01-01 00:00:09
4   2013-01-01 00:00:16
5   2013-01-01 00:00:25
6   2013-01-01 00:00:36
7   2013-01-01 00:00:49
8   2013-01-01 00:01:04
9   2013-01-01 00:01:21
dtype: datetime64[ns]

In [75]: td = s.diff()

In [76]: td
Out[76]: 
0        NaT
1   00:00:01
2   00:00:03
3   00:00:05
4   00:00:07
5   00:00:09
6   00:00:11
7   00:00:13
8   00:00:15
9   00:00:17
dtype: timedelta64[ns]

In [77]: pd.to_timedelta([int(td.quantile(.1))])
Out[77]: 
0   00:00:02.600000
dtype: timedelta64[ns]

In [78]: pd.to_timedelta([int(td.mean())])
Out[78]: 
0   00:00:09
dtype: timedelta64[ns]

In [79]: pd.to_timedelta([int(td.median())])
Out[79]: 
0   00:00:08
dtype: timedelta64[ns]
```

let me see if can do the automatic inferring....prob not too hard


Resolving patch:
diff --git a/pandas/core/nanops.py b/pandas/core/nanops.py
--- a/pandas/core/nanops.py
+++ b/pandas/core/nanops.py
@@ -5,7 +5,7 @@
 
 import numpy as np
 
-from pandas.core.common import isnull, notnull, _values_from_object
+from pandas.core.common import isnull, notnull, _values_from_object, is_float
 import pandas.core.common as com
 import pandas.lib as lib
 import pandas.algos as algos
@@ -188,6 +188,10 @@ def _wrap_results(result,dtype):
             # as series will do the right thing in py3 (and deal with numpy 1.6.2
             # bug in that it results dtype of timedelta64[us]
             from pandas import Series
+
+            # coerce float to results
+            if is_float(result):
+                result = int(result)
             result = Series([result],dtype='timedelta64[ns]')
         else:
             result = result.view(dtype)
@@ -224,11 +228,15 @@ def nanmean(values, axis=None, skipna=True):
             the_mean[ct_mask] = np.nan
     else:
         the_mean = the_sum / count if count > 0 else np.nan
-    return the_mean
+
+    return  _wrap_results(the_mean,dtype)
 
 @disallow('M8')
 @bottleneck_switch()
 def nanmedian(values, axis=None, skipna=True):
+
+    values, mask, dtype = _get_values(values, skipna)
+
     def get_median(x):
         mask = notnull(x)
         if not skipna and not mask.all():
@@ -257,7 +265,7 @@ def get_median(x):
         return ret
 
     # otherwise return a scalar value
-    return get_median(values) if notempty else np.nan
+    return _wrap_results(get_median(values),dtype) if notempty else np.nan
 
 
 @disallow('M8')
diff --git a/pandas/core/series.py b/pandas/core/series.py
--- a/pandas/core/series.py
+++ b/pandas/core/series.py
@@ -1981,7 +1981,12 @@ def quantile(self, q=0.5):
         valid_values = self.dropna().values
         if len(valid_values) == 0:
             return pa.NA
-        return _quantile(valid_values, q * 100)
+        result = _quantile(valid_values, q * 100)
+        if result.dtype == _TD_DTYPE:
+            from pandas.tseries.timedeltas import to_timedelta
+            return to_timedelta(result)
+
+        return result
 
     def ptp(self, axis=None, out=None):
         return _values_from_object(self).ptp(axis, out)
diff --git a/pandas/tseries/timedeltas.py b/pandas/tseries/timedeltas.py
--- a/pandas/tseries/timedeltas.py
+++ b/pandas/tseries/timedeltas.py
@@ -58,7 +58,7 @@ def _convert_listlike(arg, box):
     elif is_list_like(arg):
         return _convert_listlike(arg, box=box)
 
-    return _convert_listlike([ arg ], box=False)[0]
+    return _convert_listlike([ arg ], box=box)
 
 _short_search = re.compile(
     "^\s*(?P<neg>-?)\s*(?P<value>\d*\.?\d*)\s*(?P<unit>d|s|ms|us|ns)?\s*$",re.IGNORECASE)


Test patch:
diff --git a/pandas/tseries/tests/test_timedeltas.py b/pandas/tseries/tests/test_timedeltas.py
--- a/pandas/tseries/tests/test_timedeltas.py
+++ b/pandas/tseries/tests/test_timedeltas.py
@@ -7,7 +7,7 @@
 import numpy as np
 import pandas as pd
 
-from pandas import (Index, Series, DataFrame, isnull, notnull,
+from pandas import (Index, Series, DataFrame, Timestamp, isnull, notnull,
                     bdate_range, date_range, _np_version_under1p7)
 import pandas.core.common as com
 from pandas.compat import StringIO, lrange, range, zip, u, OrderedDict, long
@@ -123,8 +123,8 @@ def conv(v):
     def test_nat_converters(self):
         _skip_if_numpy_not_friendly()
 
-        self.assert_(to_timedelta('nat') == tslib.iNaT)
-        self.assert_(to_timedelta('nan') == tslib.iNaT)
+        self.assert_(to_timedelta('nat',box=False) == tslib.iNaT)
+        self.assert_(to_timedelta('nan',box=False) == tslib.iNaT)
 
     def test_to_timedelta(self):
         _skip_if_numpy_not_friendly()
@@ -133,11 +133,11 @@ def conv(v):
             return v.astype('m8[ns]')
         d1 = np.timedelta64(1,'D')
 
-        self.assert_(to_timedelta('1 days 06:05:01.00003') == conv(d1+np.timedelta64(6*3600+5*60+1,'s')+np.timedelta64(30,'us')))
-        self.assert_(to_timedelta('15.5us') == conv(np.timedelta64(15500,'ns')))
+        self.assert_(to_timedelta('1 days 06:05:01.00003',box=False) == conv(d1+np.timedelta64(6*3600+5*60+1,'s')+np.timedelta64(30,'us')))
+        self.assert_(to_timedelta('15.5us',box=False) == conv(np.timedelta64(15500,'ns')))
 
         # empty string
-        result = to_timedelta('')
+        result = to_timedelta('',box=False)
         self.assert_(result == tslib.iNaT)
 
         result = to_timedelta(['', ''])
@@ -150,7 +150,7 @@ def conv(v):
 
         # ints
         result = np.timedelta64(0,'ns')
-        expected = to_timedelta(0)
+        expected = to_timedelta(0,box=False)
         self.assert_(result == expected)
 
         # Series
@@ -163,6 +163,35 @@ def conv(v):
         expected = to_timedelta([0,10],unit='s')
         tm.assert_series_equal(result, expected)
 
+        # single element conversion
+        v = timedelta(seconds=1)
+        result = to_timedelta(v,box=False)
+        expected = to_timedelta([v])
+
+        v = np.timedelta64(timedelta(seconds=1))
+        result = to_timedelta(v,box=False)
+        expected = to_timedelta([v])
+
+    def test_timedelta_ops(self):
+        _skip_if_numpy_not_friendly()
+
+        # GH4984
+        # make sure ops return timedeltas
+        s = Series([Timestamp('20130101') + timedelta(seconds=i*i) for i in range(10) ])
+        td = s.diff()
+
+        result = td.mean()
+        expected = to_timedelta(timedelta(seconds=9))
+        tm.assert_series_equal(result, expected)
+
+        result = td.quantile(.1)
+        expected = to_timedelta('00:00:02.6')
+        tm.assert_series_equal(result, expected)
+
+        result = td.median()
+        expected = to_timedelta('00:00:08')
+        tm.assert_series_equal(result, expected)
+
 if __name__ == '__main__':
     nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
                    exit=False)