分组方式 Group by: split-apply-combine
“分组依据”是指涉及以下一个或多个步骤的过程:
-
拆分数据到基于某些标准组。
-
对每个组独立应用一个函数。
-
将结果组合成数据结构。
其中,拆分步骤是最直接的。事实上,在许多情况下,我们可能希望将数据集分成几组,并对这些组做一些事情。在应用步骤中,我们可能希望执行以下操作之一:
-
聚合:计算每个组的汇总统计(或统计)。一些例子:
-
计算组总和或均值。
-
计算组大小/计数。
-
-
转换:执行一些特定于组的计算并返回一个类似索引的对象。一些例子:
-
标准化组内的数据 (zscore)。
-
用从每个组派生的值填充组内的 NA。
-
-
过滤:根据评估 True 或 False 的分组计算丢弃一些组。一些例子:
-
丢弃属于只有少数成员的组的数据。
-
根据组总和或平均值过滤数据。
-
-
以上的一些组合: GroupBy 将检查应用步骤的结果,如果它不适合上述两个类别中的任何一个,则尝试返回一个合理的组合结果。
由于 Pandas 数据结构上的对象实例方法集通常是丰富且富有表现力的,因此我们通常只想在每个组上调用一个 DataFrame 函数。GroupBy 名称对于使用过基于 SQL 的工具(或itertools
)的人来说应该非常熟悉,您可以在其中编写如下代码:
SELECT Column1, Column2, mean(Column3), sum(Column4)
FROM SomeTable
GROUP BY Column1, Column2
我们的目标是使用 Pandas 使这样的操作变得自然且易于表达。我们将解决 GroupBy 功能的每个领域,然后提供一些重要的示例/用例。
有关一些高级策略,请参阅说明书。
将对象分成组
pandas 对象可以在它们的任何轴上拆分。分组的抽象定义是提供标签到组名的映射。要创建 GroupBy 对象(稍后将详细介绍 GroupBy 对象),您可以执行以下操作:
df = pd.DataFrame( [ ("bird", "Falconiformes", 389.0), ("bird", "Psittaciformes", 24.0), ("mammal", "Carnivora", 80.2), ("mammal", "Primates", np.nan), ("mammal", "Carnivora", 58), ], index=["falcon", "parrot", "lion", "monkey", "leopard"], columns=("class", "order", "max_speed"), ) df Out[2]: class order max_speed falcon bird Falconiformes 389.0 parrot bird Psittaciformes 24.0 lion mammal Carnivora 80.2 monkey mammal Primates NaN leopard mammal Carnivora 58.0 # default is axis=0 grouped = df.groupby("class") grouped = df.groupby("order", axis="columns") grouped = df.groupby(["class", "order"])
In [1]: df = pd.DataFrame(
...: [
...: ("bird", "Falconiformes", 389.0),
...: ("bird", "Psittaciformes", 24.0),
...: ("mammal", "Carnivora", 80.2),
...: ("mammal", "Primates", np.nan),
...: ("mammal", "Carnivora", 58),
...: ],
...: index=["falcon", "parrot", "lion", "monkey", "leopard"],
...: columns=("class", "order", "max_speed"),
...: )
...:
In [2]: df
Out[2]:
class order max_speed
falcon bird Falconiformes 389.0
parrot bird Psittaciformes 24.0
lion mammal Carnivora 80.2
monkey mammal Primates NaN
leopard mammal Carnivora 58.0
# default is axis=0
In [3]: grouped = df.groupby("class")
In [4]: grouped = df.groupby("order", axis="columns")
In [5]: grouped = df.groupby(["class", "order"])
可以通过多种不同方式指定映射:
-
一个 Python 函数,在每个轴标签上调用。
-
与所选轴长度相同的列表或 NumPy 数组。
-
一个 dict or
Series
,提供一个映射。label -> group name
-
对于
DataFrame
对象,一个字符串表示要用于分组的列名称或索引级别名称。 -
df.groupby('A')
只是df.groupby(df['A'])
. -
以上任何一项的清单。
我们将分组对象统称为键。例如,请考虑以下情况DataFrame
:
笔记
传递给的字符串groupby
可以指列或索引级别。如果字符串与列名和索引级别名称都匹配, ValueError
则会引发 a。
df = pd.DataFrame( { "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"], "B": ["one", "one", "two", "three", "two", "two", "one", "three"], "C": np.random.randn(8), "D": np.random.randn(8), } ) df Out[7]: A B C D 0 foo one 0.469112 -0.861849 1 bar one -0.282863 -2.104569 2 foo two -1.509059 -0.494929 3 bar three -1.135632 1.071804 4 foo two 1.212112 0.721555 5 bar two -0.173215 -0.706771 6 foo one 0.119209 -1.039575 7 foo three -1.044236 0.271860
In [6]: df = pd.DataFrame(
...: {
...: "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
...: "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
...: "C": np.random.randn(8),
...: "D": np.random.randn(8),
...: }
...: )
...:
In [7]: df
Out[7]:
A B C D
0 foo one 0.469112 -0.861849
1 bar one -0.282863 -2.104569
2 foo two -1.509059 -0.494929
3 bar three -1.135632 1.071804
4 foo two 1.212112 0.721555
5 bar two -0.173215 -0.706771
6 foo one 0.119209 -1.039575
7 foo three -1.044236 0.271860
在 DataFrame 上,我们通过调用 获得一个 GroupBy 对象groupby()
。我们可以自然地按A
或B
列或两者分组:
In [8]: grouped = df.groupby("A")
In [9]: grouped = df.groupby(["A", "B"])
如果我们在列A
和上也有一个多索引B
,我们可以按除指定列之外的所有列进行分组
df2 = df.set_index(["A", "B"]) grouped = df2.groupby(level=df2.index.names.difference(["B"])) grouped.sum() Out[12]: C D A bar -1.591710 -1.739537 foo -0.752861 -1.402938
这些将在其索引(行)上拆分 DataFrame。我们也可以按列拆分:
def get_letter_type(letter): if letter.lower() in 'aeiou': return 'vowel' else: return 'consonant' grouped = df.groupby(get_letter_type, axis=1)
pandasIndex
对象支持重复值。如果在 groupby 操作中使用非唯一索引作为组键,则同一索引值的所有值都将被视为在一个组中,因此聚合函数的输出将仅包含唯一索引值:
lst = [1, 2, 3, 1, 2, 3] s = pd.Series([1, 2, 3, 10, 20, 30], lst) grouped = s.groupby(level=0) grouped.first() Out[18]: 1 1 2 2 3 3 dtype: int64 grouped.last() Out[19]: 1 10 2 20 3 30 dtype: int64 grouped.sum() Out[20]: 1 11 2 22 3 33 dtype: int64
请注意,在需要之前不会发生拆分。创建 GroupBy 对象仅验证您是否已传递有效映射。
笔记
许多复杂的数据操作都可以用 GroupBy 操作来表达(虽然不能保证是最有效的)。您可以使用标签映射功能变得非常有创意。
分组排序
默认情况下,组键在groupby
操作期间排序。但是sort=False
,您可能会忽略潜在的加速:
df2 = pd.DataFrame({"X": ["B", "B", "A", "A"], "Y": [1, 2, 3, 4]}) df2.groupby(["X"]).sum() Out[22]: Y X A 7 B 3 df2.groupby(["X"], sort=False).sum() Out[23]: Y X B 3 A 7
请注意,groupby
将保留观察在每个组内排序的顺序。例如,groupby()
下面创建的组按照它们在原始中出现的顺序DataFrame
:
In [24]: df3 = pd.DataFrame({"X": ["A", "B", "A", "B"], "Y": [1, 4, 3, 2]})
In [25]: df3.groupby(["X"]).get_group("A")
Out[25]:
X Y
0 A 1
2 A 3
In [26]: df3.groupby(["X"]).get_group("B")
Out[26]:
X Y
1 B 4
3 B 2
1.1.0 版中的新功能。
GroupBy dropna
默认情况下NA
,groupby
操作期间从组键中排除值。但是,如果您想NA
在组键中包含值,您可以通过传递dropna=False
来实现它。
In [27]: df_list = [[1, 2, 3], [1, None, 4], [2, 1, 3], [1, 2, 2]]
In [28]: df_dropna = pd.DataFrame(df_list, columns=["a", "b", "c"])
In [29]: df_dropna
Out[29]:
a b c
0 1 2.0 3
1 1 NaN 4
2 2 1.0 3
3 1 2.0 2
# Default ``dropna`` is set to True, which will exclude NaNs in keys
In [30]: df_dropna.groupby(by=["b"], dropna=True).sum()
Out[30]:
a c
b
1.0 2 3
2.0 2 5
# In order to allow NaN in keys, set ``dropna`` to False
In [31]: df_dropna.groupby(by=["b"], dropna=False).sum()
Out[31]:
a c
b
1.0 2 3
2.0 2 5
NaN 1 4
dropna
参数的默认设置是True
表示NA
不包含在组键中。
GroupBy 对象属性
该groups
属性是一个字典,其键是计算出的唯一组,对应的值是属于每个组的轴标签。在上面的例子中,我们有:
In [32]: df.groupby("A").groups
Out[32]: {'bar': [1, 3, 5], 'foo': [0, 2, 4, 6, 7]}
In [33]: df.groupby(get_letter_type, axis=1).groups
Out[33]: {'consonant': ['B', 'C', 'D'], 'vowel': ['A']}
len
在 GroupBy 对象上调用标准 Python函数只返回groups
字典的长度,所以它很大程度上只是为了方便:
In [34]: grouped = df.groupby(["A", "B"])
In [35]: grouped.groups
Out[35]: {('bar', 'one'): [1], ('bar', 'three'): [3], ('bar', 'two'): [5], ('foo', 'one'): [0, 6], ('foo', 'three'): [7], ('foo', 'two'): [2, 4]}
In [36]: len(grouped)
Out[36]: 6
GroupBy
将选项卡完整的列名(和其他属性):
In [37]: df
Out[37]:
height weight gender
2000-01-01 42.849980 157.500553 male
2000-01-02 49.607315 177.340407 male
2000-01-03 56.293531 171.524640 male
2000-01-04 48.421077 144.251986 female
2000-01-05 46.556882 152.526206 male
2000-01-06 68.448851 168.272968 female
2000-01-07 70.757698 136.431469 male
2000-01-08 58.909500 176.499753 female
2000-01-09 76.435631 174.094104 female
2000-01-10 45.306120 177.540920 male
In [38]: gb = df.groupby("gender")
In [39]: gb.<TAB> # noqa: E225, E999
gb.agg gb.boxplot gb.cummin gb.describe gb.filter gb.get_group gb.height gb.last gb.median gb.ngroups gb.plot gb.rank gb.std gb.transform
gb.aggregate gb.count gb.cumprod gb.dtype gb.first gb.groups gb.hist gb.max gb.min gb.nth gb.prod gb.resample gb.sum gb.var
gb.apply gb.cummax gb.cumsum gb.fillna gb.gender gb.head gb.indices gb.mean gb.name gb.ohlc gb.quantile gb.size gb.tail gb.weight
GroupBy 与 MultiIndex
对于按层次索引的数据,按层次结构的一个级别进行分组是很自然的。
让我们创建一个具有两级MultiIndex
.
In [40]: arrays = [
....: ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
....: ["one", "two", "one", "two", "one", "two", "one", "two"],
....: ]
....:
In [41]: index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"])
In [42]: s = pd.Series(np.random.randn(8), index=index)
In [43]: s
Out[43]:
first second
bar one -0.919854
two -0.042379
baz one 1.247642
two -0.009920
foo one 0.290213
two 0.495767
qux one 0.362949
two 1.548106
dtype: float64
然后我们可以按 中的级别之一进行分组s
。
In [44]: grouped = s.groupby(level=0)
In [45]: grouped.sum()
Out[45]:
first
bar -0.962232
baz 1.237723
foo 0.785980
qux 1.911055
dtype: float64
如果 MultiIndex 指定了名称,则可以传递这些名称而不是级别编号:
In [46]: s.groupby(level="second").sum()
Out[46]:
second
one 0.980950
two 1.991575
dtype: float64
支持多级分组。
In [47]: s
Out[47]:
first second third
bar doo one -1.131345
two -0.089329
baz bee one 0.337863
two -0.945867
foo bop one -0.932132
two 1.956030
qux bop one 0.017587
two -0.016692
dtype: float64
In [48]: s.groupby(level=["first", "second"]).sum()
Out[48]:
first second
bar doo -1.220674
baz bee -0.608004
foo bop 1.023898
qux bop 0.000895
dtype: float64
索引级别名称可以作为键提供。
In [49]: s.groupby(["first", "second"]).sum()
Out[49]:
first second
bar doo -1.220674
baz bee -0.608004
foo bop 1.023898
qux bop 0.000895
dtype: float64
sum
稍后将详细介绍函数和聚合。
使用索引级别和列对 DataFrame 进行分组
通过将列名称指定为字符串并将索引级别指定为pd.Grouper
对象,可以按列和索引级别的组合对 DataFrame 进行分组。
In [50]: arrays = [
....: ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
....: ["one", "two", "one", "two", "one", "two", "one", "two"],
....: ]
....:
In [51]: index = pd.MultiIndex.from_arrays(arrays, names=["first", "second"])
In [52]: df = pd.DataFrame({"A": [1, 1, 1, 1, 2, 2, 3, 3], "B": np.arange(8)}, index=index)
In [53]: df
Out[53]:
A B
first second
bar one 1 0
two 1 1
baz one 1 2
two 1 3
foo one 2 4
two 2 5
qux one 3 6
two 3 7
以下示例df
按second
索引级别和A
列进行分组。
In [54]: df.groupby([pd.Grouper(level=1), "A"]).sum()
Out[54]:
B
second A
one 1 2
2 4
3 6
two 1 4
2 5
3 7
索引级别也可以按名称指定。
In [55]: df.groupby([pd.Grouper(level="second"), "A"]).sum()
Out[55]:
B
second A
one 1 2
2 4
3 6
two 1 4
2 5
3 7
索引级别名称可以直接指定为groupby
.
In [56]: df.groupby(["second", "A"]).sum()
Out[56]:
B
second A
one 1 2
2 4
3 6
two 1 4
2 5
3 7
GroupBy 中的 DataFrame 列选择
从 DataFrame 创建 GroupBy 对象后,您可能希望对每一列执行不同的操作。因此,使用[]
类似于从 DataFrame 获取列,您可以执行以下操作:
In [57]: grouped = df.groupby(["A"])
In [58]: grouped_C = grouped["C"]
In [59]: grouped_D = grouped["D"]
这主要是替代方案的语法糖,而且更加冗长:
In [60]: df["C"].groupby(df["A"])
Out[60]: <pandas.core.groupby.generic.SeriesGroupBy object at 0x7f4305e377f0>
此外,此方法避免重新计算从传递的密钥派生的内部分组信息。
遍历组
使用 GroupBy 对象,遍历分组数据非常自然,其功能类似于itertools.groupby()
:
In [61]: grouped = df.groupby('A')
In [62]: for name, group in grouped:
....: print(name)
....: print(group)
....:
bar
A B C D
1 bar one 0.254161 1.511763
3 bar three 0.215897 -0.990582
5 bar two -0.077118 1.211526
foo
A B C D
0 foo one -0.575247 1.346061
2 foo two -1.143704 1.627081
4 foo two 1.193555 -0.441652
6 foo one -0.408530 0.268520
7 foo three -0.862495 0.024580
在按多个键分组的情况下,组名将是一个元组:
In [63]: for name, group in df.groupby(['A', 'B']):
....: print(name)
....: print(group)
....:
('bar', 'one')
A B C D
1 bar one 0.254161 1.511763
('bar', 'three')
A B C D
3 bar three 0.215897 -0.990582
('bar', 'two')
A B C D
5 bar two -0.077118 1.211526
('foo', 'one')
A B C D
0 foo one -0.575247 1.346061
6 foo one -0.408530 0.268520
('foo', 'three')
A B C D
7 foo three -0.862495 0.02458
('foo', 'two')
A B C D
2 foo two -1.143704 1.627081
4 foo two 1.193555 -0.441652
请参阅迭代组。