代码之家  ›  专栏  ›  技术社区  ›  cs95 abhishek58g

使用熊猫执行笛卡尔积(交叉连接)

  •  22
  • cs95 abhishek58g  · 技术社区  · 7 年前

    这篇文章的内容原本是想成为 Pandas Merging 101 ,

    给定两个简单的数据帧;

    left = pd.DataFrame({'col1' : ['A', 'B', 'C'], 'col2' : [1, 2, 3]})
    right = pd.DataFrame({'col1' : ['X', 'Y', 'Z'], 'col2' : [20, 30, 50]})
    
    left
    
      col1  col2
    0    A     1
    1    B     2
    2    C     3
    
    right
    
      col1  col2
    0    X    20
    1    Y    30
    2    Z    50
    

    A       1      X      20
    A       1      Y      30
    A       1      Z      50
    B       2      X      20
    B       2      Y      30
    B       2      Z      50
    C       3      X      20
    C       3      Y      30
    C       3      Z      50
    

    3 回复  |  直到 7 年前
        1
  •  74
  •   cs95 abhishek58g    5 年前

    让我们从建立一个基准开始。解决此问题的最简单方法是使用临时“键”列:

    # pandas <= 1.1.X
    def cartesian_product_basic(left, right):
        return (
           left.assign(key=1).merge(right.assign(key=1), on='key').drop('key', 1))
    
    cartesian_product_basic(left, right)
    
    # pandas >= 1.2 (est)
    left.merge(right, how="cross")
    
      col1_x  col2_x col1_y  col2_y
    0      A       1      X      20
    1      A       1      Y      30
    2      A       1      Z      50
    3      B       2      X      20
    4      B       2      Y      30
    5      B       2      Z      50
    6      C       3      X      20
    7      C       3      Y      30
    8      C       3      Z      50
    

    其工作原理是,为两个数据帧分配一个具有相同值(例如,1)的临时“键”列。 merge 然后在“键”上执行多对多连接。

    更快的实现需要NumPy。这里有一些著名的 NumPy implementations of 1D cartesian product

    def cartesian_product(*arrays):
        la = len(arrays)
        dtype = np.result_type(*arrays)
        arr = np.empty([len(a) for a in arrays] + [la], dtype=dtype)
        for i, a in enumerate(np.ix_(*arrays)):
            arr[...,i] = a
        return arr.reshape(-1, la)  
    

    泛化:在唯一性上交叉连接 非唯一索引数据帧

    免责声明
    这些解决方案针对具有非混合标量数据类型的数据帧进行了优化。如果处理混合数据类型,请在 自担风险!

    cartesian_product ,使用此选项重新索引数据帧,然后

    def cartesian_product_generalized(left, right):
        la, lb = len(left), len(right)
        idx = cartesian_product(np.ogrid[:la], np.ogrid[:lb])
        return pd.DataFrame(
            np.column_stack([left.values[idx[:,0]], right.values[idx[:,1]]]))
    
    cartesian_product_generalized(left, right)
    
       0  1  2   3
    0  A  1  X  20
    1  A  1  Y  30
    2  A  1  Z  50
    3  B  2  X  20
    4  B  2  Y  30
    5  B  2  Z  50
    6  C  3  X  20
    7  C  3  Y  30
    8  C  3  Z  50
    
    np.array_equal(cartesian_product_generalized(left, right),
                   cartesian_product_basic(left, right))
    True
    

    同样的道理,

    left2 = left.copy()
    left2.index = ['s1', 's2', 's1']
    
    right2 = right.copy()
    right2.index = ['x', 'y', 'y']
        
    
    left2
       col1  col2
    s1    A     1
    s2    B     2
    s1    C     3
    
    right2
      col1  col2
    x    X    20
    y    Y    30
    y    Z    50
    
    np.array_equal(cartesian_product_generalized(left, right),
                   cartesian_product_basic(left2, right2))
    True
    

    此解决方案可以推广到多个数据帧。例如

    def cartesian_product_multi(*dfs):
        idx = cartesian_product(*[np.ogrid[:len(df)] for df in dfs])
        return pd.DataFrame(
            np.column_stack([df.values[idx[:,i]] for i,df in enumerate(dfs)]))
    
    cartesian_product_multi(*[left, right, left]).head()
    
       0  1  2   3  4  5
    0  A  1  X  20  A  1
    1  A  1  X  20  B  2
    2  A  1  X  20  C  3
    3  A  1  X  20  D  4
    4  A  1  Y  30  A  1
    

    在处理 只有两个 数据帧。使用 np.broadcast_arrays

    def cartesian_product_simplified(left, right):
        la, lb = len(left), len(right)
        ia2, ib2 = np.broadcast_arrays(*np.ogrid[:la,:lb])
    
        return pd.DataFrame(
            np.column_stack([left.values[ia2.ravel()], right.values[ib2.ravel()]]))
    
    np.array_equal(cartesian_product_simplified(left, right),
                   cartesian_product_basic(left2, right2))
    True
    

    性能比较

    在一些具有唯一索引的人工数据帧上对这些解决方案进行基准测试

    enter image description here

    请注意,计时可能会因您的设置、数据和选择而有所不同 笛卡尔乘积 助手功能(如适用)。


    这是计时脚本。这里调用的所有函数都在上面定义。

    from timeit import timeit
    import pandas as pd
    import matplotlib.pyplot as plt
    
    res = pd.DataFrame(
           index=['cartesian_product_basic', 'cartesian_product_generalized', 
                  'cartesian_product_multi', 'cartesian_product_simplified'],
           columns=[1, 10, 50, 100, 200, 300, 400, 500, 600, 800, 1000, 2000],
           dtype=float
    )
    
    for f in res.index: 
        for c in res.columns:
            # print(f,c)
            left2 = pd.concat([left] * c, ignore_index=True)
            right2 = pd.concat([right] * c, ignore_index=True)
            stmt = '{}(left2, right2)'.format(f)
            setp = 'from __main__ import left2, right2, {}'.format(f)
            res.at[f, c] = timeit(stmt, setp, number=5)
    
    ax = res.div(res.min()).T.plot(loglog=True) 
    ax.set_xlabel("N"); 
    ax.set_ylabel("time (relative)");
    
    plt.show()
    


    继续阅读

    跳转到101中的其他主题继续学习:

    *你来了

        2
  •  16
  •   BENY    4 年前

    熊猫1.2.0之后 merge 现在有选择了 cross

    left.merge(right, how='cross')
    

    使用 itertools product 并在dataframe中重新创建值

    import itertools
    l=list(itertools.product(left.values.tolist(),right.values.tolist()))
    pd.DataFrame(list(map(lambda x : sum(x,[]),l)))
       0  1  2   3
    0  A  1  X  20
    1  A  1  Y  30
    2  A  1  Z  50
    3  B  2  X  20
    4  B  2  Y  30
    5  B  2  Z  50
    6  C  3  X  20
    7  C  3  Y  30
    8  C  3  Z  50
    
        3
  •  6
  •   Bharath M Shetty    7 年前

    这里有一个三重的方法 concat

    m = pd.concat([pd.concat([left]*len(right)).sort_index().reset_index(drop=True),
           pd.concat([right]*len(left)).reset_index(drop=True) ], 1)
    
        col1  col2 col1  col2
    0     A     1    X    20
    1     A     1    Y    30
    2     A     1    Z    50
    3     B     2    X    20
    4     B     2    Y    30
    5     B     2    Z    50
    6     C     3    X    20
    7     C     3    Y    30
    8     C     3    Z    50
    

    enter image description here