使用 groupby 更快地重新格式化数据
Faster data reformatting with groupby
所以我有一个看起来像这样的 DataFrame:
import pandas as pd
ddd = {
'a': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
'b': [22, 25, 18, 53, 19, 8, 75, 11, 49, 64],
'c': [1, 1, 1, 2, 2, 3, 4, 4, 4, 5]
}
df = pd.DataFrame(ddd)
我需要的是按 'c'
列对数据进行分组并应用一些数据转换。目前我正在这样做:
def do_stuff(d: pd.DataFrame):
if d.shape[0] >= 2:
return pd.DataFrame(
{
'start': [d.a.values[0]],
'end': [d.a.values[d.shape[0] - 1]],
'foo': [d.a.sum()],
'bar': [d.b.mean()]
}
)
else:
return pd.DataFrame()
r = df.groupby('c').apply(lambda x: do_stuff(x))
哪个给出了正确的结果:
start end foo bar
c
1 0 1.0 3.0 6.0 21.666667
2 0 4.0 5.0 9.0 36.000000
4 0 7.0 9.0 24.0 45.000000
问题是这种方法似乎太慢了。根据我的实际数据,它运行大约 0.7 秒,这太长了,理想情况下需要更快。
有什么方法可以更快地做到这一点?或者我可以使用其他一些不涉及 groupby
的更快的方法?
我们可以先过滤 df
出现 2 次或更多次的“c”值;然后使用 groupby
+ 命名聚合:
msk = df['c'].value_counts() >= 2
out = (df[df['c'].isin(msk.index[msk])]
.groupby('c')
.agg(start=('a','first'), end=('a','last'), foo=('a','sum'), bar=('b','mean')))
你也可以这样做:
out = (df[df.groupby('c')['c'].transform('count').ge(2)]
.groupby('c')
.agg(start=('a','first'),
end=('a','last'),
foo=('a','sum'),
bar=('b','mean')))
或
msk = df['c'].value_counts() >= 2
out = (df[df['c'].isin(msk.index[msk])]
.groupby('c')
.agg({'a':['first','last','sum'], 'b':'mean'})
.set_axis(['start','end','foo','bar'], axis=1))
输出:
start end foo bar
c
1 1 3 6 21.666667
2 4 5 9 36.000000
4 7 9 24 45.000000
一些基准:
>>> %timeit out = df.groupby('c').apply(lambda x: do_stuff(x))
6.49 ms ± 335 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit msk = df['c'].value_counts() >= 2; out = (df[df['c'].isin(msk.index[msk])].groupby('c').agg(start=('a','first'), end=('a','last'), foo=('a','sum'), bar=('b','mean')))
7.6 ms ± 211 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit out = (df[df.groupby('c')['c'].transform('count').ge(2)].groupby('c').agg(start=('a','first'), end=('a','last'), foo=('a','sum'), bar=('b','mean')))
7.86 ms ± 509 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit msk = df['c'].value_counts() >= 2; out = (df[df['c'].isin(msk.index[msk])].groupby('c').agg({'a':['first','last','sum'], 'b':'mean'}).set_axis(['start','end','foo','bar'], axis=1))
4.68 ms ± 57.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
所以我有一个看起来像这样的 DataFrame:
import pandas as pd
ddd = {
'a': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
'b': [22, 25, 18, 53, 19, 8, 75, 11, 49, 64],
'c': [1, 1, 1, 2, 2, 3, 4, 4, 4, 5]
}
df = pd.DataFrame(ddd)
我需要的是按 'c'
列对数据进行分组并应用一些数据转换。目前我正在这样做:
def do_stuff(d: pd.DataFrame):
if d.shape[0] >= 2:
return pd.DataFrame(
{
'start': [d.a.values[0]],
'end': [d.a.values[d.shape[0] - 1]],
'foo': [d.a.sum()],
'bar': [d.b.mean()]
}
)
else:
return pd.DataFrame()
r = df.groupby('c').apply(lambda x: do_stuff(x))
哪个给出了正确的结果:
start end foo bar
c
1 0 1.0 3.0 6.0 21.666667
2 0 4.0 5.0 9.0 36.000000
4 0 7.0 9.0 24.0 45.000000
问题是这种方法似乎太慢了。根据我的实际数据,它运行大约 0.7 秒,这太长了,理想情况下需要更快。
有什么方法可以更快地做到这一点?或者我可以使用其他一些不涉及 groupby
的更快的方法?
我们可以先过滤 df
出现 2 次或更多次的“c”值;然后使用 groupby
+ 命名聚合:
msk = df['c'].value_counts() >= 2
out = (df[df['c'].isin(msk.index[msk])]
.groupby('c')
.agg(start=('a','first'), end=('a','last'), foo=('a','sum'), bar=('b','mean')))
你也可以这样做:
out = (df[df.groupby('c')['c'].transform('count').ge(2)]
.groupby('c')
.agg(start=('a','first'),
end=('a','last'),
foo=('a','sum'),
bar=('b','mean')))
或
msk = df['c'].value_counts() >= 2
out = (df[df['c'].isin(msk.index[msk])]
.groupby('c')
.agg({'a':['first','last','sum'], 'b':'mean'})
.set_axis(['start','end','foo','bar'], axis=1))
输出:
start end foo bar
c
1 1 3 6 21.666667
2 4 5 9 36.000000
4 7 9 24 45.000000
一些基准:
>>> %timeit out = df.groupby('c').apply(lambda x: do_stuff(x))
6.49 ms ± 335 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit msk = df['c'].value_counts() >= 2; out = (df[df['c'].isin(msk.index[msk])].groupby('c').agg(start=('a','first'), end=('a','last'), foo=('a','sum'), bar=('b','mean')))
7.6 ms ± 211 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit out = (df[df.groupby('c')['c'].transform('count').ge(2)].groupby('c').agg(start=('a','first'), end=('a','last'), foo=('a','sum'), bar=('b','mean')))
7.86 ms ± 509 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit msk = df['c'].value_counts() >= 2; out = (df[df['c'].isin(msk.index[msk])].groupby('c').agg({'a':['first','last','sum'], 'b':'mean'}).set_axis(['start','end','foo','bar'], axis=1))
4.68 ms ± 57.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)