Группирование, фильтрация и агрегирование цепей
DataFrameGroupby.filter
метод фильтрует группы и возвращает DataFrame
содержит строки, которые прошли фильтр.
но что я могу сделать, чтобы получить новый DataFrameGroupBy
вместо DataFrame
после фильтрации?
например, предположим, у меня есть DataFrame
df
С двумя колонками A
и B
. Я хочу получить среднее значение столбца B
для каждого значения столбца A
, если в этой группе не менее 5 строк:
# pandas 0.18.0
# doesn't work because `filter` returns a DF not a GroupBy object
df.groupby('A').filter(lambda x: len(x)>=5).mean()
# works but slower and awkward to write because needs to groupby('A') twice
df.groupby('A').filter(lambda x: len(x)>=5).reset_index().groupby('A').mean()
# works but more verbose than chaining
groups = df.groupby('A')
groups.mean()[groups.size() >= 5]
2 ответов
вот некоторые воспроизводимые данные:
np.random.seed(0)
df = pd.DataFrame(np.random.randint(0, 10, (10, 2)), columns=list('AB'))
>>> df
A B
0 5 0
1 3 3
2 7 9
3 3 5
4 2 4
5 7 6
6 8 8
7 1 6
8 7 7
9 8 1
пример приложения фильтра, демонстрирующего, что он работает с данными.
gb = df.groupby('A')
>>> gb.filter(lambda group: group.A.count() >= 3)
A B
2 7 9
5 7 6
8 7 7
вот некоторые из ваших вариантов:
1) вы также можете сначала фильтровать на основе значений, а затем группировать.
vc = df.A.value_counts()
>>> df.loc[df.A.isin(vc[vc >= 2].index)].groupby('A').mean()
B
A
3 4.000000
7 7.333333
8 4.500000
2) Выполните groupby дважды, до и после фильтра:
>>> (df.groupby('A', as_index=False)
.filter(lambda group: group.A.count() >= 2)
.groupby('A')
.mean())
B
A
3 4.000000
7 7.333333
8 4.500000
3) учитывая, что ваш первый groupby возвращает группы, вы также можете фильтровать те:
d = {k: v
for k, v in df.groupby('A').groups.items()
if len(v) >= 2} # gb.groups.iteritems() for Python 2
>>> d
{3: [1, 3], 7: [2, 5, 8], 8: [6, 9]}
это немного взломать, но должно быть относительно эффективным, так как вам не нужно перегруппироваться.
>>> pd.DataFrame({col: [df.ix[d[col], 'B'].mean()] for col in d}).T.rename(columns={0: 'B'})
B
3 4.000000
7 7.333333
8 4.500000
тайминги со 100k строк
np.random.seed(0)
df = pd.DataFrame(np.random.randint(0, 10, (100000, 2)), columns=list('AB'))
%timeit df.groupby('A', as_index=False).filter(lambda group: group['A'].count() >= 5).groupby('A').mean()
100 loops, best of 3: 18 ms per loop
%%timeit
vc = df.A.value_counts()
df.loc[df.A.isin(vc[vc >= 2].index)].groupby('A').mean()
100 loops, best of 3: 15.7 ms per loop
вы можете сделать это так:
In [310]: df
Out[310]:
a b
0 1 4
1 7 3
2 6 9
3 4 4
4 0 2
5 8 4
6 7 7
7 0 5
8 8 5
9 8 7
10 6 1
11 3 8
12 7 4
13 8 0
14 5 3
15 5 3
16 8 1
17 7 2
18 9 9
19 3 2
20 9 1
21 1 2
22 0 3
23 8 9
24 7 7
25 8 1
26 5 8
27 9 6
28 2 8
29 9 0
In [314]: r = df.groupby('a').apply(lambda x: x.b.mean() if len(x)>=5 else -1)
In [315]: r
Out[315]:
a
0 -1.000000
1 -1.000000
2 -1.000000
3 -1.000000
4 -1.000000
5 -1.000000
6 -1.000000
7 4.600000
8 3.857143
9 -1.000000
dtype: float64
In [316]: r[r>0]
Out[316]:
a
7 4.600000
8 3.857143
dtype: float64
однострочный, который возвращает фрейм данных вместо серии:
df.groupby('a') \
.apply(lambda x: x.b.mean() if len(x)>=5 else -1) \
.to_frame() \
.rename(columns={0:'mean'}) \
.query('mean > 0')
сравнение Timeit против DF со 100.000 строками:
def maxu():
r = df.groupby('a').apply(lambda x: x.b.mean() if len(x)>=5 else -1)
return r[r>0]
def maxu2():
return df.groupby('a') \
.apply(lambda x: x.b.mean() if len(x)>=5 else -1) \
.to_frame() \
.rename(columns={0:'mean'}) \
.query('mean > 0')
def alexander():
return df.groupby('a', as_index=False).filter(lambda group: group.a.count() >= 5).groupby('a').mean()
def alexander2():
vc = df.a.value_counts()
return df.loc[df.a.isin(vc[vc >= 5].index)].groupby('a').mean()
результаты:
In [419]: %timeit maxu()
1 loop, best of 3: 1.12 s per loop
In [420]: %timeit maxu2()
1 loop, best of 3: 1.12 s per loop
In [421]: %timeit alexander()
1 loop, best of 3: 34.9 s per loop
In [422]: %timeit alexander2()
10 loops, best of 3: 66.6 ms per loop
проверка:
In [423]: alexander2().sum()
Out[423]:
b 19220943.162
dtype: float64
In [424]: maxu2().sum()
Out[424]:
mean 19220943.162
dtype: float64
вывод:
явный победитель-это alexander2()
функции
@Alexander, поздравляю!