Groupby in python pandas:Fast Way

Nát*_*ali 9 python numpy pandas pandas-groupby

我想改善groupby蟒蛇熊猫的时间.我有这个代码:

df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len)
Run Code Online (Sandbox Code Playgroud)

目标是计算客户在一个月内签订的合同数量,并将此信息添加到新列(Nbcontrats)中.

  • Client:客户端代码
  • Month:数据提取月份
  • Contrat: 合同编号

我想改善时间.下面我只使用我的实际数据的一个子集:

%timeit df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len)
1 loops, best of 3: 391 ms per loop

df.shape
Out[309]: (7464, 61)
Run Code Online (Sandbox Code Playgroud)

如何改善执行时间?

Div*_*kar 22

这是一种方法:

  • ['Client', 'Month']将输入数据帧中的相关列()切片为NumPy数组.这主要是一个以性能为中心的想法,因为我们稍后会使用NumPy函数,这些函数经过优化可以与NumPy数组一起使用.

  • 将两列数据['Client', 'Month']转换为单个1D数组,这将是一个相当于它的线性索引,将两列中的元素视为成对.因此,我们可以假设元素来自'Client'行索引,而'Month'元素是列索引.这就好比从去2D1D.但是,问题在于决定2D网格的形状以执行这种映射.为了覆盖所有对,一个安全的假设是假设一个2D网格,其尺寸比每列的最大值大一,因为Python中基于0的索引.因此,我们将获得线性指数.

  • 接下来,我们根据它们的独特性标记每个线性索引.我认为这将对应于获得的密钥grouby.我们还需要在该1D阵列的整个长度上获得每个组/唯一键的计数.最后,使用这些标记索引计数应该为每个元素映射相应的计数.

这就是它的全部想法!这是实施 -

# Save relevant columns as a NumPy array for performing NumPy operations afterwards
arr_slice = df[['Client', 'Month']].values

# Get linear indices equivalent of those columns
lidx = np.ravel_multi_index(arr_slice.T,arr_slice.max(0)+1)

# Get unique IDs corresponding to each linear index (i.e. group) and grouped counts
unq,unqtags,counts = np.unique(lidx,return_inverse=True,return_counts=True)

# Index counts with the unique tags to map across all elements with the counts
df["Nbcontrats"] = counts[unqtags]
Run Code Online (Sandbox Code Playgroud)

运行时测试

1)定义功能:

def original_app(df):
    df["Nbcontrats"] = df.groupby(['Client', 'Month'])['Contrat'].transform(len)

def vectorized_app(df):
    arr_slice = df[['Client', 'Month']].values
    lidx = np.ravel_multi_index(arr_slice.T,arr_slice.max(0)+1)
    unq,unqtags,counts = np.unique(lidx,return_inverse=True,return_counts=True)
    df["Nbcontrats"] = counts[unqtags]
Run Code Online (Sandbox Code Playgroud)

2)验证结果:

In [143]: # Let's create a dataframe with 100 unique IDs and of length 10000
     ...: arr = np.random.randint(0,100,(10000,3))
     ...: df = pd.DataFrame(arr,columns=['Client','Month','Contrat'])
     ...: df1 = df.copy()
     ...: 
     ...: # Run the function on the inputs
     ...: original_app(df)
     ...: vectorized_app(df1)
     ...: 

In [144]: np.allclose(df["Nbcontrats"],df1["Nbcontrats"])
Out[144]: True
Run Code Online (Sandbox Code Playgroud)

3)最后计时:

In [145]: # Let's create a dataframe with 100 unique IDs and of length 10000
     ...: arr = np.random.randint(0,100,(10000,3))
     ...: df = pd.DataFrame(arr,columns=['Client','Month','Contrat'])
     ...: df1 = df.copy()
     ...: 

In [146]: %timeit original_app(df)
1 loops, best of 3: 645 ms per loop

In [147]: %timeit vectorized_app(df1)
100 loops, best of 3: 2.62 ms per loop
Run Code Online (Sandbox Code Playgroud)

  • 太完美了——谢谢你教我 numpy!不幸的是我不能多次投票;) (4认同)
  • `np.ravel_multi_index(arr_slice.T,arr_slice.max(0)+1)` 返回 `TypeError: 必须是 str,而不是 int` (3认同)

小智 5

DataFrameGroupBy.size方法:

df.set_index(['Client', 'Month'], inplace=True)
df['Nbcontrats'] = df.groupby(level=(0,1)).size()
df.reset_index(inplace=True)
Run Code Online (Sandbox Code Playgroud)

大部分工作是将结果分配回源 DataFrame 的列中。

  • 我不明白它如何提高性能,可以将运行时间与正常的 groupby 进行比较吗? (3认同)