Pandas中的笛卡尔积

175
我有两个pandas数据框:
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]})

9
从Pandas 1.2开始,您很快就可以使用left.merge(right, how="cross"),它会起到神奇的作用。请参见此github PR - cs95
1
将数据框以打印/显示格式展示可以提高问题的可读性。 - Mehdi Golari
14个回答

186

在最近版本的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 的答案等效,但更易阅读。


对于 pandas < 1.2:

如果您有一个在每行中重复出现的键,则可以使用 merge(就像在 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


13
要正确地执行这个操作,首先需要找到一个未使用的列名称,然后使用该名称添加虚拟列,合并数据,最后删除结果中的该列。使用 Pandas 创建数据相对于读取数据来说是一件很繁琐的事情。 - Bananach
3
@Bananach 哇!放松,朋友,情况并不那么糟糕,他们只是还没有处理这个问题。请记住,pandas仍然是一个正在发展的库,他们最近才发布了v1版本。无论如何,他们将在1.2中在df.merge()中添加对此的支持。更多信息请参见此处 - cs95
1
@cs95 谢谢,我没有注意到这将在1.2中推出。未来这应该是首选的方法。 - Matti John
如果您只想合并两列,您可以像这样匿名地创建df1和df2:df[["purple"]].merge(df[["red"]], how="cross")。请注意双括号 [["colname"]],它使它们成为DataFrame而不是Series。 - Samuel Prevost

110

使用 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

8
我认为这是现在Pandas>=0.21最像Pandas风格的方式。 - Shadi
9
你的回答受到了负评,因为你没有展示如何推广到包含多个列的情况。 - cs95
这个函数(https://stackoverflow.com/a/58242079/1840471)使用参数字典将其推广到任意数量的列表。它与此处的问题有些不同,该问题使用两个数据帧的笛卡尔积(即它不是在`df1.col1`和`df.col2`上进行乘法)。 - Max Ghenis
1
事实上,我认为from_product不能用于解决这个问题。 - Max Ghenis
@MaxGhenis 不认为这对这种情况有用,我们不是在谈论多个数组的笛卡尔积,而是在谈论两个或更多的数据框(完全不同的情况)。 - cs95

46

这只需要最少的代码。创建一个共同的“键”来对两个进行笛卡尔合并:

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

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

11
最终清理时,使用 df_cartesian = df_cartesian.drop(columns=['key']) 去除“key”列。 - StackG

38

这段代码可能不够短小精悍,也参考了以前的答案 - 但清楚地展示了如何添加关键字和执行join操作。它从列表创建了2个新数据框,然后添加关键字以执行笛卡尔积。

我的用例是需要一个存储ID列表,每周都要用到。因此,我创建了一个包含所有想要的周数的列表,然后创建了一个包含所有想要与之匹配的存储ID的列表。

我选择的合并方式是left,但在此设置中与inner语义相同。您可以在合并文档中看到这一点,该文档指出,如果键组合在两个表中多次出现,则执行笛卡尔积 - 这就是我们设置的内容。

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)

33
简化版:days_and_stores = pd.merge(days.assign(key=0), stores.assign(key=0), on='key').drop('key', axis=1)的含义是将daysstores两个数据集按照key进行合并,并在合并后删除key这一列。 - Eugene Pakhomov
你提到了crossJoin,但是你正在使用的是pandas dataframe,而不是spark dataframe。 - Bryce Guinta
哎呀,我没想到。我经常同时使用Spark和Pandas,所以当我看到Spark的更新时,我就想到了这篇文章。谢谢Bryce。 - Rob Guderian

25

使用方法链:

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

19

呈现给您

pandas >= 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的好处是它可以为您节省许多打字,并且可以很好地处理一些边角情况。我几乎总是建议使用这种语法作为我在pandas中进行笛卡尔积的首选,除非您正在寻找更高效的解决方案。


1
刚刚查看了https://github.com/pandas-dev/pandas/releases/tag/v1.2.0,pandas 1.2于2020年12月26日发布。跨合并对我有效! - Yi Zong Kuang

16
作为替代,可以依靠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

4
我测试了这个方法,它可以运行,但对于大型数据集而言,比上面合并答案的方法要慢得多。 - MrJ
1
@MrJ 这里除了使用iterrows()之外没有其他原因,它绝对会破坏任何效率的迹象,即使是几千行数据也需要几分钟甚至几小时的时间。不值得。 - cs95

2
如果您没有重叠的列,不想添加新列,并且数据框的索引可以被丢弃,那么这种方法可能更简单:
df1.index[:] = df2.index[:] = 0
df_cartesian = df1.join(df2, how='outer')
df_cartesian.index[:] = range(len(df_cartesian))

2
这看起来很有前途 - 但是我在第一行遇到了这个错误: TypeError: '<class 'pandas.core.index.Int64Index'>' does not support mutable operations. 不过,我可以通过在数据框定义中添加, index=[0,0] 来解决这个问题。 - Racing Tadpole
2
或者使用 df1 = df1.set_index([[0]*len(df1)]))(对于 df2 同样如此)。 - Racing Tadpole
赛跑蝌蚪(Racing Tadpole)的编辑使这个对我有效 - 谢谢! - Sevyns

2
这里有一个辅助函数,用于在两个数据框之间执行简单的笛卡尔积。内部逻辑处理使用内部键,并避免破坏任何一侧命名为“key”的列。
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

1
你可以从取 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接