produkt kartezjański w pandach


109

Mam dwie pandy dataframe:

from pandas import DataFrame
df1 = DataFrame({'col1':[1,2],'col2':[3,4]})
df2 = DataFrame({'col3':[5,6]})     

Jaka jest najlepsza praktyka, aby uzyskać ich produkt kartezjański (oczywiście bez pisania tego wyraźnie tak jak ja)?

#df1, df2 cartesian product
df_cartesian = DataFrame({'col1':[1,2,1,2],'col2':[3,4,3,4],'col3':[5,5,6,6]})

Odpowiedzi:


88

Jeśli masz klucz, który jest powtarzany dla każdego wiersza, możesz utworzyć iloczyn kartezjański za pomocą scalania (tak jak w 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']]

Wynik:

   col1  col2  col3
0     1     3     5
1     1     3     6
2     2     4     5
3     2     4     6

Zobacz dokumentację: http://pandas.pydata.org/pandas-docs/stable/merging.html#brief-primer-on-merge-methods-relational-algebra


6
Aby więc zrobić to poprawnie, należy najpierw znaleźć nieużywaną nazwę kolumny, następnie dodać kolumny fikcyjne o tej nazwie, scalić, a na koniec upuścić kolumnę na wynik? Tworzenie, w przeciwieństwie do czytania, danych z pandami to tylko ból
Bananach

69

Użyj pd.MultiIndex.from_productjako indeksu w pustej ramce danych, a następnie zresetuj jej indeks i gotowe.

a = [1, 2, 3]
b = ["a", "b", "c"]

index = pd.MultiIndex.from_product([a, b], names = ["a", "b"])

pd.DataFrame(index = index).reset_index()

na zewnątrz:

   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

6
Uważam, że jest to obecnie najbardziej podobny do pandy sposób dla pand> = 0,21
shadi

6
Masz głosy przeciw, ponieważ nie pokazałeś, jak to uogólnia cokolwiek z więcej niż jedną kolumną.
cs95

Ta funkcja ( stackoverflow.com/a/58242079/1840471 ) uogólnia ją na dowolną liczbę list przy użyciu dyktowania argumentów. To trochę różni się od pytania tutaj, które bierze iloczyn kartezjański dwóch ramek DataFrames (tj. Nie bierze iloczynu df1.col1i df.col2).
Max Ghenis

W rzeczywistości nie sądzę, aby from_productmożna go było wykorzystać do tego problemu.
Max Ghenis

34

To nie wygra zawodów w golfie kodowym i zapożycza z poprzednich odpowiedzi - ale wyraźnie pokazuje, w jaki sposób klucz jest dodawany i jak działa łączenie. Tworzy to 2 nowe ramki danych z list, a następnie dodaje klucz do wykonania iloczynu kartezjańskiego.

Moim przypadkiem użycia było to, że potrzebowałem listy wszystkich identyfikatorów sklepów na każdy tydzień na mojej liście. Utworzyłem więc listę wszystkich tygodni, które chciałem mieć, a następnie listę wszystkich identyfikatorów sklepów, według których chciałem je zmapować.

Scalanie, które wybrałem, pozostawiłoby, ale byłoby semantycznie takie same jak wewnętrzne w tej konfiguracji. Możesz to zobaczyć w dokumentacji dotyczącej scalania , która stwierdza, że ​​wykonuje iloczyn kartezjański, jeśli kombinacja klawiszy pojawia się więcej niż raz w obu tabelach - tak właśnie skonfigurowaliśmy.

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
Nieco krótsza wersja:days_and_stores = pd.merge(days.assign(key=0), stores.assign(key=0), on='key').drop('key', axis=1)
Eugene Pakhomov

Wspomniałeś o crossJoin, ale używasz Dataframe pandas, a nie Spark Dataframe.
Bryce Guinta

Cholera. Nie myślałem. Tak często używam razem spark + pandas, że gdy zobaczyłem aktualizację do spark, pomyślałem o tym poście. Dzięki Bryce.
Rob Guderian

32

Do tego potrzebny jest minimalny kod. Utwórz wspólny „klucz” do kartezjańskiego połączenia tych dwóch elementów:

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

df_cartesian = df1.merge(df2, how='outer')

8
+ df_cartesian = df_cartesian.drop(columns=['key'])posprzątać na koniec
StackG

22

W przypadku łączenia metod:

product = (
    df1.assign(key=1)
    .merge(df2.assign(key=1), on="key")
    .drop("key", axis=1)
)

14

Alternatywnie można polegać na produkcie kartezjańskim dostarczanym przez itertools: itertools.productktóry pozwala uniknąć tworzenia tymczasowego klucza lub modyfikowania indeksu:

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)

Szybki test:

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
Przetestowałem to i działa, ale jest znacznie wolniejsze niż powyższe scalanie odpowiedzi dla dużych zbiorów danych.
MrJ

2

Jeśli nie masz nakładających się kolumn, nie chcesz ich dodawać, a indeksy ramek danych można odrzucić, może to być łatwiejsze:

df1.index[:] = df2.index[:] = 0
df_cartesian = df1.join(df2, how='outer')
df_cartesian.index[:] = range(len(df_cartesian))

1
Wygląda to obiecująco - ale pojawia się błąd w pierwszym wierszu: TypeError: '<class 'pandas.core.index.Int64Index'>' does not support mutable operations. mogę to obejść, dodając , index=[0,0]do definicji ramki danych.
Racing Tadpole

2
Lub używając df1 = df1.set_index([[0]*len(df1)]))(i podobnie dla df2).
Racing Tadpole

Zmiany w Racing Tadpole sprawiły, że to zadziałało - dzięki!
Sevyns

2

Oto funkcja pomocnicza do wykonania prostego iloczynu kartezjańskiego z dwiema ramkami danych. Logika wewnętrzna obsługuje użycie klucza wewnętrznego i pozwala uniknąć zniekształcania kolumn, które mają nazwę „klucz” z dowolnej strony.

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)

przedstawia:

   number  key  digit
0       1    3      5
1       1    3      6
2       2    4      5
3       2    4      6

zrobiłem podwójne spojrzenie, kiedy zobaczyłem, że pytanie 7-latka miało 4-godzinną odpowiedź - wielkie dzięki za to :)
Bruno E

0

Można zacząć biorąc iloczyn kartezjański df1.col1i df2.col3, a następnie połączyć z powrotem df1dostać col2.

Oto ogólna funkcja iloczynu kartezjańskiego, która pobiera słownik list:

def cartesian_product(d):
    index = pd.MultiIndex.from_product(d.values(), names=d.keys())
    return pd.DataFrame(index=index).reset_index()

Zastosuj jako:

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

Możesz użyć numpy, ponieważ może być szybszy. Załóżmy, że masz dwie następujące serie:

s1 = pd.Series(np.random.randn(100,))
s2 = pd.Series(np.random.randn(100,))

Ty po prostu potrzebujesz,

pd.DataFrame(
    s1[:, None] @ s2[None, :], 
    index = s1.index, columns = s2.index
)

-1

Uważam, że używanie pand MultiIndex jest najlepszym narzędziem do pracy. Jeśli masz listę list lists_list, wywołaj pd.MultiIndex.from_product(lists_list)i iteruj po wyniku (lub użyj go w indeksie DataFrame).

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.