Operaciones con conjuntos en python usando pandas joins-merges

Implementando operaciones de conjuntos usando Pandas

Un conjunto en python se puede representar por un estructura de datos set() y podemos realizar con esta estructura, muchas operaciones de conjuntos, en mi caso no me ha tocado tener que lidiar con conjuntos enormes, Pero puedo suponer que las librerías estándar tengan problemas de memoria o procesamiento en cojuntos de datos enormes, y ademas veo que es un tema recurrente en stackoverflow, y en foros que preguntan si es posible usar numpy o scipy para estos temas.

Veo que la respuesta que comentan es que numpy, tiene ciertas funcionalidades como por ejemplo revisar si un elemento pertenece a una matriz (operación "in") y obtener los elementos únicos "unique"

Nota: para esta ultima operación de obtener los elementos únicos, realmente obtiene los elementos únicos pero después de aplanar la estructura a una dimensión, si por ejemplo tuviésemos el siguiente array numpy

matrix_numpy_a = np.array([[1,2],[2,3],[5,3],[3,5]])  

Si aplicásemos la función de unique

In [50]: np.unique(matrix_numpy_a)  
Out[50]: array([1, 2, 3, 5])  

Nos devuelve los elementos únicos, vistos desde una perspectiva de una dimensión, pero que si quisiéramos obtener las tuplas distintas o eneadas distintas, no nos funcionará.

Por la definición de matriz de numpy no puede contener tuplas como tal y otra restricción todos los elementos deben ser del mismo tipo; entonces al insertar una lista de tuplas regresara nuevamente el array

In [51]: list_a  
Out[51]: [(1, 2), (2, 3), (5, 3), (3, 5)]

In [52]: matrix_numpy_a = np.array(list_a)

In [53]: matrix_numpy_a  
Out[53]:  
array([[1, 2],  
       [2, 3],
       [5, 3],
       [3, 5]])

No encontré que diesen una solución en los foros al problema de generar conjuntos y poder realizar operaciones sobre ellos con estas librerías, y como cabe la casualidad que estoy repasando los temas básicos de conjuntos y esos temas, se me ocurrió que lo que se requiere se puede simular usando la librería pandas y las funciones de pandas como join, ya que los joins (operaciones relacionales) a final de cuentas puede decirse que podrían simular operaciones de conjuntos, si tomamos como base lo siguiente:

joins_sets

Tomando como base eso ya tenemos una gran parte de las operaciones necesarias, y quizás la operación in la podríamos lograr con un inner join haciendo una conjunto de una tupla única

Ejemplos de operaciones con pandas


Para genera el dataframe que representara las tuplas haríamos los siguiente:

1.- Crearemos 2 arreglos de tuplas y lo pasaremos a un dataframe cada uno
2.- Modificaremos el segundo arrelgo para poder mostrar los resultados de las operaciones
In [39]: list_a  
Out[39]: [(1, 2), (2, 3), (5, 3), (3, 5)]

In [40]: list_b = list_a.copy()

In [41]: list_b[1]  
Out[41]: (2, 3)

In [42]: list_b[2] =(3,3)

In [43]: list_b  
Out[43]: [(1, 2), (2, 3), (3, 3), (3, 5)]

In [44]: data_frame_a = pd.DataFrame(list_a)  
In [44]: data_frame_b = pd.DataFrame(list_b)

In [54]: data_frame_a  
Out[54]:  
   0  1
0  1  2  
1  2  3  
2  5  3  
3  3  5

In [55]: data_frame_b  
Out[55]:  
   0  1
0  1  2  
1  2  3  
2  3  3  
3  3  5  

Para hacer los joins sobre columnas modificaremos los indices de las columnas de la siguiente forma

In [58]: data_frame_a.columns=['A','B']

In [59]: data_frame_a  
Out[59]:  
   A  B
0  1  2  
1  2  3  
2  5  3  
3  3  5

In [60]: data_frame_b.columns=['A','B']

In [61]: data_frame_b  
Out[61]:  
   A  B
0  1  2  
1  2  3  
2  3  3  
3  3  5  
3.- Ahora aplicaremos las funciones de pandas join,merge para simular operaciones de conjuntos

Pandas has full-featured, high performance in-memory join operations idiomatically very similar to relational databases like SQL. These methods perform significantly better (in some cases well over an order of magnitude better) than other open source implementations
(like base::merge.data.frame in R). The reason for this is careful algorithmic design and internal layout of the data in DataFrame.

3.1 Calcular A - B
        In [56]: result_a_left_b = pd.merge(data_frame_a,data_frame_b,how='left',on=['A','B'])
        In [71]: result_a_left_b
        Out[71]: 
           A  B
        0  1  2
        1  2  3
        2  5  3
        3  3  5

Aquí en este caso tenemos un pequeño problema ya que la intersección en pandas, no nos genera las columnas repetidas como para poder filtrar la columna de lado derecho que
tenga NULL y así lograr el cometido, creo que se podría lograr, tomando una columna extra en cada dataframe con un valor simbólico, solo para detectar nulos,

Investigando entre las opciones veo que hay un parámetro que puede servir para lograr lo deseado "indicator=True" al activarlo, crea una columna extra en el dataframe,
resultante que indica si la fila existe solo en el lado izquierdo, o si esta en ambos, lo cual nos podría ayudar a simular A - B (filtrando valores)

In [73]: result_a_left_b = pd.merge(data_frame_a,data_frame_b,how='left',on=['A','B'],indicator=True)

In [74]: result_a_left_b  
Out[74]:  
   A  B     _merge
0  1  2       both  
1  2  3       both  
2  5  3  left_only  
3  3  5       both  

para filtrar sería de la siguiente manera

In [83]: result_a_left_b[result_a_left_b._merge=='left_only']

Filtramos el dataframe usando un dataframe booleano el cual contiene solo verdadero cuando es un left_only

In [98]: result_a_left_b._merge=='left_only'  
Out[98]:  
0    False  
1    False  
2     True  
3    False  
Name: _merge, dtype: bool

In [100]: result_a_left_b[result_a_left_b._merge=='left_only']  
Out[100]:  
   A  B     _merge
2  5  3  left_only  
3.2 Calcular B - A

Esto es el mismo caso pero invertido

In [87]: result_a_right_b = pd.merge(data_frame_a,data_frame_b,how='right',on=['A','B'],indicator=True)

In [88]: result_a_right_b  
Out[88]:  
   A  B      _merge
0  1  2        both  
1  2  3        both  
2  3  5        both  
3  3  3  right_only  

podemos igual dejar solo los right_only

In [92]: result_a_right_b[result_a_right_b._merge=='right_only']  
Out[92]:  
   A  B      _merge
3  3  3  right_only  
3.3 Calcular A ∪ B

Para esta operación dejaremos intacta la salida ya que ocupamos todos los elementos que están tanto solo en algunos de los conjuntos como en ambos

In [84]: result_a_outer_b = pd.merge(data_frame_a,data_frame_b,how='outer',on=['A','B'],indicator=True)

In [85]: result_a_outer_b  
Out[85]:  
A  B      _merge  
0  1  2        both  
1  2  3        both  
2  5  3   left_only  
3  3  5        both  
4  3  3  right_only  

Tomaremos como premisa que no hay repetidos porque estamos trabajando con conjuntos, entonces suponemos que cada par de valores A,B es único

3.4 Calcular A ∩ B
In [66]: result_a_inner_b = d.merge(data_frame_a,data_frame_b,how='inner',on=['A','B'])

In [67]: result_a_inner_b  
Out[67]:  
   A  B
0  1  2  
1  2  3  
2  3  5  

Como se puede observar la intersección no contiene al elemento (3,3) ni (5,3)

Las funcione de pandas para realiza joins y merges tiene muchos parámetros esto solo son unos pequeños ejemplos, habría que ver mas ejemplos en data sets reales, para irme acostumbrando a trabajar con estas funciones

Nota: el hecho de que la librería pandas pueda simular operaciones con conjuntos usando operaciones relacionales ayuda a realizar tareas de conjuntos, mas habría que realizar un test versus alguna librería si es que la hay,

Referencias