熊猫中的笛卡尔积
- 2024-12-03 08:44:00
- admin 原创
- 160
问题描述:
我有两个熊猫数据框:
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]})
解决方案 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 答案,但更易于阅读。
对于 Pandas <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
解决方案 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
解决方案 3:
这个需要最少的代码。创建一个通用的“键”来将两者进行笛卡尔合并:
df1['key'] = 0
df2['key'] = 0
df_cartesian = df1.merge(df2, how='outer')
解决方案 4:
这不会赢得代码高尔夫比赛,并借鉴了以前的答案 - 但清楚地显示了如何添加键以及连接的工作方式。这会从列表中创建 2 个新的数据框,然后添加键以进行笛卡尔积。
我的用例是,我需要一份包含每周所有商店 ID 的列表。因此,我创建了一份包含所有我想要的周数的列表,然后列出了我想要映射的所有商店 ID。
我选择的合并是左合并,但在此设置中,其语义与内合并相同。您可以在有关合并的文档中看到这一点,其中指出,如果键组合在两个表中出现多次,则它会进行笛卡尔积 - 这正是我们设置的。
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)
解决方案 5:
使用方法链:
product = (
df1.assign(key=1)
.merge(df2.assign(key=1), on="key")
.drop("key", axis=1)
)
解决方案 6:
向您呈现
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 中笛卡尔积的首选。
解决方案 7:
另外,可以依赖 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
解决方案 8:
如果没有重叠的列,不想添加列,并且可以丢弃数据框的索引,这可能会更容易:
df1.index[:] = df2.index[:] = 0
df_cartesian = df1.join(df2, how='outer')
df_cartesian.index[:] = range(len(df_cartesian))
解决方案 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:
如果您想要对两个 Series 或 DataFrame 进行叉积,并且使得结果能够通过它们各自索引的叉积正确索引,则可以这样做:
def indexed_cross_product(df1, df2):
assert df1.index.name is not None
assert df2.index.name is not None
assert df1.index.name != df2.index.name
vals = df1.reset_index().merge(df2.reset_index(), how="cross")
return vals.set_index([df1.index.name, df2.index.name], drop=True)
解决方案 14:
我发现使用 pandas MultiIndex 是完成这项工作的最佳工具。如果您有一个列表列表lists_list
,请调用pd.MultiIndex.from_product(lists_list)
并迭代结果(或在 DataFrame 索引中使用它)。