Skip to main content
 首页 » 编程设计

python之有没有比使用循环和 iloc 更快的方法在小 Pandas 数据帧上进行整行比较

2025年05月04日30kerrycode

我有大量的小 Pandas 数据帧,我必须在这些数据帧上进行整行比较并将结果写入新的数据帧,这些数据帧稍后会被连接起来。

对于行比较,我使用 iloc 在数据帧的长度上进行双循环。我不知道是否有更快的方法,我这样做的方式似乎很慢:

# -*- coding: utf-8 -*- 
import pandas as pd 
import time 
 
def processFrames1(DF): 
  LL = [] 
  for i in range(len(DF)): 
    for j in range(len(DF)): 
      if DF.iloc[i][0] != DF.iloc[j][0]: 
        T = {u'T1':DF.iloc[i][0]} 
        T[u'T2'] = DF.iloc[j][0] 
        T[u'T3'] = 1 
        if DF.iloc[i][2] > DF.iloc[j][2]: 
          T[u'T4'] = 1 
        elif DF.iloc[i][2] < DF.iloc[j][2]: 
          T[u'T4'] = -1 
        else: 
          T[u'T4'] = 0 
        if DF.iloc[i][1] < DF.iloc[j][1]: 
          T[u'T5'] = 1 
        else: 
          T[u'T5'] = -1 
        LL.append(T) 
  return pd.DataFrame.from_dict(LL) 
 
D = [{'A':'XA','B':1,'C':1.4}\ 
    ,{'A':'RT','B':2,'C':10}\ 
    ,{'A':'HO','B':3,'C':34}\ 
    ,{'A':'NJ','B':4,'C':0.41}\ 
    ,{'A':'WF','B':5,'C':114}\ 
    ,{'A':'DV','B':6,'C':74}\ 
    ,{'A':'KP','B':7,'C':2.4}] 
 
P = pd.DataFrame.from_dict(D) 
time0 = time.time() 
for i in range(10): 
  X = processFrames1(P) 
print time.time()-time0 
print X 

产生结果:

0.836999893188 
    T1  T2  T3  T4  T5 
0   XA  RT   1  -1   1 
1   XA  HO   1  -1   1 
2   XA  NJ   1   1   1 
3   XA  WF   1  -1   1 
4   XA  DV   1  -1   1 
5   XA  KP   1  -1   1 
6   RT  XA   1   1  -1 
7   RT  HO   1  -1   1 
8   RT  NJ   1   1   1 
9   RT  WF   1  -1   1 
10  RT  DV   1  -1   1 
11  RT  KP   1   1   1 
12  HO  XA   1   1  -1 
13  HO  RT   1   1  -1 
14  HO  NJ   1   1   1 
15  HO  WF   1  -1   1 
16  HO  DV   1  -1   1 
17  HO  KP   1   1   1 
18  NJ  XA   1  -1  -1 
19  NJ  RT   1  -1  -1 
20  NJ  HO   1  -1  -1 
21  NJ  WF   1  -1   1 
22  NJ  DV   1  -1   1 
23  NJ  KP   1  -1   1 
24  WF  XA   1   1  -1 
25  WF  RT   1   1  -1 
26  WF  HO   1   1  -1 
27  WF  NJ   1   1  -1 
28  WF  DV   1   1   1 
29  WF  KP   1   1   1 
30  DV  XA   1   1  -1 
31  DV  RT   1   1  -1 
32  DV  HO   1   1  -1 
33  DV  NJ   1   1  -1 
34  DV  WF   1  -1  -1 
35  DV  KP   1   1   1 
36  KP  XA   1   1  -1 
37  KP  RT   1  -1  -1 
38  KP  HO   1  -1  -1 
39  KP  NJ   1   1  -1 
40  KP  WF   1  -1  -1 
41  KP  DV   1  -1  -1 

处理这个有代表性的数据框 10 次几乎需要整整一秒,而我将不得不处理超过一百万个数据框。

有没有更快的方法来进行这些整行比较?

编辑1: 经过一些修改后,我可以让 Javier 的代码创建正确的输出:

def compare_values1(x,y): 
  if x>y: return 1 
  elif x<y: return -1 
  else: return 0 
 
def compare_values2(x,y): 
  if x<y: return 1 
  elif x>y: return -1 
  else: return 0 
 
def processFrames(P): 
  D = P.to_dict(orient='records') 
  d_A2B = {d["A"]:d["B"] for d in D} 
  d_A2C = {d["A"]:d["C"] for d in D} 
  keys = list(d_A2B.keys()) 
  LL = [] 
  for i in range(len(keys)): 
    k_i = keys[i] 
    for j in range(len(keys)): 
      if i != j: 
        k_j = keys[j] 
        LL.append([k_i,k_j,1,compare_values1(\ 
         d_A2C[k_i],d_A2C[k_j]),compare_values2(d_A2B[k_i],d_A2B[k_j])]) 
  return pd.DataFrame(LL,columns=['T1','T2','T3','T4','T5']) 

此函数的运行速度提高了大约 60 倍。

编辑2: 四种可能性的最终判断:

=============== 使用小数据框:

我原来的功能:

%timeit processFrames1(P) 
10 loops, best of 3: 85.3 ms per loop 

jezrael 的解决方案:

%timeit processFrames2(P) 
1 loop, best of 3: 286 ms per loop 

Javier 修改后的代码:

%timeit processFrames3(P) 
1000 loops, best of 3: 1.24 ms per loop 

Divakar 的方法:

%timeit processFrames4(P) 
1000 loops, best of 3: 1.98 ms per loop 

=============== 对于大数据框:

我原来的功能:

%timeit processFrames1(P) 
1 loop, best of 3: 2.22 s per loop 

jezrael 的解决方案:

%timeit processFrames2(P) 
1 loop, best of 3: 295 ms per loop 

Javier 修改后的代码:

%timeit processFrames3(P) 
100 loops, best of 3: 3.13 ms per loop 

Divakar 的方法:

%timeit processFrames4(P) 
100 loops, best of 3: 2.19 ms per loop 

所以这几乎是最后两者之间的平局。感谢大家的帮助,非常需要这种加速。

编辑 3:

Divakar 已经编辑了他们的代码,这是新的结果:

小数据框:

%timeit processFrames(P) 
1000 loops, best of 3: 492 µs per loop 

大数据框:

%timeit processFrames(P) 
1000 loops, best of 3: 844 µs per loop 

非常令人印象深刻,绝对的赢家。

编辑 4:

Divakar 的方法略有修改,因为我现在在我的程序中使用它:

def processFrames(P): 
  N = len(P) 
  N_range = np.arange(N) 
  valid_mask = (N_range[:,None] != N_range).ravel() 
  colB = P.B.values 
  colC = P.C.values 
  T2_arr = np.ones(N*N,dtype=int) 
  T4_arr = np.zeros((N,N),dtype=int) 
  T4_arr[colC[:,None] > colC] = 1 
  T4_arr[colC[:,None] < colC] = -1 
  T5_arr = np.zeros((N,N),dtype=int) 
  T5_arr[colB[:,None] > colB] = -1 
  T5_arr[colB[:,None] < colB] = 1 
  strings = P.A.values 
  c0,c1 = np.meshgrid(strings,strings) 
  arr = np.column_stack((c1.ravel(), c0.ravel(), T2_arr,T4_arr.ravel(),\ 
                         T5_arr.ravel()))[valid_mask] 
  return arr[:,0],arr[:,1],arr[:,2],arr[:,3],arr[:,4] 

我正在创建一个包含五个键的字典,每个键包含一个列表,每个键代表五个结果列,然后我只用结果扩展列表,完成后我从字典制作一个 pandas 数据框。这比连接到现有数据框要快得多。

PS:我从中学到的一件事:如果可以以任何方式避免使用 iloc,则永远不要使用它。

请您参考如下方法:

这是一种使用 NumPy broadcasting 的方法-

def processFrames1_broadcasting(P): 
 
    N = len(P) 
    N_range = np.arange(N) 
    valid_mask = (N_range[:,None] != N_range).ravel() 
 
    colB = P.B.values 
    colC = P.C.values 
 
    T2_arr = np.ones(N*N,dtype=int) 
 
    T4_arr = np.zeros((N,N),dtype=int) 
    T4_arr[colC[:,None] > colC] = 1 
    T4_arr[colC[:,None] < colC] = -1 
 
    T5_arr = np.where(colB[:,None] < colB,1,-1) 
 
    strings = P.A.values 
    c0,c1 = np.meshgrid(strings,strings) 
 
 
    arr = np.column_stack((c1.ravel(), c0.ravel(), T2_arr,T4_arr.ravel(),\ 
                            T5_arr.ravel()))[valid_mask] 
 
    df = pd.DataFrame(arr, columns=[['T1','T2','T3','T4','T5']]) 
 
    return df 

运行时测试-

对于问题中发布的示例,我最后得到的运行时间是 -

In [337]: %timeit processFrames1(P) 
10 loops, best of 3: 93.1 ms per loop 
 
In [338]: %timeit processFrames1_jezrael(P) #@jezrael's soln 
10 loops, best of 3: 74.8 ms per loop 
 
In [339]: %timeit processFrames1_broadcasting(P) 
1000 loops, best of 3: 561 µs per loop