pandas 中的笛卡尔积
Posted
技术标签:
【中文标题】pandas 中的笛卡尔积【英文标题】:cartesian product in pandas 【发布时间】:2012-10-27 12:27:27 【问题描述】:我有两个熊猫数据框:
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。
提高了问题的可读性,以打印/显示格式显示数据帧。
【参考方案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
对于熊猫
如果您有一个对每一行重复的键,那么您可以使用合并生成笛卡尔积(就像在 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。无论如何,他们在 df.merge() 中的 1.2 中添加了对此的支持。请参阅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 最像 pandas 的方式>=0.21 您投了反对票,因为您没有展示这将如何推广到超过 1 列的任何内容。 此函数 (***.com/a/58242079/1840471) 使用 args 字典将其推广到任意数量的列表。这与这里的问题有点不同,它采用两个 DataFrame 的笛卡尔积(即它不采用df1.col1
和 df.col2
的积)。
其实我觉得from_product
不能用来解决这个问题。
@MaxGhenis 不要认为这对这种情况有用,我们不是在谈论多个数组的笛卡尔积,而是在谈论 2 个或更多 DataFrame(完全不同的故事)。【参考方案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 的列表。
我选择 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)
您提到了 crossJoin,但您使用的是 pandas 数据框,而不是 spark 数据框。
当。没有想。我经常一起使用 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 中笛卡尔积的首选。
【讨论】:
刚刚检查了github.com/pandas-dev/pandas/releases/tag/v1.2.0,pandas 1.2 已于 2020 年 12 月 26 日发布。交叉合并对我有用!【参考方案8】:如果你没有重叠的列,不想加一,数据框的索引可以丢弃,这可能更容易:
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
)。
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.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
【讨论】:
【参考方案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 索引中使用它)。
【讨论】:
以上是关于pandas 中的笛卡尔积的主要内容,如果未能解决你的问题,请参考以下文章