熊猫的笛卡儿积

我有两个熊猫数据框:

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]})
162889 次浏览

在最新版本的熊猫(> = 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')

这相当于以前的熊猫 < 1.2答案,但更容易阅读。


大熊猫 < 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

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

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

这不会赢得代码高尔夫比赛,并借鉴了以前的答案-但清楚地显示了如何添加关键字,以及如何连接的工作原理。这将从列表中创建2个新的数据帧,然后添加键来执行笛卡儿积。

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

我选择的合并在左边,但是在这个设置中在语义上与 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)

作为一种替代方法,我们可以依赖 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

我发现使用熊猫 MultiIndex 是这项工作的最佳工具。如果您有一个列表 lists_list的列表,那么调用 pd.MultiIndex.from_product(lists_list)并迭代结果(或者在 DataFrame 索引中使用它)。

在一个空的数据框架中使用 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

创建一个共同的“密钥”,将两者合并为笛卡尔密钥:

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


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

方法链接:

product = (
df1.assign(key=1)
.merge(df2.assign(key=1), on="key")
.drop("key", axis=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

下面是一个 helper 函数,它用两个数据帧执行一个简单的笛卡儿积。内部逻辑使用内部键进行处理,并避免损坏任何从任何一边正好命名为“ 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.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 的好处是可以节省大量的输入工作,并且可以很好地处理一些边缘情况。我几乎总是推荐这种语法作为我对熊猫笛卡儿积的首选,除非你正在寻找 更有表现力的东西

当前版本熊猫(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

您可以使用来自 看门人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