我有两个熊猫数据框:
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]})
left.merge(right, how="cross")
,它会像魔术一样工作。请参阅此github PR。
在最新版本的 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
使用 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.col1
和 df.col2
的积)。
from_product
不能用于这个问题。
这个需要最少的代码。创建一个通用的“键”来笛卡尔合并两者:
df1['key'] = 0
df2['key'] = 0
df_cartesian = df1.merge(df2, how='outer')
df_cartesian = df_cartesian.drop(columns=['key'])
最后清理
这不会赢得代码高尔夫比赛,并从以前的答案中借用 - 但清楚地显示了密钥是如何添加的,以及连接是如何工作的。这会从列表中创建 2 个新数据框,然后添加进行笛卡尔积的键。
我的用例是我需要列表中每周的所有商店 ID 的列表。所以,我创建了一个我想要拥有的所有周数的列表,然后是我想要映射它们的所有商店 ID 的列表。
我选择 left 的合并,但在此设置中与 inner 在语义上相同。您可以看到这个 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)
使用方法链接:
product = (
df1.assign(key=1)
.merge(df2.assign(key=1), on="key")
.drop("key", axis=1)
)
作为替代方案,可以依赖 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
呈现给你
熊猫 >= 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 中笛卡尔积的首选。
如果您没有重叠列,不想添加一个,并且可以丢弃数据帧的索引,这可能更容易:
df1.index[:] = df2.index[:] = 0
df_cartesian = df1.join(df2, how='outer')
df_cartesian.index[:] = range(len(df_cartesian))
TypeError: '<class 'pandas.core.index.Int64Index'>' does not support mutable operations.
我可以通过将 , index=[0,0]
添加到数据框定义来解决这个问题。
df1 = df1.set_index([[0]*len(df1)]))
(同样适用于 df2
)。
这是一个辅助函数,用于执行具有两个数据帧的简单笛卡尔积。内部逻辑使用内部键进行处理,并避免从任一侧破坏任何恰好被命名为“键”的列。
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
您可以先取 df1.col1
和 df2.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
当前版本的 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
您可以使用 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
我发现使用 pandas MultiIndex 是完成这项工作的最佳工具。如果您有列表 lists_list
,请调用 pd.MultiIndex.from_product(lists_list)
并迭代结果(或在 DataFrame 索引中使用它)。
不定期副业成功案例分享
df[["purple"]].merge(df[["red"]], how="cross")
。注意双括号[["colname"]]
,它使它们成为 DataFrame 而不是 Series。