Curso de introducción a la programación con Python¶

    Autor: Luis Fernando Apáez Álvarez
    -Curso PyM-
    Clase 6: Uniones (parte II)
    Fecha: 12 de diciembre del 2022


Contenido¶

  • Tipos de relaciones
    • Uniendo múltiples dataframes
    • Análisis
  • Tipos de uniones
    • Unión izquierda y derecha
    • Unión externa
    • Uniendo una tabla con ella misma
    • Exportando dataframe a csv

Recordemos que en la clase pasada indagamos un poco sobre operaciones con conjuntos y su representación visual mediante los diagramas de Venn. Asimismo, indagamos en el tipo de unión denominada como unión interna, la cual, si vemos a los dataframes como conjuntos, no es más que la intersección de conjuntos.

Así, para iniciar esta clase:

In [5]:
# importaciones necesarias
import matplotlib.pyplot as plt
from matplotlib_venn import venn2 
import pandas as pd
import numpy as np
from faker import Faker

# Libreria para no ver las advertencias en la salida
import warnings
warnings.filterwarnings('ignore')

# Instanciamos
fake = Faker()

Luego definimos los dataframes con los que trabajaremos:

In [6]:
# Creamos valores para asignar ids
ID = [i for i in range(1, 1001)]
# creamos 1000 nombres 
nombres = [fake.name() + ' ' + fake.last_name() for i in range(1000)]
# 1000 correos electronicos
correos = [fake.email() for i in range(1000)]
# 1000 edades
edades = [fake.random_int(18, 80) for i in range(1000)]
# Creamos un diccionario con la info anterior
dict_info = {'ID': ID, 'Nombre': nombres, 'Edad': edades, 'Correo': correos}
# Creamos el dataframe
df_empleados = pd.DataFrame(dict_info)
# 1000 salarios
salarios = [np.random.rand() * 10000 for i in range(1000)]
# 1000 correos electronicos
correos = [fake.email() for i in range(1000)]
# Creamos un diccionario con el salario y el ID
dict_info = {'ID': ID, 'Salario': salarios, 'Correo': correos}
# Creamos el dataframe
df_salario = pd.DataFrame(dict_info)
# Empleados nuevos
emp1 = {'ID': 1001, 'Nombre': 'Diana Smith', 'Edad': 27, 'Correo': 'diana27182@edu.com'}
emp2 = {'ID': 1002, 'Nombre': 'Luka Ldresh', 'Edad': 20, 'Correo': 'lukaecdf@gmail.com'}
# Los agregamos al dataframe
df_empleados = df_empleados.append(emp1, ignore_index=True)
df_empleados = df_empleados.append(emp2, ignore_index=True)

Realizamos una unión interna:

In [7]:
# realizamos la union interna y cambiamos los sufijos:
df_union_interna = df_empleados.merge(df_salario, on='ID',
                                     suffixes=('_personal', '_compañia'))
# veamos
df_union_interna.head()
Out[7]:
ID Nombre Edad Correo_personal Salario Correo_compañia
0 1 Mark Rodriguez Park 66 listephanie@example.net 2815.715914 bmora@example.com
1 2 Maria Patrick Foley 58 annawright@example.net 8071.241496 sandrathompson@example.org
2 3 Olivia Boyle King 18 alexander11@example.org 1406.213853 gibsonkevin@example.net
3 4 Kent Lloyd Thomas 72 rroberts@example.org 8799.843576 iharris@example.net
4 5 Connie Villa Meyer 28 micheal66@example.org 3627.843246 qreynolds@example.com

Tipos de relaciones ¶

Tenemos distitos tipos de relaciones a la hora de unir dataframes. Para explicar dichos tipos supongamos que queremos unir dos dataframes (digamos $df_{1}$ y $df_{2}$), luego:

  • Relación uno a uno: indica que a cada fila del $df_{1}$ le corresponde una única fila del $df_{2}$ y viceversa. Por ejemplo, para la unión interna que realizamos para los dataframes df_empleados y df_salario tenemos que la relación uno a uno pues a cada fila le corresponde una única fila y viceversa.
  • Relación uno a muchos: indica que a cada fila del $df_{1}$ le coresponderá más de una fila del $df_{2}$. Por ejemplo, si consideraremos que a cada persona le corresponden dos correos electrónico, tenemos que la relación descrita sería de uno a muchos.

  • Relación muchos a uno: es análoga a la anterior.

  • Relación muchos a muchos: indica que a cada fila del dataframe $df_{1}$ le corresponden muchas filas del dataframe $df_{2}$ y viceversa, es decir, a cada fila del dataframe $df_{2}$ le corresponden muchas filas del dataframe $df_{1}$. Por ejemplo, si consideramos que cada persona tiene asociadas varias cuentas de banco con diferentes bancos tenemos una relación uno a muchos, pero además podemos considerar que un banco tiene cuentas de banco de muchas personas.

Veamos un ejemplo de relación uno a muchos, para ello:

In [16]:
# Creamos un dataframe vacio con dos columnas
df_correos = pd.DataFrame({'ID': [], 'Correo': []})
df_correos
Out[16]:
ID Correo

Posteriormente colocaremos la información del dataframe anterior a partir de la información dle dataframe df_unio_interna como sigue

In [18]:
# Recorremos todas las filas de df_union_interna
for i in range(df_union_interna.shape[0]):
    # Creamos una fila nueva para el primer correo de
    # la persona
    new_row1 = {'ID': df_union_interna.iloc[i][0], 
               'Correo': df_union_interna.iloc[i][3]}
    # Creamos una fila nueva para el segundo correo de
    # la persona
    new_row2 = {'ID': df_union_interna.iloc[i][0], 
               'Correo': df_union_interna.iloc[i][5]}
    # Agregamos las filas nuevas al dataframe
    df_correos = df_correos.append([new_row1, new_row2], ignore_index=True)
# Vemos el dataframe resultante
df_correos
Out[18]:
ID Correo
0 1.0 listephanie@example.net
1 1.0 bmora@example.com
2 2.0 annawright@example.net
3 2.0 sandrathompson@example.org
4 3.0 alexander11@example.org
... ... ...
1995 998.0 clarkcharles@example.net
1996 999.0 ejohnson@example.org
1997 999.0 jsmith@example.net
1998 1000.0 emilysantos@example.net
1999 1000.0 perkinsmonica@example.net

2000 rows × 2 columns

In [20]:
# Vemos que el tipo de dato de la columna ID es float64
print(df_correos['ID'].dtype)
float64
In [22]:
# Cambiamos a int64 con la funcion astype():
df_correos['ID'] = df_correos['ID'].astype('int64')
print(df_correos['ID'].dtype)
int64
In [23]:
df_correos.head()
Out[23]:
ID Correo
0 1 listephanie@example.net
1 1 bmora@example.com
2 2 annawright@example.net
3 2 sandrathompson@example.org
4 3 alexander11@example.org

tenemos entonces que a cada persona (o ID) le corresponden dos correos, de modo que la relación qye tenemos es de uno a muchos. Lo que haremos ahora será llevar a cabo una unión interna entre los dataframes df_empleados y df_correos:

In [24]:
# Antes de ello:
print(df_empleados.shape)
print(df_correos.shape)
(1002, 4)
(2000, 2)
In [27]:
# notemos que el dataframe tiene una columna de correo
df_empleados.head()
Out[27]:
ID Nombre Edad Correo
0 1 Mark Rodriguez Park 66 listephanie@example.net
1 2 Maria Patrick Foley 58 annawright@example.net
2 3 Olivia Boyle King 18 alexander11@example.org
3 4 Kent Lloyd Thomas 72 rroberts@example.org
4 5 Connie Villa Meyer 28 micheal66@example.org

donde el correo del dataframe anterior está también en la columna Correo del dataframe df_correos por lo cual no sería redundante considerarlo en el primer dataframe, de tal manera realizamos la unión interna como sigue

In [28]:
# Union interna:
#                    no consideramos la columna correo
#                    del primer dataframe
df_union_interna_2 = df_empleados.drop('Correo', axis=1).merge(df_correos, on='ID')
df_union_interna_2 
Out[28]:
ID Nombre Edad Correo
0 1 Mark Rodriguez Park 66 listephanie@example.net
1 1 Mark Rodriguez Park 66 bmora@example.com
2 2 Maria Patrick Foley 58 annawright@example.net
3 2 Maria Patrick Foley 58 sandrathompson@example.org
4 3 Olivia Boyle King 18 alexander11@example.org
... ... ... ... ...
1995 998 Todd Jenkins Anderson 28 clarkcharles@example.net
1996 999 Kimberly Mitchell Long 65 ejohnson@example.org
1997 999 Kimberly Mitchell Long 65 jsmith@example.net
1998 1000 Andrea Carson Liu 48 emilysantos@example.net
1999 1000 Andrea Carson Liu 48 perkinsmonica@example.net

2000 rows × 4 columns

In [26]:
df_union_interna_2.shape
Out[26]:
(2000, 5)

Notemos que hasta ahora hemos estado uniendo dataframes a partir de una sola columna, pero en realidad podemos realizar uniones a partir de más de una columna cuando sea necesario. Del ejemplo con el cual hemos estado trabajando hemos colocado on='ID' para especificar la columna que relaciona a los dataframes, luego, en caso de querer colocar más de una columna que relaciones a los dataframes que queremos unir, tendremos que colocar on=['col1', 'col2', ...].

Uniendo múltiples dataframes¶

Para ver un ejemplo agreguemos una columna nueva al dataframe df_empleados:

In [32]:
import random

# Creamos una lista con los departamentos de la 
# compagnia
dep = ['Seguridad', 'Oficina', 'Almacen', 'Limpieza', 'Comedor', 'Transporte']

# podemos tomar de manera aleatoria un elemento de una lista:
random.choice(dep)
Out[32]:
'Limpieza'

Luego, agregamos la columna nueva:

In [35]:
df_empleados['Dep'] = [random.choice(dep) for i in range(df_empleados.shape[0])]
df_empleados.head()
Out[35]:
ID Nombre Edad Correo Dep
0 1 Mark Rodriguez Park 66 listephanie@example.net Transporte
1 2 Maria Patrick Foley 58 annawright@example.net Transporte
2 3 Olivia Boyle King 18 alexander11@example.org Oficina
3 4 Kent Lloyd Thomas 72 rroberts@example.org Comedor
4 5 Connie Villa Meyer 28 micheal66@example.org Transporte

Asimismo, crearemos un dataframe con los departamentos y el salario mínimo que manejan.

In [36]:
dep
Out[36]:
['Seguridad', 'Oficina', 'Almacen', 'Limpieza', 'Comedor', 'Transporte']
In [37]:
sal_min = [8000, 8000, 7000, 6500, 6500, 7000]

# Creamos el dataframe
df_dep = pd.DataFrame({'Dep': dep, 'Salario_min': sal_min})
df_dep
Out[37]:
Dep Salario_min
0 Seguridad 8000
1 Oficina 8000
2 Almacen 7000
3 Limpieza 6500
4 Comedor 6500
5 Transporte 7000

De tal manera, si queremos visualizar en un solo dataframe la información de los empleados del dataframe df_empleados con sus salarios, df_salario, y con el salario mínimo por departamente, del dataframe df_dep, entonces realizaremos tres uniones internas como sigue:

In [39]:
# Primera union
df_union_interna_3 = df_empleados.merge(df_salario, on='ID', 
                                        # cambiamos los sufijos
                                        suffixes=('_personal', '_compañia'))\
# Segunda union
                                 .merge(df_dep, on='Dep')
df_union_interna_3
Out[39]:
ID Nombre Edad Correo_personal Dep Salario Correo_compañia Salario_min
0 1 Mark Rodriguez Park 66 listephanie@example.net Transporte 2815.715914 bmora@example.com 7000
1 2 Maria Patrick Foley 58 annawright@example.net Transporte 8071.241496 sandrathompson@example.org 7000
2 5 Connie Villa Meyer 28 micheal66@example.org Transporte 3627.843246 qreynolds@example.com 7000
3 22 Mary Payne Johnson 18 rdiaz@example.com Transporte 1575.459446 warellano@example.net 7000
4 36 Michael Owens Burns 27 ypowell@example.net Transporte 5883.722804 dsnyder@example.net 7000
... ... ... ... ... ... ... ... ...
995 964 Robert Sandoval Powell 24 fcoleman@example.org Almacen 7072.541551 thomasscott@example.com 7000
996 972 Maria Ortiz Schmidt 80 hluna@example.org Almacen 4497.752108 jennifermoreno@example.com 7000
997 990 Jordan Jackson Higgins 56 lewisstephen@example.net Almacen 3609.770774 thomasjames@example.net 7000
998 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen 6099.027649 morrissean@example.net 7000
999 998 Todd Jenkins Anderson 28 lisaknight@example.com Almacen 2928.811436 clarkcharles@example.net 7000

1000 rows × 8 columns

Vemos que los salarios están muy por debajo de los salarios mínimos por departamento, pero recordemos que los salarios de la columna Salario fueron generados de manera aleatoria.

Análisis¶

Podemos completar nuestro análisis respondiendo algunas preguntas:

  • Sueldo promedio por departamento:
In [40]:
df_union_interna_3.groupby('Dep')['Salario'].mean()
Out[40]:
Dep
Almacen       4973.031905
Comedor       5021.249703
Limpieza      4764.349917
Oficina       4953.526248
Seguridad     5211.811380
Transporte    4998.580920
Name: Salario, dtype: float64
  • Gasto total de sueldos por departamento:
In [41]:
df_union_interna_3.groupby('Dep')['Salario'].sum()
Out[41]:
Dep
Almacen       880226.647153
Comedor       868676.198629
Limpieza      728945.537253
Oficina       847052.988477
Seguridad     886007.934621
Transporte    779778.623530
Name: Salario, dtype: float64
In [42]:
# gasto total de sueldos
df_union_interna_3.groupby('Dep')['Salario'].sum().sum()
Out[42]:
4990687.929663037
  • Distribución de la edad
In [45]:
ax = df_union_interna_3['Edad'].plot(kind='hist', x='Edad')

notamos que la edad de los trabajadores está equilibrada. Podemos ver la distribución de la edad para el departamento de Seguridad:

In [48]:
mascara = df_union_interna_3['Dep'] == 'Seguridad'
ax = df_union_interna_3[mascara]['Edad'].plot(kind='hist', x='Edad')
  • Número de trabajadores por departamento
In [49]:
# en este caso seleccionaremos cualquier columna (['ID'])
df_union_interna_3.groupby('Dep')['ID'].count()
Out[49]:
Dep
Almacen       177
Comedor       173
Limpieza      153
Oficina       171
Seguridad     170
Transporte    156
Name: ID, dtype: int64

Notamos que, gracias a la primer unión interna, pudimos obtener resultados y conclusiones referentes a los salarios de los empleados, lo cual muestra la gran utilidad de éstas.

Tipos de uniones ¶

La única unión que hemos visto hasta el momento es la unión interna. Lo que haremos a continuación será ver otro tipo de uniones.

Unión izquierda y derecha¶

De nuevo, consideremos dos dataframes y veámoslos como conjuntos. Recordemos que la unión interna puede verse como:

In [56]:
# tamanio (largo por ancho) del grafico
plt.figure(figsize=(8,6))

# diagrama
v = venn2((1,1,1), set_labels=('A', 'B'), alpha=0.8)

# Colores
v.get_patch_by_id('10').set_color('#DCDCDC')
v.get_patch_by_id('11').set_color('#FF0202')
v.get_patch_by_id('01').set_color('#797979')
# Etiquetas
v.get_label_by_id('10').set_text("")
v.get_label_by_id('11').set_text("")
v.get_label_by_id('01').set_text("")

plt.title('Unión interna', size=18)
plt.show()

Luego, en la unión izquierda daremos prioridad a los elementos del dataframe de la izquierda (conjunto A), es decir, consideraremos a todos los elementos del conjunto A. Además, consideraremos a los elementos del conjunto B, pero solo a aquellos que se encuentren también en el conjunto A. Gráficamente:

In [87]:
# tamanio (largo por ancho) del grafico
plt.figure(figsize=(8,6))

# diagrama
v = venn2((1,1,1), set_labels=('A', 'B'), alpha=0.8)

# Colores
v.get_patch_by_id('10').set_color('#FF0202')
v.get_patch_by_id('11').set_color('#FF0202')
v.get_patch_by_id('01').set_color('#797979')
# Etiquetas
v.get_label_by_id('10').set_text(" "*5 + "Unión izquierda o")
v.get_label_by_id('11').set_text("left join" + " "*35)
v.get_label_by_id('01').set_text("")

plt.title('Unión izquierda', size=18)
plt.show()

A pesar que en el gráfico anterior parece que solo estamos contemplando al conjunto A, recordemos que el conjunto B tiene información adicional, de modo que en la unión izquierda tendremos a los elementos de A y a los de B que también se encuentran en A, pero adicionalmente los elementos de B tiene información que no se encuentra en el conjunto A.

Por ejemplo, recordemos que el dataframe df_empleados tiene dos filas más que df_salario

In [64]:
print(df_empleados.shape)
print(df_salario.shape)
(1002, 5)
(1000, 3)

Y recordemos que cuando hicimos la unión interna no se consideraron a las dos filas adicionales del dataframe df_empleados

In [67]:
df_union_interna.shape
Out[67]:
(1000, 6)

Ahora bien, lo que haremos será realizar una unión izquierda entre df_empleados y df_salario, con lo cual consideramos en la unión a todos los elementos del primer conjunto o primer dataframe, de donde las dos filas extra de df_empleados ahora sí serán consideradas en el dataframe resultante de dicha unión. En efecto:

In [71]:
# Union izquierda
df_left_join = df_empleados.merge(df_salario, on='ID',
                                  suffixes=('_personal', '_compañia'),
                                  # Especificamos el tipo
                                  # de union con how=.
                                  # Union izquierda
                                  how='left')
df_left_join.shape
Out[71]:
(1002, 7)

Notamos que el número de filas de df_left_join es de 1002, igual al número de filas de df_salario.

In [72]:
# Veamos que
df_left_join.tail()
Out[72]:
ID Nombre Edad Correo_personal Dep Salario Correo_compañia
997 998 Todd Jenkins Anderson 28 lisaknight@example.com Almacen 2928.811436 clarkcharles@example.net
998 999 Kimberly Mitchell Long 65 ejohnson@example.org Seguridad 7046.036444 jsmith@example.net
999 1000 Andrea Carson Liu 48 emilysantos@example.net Oficina 7518.045080 perkinsmonica@example.net
1000 1001 Diana Smith 27 diana27182@edu.com Oficina NaN NaN
1001 1002 Luka Ldresh 20 lukaecdf@gmail.com Transporte NaN NaN

Dado que para los dos últimos empleados no tenemos información en el dataframe df_salario se ha colocado NaN en las columnas Salario y Correo_compañia. EL punto es que con esta unión daremos prioridad al dataframe de la izquierda.

Gracias a lo anterior podríamos ver cuáles empleados aún no tienen su información registrada en el dataframe df_salario.

In [78]:
# Detectamos a los empleados que aun no tienen su informacion
# registrada en el dataframe df_salario
df_left_join[df_left_join.Salario.isnull()][['ID','Nombre']]
Out[78]:
ID Nombre
1000 1001 Diana Smith
1001 1002 Luka Ldresh

De manera totalmente análoga tenemos el concepto de unión derecha:

In [86]:
# tamanio (largo por ancho) del grafico
plt.figure(figsize=(8,6))

# diagrama
v = venn2((1,1,1), set_labels=('A', 'B'), alpha=0.8)

# Colores
v.get_patch_by_id('10').set_color('#797979')
v.get_patch_by_id('11').set_color('#FF0202')
v.get_patch_by_id('01').set_color('#FF0202')
# Etiquetas
v.get_label_by_id('10').set_text("")
v.get_label_by_id('11').set_text(" "*20 + "Unión derecha")
v.get_label_by_id('01').set_text(" o right join" + " "*22)

plt.title('Unión derecha', size=18)
plt.show()

donde si hicieramos una unión derecha entre df_empleados y df_salario no se considerarían las dos filas extra de los empleados en la unión, por lo cual el dataframe resultante tendría 1000 filas como el dataframe df_salario.

Ahora bien, la columna que relaciona a los dos dataframes puede que tenga distintos nombres. Hasta ahora la columna ha sido ID, la cual tiene el mismo nombre en los dataframes df_empleados y df_salario, pero puede ser que el nombre no sea el mismo. Por ejemplo, cambiemos el nombre de ID del dataframe df_salario por Id_s:

In [88]:
df_salario = df_salario.rename(columns={'ID': 'ID_s'})
df_salario.head()
Out[88]:
ID_s Salario Correo
0 1 2815.715914 bmora@example.com
1 2 8071.241496 sandrathompson@example.org
2 3 1406.213853 gibsonkevin@example.net
3 4 8799.843576 iharris@example.net
4 5 3627.843246 qreynolds@example.com

Luego, hagamos una unión derecha como sigue

In [89]:
# Union derecha
df_right_join = df_empleados.merge(df_salario, suffixes=('_personal', '_compañia'),
                                   # Especificamos el tipo
                                   # de union con how=.
                                   # Union derecha
                                   how='right',
                                   # (**)
                                   left_on='ID', right_on='ID_s')
df_right_join.head()
Out[89]:
ID Nombre Edad Correo_personal Dep ID_s Salario Correo_compañia
0 1 Mark Rodriguez Park 66 listephanie@example.net Transporte 1 2815.715914 bmora@example.com
1 2 Maria Patrick Foley 58 annawright@example.net Transporte 2 8071.241496 sandrathompson@example.org
2 3 Olivia Boyle King 18 alexander11@example.org Oficina 3 1406.213853 gibsonkevin@example.net
3 4 Kent Lloyd Thomas 72 rroberts@example.org Comedor 4 8799.843576 iharris@example.net
4 5 Connie Villa Meyer 28 micheal66@example.org Transporte 5 3627.843246 qreynolds@example.com
In [90]:
df_right_join.shape
Out[90]:
(1000, 8)

$(**)$: donde left_on y right_on nos permiten especificar a la unión la columna que relaciona a los dataframes. Tenemos que left_on es para especificar el nombre de la columna para el dataframe de la izquierda y right_on para el de la derecha. Cabe mencionar que, a pesar que los nombres de la columna de relación son distintos, esencialemente dichas columnas deben ser las mismas en el sentido del tipo de datos que almacenan y, justamente, la información que guardan, aunque como vemos en el ejemplo anterior, las columnas ID y ID_s no son exactamente iguales pues ID tiene dos elementos más que ID_s.

Unión externa¶

In [96]:
# tamanio (largo por ancho) del grafico
plt.figure(figsize=(8,6))

# diagrama
v = venn2((1,1,1), set_labels=('A', 'B'), alpha=0.8)

# Colores
v.get_patch_by_id('10').set_color('#FF0202')
v.get_patch_by_id('11').set_color('#FF0202')
v.get_patch_by_id('01').set_color('#FF0202')
# Etiquetas
v.get_label_by_id('10').set_text("")
v.get_label_by_id('11').set_text("")
v.get_label_by_id('01').set_text("")

plt.title('Unión externa', size=18)
plt.show()

La unión externa (outer join) es una unión propiamente conjuntista, es decir, la unión entre dos conjuntos coincide con ser una unión externa, donde todos los elementos de los conjuntos A y B son contemplados.

Básicamente puede entenderse como una unión izquierda y una derecha en simultáneo entre los dataframes involucrados. Antes de ver un ejemplo de unión externa veamos que:

In [105]:
# agregamos un nuevo departamento a df_dep
df_dep = df_dep.append({'Dep': 'Proveedores', 'Salario_min': 8000}, 
                        ignore_index=True)
df_dep
Out[105]:
Dep Salario_min
0 Seguridad 8000
1 Oficina 8000
2 Almacen 7000
3 Limpieza 6500
4 Comedor 6500
5 Transporte 7000
6 Proveedores 8000

de modo que ningún empleado de df_empleados trabaja, por ahora, en el nuevo departamento. Luego, agreguemos un nuevo empleado sin colocar departamento:

In [117]:
# empleado nuevo
emp3 = {'ID': 1003, 'Nombre': 'Donovan Cabello', 'Edad': 35, 'Correo': 'donovan31416@edu.com'}
# agregamos al dataframe
df_empleados = df_empleados.append(emp3, ignore_index=True)

Por otro lado

In [118]:
# Hacemos una union izquierda entre df_empleados y df_dep
df_left_join = df_empleados.merge(df_dep, on='Dep', how='left')
df_left_join.head() 
Out[118]:
ID Nombre Edad Correo Dep Salario_min
0 1 Mark Rodriguez Park 66 listephanie@example.net Transporte 7000.0
1 2 Maria Patrick Foley 58 annawright@example.net Transporte 7000.0
2 3 Olivia Boyle King 18 alexander11@example.org Oficina 8000.0
3 4 Kent Lloyd Thomas 72 rroberts@example.org Comedor 6500.0
4 5 Connie Villa Meyer 28 micheal66@example.org Transporte 7000.0
In [119]:
df_left_join.shape
Out[119]:
(1003, 6)

donde dimos prioridad al dataframe de la izquierda:

In [124]:
df_left_join.tail()
Out[124]:
ID Nombre Edad Correo Dep Salario_min
998 999 Kimberly Mitchell Long 65 ejohnson@example.org Seguridad 8000.0
999 1000 Andrea Carson Liu 48 emilysantos@example.net Oficina 8000.0
1000 1001 Diana Smith 27 diana27182@edu.com Oficina 8000.0
1001 1002 Luka Ldresh 20 lukaecdf@gmail.com Transporte 7000.0
1002 1003 Donovan Cabello 35 donovan31416@edu.com NaN NaN

Luego

In [120]:
# Hacemos una union derecha entre df_empleados y df_dep
df_right_join = df_empleados.merge(df_dep, on='Dep', how='right')
df_right_join.head() 
Out[120]:
ID Nombre Edad Correo Dep Salario_min
0 6.0 Mary Pierce Craig 54.0 smithveronica@example.com Seguridad 8000
1 21.0 Michael Ellis Crawford 30.0 ellen04@example.net Seguridad 8000
2 24.0 Kenneth Fisher Perez 18.0 christineclements@example.net Seguridad 8000
3 29.0 Benjamin Rivera Mcmillan 57.0 jcochran@example.com Seguridad 8000
4 32.0 Kristin White Watson 78.0 bowenmark@example.net Seguridad 8000
In [121]:
df_right_join.shape
Out[121]:
(1003, 6)
In [125]:
df_right_join.tail()
Out[125]:
ID Nombre Edad Correo Dep Salario_min
998 982.0 Andrew Alexander Giles 69.0 watsontyler@example.net Transporte 7000
999 994.0 Omar Hill Sullivan 75.0 tyler50@example.net Transporte 7000
1000 995.0 Kyle Palmer Ortiz 44.0 walterheidi@example.net Transporte 7000
1001 1002.0 Luka Ldresh 20.0 lukaecdf@gmail.com Transporte 7000
1002 NaN NaN NaN NaN Proveedores 8000

donde dimos prioridad al dataframe de la derecha, además, notamos que el último empleado que agregamos al dataframe df_empleados no es considerado en la unión derecha.

Finalmente, apliquemos una unión externa:

In [126]:
# Union externa
df_outer_join = df_empleados.merge(df_dep, on='Dep', how='outer')
df_outer_join.head() 
Out[126]:
ID Nombre Edad Correo Dep Salario_min
0 1.0 Mark Rodriguez Park 66.0 listephanie@example.net Transporte 7000.0
1 2.0 Maria Patrick Foley 58.0 annawright@example.net Transporte 7000.0
2 5.0 Connie Villa Meyer 28.0 micheal66@example.org Transporte 7000.0
3 22.0 Mary Payne Johnson 18.0 rdiaz@example.com Transporte 7000.0
4 36.0 Michael Owens Burns 27.0 ypowell@example.net Transporte 7000.0
In [127]:
df_outer_join.shape
Out[127]:
(1004, 6)
In [129]:
df_outer_join.tail()
Out[129]:
ID Nombre Edad Correo Dep Salario_min
999 990.0 Jordan Jackson Higgins 56.0 lewisstephen@example.net Almacen 7000.0
1000 992.0 Sara Rodgers Mitchell 60.0 xkrause@example.com Almacen 7000.0
1001 998.0 Todd Jenkins Anderson 28.0 lisaknight@example.com Almacen 7000.0
1002 1003.0 Donovan Cabello 35.0 donovan31416@edu.com NaN NaN
1003 NaN NaN NaN NaN Proveedores 8000.0

Vemos que en la unión externa todos los elementos de ambos dataframes fueron considerados.

Gracias al outer join podemos identificar a los elementos que solo están en el dataframe A o que solo están en el dataframe B, es decir, omitimos los elementos que están en A y B. Gráficamente

In [135]:
# tamanio (largo por ancho) del grafico
plt.figure(figsize=(8,6))

# diagrama
v = venn2((1,1,1), set_labels=('A', 'B'), alpha=0.8)

# Colores
v.get_patch_by_id('10').set_color('#FF0202')
v.get_patch_by_id('11').set_color('#FFFFFF')
v.get_patch_by_id('01').set_color('#FF0202')
# Etiquetas
v.get_label_by_id('10').set_text("Elementos que solo\n están en A")
v.get_label_by_id('11').set_text("No consideramos\n la\n intersección")
v.get_label_by_id('01').set_text("Elementos que solo\n están en B")

plt.show()

Por ejemplo a partir de la unión externa que realizamos antes podemos hallar a los empleados a los cuales aún no se les asigna departamento, asimismo, hallar los departamentos que aún no tienen empleado (¡Sí!, es un ejemplo bobo (¿qué miras bobo?) pero ilustrativo).

Para ello

In [137]:
# obtenemos los elementos que tienen valores nulos en ID,
# lo cual nos dara los departamentos que aun no tiene empleados.
# Obtenemos los elementos que tienen valores nulos en Dep,
# lo cual nos dara los empleados que aun no tienen 
# departamento asignado
mask = (df_outer_join['ID'].isnull()) | (df_outer_join['Dep'].isnull())

# Pasamos la mascara al dataframe de la union externa
df_outer_join[mask][['Nombre', 'Dep']]
Out[137]:
Nombre Dep
1002 Donovan Cabello NaN
1003 NaN Proveedores

Uniendo una tabla con ella misma¶

Este tipo de uniones se conocen como self joins y son utilizadas cuando tenemos al menos dos columnas, en el mismo dataframes, que están relacionadas de alguna manera. Por ejemplo, tomemos tres empleados por cada departamento

In [144]:
# recordemos que tenemos los distintos departamentes
# almacenados en una lista
dep
Out[144]:
['Seguridad', 'Oficina', 'Almacen', 'Limpieza', 'Comedor', 'Transporte']

Luego, con la función sample() podemos obtener una muestra aleatoria de un dataframe, por ejemplo, podemos obtener 5 empleados de manera aleatoria que pertenezcan al departamento de Oficina:

In [147]:
df_empleados[df_empleados.Dep == i].sample(5)
Out[147]:
ID Nombre Edad Correo Dep
734 735 Lori Spears Stokes 54 cmccarthy@example.net Transporte
97 98 Andrew Mora Collins 35 lisamiller@example.org Transporte
445 446 Katherine Rowland Austin 56 garrettrodney@example.net Transporte
885 886 Jason Robinson Bennett 31 gbarrera@example.net Transporte
67 68 Tiffany Phillips Smith 70 tlawson@example.com Transporte
In [154]:
# Luego, obtenemos dos empleados (sus ID's) de manera aleatoria
# por departamento y agregamos sus ID's a una lista vacia 
sup = []
for i in range(2):
    for i in dep:
        id_empleado_select = df_empleados[df_empleados.Dep == i].sample(10).iloc[0][0] 
        print(f'{i}. ID: {id_empleado_select}')
        sup.append((id_empleado_select, i))
Seguridad. ID: 652
Oficina. ID: 790
Almacen. ID: 992
Limpieza. ID: 10
Comedor. ID: 383
Transporte. ID: 734
Seguridad. ID: 182
Oficina. ID: 23
Almacen. ID: 739
Limpieza. ID: 386
Comedor. ID: 58
Transporte. ID: 995
In [155]:
# lista con los ID's de los empleados seleccionados por
# departamento
sup
Out[155]:
[(652, 'Seguridad'),
 (790, 'Oficina'),
 (992, 'Almacen'),
 (10, 'Limpieza'),
 (383, 'Comedor'),
 (734, 'Transporte'),
 (182, 'Seguridad'),
 (23, 'Oficina'),
 (739, 'Almacen'),
 (386, 'Limpieza'),
 (58, 'Comedor'),
 (995, 'Transporte')]

Lo que haremos es que los empleados seleccionados anteriormente serán supervisores de sus respectivos departamentos, luego, a cada uno de los empleados les asignaremos un supervisor y dicha información la agregaremos al dataframe df_empleados. Así

In [163]:
# ID de los de Seguridad
print(sup[0][0])
print(sup[6][0])
# ID de los de Oficina
print(sup[1][0])
print(sup[7][0])
# ID de los de Almacen
print(sup[2][0])
print(sup[8][0])
# ID de los de Limpieza
print(sup[3][0])
print(sup[9][0])
# ID de los de Comedor
print(sup[4][0])
print(sup[10][0])
# ID de los de Transporte
print(sup[5][0])
print(sup[11][0])
652
182
790
23
992
739
10
386
383
58
734
995
In [178]:
# Dependiendo del departamento del empleado,
# le asignaremos (de manera aleatoria) el ID de un supervisor
# del mismo departamento. Dichos ID los almacenamos
# en la siguiente lista
supervisor = []
for i in range(df_empleados.shape[0]):
    if df_empleados.iloc[i][4] == 'Seguridad':
        supervisor.append(random.choice([sup[0][0], sup[6][0]]))
    elif df_empleados.iloc[i][4] == 'Oficina':
        supervisor.append(random.choice([sup[1][0], sup[7][0]]))
    elif df_empleados.iloc[i][4] == 'Almacen':
        supervisor.append(random.choice([sup[2][0], sup[8][0]]))
    elif df_empleados.iloc[i][4] == 'Limpieza':
        supervisor.append(random.choice([sup[3][0], sup[9][0]]))
    elif df_empleados.iloc[i][4] == 'Comedor':
        supervisor.append(random.choice([sup[4][0], sup[10][0]]))
    else:
        supervisor.append(random.choice([sup[5][0], sup[11][0]]))
        
# Agregamos la columna nueva
df_empleados['Sup'] = supervisor
df_empleados.head()
Out[178]:
ID Nombre Edad Correo Dep Sup
0 1 Mark Rodriguez Park 66 listephanie@example.net Transporte 995
1 2 Maria Patrick Foley 58 annawright@example.net Transporte 995
2 3 Olivia Boyle King 18 alexander11@example.org Oficina 790
3 4 Kent Lloyd Thomas 72 rroberts@example.org Comedor 383
4 5 Connie Villa Meyer 28 micheal66@example.org Transporte 995

Ahora sí, con la información que tenemos podemos hacer un autojoin de modo que podamos ver el departamento al cual pertenece cada supervisor, lo cual conseguiremos gracias a que el ID y la columna Sup están relacionadas. Así, realizamos el autojoin considerando en left_on= la columna Sup y right_on= la columna ID pues querremos ligar los ID's de Sup con los ID's generales de la columna ID (puedes probar configurando left_on='ID' y right_on='Sup' y ver qué es lo que pasa).

In [181]:
df_self_join = df_empleados.merge(df_empleados, 
                                  left_on='Sup',
                                  right_on='ID',
                                  suffixes=('_df1', '_df2'))
df_self_join
Out[181]:
ID_df1 Nombre_df1 Edad_df1 Correo_df1 Dep_df1 Sup_df1 ID_df2 Nombre_df2 Edad_df2 Correo_df2 Dep_df2 Sup_df2
0 1 Mark Rodriguez Park 66 listephanie@example.net Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte 734
1 2 Maria Patrick Foley 58 annawright@example.net Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte 734
2 5 Connie Villa Meyer 28 micheal66@example.org Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte 734
3 22 Mary Payne Johnson 18 rdiaz@example.com Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte 734
4 39 Jesse Wells Johnson 64 mmorgan@example.com Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte 734
... ... ... ... ... ... ... ... ... ... ... ... ...
998 934 Scott Clark Barnes 19 hwilson@example.net Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen 739
999 941 Thomas Werner Brown 57 michelle11@example.net Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen 739
1000 955 Carlos Meyer Sullivan 66 yferguson@example.org Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen 739
1001 956 Daniel Gibson Jenkins 27 vcook@example.net Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen 739
1002 990 Jordan Jackson Higgins 56 lewisstephen@example.net Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen 739

1003 rows × 12 columns

In [185]:
# Seleccionamos solo una columnas para ver de mejor manera la informacion
df_self_join.iloc[:,[0,1,4,5,6,7,8,9,10]]
Out[185]:
ID_df1 Nombre_df1 Dep_df1 Sup_df1 ID_df2 Nombre_df2 Edad_df2 Correo_df2 Dep_df2
0 1 Mark Rodriguez Park Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte
1 2 Maria Patrick Foley Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte
2 5 Connie Villa Meyer Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte
3 22 Mary Payne Johnson Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte
4 39 Jesse Wells Johnson Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte
... ... ... ... ... ... ... ... ... ...
998 934 Scott Clark Barnes Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen
999 941 Thomas Werner Brown Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen
1000 955 Carlos Meyer Sullivan Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen
1001 956 Daniel Gibson Jenkins Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen
1002 990 Jordan Jackson Higgins Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen

1003 rows × 9 columns

con lo cual obtenemos lo que buscábamos, es decir, logramos ver a qué departamente pertenece cada supervisor. Veamos cuántas personas dirige cada supervisor, para lo cual agruparemos por nombre del supervisor y departamento, luego seleccionamos cualquier columna, digamos ID_df1 y finalmente realizamos el conteo:

In [189]:
df_self_join.groupby(['Nombre_df2', 'Dep_df2'])['ID_df1'].count()
Out[189]:
Nombre_df2                  Dep_df2   
Cheyenne Brown Jones        Oficina       98
Cynthia Martin Jones        Almacen       86
Erik Sanchez III Turner     Limpieza      68
Harry Coleman Phelps        Comedor       88
Jessica Cooper Pierce       Transporte    76
Karen Harding Warren        Seguridad     85
Kyle Palmer Ortiz           Transporte    82
Megan Woods Snow            Oficina       74
Michael Carroll PhD Jordan  Limpieza      85
Samuel Terry Boyer          Seguridad     85
Sara Rodgers Mitchell       Almacen       91
Steven Price Hall           Comedor       85
Name: ID_df1, dtype: int64

Por otro lado, notemos del dataframe que:

In [194]:
df_self_join.iloc[:,[0,1,4,5,7,8,10,-1]]
Out[194]:
ID_df1 Nombre_df1 Dep_df1 Sup_df1 Nombre_df2 Edad_df2 Dep_df2 Sup_df2
0 1 Mark Rodriguez Park Transporte 995 Kyle Palmer Ortiz 44 Transporte 734
1 2 Maria Patrick Foley Transporte 995 Kyle Palmer Ortiz 44 Transporte 734
2 5 Connie Villa Meyer Transporte 995 Kyle Palmer Ortiz 44 Transporte 734
3 22 Mary Payne Johnson Transporte 995 Kyle Palmer Ortiz 44 Transporte 734
4 39 Jesse Wells Johnson Transporte 995 Kyle Palmer Ortiz 44 Transporte 734
... ... ... ... ... ... ... ... ...
998 934 Scott Clark Barnes Almacen 992 Sara Rodgers Mitchell 60 Almacen 739
999 941 Thomas Werner Brown Almacen 992 Sara Rodgers Mitchell 60 Almacen 739
1000 955 Carlos Meyer Sullivan Almacen 992 Sara Rodgers Mitchell 60 Almacen 739
1001 956 Daniel Gibson Jenkins Almacen 992 Sara Rodgers Mitchell 60 Almacen 739
1002 990 Jordan Jackson Higgins Almacen 992 Sara Rodgers Mitchell 60 Almacen 739

1003 rows × 8 columns

Cada supervisor tiene, nuevamente, un supervisor. Por ejemplo, para la empleada Kyle Palmer Ortiz, su supervisor tiene el ID (Sup_df2) de 734

In [195]:
df_empleados[df_empleados.ID == 734]
Out[195]:
ID Nombre Edad Correo Dep Sup
733 734 Jessica Cooper Pierce 57 brucejennifer@example.net Transporte 995

De tal manera, podemos hacer una unión entre df_self_join y df_empleados para obtener los supervisores de los supervisores. Para ello realizaremos una unión izquierda, debido a que queremos priorizar los ID's (o elementos) referentes al dataframe df_self_join

In [201]:
df_left_join = df_self_join.merge(df_empleados, how='left', 
                                  left_on='Sup_df2',
                                  right_on='ID')
df_left_join
Out[201]:
ID_df1 Nombre_df1 Edad_df1 Correo_df1 Dep_df1 Sup_df1 ID_df2 Nombre_df2 Edad_df2 Correo_df2 Dep_df2 Sup_df2 ID Nombre Edad Correo Dep Sup
0 1 Mark Rodriguez Park 66 listephanie@example.net Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte 734 734 Jessica Cooper Pierce 57 brucejennifer@example.net Transporte 995
1 2 Maria Patrick Foley 58 annawright@example.net Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte 734 734 Jessica Cooper Pierce 57 brucejennifer@example.net Transporte 995
2 5 Connie Villa Meyer 28 micheal66@example.org Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte 734 734 Jessica Cooper Pierce 57 brucejennifer@example.net Transporte 995
3 22 Mary Payne Johnson 18 rdiaz@example.com Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte 734 734 Jessica Cooper Pierce 57 brucejennifer@example.net Transporte 995
4 39 Jesse Wells Johnson 64 mmorgan@example.com Transporte 995 995 Kyle Palmer Ortiz 44 walterheidi@example.net Transporte 734 734 Jessica Cooper Pierce 57 brucejennifer@example.net Transporte 995
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
998 934 Scott Clark Barnes 19 hwilson@example.net Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen 739 739 Cynthia Martin Jones 58 bowmankara@example.com Almacen 992
999 941 Thomas Werner Brown 57 michelle11@example.net Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen 739 739 Cynthia Martin Jones 58 bowmankara@example.com Almacen 992
1000 955 Carlos Meyer Sullivan 66 yferguson@example.org Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen 739 739 Cynthia Martin Jones 58 bowmankara@example.com Almacen 992
1001 956 Daniel Gibson Jenkins 27 vcook@example.net Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen 739 739 Cynthia Martin Jones 58 bowmankara@example.com Almacen 992
1002 990 Jordan Jackson Higgins 56 lewisstephen@example.net Almacen 992 992 Sara Rodgers Mitchell 60 xkrause@example.com Almacen 739 739 Cynthia Martin Jones 58 bowmankara@example.com Almacen 992

1003 rows × 18 columns

Luego, veamos cuántos empleados están bajo el mando de cada supervisor por departamento:

In [202]:
df_left_join.groupby(['Nombre', 'Dep'])['ID'].count()
Out[202]:
Nombre                   Dep       
Cheyenne Brown Jones     Oficina        98
Cynthia Martin Jones     Almacen        91
Erik Sanchez III Turner  Limpieza      153
Jessica Cooper Pierce    Transporte     82
Karen Harding Warren     Seguridad     170
Kyle Palmer Ortiz        Transporte     76
Megan Woods Snow         Oficina        74
Sara Rodgers Mitchell    Almacen        86
Steven Price Hall        Comedor       173
Name: ID, dtype: int64

En comparación con el primer nivel de supervisores

In [204]:
df_left_join.groupby(['Nombre_df2', 'Dep_df2'])['ID_df1'].count()
Out[204]:
Nombre_df2                  Dep_df2   
Cheyenne Brown Jones        Oficina       98
Cynthia Martin Jones        Almacen       86
Erik Sanchez III Turner     Limpieza      68
Harry Coleman Phelps        Comedor       88
Jessica Cooper Pierce       Transporte    76
Karen Harding Warren        Seguridad     85
Kyle Palmer Ortiz           Transporte    82
Megan Woods Snow            Oficina       74
Michael Carroll PhD Jordan  Limpieza      85
Samuel Terry Boyer          Seguridad     85
Sara Rodgers Mitchell       Almacen       91
Steven Price Hall           Comedor       85
Name: ID_df1, dtype: int64

De tal manera, con df_left_join.groupby(['Nombre', 'Dep'])['ID'].count() obtenemos cuántos supervisores están bajo el mando de los otros supervisores que aparecen en esa serie.

Exportando dataframe a csv¶

Los dataframes de pandas los podemos exportar como archivos, en particular, csv. Así, lo que haremos será exportar a csv los dataframes df_empleados, df_salario, df_dep utilizando la función to_csv() como sigue

In [212]:
# colocamos dentro del parentesis el nombre del archivo
# que le queremos dar
df_empleados.to_csv('Empleados.csv')
In [213]:
# Hacemos lo mismo con el resto de dataframes
df_salario.to_csv('Salario.csv')
df_dep.to_csv('Departamentos.csv')

Con lo hecho anteriormente podremos acceder a los dataframes anteriores desde otras notebooks. Por ejemplo, podemos cargar el archivo Salario.csv como ya sabemos

In [217]:
df = pd.read_csv('Salario.csv')
df
Out[217]:
Unnamed: 0 ID_s Salario Correo
0 0 1 2815.715914 bmora@example.com
1 1 2 8071.241496 sandrathompson@example.org
2 2 3 1406.213853 gibsonkevin@example.net
3 3 4 8799.843576 iharris@example.net
4 4 5 3627.843246 qreynolds@example.com
... ... ... ... ...
995 995 996 7529.199265 xbanks@example.org
996 996 997 904.769353 jamestyler@example.org
997 997 998 2928.811436 clarkcharles@example.net
998 998 999 7046.036444 jsmith@example.net
999 999 1000 7518.045080 perkinsmonica@example.net

1000 rows × 4 columns