cómo realizar una combinación interna o externa de DataFrames con Pandas en un criterio no simplista

Dados dos marcos de datos de la siguiente manera:

>>> import pandas as pd >>> df_a = pd.DataFrame([{"a": 1, "b": 4}, {"a": 2, "b": 5}, {"a": 3, "b": 6}]) >>> df_b = pd.DataFrame([{"c": 2, "d": 7}, {"c": 3, "d": 8}]) >>> df_a ab 0 1 4 1 2 5 2 3 6 >>> df_b cd 0 2 7 1 3 8 

nos gustaría producir una unión estilo SQL de ambos marcos de datos utilizando un criterio no simplista, digamos “df_b.c> df_a.a”. Por lo que puedo decir, aunque merge() es ciertamente parte de la solución, no puedo usarlo directamente ya que no acepta expresiones arbitrarias para los criterios de “ENCENDIDO” (¿a menos que me esté perdiendo algo?).

En SQL, los resultados se ven así:

 # inner join sqlite> select * from df_a join df_b on c > a; 1|4|2|7 1|4|3|8 2|5|3|8 # outer join sqlite> select * from df_a left outer join df_b on c > a; 1|4|2|7 1|4|3|8 2|5|3|8 3|6|| 

mi enfoque actual para la unión interna es producir un producto cartesiano de df_a y df_b, agregando una columna de “1” a ambos, luego utilizando merge () en la columna de “1”, luego aplicando la “c> a” criterios

 >>> import numpy as np >>> df_a['ones'] = np.ones(3) >>> df_b['ones'] = np.ones(2) >>> cartesian = pd.merge(df_a, df_b, left_on='ones', right_on='ones') >>> cartesian ab ones cd 0 1 4 1 2 7 1 1 4 1 3 8 2 2 5 1 2 7 3 2 5 1 3 8 4 3 6 1 2 7 5 3 6 1 3 8 >>> cartesian[cartesian.c > cartesian.a] ab ones cd 0 1 4 1 2 7 1 1 4 1 3 8 3 2 5 1 3 8 

para la combinación externa, no estoy seguro de cuál es el mejor camino, hasta ahora he estado jugando para obtener la combinación interna, luego aplicar la negación de los criterios para obtener todas las demás filas y luego tratar de editar esa “negación “colocado en el original, pero realmente no funciona.

Editar HYRY respondió la pregunta específica aquí, pero necesitaba algo más genérico y más dentro de la API de Pandas, ya que mi criterio de unión podría ser cualquier cosa, no solo esa comparación. Para la unión externa, primero estoy agregando un índice adicional al lado “izquierdo” que se mantendrá después de hacer la unión interna:

 df_a['_left_index'] = df_a.index 

Luego hacemos lo cartesiano y obtenemos la unión interna:

 cartesian = pd.merge(df_a, df_b, left_on='ones', right_on='ones') innerjoin = cartesian[cartesian.c > cartesian.a] 

luego obtengo los identificadores de índice adicionales en “df_a” que necesitaremos, y obtengo las filas de “df_a”:

 remaining_left_ids = set(df_a['_left_index']).\ difference(innerjoin['_left_index']) remaining = df_a.ix[remaining_left_ids] 

luego usamos un concat () recto, que reemplaza las columnas faltantes con “NaN” para la izquierda (pensé que no estaba haciendo esto antes, pero supongo que sí):

 outerjoin = pd.concat([innerjoin, remaining]).reset_index() 

La idea de HYRY de hacer el cartesiano solo en los collares que necesitamos comparar es básicamente la respuesta correcta, aunque en mi caso específico podría ser un poco difícil de implementar (generalizado y todo).

preguntas:

  1. ¿Cómo producirías una “unión” de df_1 y df_2 en “c> a”? ¿Haría el mismo enfoque de “producto cartesiano, filtro” o hay alguna forma mejor?

  2. ¿Cómo producirías la “unión externa izquierda” de la misma?

Utilizo el método externo de ufunc para calcular el resultado, aquí está el ejemplo:

Primero, algunos datos:

 import pandas as pd import numpy as np df_a = pd.DataFrame([{"a": 1, "b": 4}, {"a": 2, "b": 5}, {"a": 3, "b": 6}, {"a": 4, "b": 8}, {"a": 1, "b": 7}]) df_b = pd.DataFrame([{"c": 2, "d": 7}, {"c": 3, "d": 8}, {"c": 2, "d": 10}]) print "df_a" print df_a print "df_b" print df_b 

salida:

 df_a ab 0 1 4 1 2 5 2 3 6 3 4 8 4 1 7 df_b cd 0 2 7 1 3 8 2 2 10 

Unión interna, porque esto solo calcula el producto cartesiano de c & a , el uso de memoria es menor que el producto cartesiano de todo el DataFrame:

 ia, ib = np.where(np.less.outer(df_a.a, df_b.c)) print pd.concat((df_a.take(ia).reset_index(drop=True), df_b.take(ib).reset_index(drop=True)), axis=1) 

salida:

  abcd 0 1 4 2 7 1 1 4 3 8 2 1 4 2 10 3 2 5 3 8 4 1 7 2 7 5 1 7 3 8 6 1 7 2 10 

para calcular la combinación externa izquierda, use numpy.setdiff1d() para encontrar todas las filas de df_a que no están en la combinación interna:

 na = np.setdiff1d(np.arange(len(df_a)), ia) nb = -1 * np.ones_like(na) oa = np.concatenate((ia, na)) ob = np.concatenate((ib, nb)) print pd.concat([df_a.take(oa).reset_index(drop=True), df_b.take(ob).reset_index(drop=True)], axis=1) 

salida:

  abcd 0 1 4 2 7 1 1 4 3 8 2 1 4 2 10 3 2 5 3 8 4 1 7 2 7 5 1 7 3 8 6 1 7 2 10 7 3 6 NaN NaN 8 4 8 NaN NaN 

Esto se puede hacer de esta manera con broadcasting y np.where. Use cualquier operador binario que desee que se evalúe como Verdadero / Falso:

 import operator as op df_a = pd.DataFrame([{"a": 1, "b": 4}, {"a": 2, "b": 5}, {"a": 3, "b": 6}]) df_b = pd.DataFrame([{"c": 2, "d": 7}, {"c": 3, "d": 8}]) binOp = op.lt matches = np.where(binOp(df_a.a[:,None],df_b.c.values)) print pd.concat([df.ix[idxs].reset_index(drop=True) for df,idxs in zip([df_a,df_b],matches)], axis=1).to_csv() 

,a B C D

0,1,4,2,7

1,1,4,3,8

2,2,5,3,8