【问题标题】:cartesian product in pandas熊猫中的笛卡尔积
【发布时间】:2022-12-09 23:06:56
【问题描述】:

我有两个熊猫数据框:

from pandas import DataFrame
df1 = DataFrame({'col1':[1,2],'col2':[3,4]})
df2 = DataFrame({'col3':[5,6]})     

获得他们的笛卡尔积的最佳做法是什么(当然没有像我一样明确地写出来)?

#df1, df2 cartesian product
df_cartesian = DataFrame({'col1':[1,2,1,2],'col2':[3,4,3,4],'col3':[5,5,6,6]})

【问题讨论】:

  • 从 pandas 1.2 开始,您很快就可以使用left.merge(right, how="cross"),它会像魔术一样工作。看到这个github PR
  • 它提高了问题的可读性,以打印/显示格式显示数据帧。

标签: python pandas merge cartesian-product cross-join


【解决方案1】:

在最新版本的 Pandas (>= 1.2) 中,这是内置于 merge 中的,所以你可以这样做:

from pandas import DataFrame
df1 = DataFrame({'col1':[1,2],'col2':[3,4]})
df2 = DataFrame({'col3':[5,6]})    

df1.merge(df2, how='cross')

这等同于之前的 pandas < 1.2 答案,但更易于阅读。


对于 < 1.2 的熊猫:

如果您有一个为每一行重复的键,那么您可以使用合并生成笛卡尔积(就像在 SQL 中一样)。

from pandas import DataFrame, merge
df1 = DataFrame({'key':[1,1], 'col1':[1,2],'col2':[3,4]})
df2 = DataFrame({'key':[1,1], 'col3':[5,6]})

merge(df1, df2,on='key')[['col1', 'col2', 'col3']]

输出:

   col1  col2  col3
0     1     3     5
1     1     3     6
2     2     4     5
3     2     4     6

请在此处查看文档:http://pandas.pydata.org/pandas-docs/stable/merging.html

【讨论】:

  • 所以要正确地做到这一点,必须首先找到一个未使用的列名,然后添加具有该名称的虚拟列,合并,最后将列删除到结果中?使用 pandas 创建数据而不是读取数据只是一种痛苦
  • @Bananach哇!放轻松我的朋友,这并没有那么糟糕,他们只是还没有做到。请记住,pandas 仍然是一个开发中的库,他们最近才发布了 v1。无论如何,他们在 1.2 中的 df.merge() 中添加了对此的支持。有关更多信息,请参阅here
  • @cs95 谢谢,我没有注意到这是在 1.2 中出现的。将来这应该是首选方法
  • 如果你只想合并两列,你可以像这样“匿名”创建 df1 和 df2:df[["purple"]].merge(df[["red"]], how="cross")。注意双括号[["colname"]],这使它们成为 DataFrame 而不是 Series。
【解决方案2】:

使用 pd.MultiIndex.from_product 作为空数据框中的索引,然后重置其索引,您就完成了。

a = [1, 2, 3]
b = ["a", "b", "c"]

index = pd.MultiIndex.from_product([a, b], names = ["a", "b"])

pd.DataFrame(index = index).reset_index()

出去:

   a  b
0  1  a
1  1  b
2  1  c
3  2  a
4  2  b
5  2  c
6  3  a
7  3  b
8  3  c

【讨论】:

  • 我相信这是目前 pandas>=0.21 最像 pandas 的方式
  • 你有反对票,因为你没有展示这将如何概括超过 1 列的任何东西。
  • 此函数 (stackoverflow.com/a/58242079/1840471) 使用 args 字典将其概括为任意数量的列表。它与这里的问题有点不同,它采用两个 DataFrame 的笛卡尔积(即它不采用 df1.col1df.col2 的乘积)。
  • 事实上,我认为from_product 不能用于解决这个问题。
  • @MaxGhenis 不要认为这对这种情况有用,我们不是在谈论多个数组的笛卡尔积,我们在谈论 2 个或更多数据帧(完全不同的故事)。
【解决方案3】:

这个需要最少的代码。创建一个通用的“键”以笛卡尔合并两者:

df1['key'] = 0
df2['key'] = 0

df_cartesian = df1.merge(df2, how='outer')

【讨论】:

  • + df_cartesian = df_cartesian.drop(columns=['key']) 最后清理
【解决方案4】:

这不会赢得代码高尔夫比赛,并且借鉴了之前的答案 - 但清楚地显示了密钥的添加方式以及连接的工作方式。这从列表中创建了 2 个新的数据框,然后添加了进行笛卡尔积的键。

我的用例是我需要列表中每周的所有商店 ID 列表。因此,我创建了一个我想要拥有的所有周的列表,然后是我想要映射它们的所有商店 ID 的列表。

我选择左侧的合并,但在此设置中在语义上与内部相同。您可以看到这个 in the documentation on merging,它声明如果键组合在两个表中出现不止一次,它会执行笛卡尔积 - 这是我们设置的。

days = pd.DataFrame({'date':list_of_days})
stores = pd.DataFrame({'store_id':list_of_stores})
stores['key'] = 0
days['key'] = 0
days_and_stores = days.merge(stores, how='left', on = 'key')
days_and_stores.drop('key',1, inplace=True)

【讨论】:

  • 短一点的版本:days_and_stores = pd.merge(days.assign(key=0), stores.assign(key=0), on='key').drop('key', axis=1)
  • 您提到 crossJoin,但您使用的是熊猫数据框,而不是火花数据框。
  • 该死。没想到我经常一起使用 spark + pandas,所以当我看到 spark 的更新时,我想到了这篇文章。谢谢布莱斯。
【解决方案5】:

使用方法链接:

product = (
    df1.assign(key=1)
    .merge(df2.assign(key=1), on="key")
    .drop("key", axis=1)
)

【讨论】:

    【解决方案6】:

    作为替代方案,可以依赖 itertools 提供的笛卡尔积:itertools.product,它可以避免创建临时键或修改索引:

    import numpy as np 
    import pandas as pd 
    import itertools
    
    def cartesian(df1, df2):
        rows = itertools.product(df1.iterrows(), df2.iterrows())
    
        df = pd.DataFrame(left.append(right) for (_, left), (_, right) in rows)
        return df.reset_index(drop=True)
    

    快速测试:

    In [46]: a = pd.DataFrame(np.random.rand(5, 3), columns=["a", "b", "c"])
    
    In [47]: b = pd.DataFrame(np.random.rand(5, 3), columns=["d", "e", "f"])    
    
    In [48]: cartesian(a,b)
    Out[48]:
               a         b         c         d         e         f
    0   0.436480  0.068491  0.260292  0.991311  0.064167  0.715142
    1   0.436480  0.068491  0.260292  0.101777  0.840464  0.760616
    2   0.436480  0.068491  0.260292  0.655391  0.289537  0.391893
    3   0.436480  0.068491  0.260292  0.383729  0.061811  0.773627
    4   0.436480  0.068491  0.260292  0.575711  0.995151  0.804567
    5   0.469578  0.052932  0.633394  0.991311  0.064167  0.715142
    6   0.469578  0.052932  0.633394  0.101777  0.840464  0.760616
    7   0.469578  0.052932  0.633394  0.655391  0.289537  0.391893
    8   0.469578  0.052932  0.633394  0.383729  0.061811  0.773627
    9   0.469578  0.052932  0.633394  0.575711  0.995151  0.804567
    10  0.466813  0.224062  0.218994  0.991311  0.064167  0.715142
    11  0.466813  0.224062  0.218994  0.101777  0.840464  0.760616
    12  0.466813  0.224062  0.218994  0.655391  0.289537  0.391893
    13  0.466813  0.224062  0.218994  0.383729  0.061811  0.773627
    14  0.466813  0.224062  0.218994  0.575711  0.995151  0.804567
    15  0.831365  0.273890  0.130410  0.991311  0.064167  0.715142
    16  0.831365  0.273890  0.130410  0.101777  0.840464  0.760616
    17  0.831365  0.273890  0.130410  0.655391  0.289537  0.391893
    18  0.831365  0.273890  0.130410  0.383729  0.061811  0.773627
    19  0.831365  0.273890  0.130410  0.575711  0.995151  0.804567
    20  0.447640  0.848283  0.627224  0.991311  0.064167  0.715142
    21  0.447640  0.848283  0.627224  0.101777  0.840464  0.760616
    22  0.447640  0.848283  0.627224  0.655391  0.289537  0.391893
    23  0.447640  0.848283  0.627224  0.383729  0.061811  0.773627
    24  0.447640  0.848283  0.627224  0.575711  0.995151  0.804567
    

    【讨论】:

    • 我测试了这个并且它有效,但它比上面的大型数据集合并答案慢得多。
    • @MrJ 除了在这里使用 iterrows() 之外没有其他原因,它绝对破坏了任何表面上的效率,甚至几千行也需要几分钟或几小时。不值得
    【解决方案7】:

    呈现给你

    熊猫 >= 1.2

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

    import pandas as pd 
    
    pd.__version__
    # '1.2.0'
    
    left = pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})
    right = pd.DataFrame({'col3': [5, 6]}) 
    
    left.merge(right, how='cross')
    
       col1  col2  col3
    0     1     3     5
    1     1     3     6
    2     2     4     5
    3     2     4     6
    

    结果中忽略索引。

    在实施方面,这使用接受的答案中描述的公共键列方法的连接。使用 API 的好处是它可以节省您大量的输入,并且可以很好地处理一些极端情况。除非您正在寻找 something more performant,否则我几乎总是推荐这种语法作为我在 pandas 中笛卡尔积的首选。

    【讨论】:

    【解决方案8】:

    如果您没有重叠的列,不想添加一个,并且可以丢弃数据帧的索引,这可能更容易:

    df1.index[:] = df2.index[:] = 0
    df_cartesian = df1.join(df2, how='outer')
    df_cartesian.index[:] = range(len(df_cartesian))
    

    【讨论】:

    • 这看起来很有希望 - 但我在第一行收到错误:TypeError: '&lt;class 'pandas.core.index.Int64Index'&gt;' does not support mutable operations. 我可以通过将 , index=[0,0] 添加到数据框定义来解决这个问题。
    • 或者使用df1 = df1.set_index([[0]*len(df1)]))(对于df2 也是如此)。
    • Racing Tadpole 的编辑让我完成了这项工作 - 谢谢!
    【解决方案9】:

    这是一个辅助函数,用于执行具有两个数据框的简单笛卡尔积。内部逻辑使用内部键进行处理,并避免从任何一方破坏恰好被命名为“键”的任何列。

    import pandas as pd
    
    def cartesian(df1, df2):
        """Determine Cartesian product of two data frames."""
        key = 'key'
        while key in df1.columns or key in df2.columns:
            key = '_' + key
        key_d = {key: 0}
        return pd.merge(
            df1.assign(**key_d), df2.assign(**key_d), on=key).drop(key, axis=1)
    
    # Two data frames, where the first happens to have a 'key' column
    df1 = pd.DataFrame({'number':[1, 2], 'key':[3, 4]})
    df2 = pd.DataFrame({'digit': [5, 6]})
    cartesian(df1, df2)
    

    显示:

       number  key  digit
    0       1    3      5
    1       1    3      6
    2       2    4      5
    3       2    4      6
    

    【讨论】:

      【解决方案10】:

      您可以先取 df1.col1df2.col3 的笛卡尔积,然后合并回 df1 得到 col2

      这是一个通用的笛卡尔乘积函数,它采用列表字典:

      def cartesian_product(d):
          index = pd.MultiIndex.from_product(d.values(), names=d.keys())
          return pd.DataFrame(index=index).reset_index()
      

      申请为:

      res = cartesian_product({'col1': df1.col1, 'col3': df2.col3})
      pd.merge(res, df1, on='col1')
      #  col1 col3 col2
      # 0   1    5    3
      # 1   1    6    3
      # 2   2    5    4
      # 3   2    6    4
      

      【讨论】:

        【解决方案11】:

        当前版本 Pandas (1.1.5) 的另一种解决方法:如果您从非数据帧序列开始,这个方法特别有用。我没有计时。它不需要任何人工索引操作,但需要您重复第二个序列。它依赖于explode 的一个特殊属性,即右侧索引重复。

        df1 = DataFrame({'col1': [1,2], 'col2': [3,4]})
        
        series2 = Series(
            [[5, 6]]*len(df1),
            name='col3',
            index=df1.index,
        )
        
        df_cartesian = df1.join(series2.explode())
        

        这输出

           col1  col2 col3
        0     1     3    5
        0     1     3    6
        1     2     4    5
        1     2     4    6
        

        【讨论】:

          【解决方案12】:

          您可以使用 pyjanitor 中的 expand_grid 来复制交叉连接;它为更大的数据集提供了一些速度性能(它在下面使用np.meshgrid):

          pip install git+https://github.com/pyjanitor-devs/pyjanitor.git
          import pandas as pd
          import janitor as jn
          jn.expand_grid(others = {"df1":df1, "df2":df2})
          
             df1       df2
            col1 col2 col3
          0    1    3    5
          1    1    3    6
          2    2    4    5
          3    2    4    6
          

          【讨论】:

            【解决方案13】:

            我发现使用 pandas MultiIndex 是完成这项工作的最佳工具。如果您有列表列表 lists_list,请调用 pd.MultiIndex.from_product(lists_list) 并迭代结果(或在 DataFrame 索引中使用它)。

            【讨论】:

              猜你喜欢
              • 2021-10-31
              • 2020-02-03
              • 1970-01-01
              • 2019-04-29
              • 2019-02-07
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多