有没有比使用循环和iloc更快的方法在小型pandas数据帧上进行完整行比较?

时间:2016-08-11 13:14:26

标签: python performance pandas dataframe vectorization

我有大量的小熊猫数据帧,我必须在这些数据帧上进行完整的行比较,并将结果写入新的数据帧,这些数据帧将在以后连接。

对于行比较,我正在使用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次,我需要花费超过一百万的时间。

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

EDIT1: 经过一些修改后,我可以让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倍。

EDIT2: 四种可能性的最终判决:

===============使用小数据帧:

我原来的功能:

%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,就不要使用iloc。

3 个答案:

答案 0 :(得分:3)

以下是使用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

答案 1 :(得分:1)

不要使用熊猫。使用词典并保存:

def compare_values(x,y):
  if x>y: return 1
  elif x<y: return -1
  else: return 0

def processFrames(P):
  d_A2B = dict(zip(P["A"],P["B"]))
  d_A2C = dict(zip(P["A"],P["C"]))

  keys = list(d_A2B.keys())
  d_ind2key = dict(zip(range(len(keys)),keys))
  LL = []
  for i in range(len(keys)):
    k_i = keys[i]
    for j in range(i+1,len(keys)):
        k_j = keys[j]
        c1 = compare_values(d_A2C[k_i],d_A2C[k_j])
        c2 = -compare_values(d_A2B[k_i],d_A2B[k_j])
        LL.append([k_i,k_j,1,c1,c2])
        LL.append([k_j,k_i,1,-c1,-c2])
  return pd.DataFrame(LL,columns=['T1','T2','T3','T4','T5'])

答案 2 :(得分:1)

您可以使用:

#cross join
P['one'] = 1
df = pd.merge(P,P, on='one')
df = df.rename(columns={'A_x':'T1','A_y':'T2'})

#remove duplicates
df = df[df.T1 != df.T2]
df.reset_index(drop=True, inplace=True)

#creates new columns
df['T3'] = 1
df['T4'] = (df.C_x > df.C_y).astype(int).replace({0:-1})
df['T5'] = (df.B_x < df.B_y).astype(int).replace({0:-1})
#remove other columns by subset
df = df[['T1','T2','T3','T4','T5']]
print (df)
    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

<强>的时间设置

In [339]: %timeit processFrames1(P)
10 loops, best of 3: 44.2 ms per loop

In [340]: %timeit jez(P1)
10 loops, best of 3: 43.3 ms per loop

如果使用你的时间:

time0 = time.time()
for i in range(10):
  X = processFrames1(P)
print (time.time()-time0)
0.4760475158691406

time0 = time.time()
for i in range(10):
  X = jez(P1)
print (time.time()-time0)
0.4400441646575928

测试代码:

P1 = P.copy()

def jez(P):
    P['one'] = 1
    df = pd.merge(P,P, on='one')
    df = df.rename(columns={'A_x':'T1','A_y':'T2'})

    df = df[df.T1 != df.T2]
    df.reset_index(drop=True, inplace=True)
    df['T3'] = 1
    df['T4'] = (df.C_x > df.C_y).astype(int).replace({0:-1})
    df['T5'] = (df.B_x < df.B_y).astype(int).replace({0:-1})
    df = df[['T1','T2','T3','T4','T5']]
    return (df)

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)

EDIT1:

我尝试用5倍更大的dataFrame测试:

D = [{'A':'XA','B':1,'C':1.4}\
    ,{'A':'RB','B':2,'C':10}\
    ,{'A':'HC','B':3,'C':34}\
    ,{'A':'ND','B':4,'C':0.41}\
    ,{'A':'WE','B':5,'C':114}\
    ,{'A':'DF','B':6,'C':74}\
    ,{'A':'KG','B':7,'C':2.4}\
    ,{'A':'XH','B':1,'C':1.4}\
    ,{'A':'RI','B':2,'C':10}\
    ,{'A':'HJ','B':3,'C':34}\
    ,{'A':'NK','B':4,'C':0.41}\
    ,{'A':'WL','B':5,'C':114}\
    ,{'A':'DM','B':6,'C':74}\
    ,{'A':'KN','B':7,'C':2.4}\
    ,{'A':'XO','B':1,'C':1.4}\
    ,{'A':'RP','B':2,'C':10}\
    ,{'A':'HQ','B':3,'C':34}\
    ,{'A':'NR','B':4,'C':0.41}\
    ,{'A':'WS','B':5,'C':114}\
    ,{'A':'DT','B':6,'C':74}\
    ,{'A':'KU','B':7,'C':2.4}\
    ,{'A':'XV','B':1,'C':1.4}\
    ,{'A':'RW','B':2,'C':10}\
    ,{'A':'HX','B':3,'C':34}\
    ,{'A':'NY','B':4,'C':0.41}\
    ,{'A':'WZ','B':5,'C':114}\
    ,{'A':'D1','B':6,'C':74}\
    ,{'A':'K2','B':7,'C':2.4}\
    ,{'A':'X3','B':1,'C':1.4}\
    ,{'A':'R4','B':2,'C':10}\
    ,{'A':'H5','B':3,'C':34}\
    ,{'A':'N6','B':4,'C':0.41}\
    ,{'A':'W7','B':5,'C':114}\
    ,{'A':'D8','B':6,'C':74}\
    ,{'A':'K9','B':7,'C':2.4}    ]

P = pd.DataFrame.from_dict(D)
P1 = P.copy()

time0 = time.time()
for i in range(10):
  X = processFrames1(P)
print (time.time()-time0)
12.230222940444946

time0 = time.time()
for i in range(10):
  X = jez(P1)
print (time.time()-time0)
0.4440445899963379
In [351]: %timeit processFrames1(P)
1 loop, best of 3: 1.21 s per loop

In [352]: %timeit jez(P1)
10 loops, best of 3: 43.7 ms per loop