Repository: pandas-dev/pandas

Issue:
Groupby transform idxmax return floats
`Transform` with `idxmax` return wrong output - floats instead `datetimes`

Sample: 

    rng = pd.date_range('1/1/2011', periods=15, freq='D') 
    np.random.seed(4)
    
    stocks = pd.DataFrame({ 
        'price':(np.random.randn(15).cumsum() + 10) },index = rng)
    
    stocks['week_id'] = pd.to_datetime(stocks.index).week #used for the groupby
    print (stocks)
                    price  week_id
    2011-01-01  10.050562       52
    2011-01-02  10.550513       52
    2011-01-03   9.554604        1
    2011-01-04  10.248203        1
    2011-01-05   9.829901        1
    2011-01-06   8.245324        1
    2011-01-07   7.597617        1
    2011-01-08   8.196192        1
    2011-01-09   8.528442        1
    2011-01-10   7.380966        2
    2011-01-11   7.999635        2
    2011-01-12   7.911648        2
    2011-01-13   8.336721        2
    2011-01-14   8.668974        2
    2011-01-15   7.512158        2

<!-- -->
    
    print (stocks.groupby(stocks['week_id'])['price'].transform('idxmax'))
    2011-01-01    1.293926e+18
    2011-01-02    1.293926e+18
    2011-01-03    1.294099e+18
    2011-01-04    1.294099e+18
    2011-01-05    1.294099e+18
    2011-01-06    1.294099e+18
    2011-01-07    1.294099e+18
    2011-01-08    1.294099e+18
    2011-01-09    1.294099e+18
    2011-01-10    1.294963e+18
    2011-01-11    1.294963e+18
    2011-01-12    1.294963e+18
    2011-01-13    1.294963e+18
    2011-01-14    1.294963e+18
    2011-01-15    1.294963e+18
    Freq: D, Name: price, dtype: float64
    
    print (stocks.groupby(stocks['week_id'])['price'].idxmax())
    week_id
    1    2011-01-04
    2    2011-01-14
    52   2011-01-02
    Name: price, dtype: datetime64[ns]

[SO question](http://stackoverflow.com/questions/42045549/using-groupby-and-transform-to-get-idxmax-per-week).

    print (pd.show_versions())
    
    INSTALLED VERSIONS
    ------------------
    commit: None
    python: 3.5.1.final.0
    python-bits: 64
    OS: Windows
    OS-release: 7
    machine: AMD64
    processor: Intel64 Family 6 Model 42 Stepping 7, GenuineIntel
    byteorder: little
    LC_ALL: None
    LANG: sk_SK
    LOCALE: None.None
    
    pandas: 0.19.2+0.g825876c.dirty
    nose: 1.3.7
    pip: 8.1.1
    setuptools: 20.3
    Cython: 0.23.4
    numpy: 1.11.0
    scipy: 0.17.0
    statsmodels: 0.6.1
    xarray: None
    IPython: 4.1.2
    sphinx: 1.3.1
    patsy: 0.4.0
    dateutil: 2.5.1
    pytz: 2016.2
    blosc: None
    bottleneck: 1.0.0
    tables: 3.2.2
    numexpr: 2.5.1
    matplotlib: 1.5.1
    openpyxl: 2.3.2
    xlrd: 0.9.4
    xlwt: 1.0.0
    xlsxwriter: 0.8.4
    lxml: 3.6.0
    bs4: 4.4.1
    html5lib: 0.999
    httplib2: None
    apiclient: None
    sqlalchemy: 1.0.12
    pymysql: None
    psycopg2: None
    jinja2: 2.8
    boto: 2.39.0
    pandas_datareader: 0.2.1
    None




Discussion:
yeah ``.idxmax()`` is just defined as a passthru back to Series, then it is coerce. so need to be directly defined on ``GroupBy``, xref to #15260 

you can achieve this like this currently
```
In [16]: stocks.index[stocks.reset_index().groupby(stocks.index.week).price.transform('idxmax').astype(int)]
Out[16]: 
DatetimeIndex(['2011-01-02', '2011-01-02', '2011-01-04', '2011-01-04', '2011-01-04', '2011-01-04', '2011-01-04', '2011-01-04', '2011-01-04', '2011-01-14', '2011-01-14', '2011-01-14', '2011-01-14',
               '2011-01-14', '2011-01-14'],
              dtype='datetime64[ns]', freq=None)
```
if you'd like to put up a PR would be appreciated.
@jreback: would it suffice just to add idxmax/min to cython_cast_blacklist?

Resolving patch:
diff --git a/pandas/core/groupby/base.py b/pandas/core/groupby/base.py
--- a/pandas/core/groupby/base.py
+++ b/pandas/core/groupby/base.py
@@ -89,7 +89,8 @@ def _gotitem(self, key, ndim, subset=None):
 cython_transforms = frozenset(['cumprod', 'cumsum', 'shift',
                                'cummin', 'cummax'])
 
-cython_cast_blacklist = frozenset(['rank', 'count', 'size'])
+cython_cast_blacklist = frozenset(['rank', 'count', 'size', 'idxmin',
+                                   'idxmax'])
 
 
 def whitelist_method_generator(base, klass, whitelist):
diff --git a/pandas/core/groupby/generic.py b/pandas/core/groupby/generic.py
--- a/pandas/core/groupby/generic.py
+++ b/pandas/core/groupby/generic.py
@@ -254,8 +254,13 @@ def _aggregate_item_by_item(self, func, *args, **kwargs):
                 data = obj[item]
                 colg = SeriesGroupBy(data, selection=item,
                                      grouper=self.grouper)
-                result[item] = self._try_cast(
-                    colg.aggregate(func, *args, **kwargs), data)
+
+                cast = self._transform_should_cast(func)
+
+                result[item] = colg.aggregate(func, *args, **kwargs)
+                if cast:
+                    result[item] = self._try_cast(result[item], data)
+
             except ValueError:
                 cannot_agg.append(item)
                 continue


Test patch:
diff --git a/pandas/tests/groupby/test_function.py b/pandas/tests/groupby/test_function.py
--- a/pandas/tests/groupby/test_function.py
+++ b/pandas/tests/groupby/test_function.py
@@ -400,6 +400,25 @@ def test_groupby_non_arithmetic_agg_int_like_precision(i):
         assert res.iloc[0].b == data["expected"]
 
 
+@pytest.mark.parametrize("func, values", [
+    ("idxmin", {'c_int': [0, 2], 'c_float': [1, 3], 'c_date': [1, 2]}),
+    ("idxmax", {'c_int': [1, 3], 'c_float': [0, 2], 'c_date': [0, 3]})
+])
+def test_idxmin_idxmax_returns_int_types(func, values):
+    # GH 25444
+    df = pd.DataFrame({'name': ['A', 'A', 'B', 'B'],
+                       'c_int': [1, 2, 3, 4],
+                       'c_float': [4.02, 3.03, 2.04, 1.05],
+                       'c_date': ['2019', '2018', '2016', '2017']})
+    df['c_date'] = pd.to_datetime(df['c_date'])
+
+    result = getattr(df.groupby('name'), func)()
+
+    expected = pd.DataFrame(values, index=Index(['A', 'B'], name="name"))
+
+    tm.assert_frame_equal(result, expected)
+
+
 def test_fill_consistency():
 
     # GH9221
diff --git a/pandas/tests/groupby/test_transform.py b/pandas/tests/groupby/test_transform.py
--- a/pandas/tests/groupby/test_transform.py
+++ b/pandas/tests/groupby/test_transform.py
@@ -845,3 +845,22 @@ def test_groupby_transform_timezone_column(func):
     expected = pd.DataFrame([[ts, 1, ts]], columns=['end_time', 'id',
                                                     'max_end_time'])
     tm.assert_frame_equal(result, expected)
+
+
+@pytest.mark.parametrize("func, values", [
+    ("idxmin", ["1/1/2011"] * 2 + ["1/3/2011"] * 7 + ["1/10/2011"]),
+    ("idxmax", ["1/2/2011"] * 2 + ["1/9/2011"] * 7 + ["1/10/2011"])
+])
+def test_groupby_transform_with_datetimes(func, values):
+    # GH 15306
+    dates = pd.date_range('1/1/2011', periods=10, freq='D')
+
+    stocks = pd.DataFrame({'price': np.arange(10.0)}, index=dates)
+    stocks['week_id'] = pd.to_datetime(stocks.index).week
+
+    result = stocks.groupby(stocks['week_id'])['price'].transform(func)
+
+    expected = pd.Series(data=pd.to_datetime(values),
+                         index=dates, name="price")
+
+    tm.assert_series_equal(result, expected)