Repository: pandas-dev/pandas

Issue:
groupby.agg (first, last, min, etc...) returns incorrect results for uint64 columns
#### Code Sample, a copy-pastable example if possible

```
import pandas as pd
import numpy as np

df = pd.DataFrame({'x': 6903052872240755750, 'y': [1,2]})
print(df.groupby('y').agg({'x': 'first'}))
df.x = df.x.astype(np.uint64)
print(df.groupby('y').agg({'x': 'first'}))
```
#### Problem description

groupby.agg (first, last, min, etc...) returns incorrect results for uint64 columns

#### Expected Output

expect same results in both cases below:
                     x
y                     
1  6903052872240755750
2  6903052872240755750
                     x
y                     
1  6903052872240755712
2  6903052872240755712

#### Output of ``pd.show_versions()``

<details>

[paste the output of ``pd.show_versions()`` here below this line]
INSTALLED VERSIONS
------------------
commit: None
python: 3.6.8.final.0
python-bits: 64
OS: Darwin
OS-release: 18.5.0
machine: x86_64
processor: i386
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.24.2
pytest: None
pip: 19.1
setuptools: 41.0.1
Cython: None
numpy: 1.16.3
scipy: 1.2.1
pyarrow: None
xarray: None
IPython: None
sphinx: None
patsy: None
dateutil: 2.8.0
pytz: 2019.1
blosc: None
bottleneck: None
tables: 3.5.1
numexpr: 2.6.9
feather: None
matplotlib: None
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml.etree: None
bs4: None
html5lib: None
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: None
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
gcsfs: None

</details>



Discussion:
Looks like there is coercion behind the scenes to float which is causing the precision loss. 

Investigation and PRs would certainly be welcome
this is a duplicate issue / have to search for it
@WillAyd is correct: It seems to happen on line 489 here:
https://github.com/pandas-dev/pandas/blob/6d7ba051508b93fbbc80f6929ed6f2254ab7b938/pandas/core/groupby/ops.py#L483-L489
uint64 won't convert to int64 so it's coerced to float64.

Resolving patch:
diff --git a/pandas/core/dtypes/common.py b/pandas/core/dtypes/common.py
--- a/pandas/core/dtypes/common.py
+++ b/pandas/core/dtypes/common.py
@@ -19,6 +19,8 @@
     is_named_tuple, is_nested_list_like, is_number, is_re, is_re_compilable,
     is_scalar, is_sequence, is_string_like)
 
+from pandas._typing import ArrayLike
+
 _POSSIBLY_CAST_DTYPES = {np.dtype(t).name
                          for t in ['O', 'int8', 'uint8', 'int16', 'uint16',
                                    'int32', 'uint32', 'int64', 'uint64']}
@@ -87,10 +89,10 @@ def ensure_categorical(arr):
     return arr
 
 
-def ensure_int64_or_float64(arr, copy=False):
+def ensure_int_or_float(arr: ArrayLike, copy=False) -> np.array:
     """
     Ensure that an dtype array of some integer dtype
-    has an int64 dtype if possible
+    has an int64 dtype if possible.
     If it's not possible, potentially because of overflow,
     convert the array to float64 instead.
 
@@ -107,9 +109,18 @@ def ensure_int64_or_float64(arr, copy=False):
     out_arr : The input array cast as int64 if
               possible without overflow.
               Otherwise the input array cast to float64.
+
+    Notes
+    -----
+    If the array is explicitly of type uint64 the type
+    will remain unchanged.
     """
     try:
         return arr.astype('int64', copy=copy, casting='safe')
+    except TypeError:
+        pass
+    try:
+        return arr.astype('uint64', copy=copy, casting='safe')
     except TypeError:
         return arr.astype('float64', copy=copy)
 
diff --git a/pandas/core/groupby/ops.py b/pandas/core/groupby/ops.py
--- a/pandas/core/groupby/ops.py
+++ b/pandas/core/groupby/ops.py
@@ -17,7 +17,7 @@
 from pandas.util._decorators import cache_readonly
 
 from pandas.core.dtypes.common import (
-    ensure_float64, ensure_int64, ensure_int64_or_float64, ensure_object,
+    ensure_float64, ensure_int64, ensure_int_or_float, ensure_object,
     ensure_platform_int, is_bool_dtype, is_categorical_dtype, is_complex_dtype,
     is_datetime64_any_dtype, is_integer_dtype, is_numeric_dtype,
     is_timedelta64_dtype, needs_i8_conversion)
@@ -486,7 +486,7 @@ def _cython_operation(self, kind, values, how, axis, min_count=-1,
             if (values == iNaT).any():
                 values = ensure_float64(values)
             else:
-                values = ensure_int64_or_float64(values)
+                values = ensure_int_or_float(values)
         elif is_numeric and not is_complex_dtype(values):
             values = ensure_float64(values)
         else:


Test patch:
diff --git a/pandas/tests/groupby/aggregate/test_aggregate.py b/pandas/tests/groupby/aggregate/test_aggregate.py
--- a/pandas/tests/groupby/aggregate/test_aggregate.py
+++ b/pandas/tests/groupby/aggregate/test_aggregate.py
@@ -313,3 +313,16 @@ def test_order_aggregate_multiple_funcs():
     expected = pd.Index(['sum', 'max', 'mean', 'ohlc', 'min'])
 
     tm.assert_index_equal(result, expected)
+
+
+@pytest.mark.parametrize('dtype', [np.int64, np.uint64])
+@pytest.mark.parametrize('how', ['first', 'last', 'min',
+                                 'max', 'mean', 'median'])
+def test_uint64_type_handling(dtype, how):
+    # GH 26310
+    df = pd.DataFrame({'x': 6903052872240755750, 'y': [1, 2]})
+    expected = df.groupby('y').agg({'x': how})
+    df.x = df.x.astype(dtype)
+    result = df.groupby('y').agg({'x': how})
+    result.x = result.x.astype(np.int64)
+    tm.assert_frame_equal(result, expected, check_exact=True)