[英]Different groupers for each column with pandas GroupBy
我怎么能使用多維Grouper,在這種情況下是另一個數據幀,作為另一個數據幀的Grouper? 可以一步完成嗎?
我的問題主要是關於如何在這些情況下執行實際的分組,但為了使其更具體,說我想transform
並獲取sum
。
考慮例如:
df1 = pd.DataFrame({'a':[1,2,3,4], 'b':[5,6,7,8]})
print(df1)
a b
0 1 5
1 2 6
2 3 7
3 4 8
df2 = pd.DataFrame({'a':['A','B','A','B'], 'b':['A','A','B','B']})
print(df2)
a b
0 A A
1 B A
2 A B
3 B B
然后,預期的輸出將是:
a b
0 4 11
1 6 11
2 4 15
3 6 15
凡列a
和b
在df1
已經被列分組a
和b
從df2
分別。
嘗試使用apply
將lambda函數應用於數據幀的每一列,然后使用該pd.Series的名稱按第二個數據幀分組:
df1.apply(lambda x: x.groupby(df2[x.name]).transform('sum'))
輸出:
a b
0 4 11
1 6 11
2 4 15
3 6 15
您必須單獨對每列進行分組,因為每列使用不同的分組方案。
如果你想要一個更干凈的版本,我建議對列名稱進行列表理解,並在結果系列上調用pd.concat
:
pd.concat([df1[c].groupby(df2[c]).transform('sum') for c in df1.columns], axis=1)
a b
0 4 11
1 6 11
2 4 15
3 6 15
不是說使用apply
在其他答案中有什么問題,只是因為我不喜歡apply
,所以這是我的建議:-)
以下是您的細讀時間。 只是為了您的樣本數據,您會注意到時間上的差異是顯而易見的。
%%timeit
(df1.stack()
.groupby([df2.stack().index.get_level_values(level=1), df2.stack()])
.transform('sum').unstack())
%%timeit
df1.apply(lambda x: x.groupby(df2[x.name]).transform('sum'))
%%timeit
pd.concat([df1[c].groupby(df2[c]).transform('sum') for c in df1.columns], axis=1)
8.99 ms ± 4.55 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
8.35 ms ± 859 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
6.13 ms ± 279 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
不是說apply
很慢,但在這種情況下顯式迭代更快。 此外,您會注意到,由於迭代次數取決於列數,因此第二次和第三次定時解決方案將以更大的長度v / s寬度進行更好的擴展。
使用stack
和unstack
stack
df1.stack().groupby([df2.stack().index.get_level_values(level=1),df2.stack()]).transform('sum').unstack()
Out[291]:
a b
0 4 11
1 6 11
2 4 15
3 6 15
我要提出一個(大部分)numpythonic使用的解決方案scipy.sparse_matrix
進行量化groupby
對整個數據幀一次由列,而不是列。
有效執行此操作的關鍵是找到一種高效的方法來分解整個DataFrame,同時避免任何列中的重復。 由於您的組由字符串表示,您可以簡單地在每個值的末尾連接列名稱(因為列應該是唯一的),然后分解結果,如此[*]
>>> df2 + df2.columns
a b
0 Aa Ab
1 Ba Ab
2 Aa Bb
3 Ba Bb
>>> pd.factorize((df2 + df2.columns).values.ravel())
(array([0, 1, 2, 1, 0, 3, 2, 3], dtype=int64),
array(['Aa', 'Ab', 'Ba', 'Bb'], dtype=object))
一旦我們有了一個唯一的分組,我們就可以利用我們的scipy.sparse
矩陣,在flattened數組上一次執行groupby,並使用高級索引和整形操作將結果轉換回原始形狀。
from scipy import sparse
a = df1.values.ravel()
b, _ = pd.factorize((df2 + df2.columns).values.ravel())
o = sparse.csr_matrix(
(a, b, np.arange(a.shape[0] + 1)), (a.shape[0], b.max() + 1)
).sum(0).A1
res = o[b].reshape(df1.shape)
array([[ 4, 11],
[ 6, 11],
[ 4, 15],
[ 6, 15]], dtype=int64)
功能
def gp_chris(f1, f2):
a = f1.values.ravel()
b, _ = pd.factorize((f2 + f2.columns).values.ravel())
o = sparse.csr_matrix(
(a, b, np.arange(a.shape[0] + 1)), (a.shape[0], b.max() + 1)
).sum(0).A1
return pd.DataFrame(o[b].reshape(f1.shape), columns=df1.columns)
def gp_cs(f1, f2):
return pd.concat([f1[c].groupby(f2[c]).transform('sum') for c in f1.columns], axis=1)
def gp_scott(f1, f2):
return f1.apply(lambda x: x.groupby(f2[x.name]).transform('sum'))
def gp_wen(f1, f2):
return f1.stack().groupby([f2.stack().index.get_level_values(level=1), f2.stack()]).transform('sum').unstack()
建立
import numpy as np
from scipy import sparse
import pandas as pd
import string
from timeit import timeit
import matplotlib.pyplot as plt
res = pd.DataFrame(
index=[f'gp_{f}' for f in ('chris', 'cs', 'scott', 'wen')],
columns=[10, 50, 100, 200, 400],
dtype=float
)
for f in res.index:
for c in res.columns:
df1 = pd.DataFrame(np.random.rand(c, c))
df2 = pd.DataFrame(np.random.choice(list(string.ascii_uppercase), (c, c)))
df1.columns = df1.columns.astype(str)
df2.columns = df2.columns.astype(str)
stmt = '{}(df1, df2)'.format(f)
setp = 'from __main__ import df1, df2, {}'.format(f)
res.at[f, c] = timeit(stmt, setp, number=50)
ax = res.div(res.min()).T.plot(loglog=True)
ax.set_xlabel("N")
ax.set_ylabel("time (relative)")
plt.show()
結果
驗證
df1 = pd.DataFrame(np.random.rand(10, 10))
df2 = pd.DataFrame(np.random.choice(list(string.ascii_uppercase), (10, 10)))
df1.columns = df1.columns.astype(str)
df2.columns = df2.columns.astype(str)
v = np.stack([gp_chris(df1, df2), gp_cs(df1, df2), gp_scott(df1, df2), gp_wen(df1, df2)])
print(np.all(v[:-1] == v[1:]))
True
要么我們都錯了,要么我們都是正確的:)
[*]如果在連接發生之前一個項目是列和另一個項目的串聯,則有可能在此處獲得重復值。 但是,如果是這種情況,您不需要調整太多來修復它。
您可以執行以下操作:
res = df1.assign(a_sum=lambda df: df['a'].groupby(df2['a']).transform('sum'))\
.assign(b_sum=lambda df: df['b'].groupby(df2['b']).transform('sum'))
結果:
a b
0 4 11
1 6 11
2 4 15
3 6 15
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.