在Python中使用pandas对数据框进行分组整理

46

在 pandas 中给定以下数据框:

import numpy as np
df = pandas.DataFrame({"a": np.random.random(100), "b": np.random.random(100), "id": np.arange(100)})

在每个点都由一个ab值组成的id存在的情况下,如何将ab分入指定的一组箱中(以便我可以然后在每个箱中取ab的中位数/平均值)?对于df来说,在df的任意给定行中,ab(或两者都有)可能具有NaN值。

以下是使用Joe Kington的解决方案并带有更实际的df的更好示例。我不确定如何访问下面每个df.a组的df.b元素:

a = np.random.random(20)
df = pandas.DataFrame({"a": a, "b": a + 10})
# bins for df.a
bins = np.linspace(0, 1, 10)
# bin df according to a
groups = df.groupby(np.digitize(df.a,bins))
# Get the mean of a in each group
print groups.mean()
## But how to get the mean of b for each group of a?
# ...
4个回答

63

可能有一种更有效的方法(我有一种感觉 pandas.crosstab 在这里会很有用),但是这是我会怎么做:

可能有一种更有效的方法(我有一种感觉 pandas.crosstab 在这里会很有用),但是这是我会怎么做:

import numpy as np
import pandas

df = pandas.DataFrame({"a": np.random.random(100),
                       "b": np.random.random(100),
                       "id": np.arange(100)})

# Bin the data frame by "a" with 10 bins...
bins = np.linspace(df.a.min(), df.a.max(), 10)
groups = df.groupby(np.digitize(df.a, bins))

# Get the mean of each bin:
print groups.mean() # Also could do "groups.aggregate(np.mean)"

# Similarly, the median:
print groups.median()

# Apply some arbitrary function to aggregate binned data
print groups.aggregate(lambda x: np.mean(x[x > 0.5]))
编辑:由于OP特别要求只是按a中的值将b分组,所以只需执行以下操作:
groups.mean().b

另外,如果您希望索引看起来更好(例如,将间隔显示为索引),就像@bdiamante的示例中那样,可以使用pandas.cut而不是numpy.digitize。(向bidamante致敬。我没有意识到pandas.cut的存在。)

import numpy as np
import pandas

df = pandas.DataFrame({"a": np.random.random(100), 
                       "b": np.random.random(100) + 10})

# Bin the data frame by "a" with 10 bins...
bins = np.linspace(df.a.min(), df.a.max(), 10)
groups = df.groupby(pandas.cut(df.a, bins))

# Get the mean of b, binned by the values in a
print groups.mean().b

这会导致:

a
(0.00186, 0.111]    10.421839
(0.111, 0.22]       10.427540
(0.22, 0.33]        10.538932
(0.33, 0.439]       10.445085
(0.439, 0.548]      10.313612
(0.548, 0.658]      10.319387
(0.658, 0.767]      10.367444
(0.767, 0.876]      10.469655
(0.876, 0.986]      10.571008
Name: b

非常出色和优雅!正是我所寻找的。根本不需要对数据框进行排序。 - user248237
如果您想根据组访问b值怎么办?我相信groups.mean()只会给出a的平均值。 - user248237
@user248237dfsf - 不是,它给出了ab的平均值(或者更确切地说,它给出了按照a中的值分组后b的平均值,这也是我认为你所要求的)。 - Joe Kington
1
groups.mean() 返回一个 DataFrame,所以你可以使用 groups.mean()["b"] 来访问按 a 分组后 b 的平均值。 - bdiamante

27

我不确定这是否符合您的要求,但我认为您想要表达的是:

In [144]: df = DataFrame({"a": np.random.random(100), "b": np.random.random(100), "id":   np.arange(100)})

In [145]: bins = [0, .25, .5, .75, 1]

In [146]: a_bins = df.a.groupby(cut(df.a,bins))

In [147]: b_bins = df.b.groupby(cut(df.b,bins))

In [148]: a_bins.agg([mean,median])
Out[148]:
                 mean    median
a
(0, 0.25]    0.124173  0.114613
(0.25, 0.5]  0.367703  0.358866
(0.5, 0.75]  0.624251  0.626730
(0.75, 1]    0.875395  0.869843

In [149]: b_bins.agg([mean,median])
Out[149]:
                 mean    median
b
(0, 0.25]    0.147936  0.166900
(0.25, 0.5]  0.394918  0.386729
(0.5, 0.75]  0.636111  0.655247
(0.75, 1]    0.851227  0.838805
当然,我不知道你心中想要的垃圾箱是什么样子的,所以你需要用你自己的情况来替换我的建议。

不错!我假设 OP 想要通过 "a" 对 "b" 进行分组,但回想起来,你的答案可能是他们正在寻找的。我会保留我的答案,因为我们的答案略有不同。 - Joe Kington
1
也许值得一提的是,它是 pandas.Dataframe({..})a_bins.agg([numpy.mean,numpy.median]) - Guido

16

Joe Kington的回答非常有帮助,但是我注意到它并没有将所有数据分组。实际上,它略过了a = a.min()的那一行。总结groups.size()只有99而不是100。

为确保所有数据都被分组,请传入要切割成的箱子数到cut()函数中,该函数将自动用0.1%填充第一个[最后一个]箱子以确保包含所有数据。

df = pandas.DataFrame({"a": np.random.random(100), 
                    "b": np.random.random(100) + 10})

# Bin the data frame by "a" with 10 bins...
groups = df.groupby(pandas.cut(df.a, 10))

# Get the mean of b, binned by the values in a
print(groups.mean().b)
在这种情况下,对于groups.size()的求和结果为100。
我知道这对于这个特定问题来说是一个挑剔的点,但对于我尝试解决的类似问题来说,获得正确答案非常重要。

2

如果您不必坚持使用 pandas 进行分组,您可以使用 scipy.stats.binned_statistic

from scipy.stats import binned_statistic

means = binned_statistic(df.a, df.b, bins=np.linspace(min(df.a), max(df.a), 10))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接