目录
通过“分组依据”,我们指的是涉及以下一个或多个步骤的过程:
- 根据某些标准将数据拆分为组。
- 将功能独立应用于每个组。
- 将结果组合到数据结构中。
其中,分割步骤是最直接的。实际上,在许多情况下,我们可能希望将数据集拆分成组并对这些组执行某些操作。在应用步骤中,我们可能希望执行以下操作之一:
聚合:计算每个组的摘要统计(或统计),一些例子:
- 计算组总和或均值。
- 计算组大小/计数。
转换:执行一些特定于组的计算并返回类似索引的对象,一些例子:
- 标准化组内的数据(zscore)。
- 使用从每个组派生的值在组内填充NA。
过滤:根据评估True或False的分组计算丢弃一些组,一些例子:
- 丢弃属于只有少数成员的组的数据。
- 根据组总和或平均值过滤掉数据。
上述的一些组合:GroupBy将检查应用步骤的结果,并尝试返回合理的组合结果,如果它不适合上述两个类别中的任何一个。
由于pandas数据结构上的对象实例方法集通常丰富且富有表现力,因此我们通常只想在每个组上调用DataFrame函数。对于那些使用基于SQL的工具(或itertools
)的人来说,GroupBy这个名称应该非常熟悉,你可以在其中编写如下代码:
SELECT Column1, Column2, mean(Column3), sum(Column4)
FROM SomeTable
GROUP BY Column1, Column2
我们的目标是使用大熊猫这样的操作自然而且易于表达。我们将解决GroupBy功能的每个区域,然后提供一些非平凡的示例/用例。
有关一些高级策略,请参阅食谱。
将对象拆分成组
pandas对象可以在任何轴上分割。分组的抽象定义是提供标签到组名的映射。要创建GroupBy对象(稍后将详细介绍GroupBy对象),您可以执行以下操作:
# default is axis=0
grouped = obj.groupby(key)
grouped = obj.groupby(key, axis=1)
grouped = obj.groupby([key1, key2])
可以通过多种不同方式指定映射:
- 要在每个轴标签上调用的Python函数。
- 与所选轴长度相同的列表或NumPy数组。
- 字典或
Series
提供映射。label -> group name
- 对于
DataFrame
对象,表示要用于分组的列的字符串。当然df.groupby('A')
只是语法糖df.groupby(df['A'])
,但它使生活更简单。- 对于
DataFrame
对象,表示要用于分组的索引级别的字符串。- 以上任何事情的清单。
我们统称将分组对象称为键。例如,请考虑以下事项DataFrame
:
注意:
版本0.20中的新功能。
传递给的字符串
groupby
可以指列或索引级别。如果字符串与列名称和索引级别名称匹配,则会发出警告并且该列优先。这将导致未来版本中出现歧义错误。
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
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 [3]: grouped = df.groupby('A')
In [4]: grouped = df.groupby(['A', 'B'])
这些将在其索引(行)上拆分DataFrame。我们也可以按列拆分:
In [5]: def get_letter_type(letter):
...: if letter.lower() in 'aeiou':
...: return 'vowel'
...: else:
...: return 'consonant'
...:
In [6]: grouped = df.groupby(get_letter_type, axis=1)
pandas Index
对象支持重复值。如果在groupby操作中将非唯一索引用作组键,则相同索引值的所有值将被视为在一个组中,因此聚合函数的输出将仅包含唯一索引值:
In [7]: lst = [1, 2, 3, 1, 2, 3]
In [8]: s = pd.Series([1, 2, 3, 10, 20, 30], lst)
In [9]: grouped = s.groupby(level=0)
In [10]: grouped.first()
Out[10]:
1 1
2 2
3 3
dtype: int64
In [11]: grouped.last()
Out[11]:
1 10
2 20
3 30
dtype: int64
In [12]: grouped.sum()
Out[12]:
1 11
2 22
3 33
dtype: int64
请注意,在需要之前不会发生拆分。创建GroupBy对象仅验证您是否已传递有效映射。
注意:可以用GroupBy操作表达多种复杂的数据操作(尽管不能保证最有效)。您可以使用标签映射功能获得相当的创意。
GroupBy排序
默认情况下,组键在groupby
操作期间进行排序。然而sort=False
,您可以通过潜在的加速:
In [13]: df2 = pd.DataFrame({'X' : ['B', 'B', 'A', 'A'], 'Y' : [1, 2, 3, 4]})
In [14]: df2.groupby(['X']).sum()
Out[14]:
Y
X
A 7
B 3
In [15]: df2.groupby(['X'], sort=False).sum()
Out[15]:
Y
X
B 3
A 7
请注意,这groupby
将保留观察在每个组中的排序顺序。例如,groupby()
下面创建的组按它们在原始中出现的顺序排列DataFrame
:
In [16]: df3 = pd.DataFrame({'X' : ['A', 'B', 'A', 'B'], 'Y' : [1, 4, 3, 2]})
In [17]: df3.groupby(['X']).get_group('A')
Out[17]:
X Y
0 A 1
2 A 3
In [18]: df3.groupby(['X']).get_group('B')
Out[18]:
X Y
1 B 4
3 B 2
GroupBy对象属性
该groups
属性是一个字典,其键是计算的唯一组,相应的值是属于每个组的轴标签。在上面的例子中,我们有:
In [19]: df.groupby('A').groups
Out[19]:
{'bar': Int64Index([1, 3, 5], dtype='int64'),
'foo': Int64Index([0, 2, 4, 6, 7], dtype='int64')}
In [20]: df.groupby(get_letter_type, axis=1).groups
Out[20]:
{'consonant': Index(['B', 'C', 'D'], dtype='object'),
'vowel': Index(['A'], dtype='object')}
len
在GroupBy对象上调用标准Python 函数只返回groups
dict 的长度,因此它只是一个方便:
In [21]: grouped = df.groupby(['A', 'B'])
In [22]: grouped.groups
Out[22]:
{('bar', 'one'): Int64Index([1], dtype='int64'),
('bar', 'three'): Int64Index([3], dtype='int64'),
('bar', 'two'): Int64Index([5], dtype='int64'),
('foo', 'one'): Int64Index([0, 6], dtype='int64'),
('foo', 'three'): Int64Index([7], dtype='int64'),
('foo', 'two'): Int64Index([2, 4], dtype='int64')}
In [23]: len(grouped)
Out[23]: 6
GroupBy
将选项卡完成列名称(和其他属性):
In [24]: df
Out[24]:
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 [25]: gb = df.groupby('gender')
In [26]: gb.<TAB>
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
。
In [27]: arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
....: ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
....:
In [28]: index = pd.MultiIndex.from_arrays(arrays, names=['first', 'second'])
In [29]: s = pd.Series(np.random.randn(8), index=index)
In [30]: s
Out[30]:
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 [31]: grouped = s.groupby(level=0)
In [32]: grouped.sum()
Out[32]:
first
bar -0.962232
baz 1.237723
foo 0.785980
qux 1.911055
dtype: float64
如果MultiIndex指定了名称,则可以传递这些名称而不是级别号:
In [33]: s.groupby(level='second').sum()
Out[33]:
second
one 0.980950
two 1.991575
dtype: float64
聚合函数如sum
直接采用level参数。此外,结果索引将根据所选级别命名:
In [34]: s.sum(level='second')
Out[34]:
second
one 0.980950
two 1.991575
dtype: float64
支持多级分组。
In [35]: s
Out[35]:
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 [36]: s.groupby(level=['first', 'second']).sum()
Out[36]:
first second
bar doo -1.220674
baz bee -0.608004
foo bop 1.023898
qux bop 0.000895
dtype: float64
版本0.20中的新功能。
索引级别名称可以作为键提供。
In [37]: s.groupby(['first', 'second']).sum()
Out[37]:
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 [38]: arrays = [['bar', 'bar', 'baz', 'baz', 'foo', 'foo', 'qux', 'qux'],
....: ['one', 'two', 'one', 'two', 'one', 'two', 'one', 'two']]
....:
In [39]: index = pd.MultiIndex.from_arrays(arrays, names=['first', 'second'])
In [40]: df = pd.DataFrame({'A': [1, 1, 1, 1, 2, 2, 3, 3],
....: 'B': np.arange(8)},
....: index=index)
....:
In [41]: df
Out[41]:
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 [42]: df.groupby([pd.Grouper(level=1), 'A']).sum()
Out[42]:
B
second A
one 1 2
2 4
3 6
two 1 4
2 5
3 7
索引级别也可以通过名称指定。
In [43]: df.groupby([pd.Grouper(level='second'), 'A']).sum()
Out[43]:
B
second A
one 1 2
2 4
3 6
two 1 4
2 5
3 7
版本0.20中的新功能。
索引级别名称可以直接指定为键groupby
。
In [44]: df.groupby(['second', 'A']).sum()
Out[44]:
B
second A
one 1 2
2 4
3 6
two 1 4
2 5
3 7
GroupBy中的DataFrame列选择
从DataFrame创建GroupBy对象后,您可能希望为每个列执行不同的操作。因此,使用[]
类似于从DataFrame获取列,您可以执行以下操作:
In [45]: grouped = df.groupby(['A'])
In [46]: grouped_C = grouped['C']
In [47]: grouped_D = grouped['D']
这主要是替代的语法糖,更冗长:
In [48]: df['C'].groupby(df['A'])
Out[48]: <pandas.core.groupby.groupby.SeriesGroupBy object at 0x7f21344f5b70>
另外,该方法避免重新计算从传递的密钥导出的内部分组信息。
迭代组
使用GroupBy对象,迭代分组数据非常自然,其功能类似于itertools.groupby()
:
In [49]: grouped = df.groupby('A')
In [50]: 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 [51]: 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
它是标准的Python-fu但是请记住,如果你愿意,你可以在for循环语句中解压缩元组:。for (k1, k2), group ingrouped:
选择组
可以使用get_group()
以下方法选择单个组 :
In [52]: grouped.get_group('bar')
Out[52]:
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
或者对于在多列上分组的对象:
In [53]: df.groupby(['A', 'B']).get_group(('bar', 'one'))
Out[53]:
A B C D
1 bar one 0.254161 1.511763
聚合
创建GroupBy对象后,可以使用多种方法对分组数据执行计算。这些操作类似于 聚合API,窗口函数API和重新采样API。
一个显而易见的是通过aggregate()
或等效 agg()
方法聚合 :
In [54]: grouped = df.groupby('A')
In [55]: grouped.aggregate(np.sum)
Out[55]:
C D
A
bar 0.392940 1.732707
foo -1.796421 2.824590
In [56]: grouped = df.groupby(['A', 'B'])
In [57]: grouped.aggregate(np.sum)
Out[57]:
C D
A B
bar one 0.254161 1.511763
three 0.215897 -0.990582
two -0.077118 1.211526
foo one -0.983776 1.614581
three -0.862495 0.024580
two 0.049851 1.185429
如您所见,聚合的结果将组名称作为沿分组轴的新索引。在多个键的情况下,默认情况下结果是 MultiIndex,但可以使用以下as_index
选项更改:
In [58]: grouped = df.groupby(['A', 'B'], as_index=False)
In [59]: grouped.aggregate(np.sum)
Out[59]:
A B C D
0 bar one 0.254161 1.511763
1 bar three 0.215897 -0.990582
2 bar two -0.077118 1.211526
3 foo one -0.983776 1.614581
4 foo three -0.862495 0.024580
5 foo two 0.049851 1.185429
In [60]: df.groupby('A', as_index=False).sum()
Out[60]:
A C D
0 bar 0.392940 1.732707
1 foo -1.796421 2.824590
请注意,您可以使用reset_index
DataFrame函数来实现与结果中存储的列名相同的结果MultiIndex
:
另一个简单的聚合示例是计算每个组的大小。这包含在GroupBy中作为size
方法。它返回一个Series,其索引是组名,其值是每个组的大小。
In [62]: grouped.size()
Out[62]:
A B
bar one 1
three 1
two 1
foo one 2
three 1
two 2
dtype: int64
In [63]: grouped.describe()
Out[63]:
C D
count mean std min 25% 50% 75% max count mean std min 25% 50% 75% max
0 1.0 0.254161 NaN 0.254161 0.254161 0.254161 0.254161 0.254161 1.0 1.511763 NaN 1.511763 1.511763 1.511763 1.511763 1.511763
1 1.0 0.215897 NaN 0.215897 0.215897 0.215897 0.215897 0.215897 1.0 -0.990582 NaN -0.990582 -0.990582 -0.990582 -0.990582 -0.990582
2 1.0 -0.077118 NaN -0.077118 -0.077118 -0.077118 -0.077118 -0.077118 1.0 1.211526 NaN 1.211526 1.211526 1.211526 1.211526 1.211526
3 2.0 -0.491888 0.117887 -0.575247 -0.533567 -0.491888 -0.450209 -0.408530 2.0 0.807291 0.761937 0.268520 0.537905 0.807291 1.076676 1.346061
4 1.0 -0.862495 NaN -0.862495 -0.862495 -0.862495 -0.862495 -0.862495 1.0 0.024580 NaN 0.024580 0.024580 0.024580 0.024580 0.024580
5 2.0 0.024925 1.652692 -1.143704 -0.559389 0.024925 0.609240 1.193555 2.0 0.592714 1.462816 -0.441652 0.075531 0.592714 1.109898 1.627081
注意:聚合函数将不会返回您汇总了,如果它们被命名为组列,时
as_index=True
,默认的。分组列将是返回对象的索引。如果它们是命名列,则传递
as_index=False
将返回您聚合的组。
聚合函数是减少返回对象的维度的函数。一些常见的聚合函数列表如下:
功能 | 描述 |
---|---|
mean() | 计算群体的平均值 |
sum() | 计算组值的总和 |
size() | 计算组大小 |
count() | 计算组数 |
std() | 组的标准偏差 |
var() | 计算组的方差 |
sem() | 组平均值的标准误差 |
describe() | 生成描述性统计信息 |
first() | 首先计算组值 |
last() | 计算最后一组值 |
nth() | 如果n是列表,则取第n个值或子集 |
min() | 计算组值的最小值 |
max() | 计算组值的最大值 |
上面的聚合函数将排除NA值。将a减少Series
到标量值的任何函数都是聚合函数并且可以工作,这是一个简单的例子。请注意, 可以充当缩减器或过滤器,请参见此处。df.groupby('A').agg(lambda ser: 1)
nth()
一次应用多个功能
通过分组,Series
您还可以传递函数的列表或字典以进行聚合,输出DataFrame:
In [64]: grouped = df.groupby('A')
In [65]: grouped['C'].agg([np.sum, np.mean, np.std])
Out[65]:
sum mean std
A
bar 0.392940 0.130980 0.181231
foo -1.796421 -0.359284 0.912265
在分组上DataFrame
,您可以传递要应用于每个列的函数列表,从而生成具有分层索引的聚合结果:
In [66]: grouped.agg([np.sum, np.mean, np.std])
Out[66]:
C D
sum mean std sum mean std
A
bar 0.392940 0.130980 0.181231 1.732707 0.577569 1.366330
foo -1.796421 -0.359284 0.912265 2.824590 0.564918 0.884785
生成的聚合以函数本身命名。如果您需要重命名,那么您可以添加链接操作,Series
如下所示:
In [67]: (grouped['C'].agg([np.sum, np.mean, np.std])
....: .rename(columns={'sum': 'foo',
....: 'mean': 'bar',
....: 'std': 'baz'})
....: )
....:
Out[67]:
foo bar baz
A
bar 0.392940 0.130980 0.181231
foo -1.796421 -0.359284 0.912265
对于分组DataFrame,您可以以类似的方式重命名:
In [68]: (grouped.agg([np.sum, np.mean, np.std])
....: .rename(columns={'sum': 'foo',
....: 'mean': 'bar',
....: 'std': 'baz'})
....: )
....:
Out[68]:
C D
foo bar baz foo bar baz
A
bar 0.392940 0.130980 0.181231 1.732707 0.577569 1.366330
foo -1.796421 -0.359284 0.912265 2.824590 0.564918 0.884785
将不同的函数应用于DataFrame列
通过将dict传递给aggregate
您可以将不同的聚合应用于DataFrame的列:
In [69]: grouped.agg({'C' : np.sum,
....: 'D' : lambda x: np.std(x, ddof=1)})
....:
Out[69]:
C D
A
bar 0.392940 1.366330
foo -1.796421 0.884785
函数名称也可以是字符串。为了使字符串有效,它必须在GroupBy上实现或通过调度可用:
In [70]: grouped.agg({'C' : 'sum', 'D' : 'std'})
Out[70]:
C D
A
bar 0.392940 1.366330
foo -1.796421 0.884785
注意:如果传递dict
aggregate
,则输出列的顺序是不确定的。如果要确保输出列按特定顺序排列,可以使用OrderedDict
。比较以下两个命令的输出:
In [71]: grouped.agg({'D': 'std', 'C': 'mean'})
Out[71]:
D C
A
bar 1.366330 0.130980
foo 0.884785 -0.359284
In [72]: grouped.agg(OrderedDict([('D', 'std'), ('C', 'mean')]))
Out[72]:
D C
A
bar 1.366330 0.130980
foo 0.884785 -0.359284
Cython优化的聚合函数
一些常见的聚集,目前只sum
,mean
,std
,和sem
,优化了用Cython实现:
In [73]: df.groupby('A').sum()
Out[73]:
C D
A
bar 0.392940 1.732707
foo -1.796421 2.824590
In [74]: df.groupby(['A', 'B']).mean()
Out[74]:
C D
A B
bar one 0.254161 1.511763
three 0.215897 -0.990582
two -0.077118 1.211526
foo one -0.491888 0.807291
three -0.862495 0.024580
two 0.024925 0.592714
当然sum
并且mean
是在pandas对象上实现的,所以即使没有通过调度的特殊版本,上面的代码也可以工作(见下文)。
转型
该transform
方法返回一个对象,该对象的索引与被分组的对象相同(大小相同)。转换函数必须:
- 返回与组块大小相同或可广播到组块大小的结果(例如,标量 )。
grouped.transform(lambda x: x.iloc[-1])
- 在组块上逐列操作。使用chunk.apply将变换应用于第一个组块。
- 不对组块执行就地操作。应将组块视为不可变,并且对组块的更改可能会产生意外结果。例如,使用时
fillna
,inplace
必须是False
()。grouped.transform(lambda x: x.fillna(inplace=False))
- (可选)对整个组块进行操作。如果支持,则从第二个块开始使用快速路径。
例如,假设我们希望标准化每个组中的数据:
In [75]: index = pd.date_range('10/1/1999', periods=1100)
In [76]: ts = pd.Series(np.random.normal(0.5, 2, 1100), index)
In [77]: ts = ts.rolling(window=100,min_periods=100).mean().dropna()
In [78]: ts.head()
Out[78]:
2000-01-08 0.779333
2000-01-09 0.778852
2000-01-10 0.786476
2000-01-11 0.782797
2000-01-12 0.798110
Freq: D, dtype: float64
In [79]: ts.tail()
Out[79]:
2002-09-30 0.660294
2002-10-01 0.631095
2002-10-02 0.673601
2002-10-03 0.709213
2002-10-04 0.719369
Freq: D, dtype: float64
In [80]: key = lambda x: x.year
In [81]: zscore = lambda x: (x - x.mean()) / x.std()
In [82]: transformed = ts.groupby(key).transform(zscore)
我们希望结果现在在每个组中都有0和标准差1,我们可以很容易地检查:
# Original Data
In [83]: grouped = ts.groupby(key)
In [84]: grouped.mean()
Out[84]:
2000 0.442441
2001 0.526246
2002 0.459365
dtype: float64
In [85]: grouped.std()
Out[85]:
2000 0.131752
2001 0.210945
2002 0.128753
dtype: float64
# Transformed Data
In [86]: grouped_trans = transformed.groupby(key)
In [87]: grouped_trans.mean()
Out[87]:
2000 1.168208e-15
2001 1.454544e-15
2002 1.726657e-15
dtype: float64
In [88]: grouped_trans.std()
Out[88]:
2000 1.0
2001 1.0
2002 1.0
dtype: float64
我们还可以直观地比较原始数据集和转换数据集。
In [89]: compare = pd.DataFrame({'Original': ts, 'Transformed': transformed})
In [90]: compare.plot()
Out[90]: <matplotlib.axes._subplots.AxesSubplot at 0x7f21345fcb00>
具有较低维度输出的转换函数被广播以匹配输入数组的形状。
In [91]: data_range = lambda x: x.max() - x.min()
In [92]: ts.groupby(key).transform(data_range)
Out[92]:
2000-01-08 0.623893
2000-01-09 0.623893
2000-01-10 0.623893
2000-01-11 0.623893
2000-01-12 0.623893
2000-01-13 0.623893
2000-01-14 0.623893
...
2002-09-28 0.558275
2002-09-29 0.558275
2002-09-30 0.558275
2002-10-01 0.558275
2002-10-02 0.558275
2002-10-03 0.558275
2002-10-04 0.558275
Freq: D, Length: 1001, dtype: float64
或者,内置方法可用于产生相同的输出
In [93]: ts.groupby(key).transform('max') - ts.groupby(key).transform('min')
Out[93]:
2000-01-08 0.623893
2000-01-09 0.623893
2000-01-10 0.623893
2000-01-11 0.623893
2000-01-12 0.623893
2000-01-13 0.623893
2000-01-14 0.623893
...
2002-09-28 0.558275
2002-09-29 0.558275
2002-09-30 0.558275
2002-10-01 0.558275
2002-10-02 0.558275
2002-10-03 0.558275
2002-10-04 0.558275
Freq: D, Length: 1001, dtype: float64
另一种常见的数据转换是用组均值替换丢失的数据。
In [94]: data_df
Out[94]:
A B C
0 1.539708 -1.166480 0.533026
1 1.302092 -0.505754 NaN
2 -0.371983 1.104803 -0.651520
3 -1.309622 1.118697 -1.161657
4 -1.924296 0.396437 0.812436
5 0.815643 0.367816 -0.469478
6 -0.030651 1.376106 -0.645129
.. ... ... ...
993 0.012359 0.554602 -1.976159
994 0.042312 -1.628835 1.013822
995 -0.093110 0.683847 -0.774753
996 -0.185043 1.438572 NaN
997 -0.394469 -0.642343 0.011374
998 -1.174126 1.857148 NaN
999 0.234564 0.517098 0.393534
[1000 rows x 3 columns]
In [95]: countries = np.array(['US', 'UK', 'GR', 'JP'])
In [96]: key = countries[np.random.randint(0, 4, 1000)]
In [97]: grouped = data_df.groupby(key)
# Non-NA count in each group
In [98]: grouped.count()
Out[98]:
A B C
GR 209 217 189
JP 240 255 217
UK 216 231 193
US 239 250 217
In [99]: f = lambda x: x.fillna(x.mean())
In [100]: transformed = grouped.transform(f)
我们可以验证组意味着在转换的数据中没有改变,并且转换的数据不包含NA。
In [101]: grouped_trans = transformed.groupby(key)
In [102]: grouped.mean() # original group means
Out[102]:
A B C
GR -0.098371 -0.015420 0.068053
JP 0.069025 0.023100 -0.077324
UK 0.034069 -0.052580 -0.116525
US 0.058664 -0.020399 0.028603
In [103]: grouped_trans.mean() # transformation did not change group means
Out[103]:
A B C
GR -0.098371 -0.015420 0.068053
JP 0.069025 0.023100 -0.077324
UK 0.034069 -0.052580 -0.116525
US 0.058664 -0.020399 0.028603
In [104]: grouped.count() # original has some missing data points
Out[104]:
A B C
GR 209 217 189
JP 240 255 217
UK 216 231 193
US 239 250 217
In [105]: grouped_trans.count() # counts after transformation
Out[105]:
A B C
GR 228 228 228
JP 267 267 267
UK 247 247 247
US 258 258 258
In [106]: grouped_trans.size() # Verify non-NA count equals group size
Out[106]:
GR 228
JP 267
UK 247
US 258
dtype: int64
注意:某些函数在应用于GroupBy对象时会自动转换输入,但返回与原始对象形状相同的对象。传递
as_index=False
不会影响这些转换方法。
例如:。fillna, ffill, bfill, shift.
In [107]: grouped.ffill()
Out[107]:
NaN A B C
0 US 1.539708 -1.166480 0.533026
1 US 1.302092 -0.505754 0.533026
2 US -0.371983 1.104803 -0.651520
3 JP -1.309622 1.118697 -1.161657
4 JP -1.924296 0.396437 0.812436
5 US 0.815643 0.367816 -0.469478
6 GR -0.030651 1.376106 -0.645129
.. .. ... ... ...
993 US 0.012359 0.554602 -1.976159
994 GR 0.042312 -1.628835 1.013822
995 JP -0.093110 0.683847 -0.774753
996 JP -0.185043 1.438572 -0.774753
997 GR -0.394469 -0.642343 0.011374
998 JP -1.174126 1.857148 -0.774753
999 UK 0.234564 0.517098 0.393534
[1000 rows x 4 columns]
窗口和重采样操作的新语法
版本0.18.1中的新功能。
使用重新采样,在groupby级别上扩展或滚动操作用于要求应用辅助函数。不过,现在就可以使用resample()
,expanding()
并 rolling()
作为groupbys方法。
以下示例将rolling()
根据A列组对B列样本应用该方法。
In [108]: df_re = pd.DataFrame({'A': [1] * 10 + [5] * 10,
.....: 'B': np.arange(20)})
.....:
In [109]: df_re
Out[109]:
A B
0 1 0
1 1 1
2 1 2
3 1 3
4 1 4
5 1 5
6 1 6
.. .. ..
13 5 13
14 5 14
15 5 15
16 5 16
17 5 17
18 5 18
19 5 19
[20 rows x 2 columns]
In [110]: df_re.groupby('A').rolling(4).B.mean()
Out[110]:
A
1 0 NaN
1 NaN
2 NaN
3 1.5
4 2.5
5 3.5
6 4.5
...
5 13 11.5
14 12.5
15 13.5
16 14.5
17 15.5
18 16.5
19 17.5
Name: B, Length: 20, dtype: float64
该expanding()
方法将为sum()
每个特定组的所有成员累积给定操作(在该示例中)。
In [111]: df_re.groupby('A').expanding().sum()
Out[111]:
A B
A
1 0 1.0 0.0
1 2.0 1.0
2 3.0 3.0
3 4.0 6.0
4 5.0 10.0
5 6.0 15.0
6 7.0 21.0
... ... ...
5 13 20.0 46.0
14 25.0 60.0
15 30.0 75.0
16 35.0 91.0
17 40.0 108.0
18 45.0 126.0
19 50.0 145.0
[20 rows x 2 columns]
假设您要使用该resample()
方法获取数据帧的每个组中的每日频率,并希望使用该ffill()
方法完成缺失值。
In [112]: df_re = pd.DataFrame({'date': pd.date_range(start='2016-01-01',
.....: periods=4,
.....: freq='W'),
.....: 'group': [1, 1, 2, 2],
.....: 'val': [5, 6, 7, 8]}).set_index('date')
.....:
In [113]: df_re
Out[113]:
group val
date
2016-01-03 1 5
2016-01-10 1 6
2016-01-17 2 7
2016-01-24 2 8
In [114]: df_re.groupby('group').resample('1D').ffill()
Out[114]:
group val
group date
1 2016-01-03 1 5
2016-01-04 1 5
2016-01-05 1 5
2016-01-06 1 5
2016-01-07 1 5
2016-01-08 1 5
2016-01-09 1 5
... ... ...
2 2016-01-18 2 7
2016-01-19 2 7
2016-01-20 2 7
2016-01-21 2 7
2016-01-22 2 7
2016-01-23 2 7
2016-01-24 2 8
[16 rows x 2 columns]
过滤
该filter
方法返回原始对象的子集。假设我们只想采用属于组总和大于2的组的元素。
In [115]: sf = pd.Series([1, 1, 2, 3, 3, 3])
In [116]: sf.groupby(sf).filter(lambda x: x.sum() > 2)
Out[116]:
3 3
4 3
5 3
dtype: int64
参数filter
必须是一个函数,作为一个整体应用于组,返回True
或False
。
另一个有用的操作是过滤掉属于只有几个成员的组的元素。
In [117]: dff = pd.DataFrame({'A': np.arange(8), 'B': list('aabbbbcc')})
In [118]: dff.groupby('B').filter(lambda x: len(x) > 2)
Out[118]:
A B
2 2 b
3 3 b
4 4 b
5 5 b
或者,我们可以返回一个类似索引的对象,而不是丢弃违规组,而不通过过滤器的组会用NaN填充。
In [119]: dff.groupby('B').filter(lambda x: len(x) > 2, dropna=False)
Out[119]:
A B
0 NaN NaN
1 NaN NaN
2 2.0 b
3 3.0 b
4 4.0 b
5 5.0 b
6 NaN NaN
7 NaN NaN
对于具有多列的DataFrame,过滤器应明确指定列作为过滤条件。
In [120]: dff['C'] = np.arange(8)
In [121]: dff.groupby('B').filter(lambda x: len(x['C']) > 2)
Out[121]:
A B C
2 2 b 2
3 3 b 3
4 4 b 4
5 5 b 5
注意:应用于groupby对象时的某些函数将充当输入的过滤器,返回原始的缩小形状(并可能消除组),但索引不变。传递
as_index=False
不会影响这些转换方法。例如:。
head, tail
In [122]: dff.groupby('B').head(2) Out[122]: A B C 0 0 a 0 1 1 a 1 2 2 b 2 3 3 b 3 6 6 c 6 7 7 c 7
调度实例方法
在进行聚合或转换时,您可能只想在每个数据组上调用实例方法。通过传递lambda函数很容易做到:
In [123]: grouped = df.groupby('A')
In [124]: grouped.agg(lambda x: x.std())
Out[124]:
C D
A
bar 0.181231 1.366330
foo 0.912265 0.884785
但是,如果你需要传递额外的参数,它相当冗长并且可能不整洁。使用一点元编程聪明,GroupBy现在能够“调度”方法调用到组:
In [125]: grouped.std()
Out[125]:
C D
A
bar 0.181231 1.366330
foo 0.912265 0.884785
这里实际发生的是正在生成函数包装器。在调用时,它接受任何传递的参数,并使用每个组上的任何参数调用该函数(在上面的示例中,该std
函数)。然后将结果组合在一起的风格agg
和transform
(它实际上用于apply
推断胶合,接下来记录)。这使得一些操作可以相当简洁地执行:
In [126]: tsdf = pd.DataFrame(np.random.randn(1000, 3),
.....: index=pd.date_range('1/1/2000', periods=1000),
.....: columns=['A', 'B', 'C'])
.....:
In [127]: tsdf.iloc[::2] = np.nan
In [128]: grouped = tsdf.groupby(lambda x: x.year)
In [129]: grouped.fillna(method='pad')
Out[129]:
A B C
2000-01-01 NaN NaN NaN
2000-01-02 -0.353501 -0.080957 -0.876864
2000-01-03 -0.353501 -0.080957 -0.876864
2000-01-04 0.050976 0.044273 -0.559849
2000-01-05 0.050976 0.044273 -0.559849
2000-01-06 0.030091 0.186460 -0.680149
2000-01-07 0.030091 0.186460 -0.680149
... ... ... ...
2002-09-20 2.310215 0.157482 -0.064476
2002-09-21 2.310215 0.157482 -0.064476
2002-09-22 0.005011 0.053897 -1.026922
2002-09-23 0.005011 0.053897 -1.026922
2002-09-24 -0.456542 -1.849051 1.559856
2002-09-25 -0.456542 -1.849051 1.559856
2002-09-26 1.123162 0.354660 1.128135
[1000 rows x 3 columns]
在这个例子中,我们将时间序列集合切换成年度块,然后在组中独立地称为fillna。
该nlargest
和nsmallest
方法上工作Series
作风groupbys:
In [130]: s = pd.Series([9, 8, 7, 5, 19, 1, 4.2, 3.3])
In [131]: g = pd.Series(list('abababab'))
In [132]: gb = s.groupby(g)
In [133]: gb.nlargest(3)
Out[133]:
a 4 19.0
0 9.0
2 7.0
b 1 8.0
3 5.0
7 3.3
dtype: float64
In [134]: gb.nsmallest(3)
Out[134]:
a 6 4.2
2 7.0
0 9.0
b 5 1.0
7 3.3
3 5.0
dtype: float64
灵活apply
对分组数据的某些操作可能不适合聚合或转换类别。或者,您可能只是希望GroupBy推断如何组合结果。对于这些,使用该apply
函数,它可以替代两者aggregate
和transform
许多标准用例。但是, apply
可以处理一些特殊用例,例如:
In [135]: df
Out[135]:
A B C D
0 foo one -0.575247 1.346061
1 bar one 0.254161 1.511763
2 foo two -1.143704 1.627081
3 bar three 0.215897 -0.990582
4 foo two 1.193555 -0.441652
5 bar two -0.077118 1.211526
6 foo one -0.408530 0.268520
7 foo three -0.862495 0.024580
In [136]: grouped = df.groupby('A')
# could also just call .describe()
In [137]: grouped['C'].apply(lambda x: x.describe())
Out[137]:
A
bar count 3.000000
mean 0.130980
std 0.181231
min -0.077118
25% 0.069390
50% 0.215897
75% 0.235029
...
foo mean -0.359284
std 0.912265
min -1.143704
25% -0.862495
50% -0.575247
75% -0.408530
max 1.193555
Name: C, Length: 16, dtype: float64
返回结果的维度也可以更改:
In [138]: grouped = df.groupby('A')['C']
In [139]: def f(group):
.....: return pd.DataFrame({'original' : group,
.....: 'demeaned' : group - group.mean()})
.....:
In [140]: grouped.apply(f)
Out[140]:
original demeaned
0 -0.575247 -0.215962
1 0.254161 0.123181
2 -1.143704 -0.784420
3 0.215897 0.084917
4 1.193555 1.552839
5 -0.077118 -0.208098
6 -0.408530 -0.049245
7 -0.862495 -0.503211
apply
在一个系列上可以对应用函数的返回值进行操作,该函数本身就是一个序列,并且可能将结果向上转换为DataFrame:
In [141]: def f(x):
.....: return pd.Series([ x, x**2 ], index = ['x', 'x^2'])
.....:
In [142]: s
Out[142]:
0 9.0
1 8.0
2 7.0
3 5.0
4 19.0
5 1.0
6 4.2
7 3.3
dtype: float64
In [143]: s.apply(f)
Out[143]:
x x^2
0 9.0 81.00
1 8.0 64.00
2 7.0 49.00
3 5.0 25.00
4 19.0 361.00
5 1.0 1.00
6 4.2 17.64
7 3.3 10.89
注意:
apply
可以充当减速器,变压器或滤波器功能,具体取决于传递给它的确切内容。因此,取决于所采用的路径,以及您正在分组的内容。因此,分组的列可以包括在输出中以及设置索引。
警告:在当前实现中,在第一组上应用调用func两次以确定它是否可以采用快速或慢速代码路径。如果func有副作用,这可能会导致意外行为,因为它们将对第一组生效两次。
In [144]: d = pd.DataFrame({"a":["x", "y"], "b":[1,2]}) In [145]: def identity(df): .....: print(df) .....: return df .....: In [146]: d.groupby("a").apply(identity) a b 0 x 1 a b 0 x 1 a b 1 y 2 Out[146]: a b 0 x 1 1 y 2
其他有用的功能
自动排除“烦扰”列
再次考虑我们一直在关注的示例DataFrame:
In [147]: df
Out[147]:
A B C D
0 foo one -0.575247 1.346061
1 bar one 0.254161 1.511763
2 foo two -1.143704 1.627081
3 bar three 0.215897 -0.990582
4 foo two 1.193555 -0.441652
5 bar two -0.077118 1.211526
6 foo one -0.408530 0.268520
7 foo three -0.862495 0.024580
假设我们希望计算按A
列分组的标准偏差。有一个小问题,即我们不关心列中的数据B
。我们将此称为“讨厌”栏目。如果传递的聚合函数不能应用于某些列,则会(静默地)删除麻烦的列。因此,这不会造成任何问题:
In [148]: df.groupby('A').std()
Out[148]:
C D
A
bar 0.181231 1.366330
foo 0.912265 0.884785
注意,df.groupby('A').colname.std().
它比效率更高 df.groupby('A').std().colname
,因此如果聚合函数的结果仅对一列(此处colname
)感兴趣,则可以在应用聚合函数之前对其进行过滤 。
处理(未)观察到的分类值
当使用Categorical
石斑鱼(作为单个石斑鱼,或作为多次石斑鱼的一部分)时,observed
关键字控制是否返回所有可能的石斑鱼值(observed=False
)的笛卡尔积或仅返回观察到的石斑鱼(observed=True
)的那些。
显示所有值:
In [149]: pd.Series([1, 1, 1]).groupby(pd.Categorical(['a', 'a', 'a'], categories=['a', 'b']), observed=False).count()
Out[149]:
a 3
b 0
dtype: int64
仅显示观察到的值:
In [150]: pd.Series([1, 1, 1]).groupby(pd.Categorical(['a', 'a', 'a'], categories=['a', 'b']), observed=True).count()
Out[150]:
a 3
dtype: int64
返回的分组dtype 将始终包含所有分组的catergories。
In [151]: s = pd.Series([1, 1, 1]).groupby(pd.Categorical(['a', 'a', 'a'], categories=['a', 'b']), observed=False).count()
In [152]: s.index.dtype
Out[152]: CategoricalDtype(categories=['a', 'b'], ordered=False)
NA和NaT组处理
如果分组键中有任何NaN或NaT值,则会自动排除这些值。换句话说,永远不会有“NA组”或“NaT组”。在旧版本的熊猫中并非如此,但是用户通常会丢弃NA组(并且支持它是一个实施问题)。
用有序因子分组
表示为pandas Categorical
类的实例的分类变量可用作组键。如果是这样,将保留级别的顺序:
In [153]: data = pd.Series(np.random.randn(100))
In [154]: factor = pd.qcut(data, [0, .25, .5, .75, 1.])
In [155]: data.groupby(factor).mean()
Out[155]:
(-2.618, -0.684] -1.331461
(-0.684, -0.0232] -0.272816
(-0.0232, 0.541] 0.263607
(0.541, 2.369] 1.166038
dtype: float64
使用Grouper规范进行分组
您可能需要指定更多数据才能正确分组。您可以使用它pd.Grouper
来提供此本地控件。
In [156]: import datetime
In [157]: df = pd.DataFrame({
.....: 'Branch' : 'A A A A A A A B'.split(),
.....: 'Buyer': 'Carl Mark Carl Carl Joe Joe Joe Carl'.split(),
.....: 'Quantity': [1,3,5,1,8,1,9,3],
.....: 'Date' : [
.....: datetime.datetime(2013,1,1,13,0),
.....: datetime.datetime(2013,1,1,13,5),
.....: datetime.datetime(2013,10,1,20,0),
.....: datetime.datetime(2013,10,2,10,0),
.....: datetime.datetime(2013,10,1,20,0),
.....: datetime.datetime(2013,10,2,10,0),
.....: datetime.datetime(2013,12,2,12,0),
.....: datetime.datetime(2013,12,2,14,0),
.....: ]
.....: })
.....:
In [158]: df
Out[158]:
Branch Buyer Quantity Date
0 A Carl 1 2013-01-01 13:00:00
1 A Mark 3 2013-01-01 13:05:00
2 A Carl 5 2013-10-01 20:00:00
3 A Carl 1 2013-10-02 10:00:00
4 A Joe 8 2013-10-01 20:00:00
5 A Joe 1 2013-10-02 10:00:00
6 A Joe 9 2013-12-02 12:00:00
7 B Carl 3 2013-12-02 14:00:00
按具有所需频率的特定列进行分组。这就像重新采样。
In [159]: df.groupby([pd.Grouper(freq='1M',key='Date'),'Buyer']).sum()
Out[159]:
Quantity
Date Buyer
2013-01-31 Carl 1
Mark 3
2013-10-31 Carl 6
Joe 9
2013-12-31 Carl 3
Joe 9
你有一个模糊的规范,你有一个命名索引和一个可能是潜在石斑鱼的列。
In [160]: df = df.set_index('Date')
In [161]: df['Date'] = df.index + pd.offsets.MonthEnd(2)
In [162]: df.groupby([pd.Grouper(freq='6M',key='Date'),'Buyer']).sum()
Out[162]:
Quantity
Date Buyer
2013-02-28 Carl 1
Mark 3
2014-02-28 Carl 9
Joe 18
In [163]: df.groupby([pd.Grouper(freq='6M',level='Date'),'Buyer']).sum()
Out[163]:
Quantity
Date Buyer
2013-01-31 Carl 1
Mark 3
2014-01-31 Carl 9
Joe 18
取每组的第一行
就像DataFrame或Series一样,你可以在groupby上调用head和tail:
In [164]: df = pd.DataFrame([[1, 2], [1, 4], [5, 6]], columns=['A', 'B'])
In [165]: df
Out[165]:
A B
0 1 2
1 1 4
2 5 6
In [166]: g = df.groupby('A')
In [167]: g.head(1)
Out[167]:
A B
0 1 2
2 5 6
In [168]: g.tail(1)
Out[168]:
A B
1 1 4
2 5 6
这显示了每组的第一行或最后一行。
取每组的第n行
要从DataFrame或Series中选择第n个项目,请使用 nth()
。这是一种简化方法,如果为n传递int,则每个组将返回一行(或没有行):
In [169]: df = pd.DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=['A', 'B'])
In [170]: g = df.groupby('A')
In [171]: g.nth(0)
Out[171]:
B
A
1 NaN
5 6.0
In [172]: g.nth(-1)
Out[172]:
B
A
1 4.0
5 6.0
In [173]: g.nth(1)
Out[173]:
B
A
1 4.0
如果要选择第n个非空项,请使用dropna
kwarg。对于一个数据帧这应该是'any'
或者'all'
就像你传递给dropna:
# nth(0) is the same as g.first()
In [174]: g.nth(0, dropna='any')
Out[174]:
B
A
1 4.0
5 6.0
In [175]: g.first()
Out[175]:
B
A
1 4.0
5 6.0
# nth(-1) is the same as g.last()
In [176]: g.nth(-1, dropna='any') # NaNs denote group exhausted when using dropna
Out[176]:
B
A
1 4.0
5 6.0
In [177]: g.last()
Out[177]:
B
A
1 4.0
5 6.0
In [178]: g.B.nth(0, dropna='all')
Out[178]:
A
1 4.0
5 6.0
Name: B, dtype: float64
与其他方法一样,传递as_index=False
,将实现过滤,返回分组行。
In [179]: df = pd.DataFrame([[1, np.nan], [1, 4], [5, 6]], columns=['A', 'B'])
In [180]: g = df.groupby('A',as_index=False)
In [181]: g.nth(0)
Out[181]:
A B
0 1 NaN
2 5 6.0
In [182]: g.nth(-1)
Out[182]:
A B
1 1 4.0
2 5 6.0
您还可以通过将多个第n个值指定为整数列表来从每个组中选择多个行。
In [183]: business_dates = pd.date_range(start='4/1/2014', end='6/30/2014', freq='B')
In [184]: df = pd.DataFrame(1, index=business_dates, columns=['a', 'b'])
# get the first, 4th, and last date index for each month
In [185]: df.groupby([df.index.year, df.index.month]).nth([0, 3, -1])
Out[185]:
a b
2014 4 1 1
4 1 1
4 1 1
5 1 1
5 1 1
5 1 1
6 1 1
6 1 1
6 1 1
枚举组项
要查看每行在其组中的显示顺序,请使用以下 cumcount
方法:
In [186]: dfg = pd.DataFrame(list('aaabba'), columns=['A'])
In [187]: dfg
Out[187]:
A
0 a
1 a
2 a
3 b
4 b
5 a
In [188]: dfg.groupby('A').cumcount()
Out[188]:
0 0
1 1
2 2
3 0
4 1
5 3
dtype: int64
In [189]: dfg.groupby('A').cumcount(ascending=False)
Out[189]:
0 3
1 2
2 1
3 1
4 0
5 0
dtype: int64
枚举组
版本0.20.2中的新功能。
要查看组的顺序(而不是给定组中的行的顺序cumcount
),您可以使用 ngroup()
。
请注意,赋予组的数字与迭代groupby对象时看到的组的顺序相匹配,而不是首先观察它们的顺序。
In [190]: dfg = pd.DataFrame(list('aaabba'), columns=['A'])
In [191]: dfg
Out[191]:
A
0 a
1 a
2 a
3 b
4 b
5 a
In [192]: dfg.groupby('A').ngroup()
Out[192]:
0 0
1 0
2 0
3 1
4 1
5 0
dtype: int64
In [193]: dfg.groupby('A').ngroup(ascending=False)
Out[193]:
0 1
1 1
2 1
3 0
4 0
5 1
dtype: int64
绘图
Groupby还使用一些绘图方法。例如,假设我们怀疑DataFrame中的某些功能可能因组而异,在这种情况下,第1列中组为“B”的值平均高3。
In [194]: np.random.seed(1234)
In [195]: df = pd.DataFrame(np.random.randn(50, 2))
In [196]: df['g'] = np.random.choice(['A', 'B'], size=50)
In [197]: df.loc[df['g'] == 'B', 1] += 3
我们可以通过boxplot轻松地将其可视化:
In [198]: df.groupby('g').boxplot()
Out[198]:
A AxesSubplot(0.1,0.15;0.363636x0.75)
B AxesSubplot(0.536364,0.15;0.363636x0.75)
dtype: object
调用的结果boxplot
是一个字典,其键是我们的分组列g
(“A”和“B”)的值。结果字典的值可以通过return_type
关键字of 来控制boxplot
。请参阅可视化文档以获取更多信
警告:由于历史原因,
df.groupby("g").boxplot()
不等于df.boxplot(by="g")
。请看这里的解释。
管道功能调用
版本0.21.0中的新功能。
与由DataFrame
和提供的功能类似,可以使用方法将Series
获取GroupBy
对象的函数链接在一起,pipe
以允许更清晰,更易读的语法。要.pipe
概括地阅读,请参阅此处。
当您需要重用GroupBy对象时,组合.groupby
和.pipe
通常很有用。
例如,假设有一个DataFrame,其中包含商店,产品,收入和销售数量的列。我们想对 每个商店和每个产品的价格(即收入/数量)进行分组计算。我们可以在多步操作中执行此操作,但在管道方面表达它可以使代码更具可读性。首先我们设置数据:
In [199]: import numpy as np
In [200]: n = 1000
In [201]: df = pd.DataFrame({'Store': np.random.choice(['Store_1', 'Store_2'], n),
.....: 'Product': np.random.choice(['Product_1',
.....: 'Product_2'], n),
.....: 'Revenue': (np.random.random(n)*50+10).round(2),
.....: 'Quantity': np.random.randint(1, 10, size=n)})
.....:
In [202]: df.head(2)
Out[202]:
Store Product Revenue Quantity
0 Store_2 Product_1 26.12 1
1 Store_2 Product_1 28.86 1
现在,要查找每个商店/产品的价格,我们可以简单地做:
In [203]: (df.groupby(['Store', 'Product'])
.....: .pipe(lambda grp: grp.Revenue.sum()/grp.Quantity.sum())
.....: .unstack().round(2))
.....:
Out[203]:
Product Product_1 Product_2
Store
Store_1 6.82 7.05
Store_2 6.30 6.64
当您想要将分组对象传递给某个任意函数时,管道也可以表达,例如:
(df.groupby(['Store', 'Product']).pipe(report_func)
这里report_func
需要的GroupBy对象,并创建从一个报告。
示例
按因子重新组合
根据数据框的总和重新组合数据框的列,并对聚合的列进行求和。
In [204]: df = pd.DataFrame({'a':[1,0,0], 'b':[0,1,0], 'c':[1,0,0], 'd':[2,3,4]})
In [205]: df
Out[205]:
a b c d
0 1 0 1 2
1 0 1 0 3
2 0 0 0 4
In [206]: df.groupby(df.sum(), axis=1).sum()
Out[206]:
1 9
0 2 2
1 1 3
2 0 4
多列分解
通过使用ngroup()
,我们可以以类似于factorize()
(如重塑API中进一步描述的)的方式提取关于组的信息,但是其自然地应用于混合类型和不同源的多个列。当组行之间的关系比其内容更重要时,或者作为仅接受整数编码的算法的输入时,这可以用作处理中的类似中间类别的步骤。(有关完整分类数据的pandas支持的更多信息,请参阅分类介绍和API文档。)
In [207]: dfg = pd.DataFrame({"A": [1, 1, 2, 3, 2], "B": list("aaaba")})
In [208]: dfg
Out[208]:
A B
0 1 a
1 1 a
2 2 a
3 3 b
4 2 a
In [209]: dfg.groupby(["A", "B"]).ngroup()
Out[209]:
0 0
1 0
2 1
3 2
4 1
dtype: int64
In [210]: dfg.groupby(["A", [0, 0, 0, 1, 1]]).ngroup()
Out[210]:
0 0
1 0
2 1
3 3
4 2
dtype: int64
由索引器分组以'重新采样'数据
重新采样从现有的观察数据或生成数据的模型中生成新的假设样本(重新采样)。这些新样本与预先存在的样本类似。
为了重新采样以处理非日期时间的索引,可以使用以下过程。
在以下示例中,df.index // 5返回二进制数组,该数组用于确定为groupby操作选择的内容。
注意:以下示例显示了我们如何通过将样本合并为更少的样本来进行下采样。在这里,通过使用df.index // 5,我们聚集了箱中的样本。通过应用std()函数,我们将许多样本中包含的信息聚合成一小部分值,这是它们的标准偏差,从而减少了样本数量。
In [211]: df = pd.DataFrame(np.random.randn(10,2))
In [212]: df
Out[212]:
0 1
0 -0.793893 0.321153
1 0.342250 1.618906
2 -0.975807 1.918201
3 -0.810847 -1.405919
4 -1.977759 0.461659
5 0.730057 -1.316938
6 -0.751328 0.528290
7 -0.257759 -1.081009
8 0.505895 -1.701948
9 -1.006349 0.020208
In [213]: df.index // 5
Out[213]: Int64Index([0, 0, 0, 0, 0, 1, 1, 1, 1, 1], dtype='int64')
In [214]: df.groupby(df.index // 5).std()
Out[214]:
0 1
0 0.823647 1.312912
1 0.760109 0.942941
返回系列以传播名称
分组DataFrame列,计算一组指标并返回命名系列。系列名称用作列索引的名称。这与重组操作(例如堆栈)特别有用,其中列索引名称将用作插入列的名称:
In [215]: df = pd.DataFrame({
.....: 'a': [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2],
.....: 'b': [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1],
.....: 'c': [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
.....: 'd': [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1],
.....: })
.....:
In [216]: def compute_metrics(x):
.....: result = {'b_sum': x['b'].sum(), 'c_mean': x['c'].mean()}
.....: return pd.Series(result, name='metrics')
.....:
In [217]: result = df.groupby('a').apply(compute_metrics)
In [218]: result
Out[218]:
metrics b_sum c_mean
a
0 2.0 0.5
1 2.0 0.5
2 2.0 0.5
In [219]: result.stack()
Out[219]:
a metrics
0 b_sum 2.0
c_mean 0.5
1 b_sum 2.0
c_mean 0.5
2 b_sum 2.0
c_mean 0.5
dtype: float64