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

109

У меня есть два фрейма данных pandas:

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]})
Идок
источник

Ответы:

90

Если у вас есть ключ, который повторяется для каждой строки, вы можете создать декартово произведение, используя слияние (как в 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#brief-primer-on-merge-methods-relational-algebra

Матти Джон
источник
7
Итак, чтобы сделать это правильно, нужно сначала найти неиспользуемое имя столбца, затем добавить фиктивные столбцы с этим именем, объединить и, наконец, отбросить столбец в результате? Создание данных с помощью pandas, в отличие от чтения, - это просто головная боль
Bananach
69

Используйте 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
Gijs
источник
6
Я считаю, что это самый похожий на панд способ в наши дни для панд> = 0,21
шади
7
У вас есть отрицательные голоса, потому что вы не показали, как это будет обобщаться для чего-либо с более чем одним столбцом.
cs95
Эта функция ( stackoverflow.com/a/58242079/1840471 ) обобщает ее на произвольное количество списков, используя словарь аргументов. Это немного отличается от вопроса здесь, который берет декартово произведение двух DataFrames (то есть не принимает произведение df1.col1и df.col2).
Макс Генис
1
На самом деле я не думаю, что from_productэто можно использовать для решения этой проблемы.
Макс Генис
34

Это не выиграет соревнование по гольфу кода и заимствует из предыдущих ответов, но ясно показывает, как добавляется ключ и как работает соединение. Это создает 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)
Роб Гудериан
источник
25
Немного укороченная версия:days_and_stores = pd.merge(days.assign(key=0), stores.assign(key=0), on='key').drop('key', axis=1)
Евгений Пахомов
Вы упоминаете crossJoin, но вы используете фреймворк pandas, а не искровый фреймворк.
Брайс Гуинта
Блин. Не думал. Я так часто использую Spark + Pandas вместе, что, когда я увидел обновление для Spark, я подумал об этом посте. Спасибо, Брайс.
Роб Гудериан
32

Минимальный код, необходимый для этого. Создайте общий "ключ" для декартового слияния двух:

df1['key'] = 0
df2['key'] = 0

df_cartesian = df1.merge(df2, how='outer')
А.Кот
источник
8
+ df_cartesian = df_cartesian.drop(columns=['key'])очистить в конце
StackG
22

С цепочкой методов:

product = (
    df1.assign(key=1)
    .merge(df2.assign(key=1), on="key")
    .drop("key", axis=1)
)
напыщенный
источник
14

В качестве альтернативы можно полагаться на декартово произведение, предоставляемое 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
Свенд
источник
4
Я тестировал это, и он работает, но это намного медленнее, чем приведенные выше ответы на слияние для больших наборов данных.
MrJ
2

Если у вас нет перекрывающихся столбцов, вы не хотите их добавлять, а индексы кадров данных можно отбросить, это может быть проще:

df1.index[:] = df2.index[:] = 0
df_cartesian = df1.join(df2, how='outer')
df_cartesian.index[:] = range(len(df_cartesian))
сергейк
источник
1
Это выглядит многообещающе, но я получаю ошибку в первой строке: TypeError: '<class 'pandas.core.index.Int64Index'>' does not support mutable operations. я могу обойти это, добавив , index=[0,0]в определение фрейма данных.
Racing Tadpole
2
Или используя df1 = df1.set_index([[0]*len(df1)]))(и аналогично для df2).
Racing Tadpole
Правки Racing Tadpole сделали эту работу для меня - спасибо!
Sevyns
2

Вот вспомогательная функция для выполнения простого декартова произведения с двумя фреймами данных. Внутренняя логика обрабатывает использование внутреннего ключа и избегает искажения любых столбцов, которые случайно названы "ключом" с любой стороны.

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
Майк Т
источник
сделал двойной дубль, когда увидел, что на вопрос 7-летней давности был ответ 4-часовой давности - большое спасибо за это :)
Бруно Э.
0

Вы можете начать с декартова произведения на 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
Макс Генис
источник
0

Вы можете использовать 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
)
Яньци Хуан
источник
-1

Я считаю использование pandas MultiIndex лучшим инструментом для работы. Если у вас есть список списков lists_list, вызовите pd.MultiIndex.from_product(lists_list)и переберите результат (или используйте его в индексе DataFrame).

Анкур Канория
источник