producto cartesiano en pandas

107

Tengo dos marcos de datos de pandas:

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

¿Cuál es la mejor práctica para obtener su producto cartesiano (por supuesto, sin escribirlo explícitamente como yo)?

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

Respuestas:

88

Si tiene una clave que se repite para cada fila, entonces puede producir un producto cartesiano usando merge (como lo haría en 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']]

Salida:

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

Consulte aquí la documentación: http://pandas.pydata.org/pandas-docs/stable/merging.html#brief-primer-on-merge-methods-relational-algebra

Matti John
fuente
6
Entonces, para hacer esto correctamente, primero tiene que encontrar un nombre de columna no utilizado, luego agregar columnas ficticias con ese nombre, fusionar y finalmente soltar la columna en el resultado. Crear, en lugar de leer, datos con pandas es solo un dolor
Bananach
68

Úselo pd.MultiIndex.from_productcomo un índice en un marco de datos que de otra manera estaría vacío, luego restablezca su índice y listo.

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

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

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

fuera:

   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
fuente
6
Creo que esta es la forma más parecida a los pandas en estos días para los pandas> = 0.21
shadi
6
Tiene votos negativos porque no ha mostrado cómo se generalizará esto para nada con más de una columna.
cs95
Esta función ( stackoverflow.com/a/58242079/1840471 ) la generaliza a un número arbitrario de listas usando un dictado de argumentos. Es un poco diferente de la pregunta aquí, que toma el producto cartesiano de dos DataFrames (es decir, no toma el producto de df1.col1y df.col2).
Max Ghenis
De hecho, no creo que from_productpueda usarse para este problema.
Max Ghenis
34

Esto no ganará una competencia de golf de código y se basa en las respuestas anteriores, pero muestra claramente cómo se agrega la clave y cómo funciona la combinación. Esto crea 2 nuevos marcos de datos a partir de listas, luego agrega la clave para hacer el producto cartesiano.

Mi caso de uso fue que necesitaba una lista de todos los ID de tienda para cada semana en mi lista. Entonces, creé una lista de todas las semanas que quería tener, luego una lista de todas las ID de tienda con las que quería asignarlas.

La combinación que elegí a la izquierda, pero sería semánticamente la misma que la interior en esta configuración. Puede ver esto en la documentación sobre la fusión , que establece que hace un producto cartesiano si la combinación de teclas aparece más de una vez en ambas tablas, que es lo que configuramos.

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)
Rob Guderian
fuente
25
Versión un poco más corta:days_and_stores = pd.merge(days.assign(key=0), stores.assign(key=0), on='key').drop('key', axis=1)
Eugene Pakhomov
Mencionas crossJoin, pero estás usando un marco de datos de pandas, no un marco de datos de chispa.
Bryce Guinta
Dang. No estaba pensando. Utilizo spark + pandas juntos con tanta frecuencia que cuando vi la actualización de Spark pensé en esta publicación. Gracias Bryce.
Rob Guderian
32

Se necesita un código mínimo para este. Cree una 'clave' común para fusionar los dos cartesianos:

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

df_cartesian = df1.merge(df2, how='outer')
A.Kot
fuente
8
+ df_cartesian = df_cartesian.drop(columns=['key'])para limpiar al final
StackG
22

Con encadenamiento de métodos:

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

Como alternativa, se puede confiar en el producto cartesiano proporcionado por itertools:, itertools.productque evita crear una clave temporal o modificar el índice:

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)

Examen rápido:

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
Svend
fuente
4
Probé esto y funciona, pero es mucho más lento que las respuestas de combinación anteriores para grandes conjuntos de datos.
MrJ
2

Si no tiene columnas superpuestas, no desea agregar una y los índices de los marcos de datos se pueden descartar, esto puede ser más fácil:

df1.index[:] = df2.index[:] = 0
df_cartesian = df1.join(df2, how='outer')
df_cartesian.index[:] = range(len(df_cartesian))
Sergeyk
fuente
1
Esto parece prometedor, pero aparece el error en la primera línea: TypeError: '<class 'pandas.core.index.Int64Index'>' does not support mutable operations. puedo evitar esto agregando , index=[0,0]a la definición del marco de datos.
Racing Tadpole
2
O usando df1 = df1.set_index([[0]*len(df1)]))(y de manera similar para df2).
Racing Tadpole
Las ediciones de Racing Tadpole hicieron que esto funcionara para mí, ¡gracias!
Sevyns
2

Aquí hay una función auxiliar para realizar un producto cartesiano simple con dos marcos de datos. La lógica interna maneja el uso de una clave interna y evita alterar cualquier columna que se llame "clave" de cualquier lado.

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)

muestra:

   number  key  digit
0       1    3      5
1       1    3      6
2       2    4      5
3       2    4      6
Mike T
fuente
Hice una doble toma cuando vi que una pregunta de 7 años tenía una respuesta de 4 horas, muchas gracias por esto :)
Bruno E
0

Puede comenzar tomando el producto cartesiano de df1.col1y df2.col3, luego fusionar de nuevo con df1para obtener col2.

Aquí hay una función de producto cartesiana general que toma un diccionario de listas:

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

Aplicar como:

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
Max Ghenis
fuente
0

Puedes usar numpy ya que podría ser más rápido. Suponga que tiene dos series como sigue,

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

Solo necesitas,

pd.DataFrame(
    s1[:, None] @ s2[None, :], 
    index = s1.index, columns = s2.index
)
Yanqi Huang
fuente
-1

Encuentro que usar pandas MultiIndex es la mejor herramienta para el trabajo. Si tiene una lista de listas lists_list, llame pd.MultiIndex.from_product(lists_list)e itere sobre el resultado (o utilícelo en el índice DataFrame).

Ankur Kanoria
fuente