Skip to content

Commit 16b626a

Browse files
authored
BUG: groupby sum, mean, var should always be floats (#41139)
1 parent 17e92bd commit 16b626a

File tree

15 files changed

+91
-50
lines changed

15 files changed

+91
-50
lines changed

doc/source/whatsnew/v1.3.0.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,31 @@ values as measured by ``np.allclose``. Now no such casting occurs.
333333
334334
df.groupby('key').agg(lambda x: x.sum())
335335
336+
``float`` result for :meth:`.GroupBy.mean`, :meth:`.GroupBy.median`, and :meth:`.GroupBy.var`
337+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
338+
339+
Previously, these methods could result in different dtypes depending on the input values.
340+
Now, these methods will always return a float dtype. (:issue:`41137`)
341+
342+
.. ipython:: python
343+
344+
df = pd.DataFrame({'a': [True], 'b': [1], 'c': [1.0]})
345+
346+
*pandas 1.2.x*
347+
348+
.. code-block:: ipython
349+
350+
In [5]: df.groupby(df.index).mean()
351+
Out[5]:
352+
a b c
353+
0 True 1 1.0
354+
355+
*pandas 1.3.0*
356+
357+
.. ipython:: python
358+
359+
df.groupby(df.index).mean()
360+
336361
Try operating inplace when setting values with ``loc`` and ``iloc``
337362
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
338363

pandas/core/groupby/groupby.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,12 +1600,12 @@ def mean(self, numeric_only: bool = True):
16001600
Groupby two columns and return the mean of the remaining column.
16011601
16021602
>>> df.groupby(['A', 'B']).mean()
1603-
C
1603+
C
16041604
A B
1605-
1 2.0 2
1606-
4.0 1
1607-
2 3.0 1
1608-
5.0 2
1605+
1 2.0 2.0
1606+
4.0 1.0
1607+
2 3.0 1.0
1608+
5.0 2.0
16091609
16101610
Groupby one column and return the mean of only particular column in
16111611
the group.

pandas/core/groupby/grouper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ class Grouper:
150150
>>> df.groupby(pd.Grouper(key="Animal")).mean()
151151
Speed
152152
Animal
153-
Falcon 200
154-
Parrot 10
153+
Falcon 200.0
154+
Parrot 10.0
155155
156156
Specify a resample operation on the column 'Publish date'
157157

pandas/core/groupby/ops.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
is_categorical_dtype,
5050
is_complex_dtype,
5151
is_datetime64_any_dtype,
52+
is_float_dtype,
5253
is_integer_dtype,
5354
is_numeric_dtype,
5455
is_sparse,
@@ -304,10 +305,13 @@ def _get_result_dtype(self, dtype: DtypeObj) -> DtypeObj:
304305
return np.dtype(np.int64)
305306
elif isinstance(dtype, (BooleanDtype, _IntegerDtype)):
306307
return Int64Dtype()
307-
elif how in ["mean", "median", "var"] and isinstance(
308-
dtype, (BooleanDtype, _IntegerDtype)
309-
):
310-
return Float64Dtype()
308+
elif how in ["mean", "median", "var"]:
309+
if isinstance(dtype, (BooleanDtype, _IntegerDtype)):
310+
return Float64Dtype()
311+
elif is_float_dtype(dtype):
312+
return dtype
313+
elif is_numeric_dtype(dtype):
314+
return np.dtype(np.float64)
311315
return dtype
312316

313317
def uses_mask(self) -> bool:

pandas/tests/extension/base/groupby.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def test_groupby_extension_agg(self, as_index, data_for_grouping):
2525
_, index = pd.factorize(data_for_grouping, sort=True)
2626

2727
index = pd.Index(index, name="B")
28-
expected = pd.Series([3, 1, 4], index=index, name="A")
28+
expected = pd.Series([3.0, 1.0, 4.0], index=index, name="A")
2929
if as_index:
3030
self.assert_series_equal(result, expected)
3131
else:
@@ -54,7 +54,7 @@ def test_groupby_extension_no_sort(self, data_for_grouping):
5454
_, index = pd.factorize(data_for_grouping, sort=False)
5555

5656
index = pd.Index(index, name="B")
57-
expected = pd.Series([1, 3, 4], index=index, name="A")
57+
expected = pd.Series([1.0, 3.0, 4.0], index=index, name="A")
5858
self.assert_series_equal(result, expected)
5959

6060
def test_groupby_extension_transform(self, data_for_grouping):

pandas/tests/extension/test_boolean.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def test_groupby_extension_agg(self, as_index, data_for_grouping):
272272
_, index = pd.factorize(data_for_grouping, sort=True)
273273

274274
index = pd.Index(index, name="B")
275-
expected = pd.Series([3, 1], index=index, name="A")
275+
expected = pd.Series([3.0, 1.0], index=index, name="A")
276276
if as_index:
277277
self.assert_series_equal(result, expected)
278278
else:
@@ -301,7 +301,7 @@ def test_groupby_extension_no_sort(self, data_for_grouping):
301301
_, index = pd.factorize(data_for_grouping, sort=False)
302302

303303
index = pd.Index(index, name="B")
304-
expected = pd.Series([1, 3], index=index, name="A")
304+
expected = pd.Series([1.0, 3.0], index=index, name="A")
305305
self.assert_series_equal(result, expected)
306306

307307
def test_groupby_extension_transform(self, data_for_grouping):

pandas/tests/groupby/aggregate/test_aggregate.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,9 @@ def test_uint64_type_handling(dtype, how):
512512
expected = df.groupby("y").agg({"x": how})
513513
df.x = df.x.astype(dtype)
514514
result = df.groupby("y").agg({"x": how})
515-
result.x = result.x.astype(np.int64)
515+
if how not in ("mean", "median"):
516+
# mean and median always result in floats
517+
result.x = result.x.astype(np.int64)
516518
tm.assert_frame_equal(result, expected, check_exact=True)
517519

518520

pandas/tests/groupby/aggregate/test_cython.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,6 @@ def test_cython_agg_empty_buckets(op, targop, observed):
196196

197197
g = df.groupby(pd.cut(df[0], grps), observed=observed)
198198
expected = g.agg(lambda x: targop(x))
199-
if observed and op not in ("min", "max"):
200-
# TODO: GH 41137
201-
expected = expected.astype("int64")
202199
tm.assert_frame_equal(result, expected)
203200

204201

pandas/tests/groupby/test_categorical.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,8 +285,6 @@ def test_apply(ordered):
285285
result = grouped.apply(lambda x: np.mean(x))
286286
tm.assert_frame_equal(result, expected)
287287

288-
# we coerce back to ints
289-
expected = expected.astype("int")
290288
result = grouped.mean()
291289
tm.assert_frame_equal(result, expected)
292290

@@ -371,7 +369,7 @@ def test_observed(observed, using_array_manager):
371369
result = groups_double_key.agg("mean")
372370
expected = DataFrame(
373371
{
374-
"val": [10, 30, 20, 40],
372+
"val": [10.0, 30.0, 20.0, 40.0],
375373
"cat": Categorical(
376374
["a", "a", "b", "b"], categories=["a", "b", "c"], ordered=True
377375
),
@@ -418,7 +416,9 @@ def test_observed_codes_remap(observed):
418416
groups_double_key = df.groupby([values, "C2"], observed=observed)
419417

420418
idx = MultiIndex.from_arrays([values, [1, 2, 3, 4]], names=["cat", "C2"])
421-
expected = DataFrame({"C1": [3, 3, 4, 5], "C3": [10, 100, 200, 34]}, index=idx)
419+
expected = DataFrame(
420+
{"C1": [3.0, 3.0, 4.0, 5.0], "C3": [10.0, 100.0, 200.0, 34.0]}, index=idx
421+
)
422422
if not observed:
423423
expected = cartesian_product_for_groupers(
424424
expected, [values.values, [1, 2, 3, 4]], ["cat", "C2"]
@@ -1515,7 +1515,9 @@ def test_read_only_category_no_sort():
15151515
df = DataFrame(
15161516
{"a": [1, 3, 5, 7], "b": Categorical([1, 1, 2, 2], categories=Index(cats))}
15171517
)
1518-
expected = DataFrame(data={"a": [2, 6]}, index=CategoricalIndex([1, 2], name="b"))
1518+
expected = DataFrame(
1519+
data={"a": [2.0, 6.0]}, index=CategoricalIndex([1, 2], name="b")
1520+
)
15191521
result = df.groupby("b", sort=False).mean()
15201522
tm.assert_frame_equal(result, expected)
15211523

pandas/tests/groupby/test_groupby.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,7 +1257,7 @@ def test_groupby_keys_same_size_as_index():
12571257
)
12581258
df = DataFrame([["A", 10], ["B", 15]], columns=["metric", "values"], index=index)
12591259
result = df.groupby([Grouper(level=0, freq=freq), "metric"]).mean()
1260-
expected = df.set_index([df.index, "metric"])
1260+
expected = df.set_index([df.index, "metric"]).astype(float)
12611261

12621262
tm.assert_frame_equal(result, expected)
12631263

@@ -1350,7 +1350,7 @@ def test_groupby_2d_malformed():
13501350
d["ones"] = [1, 1]
13511351
d["label"] = ["l1", "l2"]
13521352
tmp = d.groupby(["group"]).mean()
1353-
res_values = np.array([[0, 1], [0, 1]], dtype=np.int64)
1353+
res_values = np.array([[0.0, 1.0], [0.0, 1.0]])
13541354
tm.assert_index_equal(tmp.columns, Index(["zeros", "ones"]))
13551355
tm.assert_numpy_array_equal(tmp.values, res_values)
13561356

@@ -2114,7 +2114,7 @@ def test_groupby_crash_on_nunique(axis):
21142114

21152115
def test_groupby_list_level():
21162116
# GH 9790
2117-
expected = DataFrame(np.arange(0, 9).reshape(3, 3))
2117+
expected = DataFrame(np.arange(0, 9).reshape(3, 3), dtype=float)
21182118
result = expected.groupby(level=[0]).mean()
21192119
tm.assert_frame_equal(result, expected)
21202120

0 commit comments

Comments
 (0)