Декартово произведение в пандах
У меня есть два кадра данных панд:
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]})
16 ответов
Если у вас есть ключ, который повторяется для каждой строки, вы можете создать декартово произведение, используя слияние (как в 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['key'] = 0
df2['key'] = 0
df_cartesian = df1.merge(df2, how='outer')
Это не выиграет соревнование по коду для гольфа и будет заимствовано из предыдущих ответов - но ясно показывает, как добавляется ключ и как работает объединение. Это создает 2 новых фрейма данных из списков, а затем добавляет ключ для выполнения декартового произведения.
Мой вариант использования состоял в том, что мне требовался список всех идентификаторов магазинов для каждой недели в моем списке. Итак, я создал список всех недель, которые я хотел получить, а затем список всех идентификаторов магазинов, с которыми я хотел сопоставить их.
Слияние, которое я выбрал слева, будет семантически таким же, как и внутреннее в этой настройке. Вы можете увидеть это в документации по слиянию, в которой говорится, что он делает декартово произведение, если комбинация клавиш встречается в обеих таблицах более одного раза - это то, что мы настроили.
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)
С методом цепочки:
product = (
df1.assign(key=1)
.merge(df2.assign(key=1), on="key")
.drop("key", axis=1)
)
Представляя вам
панды>= 1,2 [оценка]
left.merge(right, how='cross')
import pandas as pd
pd.__version__
# '1.1.0.dev0+3475.gd9845cf5d'
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 заключаются в том, что он избавляет вас от лишнего набора текста и неплохо справляется с некоторыми угловыми случаями. Я почти всегда рекомендую этот синтаксис в качестве первого предпочтения для декартовых продуктов в пандах, если вы не ищете что-то более производительное.
В качестве альтернативы можно полагаться на декартово произведение, предоставляемое 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
Вот вспомогательная функция для выполнения простого декартова произведения с двумя фреймами данных. Внутренняя логика обрабатывает использование внутреннего ключа и избегает искажения любых столбцов, которые случайно названы "ключом" с любой стороны.
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.index[:] = df2.index[:] = 0
df_cartesian = df1.join(df2, how='outer')
df_cartesian.index[:] = range(len(df_cartesian))
Вы можете начать с декартова произведения 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
Вы можете использовать numpy, так как это может быть быстрее. Предположим, у вас есть две следующих серии:
s1 = pd.Series(np.random.randn(100,))
s2 = pd.Series(np.random.randn(100,))
Вам просто нужно,
pd.DataFrame(
s1[:, None] @ s2[None, :],
index = s1.index, columns = s2.index
)
Вы можете использовать expand_grid из pyjanitor для репликации перекрестного соединения; он предлагает некоторую скорость для больших наборов данных (он использует
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
Если вы хотите взять перекрестное произведение двух серий или DataFrames таким образом, чтобы результат правильно индексировался перекрестным произведением их двух соответствующих индексов, это можно сделать следующим образом:
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)
map
а также zip
в понимании
DataFrame([
d1 + d2
for d1 in zip(*map(df1.get, df1))
for d2 in zip(*map(df2.get, df2))
], columns=df1.columns.append(df2.columns))
col1 col2 col3
0 1 3 5
1 1 3 6
2 2 4 5
3 2 4 6
Я считаю использование панд MultiIndex, чтобы быть лучшим инструментом для работы. Если у вас есть список списков lists_list
, вызов pd.MultiIndex.from_product(lists_list)
и перебрать результат (или использовать его в индексе DataFrame).