【问题标题】:Fastest way to calculate difference in all columns计算所有列中差异的最快方法
【发布时间】:2017-10-24 03:25:56
【问题描述】:

我有一个包含所有浮点列的数据框。例如:

import numpy as np
import pandas as pd

df = pd.DataFrame(np.arange(12.0).reshape(3,4), columns=list('ABCD'))
#    A    B     C     D
# 0  0.0  1.0   2.0   3.0
# 1  4.0  5.0   6.0   7.0
# 2  8.0  9.0  10.0  11.0

我想计算所有列组合(例如 A-B、A-C、B-C 等)的列差异。

例如,所需的输出将类似于:

 A_B   A_C   A_D   B_C   B_D   C_D
-1.0  -2.0  -3.0  -1.0  -2.0  -1.0
-1.0  -2.0  -3.0  -1.0  -2.0  -1.0
-1.0  -2.0  -3.0  -1.0  -2.0  -1.0

由于列数可能很大,我想尽可能高效/快速地进行计算。我假设通过首先将数据帧转换为 numpy 数组,我会获得很大的减速,所以我会这样做,但我想知道是否有任何其他策略可能会导致性能大幅提升。也许是一些矩阵代数或多维数据格式技巧,导致不必遍历所有独特的组合。欢迎任何建议。该项目使用 Python 3。

【问题讨论】:

  • 对于A-B,你想要一个数组[-1,-1,-1]吗?您对此有何期待?
  • 我添加了一些示例输出。我正在寻找每组列的数组/向量输出,但如果它们是独立的或收集在列表、多维数组等中,这并不重要。

标签: python performance python-3.x pandas numpy


【解决方案1】:

本文列出了两种 NumPy 性能方法 - 一种是完全矢量化方法,另一种是一个循环。

方法#1

def numpy_triu1(df):          
    a = df.values
    r,c = np.triu_indices(a.shape[1],1)
    cols = df.columns
    nm = [cols[i]+"_"+cols[j] for i,j in zip(r,c)]
    return pd.DataFrame(a[:,r] - a[:,c], columns=nm)

示例运行 -

In [72]: df
Out[72]: 
     A    B     C     D
0  0.0  1.0   2.0   3.0
1  4.0  5.0   6.0   7.0
2  8.0  9.0  10.0  11.0

In [78]: numpy_triu(df)
Out[78]: 
   A_B  A_C  A_D  B_C  B_D  C_D
0 -1.0 -2.0 -3.0 -1.0 -2.0 -1.0
1 -1.0 -2.0 -3.0 -1.0 -2.0 -1.0
2 -1.0 -2.0 -3.0 -1.0 -2.0 -1.0

方法 #2

如果我们可以将数组作为输出或没有专门的列名的数据框,这里是另一个 -

def pairwise_col_diffs(a): # a would df.values
    n = a.shape[1]
    N = n*(n-1)//2
    idx = np.concatenate(( [0], np.arange(n-1,0,-1).cumsum() ))
    start, stop = idx[:-1], idx[1:]
    out = np.empty((a.shape[0],N),dtype=a.dtype)
    for j,i in enumerate(range(n-1)):
        out[:, start[j]:stop[j]] = a[:,i,None] - a[:,i+1:]
    return out

运行时测试

由于 OP 已经提到多维数组输出也适用于他们,这里是其他作者提供的基于数组的方法 -

# @Allen's soln
def Allen(arr):
    n = arr.shape[1]
    idx = np.asarray(list(itertools.combinations(range(n),2))).T
    return arr[:,idx[0]]-arr[:,idx[1]]

# @DYZ's soln
def DYZ(arr):
    result = np.concatenate([(arr.T - arr.T[x])[x+1:] \
            for x in range(arr.shape[1])]).T
    return result

@Gerges Dib 帖子中基于pandas 的解决方案未包含在内,因为与其他解决方案相比,它的运行速度非常慢。

时间安排 -

我们将使用三种数据集大小 - 1005001000

In [118]: df = pd.DataFrame(np.random.randint(0,9,(3,100)))
     ...: a = df.values
     ...: 

In [119]: %timeit DYZ(a)
     ...: %timeit Allen(a)
     ...: %timeit pairwise_col_diffs(a)
     ...: 
1000 loops, best of 3: 258 µs per loop
1000 loops, best of 3: 1.48 ms per loop
1000 loops, best of 3: 284 µs per loop

In [121]: df = pd.DataFrame(np.random.randint(0,9,(3,500)))
     ...: a = df.values
     ...: 

In [122]: %timeit DYZ(a)
     ...: %timeit Allen(a)
     ...: %timeit pairwise_col_diffs(a)
     ...: 
100 loops, best of 3: 2.56 ms per loop
10 loops, best of 3: 39.9 ms per loop
1000 loops, best of 3: 1.82 ms per loop

In [123]: df = pd.DataFrame(np.random.randint(0,9,(3,1000)))
     ...: a = df.values
     ...: 

In [124]: %timeit DYZ(a)
     ...: %timeit Allen(a)
     ...: %timeit pairwise_col_diffs(a)
     ...: 
100 loops, best of 3: 8.61 ms per loop
10 loops, best of 3: 167 ms per loop
100 loops, best of 3: 5.09 ms per loop

【讨论】:

  • 抱歉,是否可以添加时间来比较解决方案?
  • @jezrael 已添加。
【解决方案2】:

我认为你可以用 NumPy 做到这一点。让arr=df.values。首先,让我们找到所有的两列组合:

from itertools import combinations    
column_combos = combinations(range(arr.shape[1]), 2)

现在,成对减去列并将数组列表转换回二维数组:

result = np.array([(arr[:,x[1]] - arr[:,x[0]]) for x in column_combos]).T
#array([[1., 2., 3., 1., 2., 1.],
#       [1., 2., 3., 1., 2., 1.],
#       [1., 2., 3., 1., 2., 1.]])

另一种解决方案稍微 (~15%) 更快,因为它减去整个 2D 数组而不是列,并且 Python 端迭代次数更少:

result = np.concatenate([(arr.T - arr.T[x])[x+1:] for x in range(arr.shape[1])]).T
#array([[ 1., 2., 3., 1., 2., 1.],
#       [ 1., 2., 3., 1., 2., 1.],
#       [ 1., 2., 3., 1., 2., 1.]])

如果需要,您可以将结果转换回 DataFrame:

columns = list(map(lambda x: x[1]+x[0], combinations(df.columns, 2)))
#['BA', 'CA', 'DA', 'CB', 'DB', 'DC']

pd.DataFrame(result, columns=columns)
#    BA   CA   DA   CB   DB   DC
#0  1.0  2.0  3.0  1.0  2.0  1.0
#1  1.0  2.0  3.0  1.0  2.0  1.0
#2  1.0  2.0  3.0  1.0  2.0  1.0

【讨论】:

    【解决方案3】:
    import itertools
    df = pd.DataFrame(np.arange(12.0).reshape(3,4), columns=list('ABCD'))
    df_cols = df.columns.tolist()
    #build a index array of all the pairs need to do the subtraction
    idx = np.asarray(list(itertools.combinations(range(len(df_cols)),2))).T
    #build a new DF using the pairwise difference and column names
    df_new = pd.DataFrame(data=df.values[:,idx[0]]-df.values[:,idx[1]], 
                          columns=[''.join(e) for e in (itertools.combinations(df_cols,2))])
    
    df_new
    Out[43]: 
        AB   AC   AD   BC   BD   CD
    0 -1.0 -2.0 -3.0 -1.0 -2.0 -1.0
    1 -1.0 -2.0 -3.0 -1.0 -2.0 -1.0
    2 -1.0 -2.0 -3.0 -1.0 -2.0 -1.0
    

    【讨论】:

      【解决方案4】:

      我不确定这与其他可能的方法相比有多快,但这里是:

      df = pd.DataFrame(np.arange(12.0).reshape(3,4), columns=list('ABCD'))
      
      # get the columns as list
      cols = list(df.columns)
      # define output dataframe
      out = pd.DataFrame()
      
      # loop over possible periods
      for period in range(1, df.shape[1]):
          names = [l1 + l2 for l1, l2, in zip(cols, cols[period:])]
          out[names] = df.diff(periods=period, axis=1).dropna(axis=1, how='all')
      
      print(out)
      
      # column name shows which two columns are subtracted
      
          AB   BC   CD   AC   BD   AD
      0  1.0  1.0  1.0  2.0  2.0  3.0
      1  1.0  1.0  1.0  2.0  2.0  3.0
      2  1.0  1.0  1.0  2.0  2.0  3.0
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-04-15
        • 1970-01-01
        • 1970-01-01
        • 2011-06-22
        • 2020-07-25
        • 2011-11-15
        • 1970-01-01
        • 2011-05-14
        相关资源
        最近更新 更多