Декартово произведение в пандах

У меня есть два кадра данных панд:

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).

Другие вопросы по тегам