python - dataframes - rename columns pandas
Producto cartesiano performante(CROSS JOIN) con pandas. (3)
Aquí hay un enfoque con triple
concat
m = pd.concat([pd.concat([left]*len(right)).sort_index().reset_index(drop=True),
pd.concat([right]*len(left)).reset_index(drop=True) ], 1)
col1 col2 col1 col2
0 A 1 X 20
1 A 1 Y 30
2 A 1 Z 50
3 B 2 X 20
4 B 2 Y 30
5 B 2 Z 50
6 C 3 X 20
7 C 3 Y 30
8 C 3 Z 50
El contenido de esta publicación originalmente estaba destinado a ser parte de Pandas Merging 101 , pero debido a la naturaleza y el tamaño del contenido requerido para hacer justicia a este tema, se ha trasladado a su propia QnA.
Dados dos cuadros de datos simples;
left = pd.DataFrame({''col1'' : [''A'', ''B'', ''C''], ''col2'' : [1, 2, 3]})
right = pd.DataFrame({''col1'' : [''X'', ''Y'', ''Z''], ''col2'' : [20, 30, 50]})
left
col1 col2
0 A 1
1 B 2
2 C 3
right
col1 col2
0 X 20
1 Y 30
2 Z 50
El producto cruzado de estos marcos se puede calcular, y se verá algo así como:
A 1 X 20
A 1 Y 30
A 1 Z 50
B 2 X 20
B 2 Y 30
B 2 Z 50
C 3 X 20
C 3 Y 30
C 3 Z 50
¿Cuál es el método más eficaz para calcular este resultado?
Empecemos por establecer un punto de referencia. El método más fácil para resolver esto es usar una columna "clave" temporal:
def cartesian_product_basic(left, right):
return (
left.assign(key=1).merge(right.assign(key=1), on=''key'').drop(''key'', 1))
cartesian_product_basic(left, right)
col1_x col2_x col1_y col2_y
0 A 1 X 20
1 A 1 Y 30
2 A 1 Z 50
3 B 2 X 20
4 B 2 Y 30
5 B 2 Z 50
6 C 3 X 20
7 C 3 Y 30
8 C 3 Z 50
Cómo funciona esto es que a ambos DataFrames se les asigna una columna "clave" temporal con el mismo valor (por ejemplo, 1).
luego
merge
realiza una
merge
muchos a muchos en "clave".
Si bien el truco JOIN de muchos a muchos funciona para DataFrames de tamaño razonable, verá un rendimiento relativamente menor en datos más grandes.
Una implementación más rápida requerirá NumPy. Aquí hay algunas aplicaciones NumPy famosas de producto cartesiano 1D . Podemos aprovechar algunas de estas soluciones de rendimiento para obtener el resultado deseado. Mi favorito, sin embargo, es la primera implementación de @ senderle.
def cartesian_product(*arrays):
la = len(arrays)
dtype = np.result_type(*arrays)
arr = np.empty([len(a) for a in arrays] + [la], dtype=dtype)
for i, a in enumerate(np.ix_(*arrays)):
arr[...,i] = a
return arr.reshape(-1, la)
Generalización: CROSS JOIN en marcos de datos indexados únicos o no únicos
Renuncia
Estas soluciones están optimizadas para DataFrames con tipos de datos escalares no mixtos. Si se trata de tipos de letras mixtas, úselo bajo su propio riesgo.
Este truco funcionará en cualquier tipo de DataFrame.
Calculamos el producto cartesiano de los índices numéricos de los DataFrames usando el producto
cartesian_product
mencionado anteriormente, usamos esto para reindexar los DataFrames, y
def cartesian_product_generalized(left, right):
la, lb = len(left), len(right)
idx = cartesian_product(np.ogrid[:la], np.ogrid[:lb])
return pd.DataFrame(
np.column_stack([left.values[idx[:,0]], right.values[idx[:,1]]]))
cartesian_product_generalized(left, right)
0 1 2 3
0 A 1 X 20
1 A 1 Y 30
2 A 1 Z 50
3 B 2 X 20
4 B 2 Y 30
5 B 2 Z 50
6 C 3 X 20
7 C 3 Y 30
8 C 3 Z 50
np.array_equal(cartesian_product_generalized(left, right),
cartesian_product_basic(left, right))
True
Y, a lo largo de líneas similares,
left2 = left.copy()
left2.index = [''s1'', ''s2'', ''s1'']
right2 = right.copy()
right2.index = [''x'', ''y'', ''y'']
left2
col1 col2
s1 A 1
s2 B 2
s1 C 3
right2
col1 col2
x X 20
y Y 30
y Z 50
np.array_equal(cartesian_product_generalized(left, right),
cartesian_product_basic(left2, right2))
True
Esta solución puede generalizar a múltiples DataFrames. Por ejemplo,
def cartesian_product_multi(*dfs):
idx = cartesian_product(*[np.ogrid[:len(df)] for df in dfs])
return pd.DataFrame(
np.column_stack([df.values[idx[:,i]] for i,df in enumerate(dfs)]))
cartesian_product_multi(*[left, right, left]).head()
0 1 2 3 4 5
0 A 1 X 20 A 1
1 A 1 X 20 B 2
2 A 1 X 20 C 3
3 A 1 X 20 D 4
4 A 1 Y 30 A 1
Simplificación adicional
Una solución más simple que no implique
cartesian_product
@ senderle es posible cuando se trata de
solo dos
DataFrames.
Usando
np.broadcast_arrays
, podemos lograr casi el mismo nivel de rendimiento.
def cartesian_product_simplified(left, right):
la, lb = len(left), len(right)
ia2, ib2 = np.broadcast_arrays(*np.ogrid[:la,:lb])
return pd.DataFrame(
np.column_stack([left.values[ia2.ravel()], right.values[ib2.ravel()]]))
np.array_equal(cartesian_product_simplified(left, right),
cartesian_product_basic(left2, right2))
True
Comparación de rendimiento
Al evaluar estas soluciones en algunos DataFrames ideados con índices únicos, tenemos
Tenga en cuenta que los tiempos pueden variar según la configuración, los datos y la elección de la función auxiliar
cartesian_product
, según corresponda.
Código de rendimiento de referencia
Este es el script de tiempo.
Todas las funciones llamadas aquí se definen arriba.
from timeit import timeit
import pandas as pd
import matplotlib.pyplot as plt
res = pd.DataFrame(
index=[''cartesian_product_basic'', ''cartesian_product_generalized'',
''cartesian_product_multi'', ''cartesian_product_simplified''],
columns=[1, 10, 50, 100, 200, 300, 400, 500, 600, 800, 1000, 2000],
dtype=float
)
for f in res.index:
for c in res.columns:
# print(f,c)
left2 = pd.concat([left] * c, ignore_index=True)
right2 = pd.concat([right] * c, ignore_index=True)
stmt = ''{}(left2, right2)''.format(f)
setp = ''from __main__ import left2, right2, {}''.format(f)
res.at[f, c] = timeit(stmt, setp, number=5)
ax = res.div(res.min()).T.plot(loglog=True)
ax.set_xlabel("N");
ax.set_ylabel("time (relative)");
plt.show()
Usando el
product
itertools
y recrea el valor en el marco de datos
import itertools
l=list(itertools.product(left.values.tolist(),right.values.tolist()))
pd.DataFrame(list(map(lambda x : sum(x,[]),l)))
0 1 2 3
0 A 1 X 20
1 A 1 Y 30
2 A 1 Z 50
3 B 2 X 20
4 B 2 Y 30
5 B 2 Z 50
6 C 3 X 20
7 C 3 Y 30
8 C 3 Z 50