Gráficos y visualización de datos en Python#

La visualización es una de las mejores herramientas disponibles en el análisis de datos, ya que es la mejor forma de resumen y representación de los datos para interpretarlos y conocer la relación que guardan entre ellos.

Python dispone de diferentes librerías de visualización. Lo importante es hacer uso de aquellos gráficos que representen el cruce de datos de la mejor forma. Esto permitirá que se entienda bien el análisis que se realiza.

Veamos las librerías más potentes y útiles para realizar gráficos en Python

Matplotlib#

# Vamos a instalar la librería (En Colab ya se encuentra instalada por defecto)
!pip install matplotlib
import matplotlib.pyplot as plt  # El módulo pyplot es el que contiene muchos
                                 # de los comandos importantes para graficar.
import numpy as np  # Numpy es la librería para trabajar con números facilmente

%matplotlib inline
# Esta linea es para que en los notebooks, las imaágenes se
# muestren en el mismo lugar dentro del notebook

Plot#

Este es el comando básico para graficar cualquier linea de puntos continua.

x = np.linspace(-4,4,20)# Creemos un vector de 20 elementos linealmente
                        # distribuidos desde -4 hasta 4
y=x**2                  # Elevamos cada elemento del vector x al cuadrado
z=x**3                  # Elevamos cada elemento del vector x al cubo

plt.plot(x, y, label="x^2 plot") # Graficamos el vector x contra el vector y
plt.scatter(x, y, label="muestras") # Graficamos el vector x contra el vector y
plt.plot(x, z, label="$x^3$", color="red") # graficamos x vs z
plt.xlim([-4,4]) # Establece límites del eje X solo se muestra desde -4 hasta 4
plt.ylim([-15, 15]) # Límites del eje y
plt.legend() # Muestra la leyenda
plt.grid(True) # Muestra una cuadrícula
plt.xlabel('eje X') # Añade el título al eje X
plt.ylabel('eje y') # Añade el título al eje Y
plt.title('Gráficas') # Añade el título al gráfico
plt.show() # Muestra el gráfico
_images/2195d4f672b0ee995855d0bea895ccc144530e3023d28271d88ed5b7cc150ac1.png

Scatter#

Este permite ver cada uno de los puntos de una serie o arreglo individualmente

x = np.linspace(-4,4,20)
y=x**2

plt.figure(figsize=(10,10)) # Este parámetro permite crear lienzos diferentes
                            # con un tamaño fijo para separar los gráficos.
plt.scatter(x, y)
plt.grid(True)
plt.xlabel('eje X')
plt.ylabel('eje y')
plt.title('Parabola')

plt.figure(figsize=(10,10)) # Esto crea un nuevo lienzo y todo lo que se le de
                            # plot en adelante se genera en un nuevo espacio.
plt.plot(x, x**3)
plt.grid(True)
plt.xlabel('eje X')
plt.ylabel('eje y')
plt.title('$x^3$')
Text(0.5, 1.0, '$x^3$')
_images/ba9b716640dfe71f31c5b561893ae322a44210be29728ed4d30171c0e3faa3a9.png _images/64b3dd9498f288835a362dab463fbc977a30e1d9147070ef227cb2e388ec24e4.png
## Veamos algunos parámetros para modificar nuestros gráficos

import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
plt.figure(figsize=(10,10))
x = np.linspace(-4,4,10)


plt.plot(x, x**2, color="black", linewidth=6)
plt.scatter(x, x**2, c="green", s=1000)


x_r = np.linspace(-4,4,100)
x_ruido = x_r**2 + (np.random.random(x_r.shape)-0.5)*4
plt.scatter(x_r,x_ruido, c="red", alpha=0.3)
plt.grid(True)
plt.xlabel('eje X')
plt.ylabel('eje y')
plt.title('Parabola')
Text(0.5, 1.0, 'Parabola')
_images/6eb68ff43a9c3336658c7316ccd92657872cf66ccb144404c2f09350ee28e97e.png
  • Color/c = permite seleccionar el color de la linea o de los puntos que se están graficando

  • Linewidth = Este permite modificar el grueso de la linea

  • s = Tamaño de los puntos que se grafican con scatter

  • alpha = Este parámetro me permite modificar la opacidad de los puntos

  • label = este es el nombre de cada linea dentro del gráfico, sirve para identificar cada trazo cuando se coloca un cuadro de leyenda.

Subplots#

Los subplots permiten en una misma figura, dividir el lienzo en secciones y a cada sección asignarle un gráfico diferente.

x=np.linspace(-4,4,100)
y=x
z=x**2
w=x**3

#plt.figure()
plt.subplot(1,3,1)
plt.plot(x,y)
plt.xlabel('ejex')
plt.ylabel('ejey')
plt.title('recta')
plt.grid()

#plt.figure()
plt.subplot(1,3,2)
plt.plot(x,z)
plt.xlabel('ejex')
plt.ylabel('ejey')
plt.title('parabola')
plt.grid()

#plt.figure()
plt.subplot(1,3,3)
plt.plot(x,w)
plt.xlabel('ejex')
plt.ylabel('ejey')
plt.title('cúbica')
plt.grid()
_images/a0a6487ceb930431e6c0080d2a242db40ccf95d8d0be5a1136626928bea32a72.png

Ejercicio#

Realiza el gráfico de los datos que se presentan a continuación. Asigna un título a la gráfica, a los ejes, y muestra el legend para los trazos

x = np.linspace(-2*np.pi,2*np.pi,100)
y = np.sin(x)
z = np.cos(x)
# Tu código va aquí

Pandas#

Pandas proporciona dentro de su librería componentes para realizar gráficos rápidos directamente de los DataFrames. Es una herramienta potente para el análisis y visualización de los datos que estemos manipulando.

También Colab tiene una opción de gráficos recomendados, puedes explorar algunas cosas directamente allí.

# Vamos a cargar algunos datos
import pandas as pd

df = pd.read_csv("https://raw.githubusercontent.com/BioAITeamLearning/IntroPython_2024_01_UAI/main/Data/Food_Preference.csv")
df.head()
Timestamp Participant_ID Gender Nationality Age Food Juice Dessert
0 2019/05/07 2:59:13 PM GMT+8 FPS001 Male Indian 24 Traditional food Fresh Juice Maybe
1 2019/05/07 2:59:45 PM GMT+8 FPS002 Female Indian 22 Western Food Carbonated drinks Yes
2 2019/05/07 3:00:05 PM GMT+8 FPS003 Male Indian 31 Western Food Fresh Juice Maybe
3 2019/05/07 3:00:11 PM GMT+8 FPS004 Female Indian 25 Traditional food Fresh Juice Maybe
4 2019/05/07 3:02:50 PM GMT+8 FPS005 Male Indian 27 Traditional food Fresh Juice Maybe

Pie (Gráfico de torta)#

Vamos a revisar algunos datos importantes, por ejemplo, la distribución de edad de las personas.

# Normalmente en pandas lo hariamos así:

df['Gender'].value_counts()
Gender
Female    165
Male      119
Name: count, dtype: int64
# Pero si queremos realizar un informe, esta opción no es las mejor...
# veamos como hacerlo con dos simples métodos de python

df['Gender'].value_counts().plot.pie(autopct='%1.1f%%')
# El parametro, autopct agrega el porcentaje automático de cada clase
<Axes: ylabel='count'>
_images/57d9e6b05c900a1d6a46c0d50504da6add62da51fe4fdadf05c9092bf03c05f6.png

Gráfico de barras#

df['Gender'].value_counts().plot.bar() # Con solo encadenar un método de plot.
<Axes: xlabel='Gender'>
_images/f99fa0a569dffd57827ffc004a712999c717b47296b8d5eebd4d911c36ccaaad.png
# Veamos la distribución de la columna nacionalidad en el dataset
df['Nationality'].value_counts().plot.bar()
<Axes: xlabel='Nationality'>
_images/145378df4197029fab7d8aad149e63289f4dc5a28406800aabe8f22c9a547f07.png

Visualizaciones combinadas pandas/matplotlib: Caso data Analytics#

# Importemos las librerías que necesitaremos
import pandas as pd
import matplotlib.pyplot as plt

Craga Preprocesamiento de la data#

df = pd.read_csv('https://raw.githubusercontent.com/BioAITeamLearning/IntroPython_2024_01_UAI/main/Data/Food_Preference.csv')
df = df.dropna() # Eliminamos los valores nulos
df.head()
Timestamp Participant_ID Gender Nationality Age Food Juice Dessert
0 2019/05/07 2:59:13 PM GMT+8 FPS001 Male Indian 24 Traditional food Fresh Juice Maybe
1 2019/05/07 2:59:45 PM GMT+8 FPS002 Female Indian 22 Western Food Carbonated drinks Yes
2 2019/05/07 3:00:05 PM GMT+8 FPS003 Male Indian 31 Western Food Fresh Juice Maybe
3 2019/05/07 3:00:11 PM GMT+8 FPS004 Female Indian 25 Traditional food Fresh Juice Maybe
4 2019/05/07 3:02:50 PM GMT+8 FPS005 Male Indian 27 Traditional food Fresh Juice Maybe

Manipulación de fechas#

Esta columna de Timestamp tiene un formato de fechas, pero no está casteado en tipo fecha, por lo que debemos decirle a pandas que lo trate como fechas para poder luego hacer algunas operaciones especiales para este tipo de datos.

df['Date'] = pd.to_datetime(df['Timestamp'])
<ipython-input-35-d0a3a53dbc13>:1: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format.
  df['Date'] = pd.to_datetime(df['Timestamp'])
df['Day'] = df.Date.dt.day
df.head()
Timestamp Participant_ID Gender Nationality Age Food Juice Dessert Date Day
0 2019/05/07 2:59:13 PM GMT+8 FPS001 Male Indian 24 Traditional food Fresh Juice Maybe 2019-05-07 14:59:13-08:00 7
1 2019/05/07 2:59:45 PM GMT+8 FPS002 Female Indian 22 Western Food Carbonated drinks Yes 2019-05-07 14:59:45-08:00 7
2 2019/05/07 3:00:05 PM GMT+8 FPS003 Male Indian 31 Western Food Fresh Juice Maybe 2019-05-07 15:00:05-08:00 7
3 2019/05/07 3:00:11 PM GMT+8 FPS004 Female Indian 25 Traditional food Fresh Juice Maybe 2019-05-07 15:00:11-08:00 7
4 2019/05/07 3:02:50 PM GMT+8 FPS005 Male Indian 27 Traditional food Fresh Juice Maybe 2019-05-07 15:02:50-08:00 7

Vamos a realizar gráficas con matplotlib.#

# Vamos a cambiar el estilo plano de nuestras gráficas
# En el siguiente enlace encuentras más estilos
# https://matplotlib.org/stable/gallery/style_sheets/style_sheets_reference.html

plt.style.use('ggplot')

Tipos de comida#

vamos a revisar como hacer un gráfico de barras para esta columna pero con matplotlib.

x_values = df['Food'].unique()
y_values = df['Food'].value_counts().tolist()
x_values
array(['Traditional food', 'Western Food'], dtype=object)
y_values
[234, 50]
plt.bar(x_values, y_values)
plt.show()
plt.close('all')
_images/e4131c3a8e3614cfb13ef40bf149f8dccd69e6f6caa26ef919de4ae7671ea6f0.png

Tipos de postre#

Veamos cómo modificar algunos parámetros de la imágen

x_values = df.Dessert.unique()
y_values = df.Dessert.value_counts().tolist()

plt.figure()    #Figura. Puede incluirse el tamaño con figsize
plt.bar(x_values, y_values)          #El gráfico
plt.title('Pedidos de postres')      #El título
ax = plt.subplot()                   #Axis
ax.set_xticks(x_values)             #Eje x
ax.set_xticklabels(x_values)        #Etiquetas del eje x
ax.set_xlabel('Petición de postre')  #Nombre del eje x
ax.set_ylabel('Volumen de peticiones')  #Nombre del eje y
plt.show()
plt.close('all')
_images/e29f1d842f0a3bf0af527837226561a14a4b165927fe4b82080c9da26edeab7d.png

Comida, Bebidas y postres#

Veamos como colocar varios gráficos en una sola imágen

plt.figure()    #Figura. Puede incluirse el tamaño con figsize
x_values1 = df.Food.unique()
y_values1 = df.Food.value_counts().tolist()
ax = plt.subplot(1, 3, 1)                   #Axis
plt.bar(x_values1, y_values1)          #El gráfico
plt.title('Pedidos de comida')      #El título
ax.set_xticks(x_values1)             #Eje x
ax.set_xticklabels(x_values1, rotation=60)        #Etiquetas del eje x
ax.set_xlabel('Tipos de comida')  #Nombre del eje x
ax.set_ylabel('Volumen de peticiones')  #Nombre del eje y

x_values2 = df.Juice.unique()
y_values2 = df.Juice.value_counts().tolist()
ax = plt.subplot(1, 3, 2)                   #Axis
plt.bar(x_values2, y_values2)          #El gráfico
plt.title('Pedidos de bebidas')      #El título
ax.set_xticks(x_values2)             #Eje x
ax.set_xticklabels(x_values2, rotation=60)        #Etiquetas del eje x
ax.set_xlabel('Tipos de bebida')  #Nombre del eje x
ax.set_ylabel('Volumen de peticiones')  #Nombre del eje y

x_values3 = df.Dessert.unique()
y_values3 = df.Dessert.value_counts().tolist()
ax = plt.subplot(1, 3, 3)                   #Axis
plt.bar(x_values3, y_values3)          #El gráfico
plt.title('Pedidos de postres')      #El título
ax.set_xticks(x_values3)             #Eje x
ax.set_xticklabels(x_values3, rotation=60)        #Etiquetas del eje x
ax.set_xlabel('Petición de postre')  #Nombre del eje x
ax.set_ylabel('Volumen de peticiones')  #Nombre del eje y

plt.subplots_adjust(wspace=2, bottom=0.3) # Ajuste de el espacio entre cada plot
plt.show()
plt.close('all')
_images/2445b5af32aa334fbd36d092f622407b88f276e43fd4a3370cbd54a31bb98adb.png

Ordenes por día#

Vamos a revisar como agrupar los datos del dataframe por la columna días y graficar información.

x_values5 = ['Martes', 'Miércoles', 'Jueves', 'Viernes']
y_values5 = df['Day'].value_counts().tolist()
x_values5
['Martes', 'Miércoles', 'Jueves', 'Viernes']
y_values5
[177, 76, 24, 7]
plt.figure(figsize=(8, 6))

ax = plt.subplot(1, 2, 1)
plt.plot(x_values5, y_values5, color='#a12424', linestyle='--', marker='o')
plt.title('Volumen de pedidos por día')
ax.set_xlabel('Días de la semana') #Nombre del eje x
ax.set_ylabel('Número de pedidos') #Nombre del eje y
for i, txt in enumerate(y_values5):  # Con esto podemos agregarle los valores especificos en el gráfico al lado de cada punto
    ax.annotate(txt, (x_values5[i], y_values5[i]))
_images/1430f52811952dbbed94ed359b26e3d85787bb11d0ec8ae789d1b91aebef486f.png
y_values6 = df[(df['Food'] == 'Traditional food')]['Day'].value_counts().tolist()
y_values7 = df[(df['Food'] == 'Western Food')]['Day'].value_counts().tolist()
y_values6
[137, 69, 21, 7]
y_values7
[40, 7, 3]
ax = plt.subplot(1, 2, 2)
plt.plot(x_values5, y_values6, color='#ee7b06', linestyle='--', marker='*')
plt.plot(y_values7, color='#ffdb00', linestyle='--', marker='s')
plt.title('Volumen de pedidos por día')
plt.legend(['Traditional food', 'Western food'], loc=1)
ax.set_xlabel('Días de la semana') #Nombre del eje x
ax.set_ylabel('Número de pedidos') #Nombre del eje y
for i, txt in enumerate(y_values6):
    ax.annotate(txt, (x_values5[i], y_values6[i]))

for i, txt in enumerate(y_values7):
    ax.annotate(txt, (x_values5[i], y_values7[i]))

plt.subplots_adjust(wspace=0.85, bottom=0.3)
plt.show()
plt.close('all')
_images/df3d2100dec4cb71511a754802040060d31cf2f818cae2042148b28da97b8915.png
plt.figure()
plt.subplot(1, 2, 1)
plt.plot(x_values5, y_values5)
plt.subplot(1, 2, 2)
plt.plot(x_values5, y_values6)
plt.plot(y_values7)
[<matplotlib.lines.Line2D at 0x79e7bd919fc0>]
_images/6fd4a679179641ec99f182a1827a89f211d45ed94366c8635f30dc09a2a35ed2.png

Edades#

Revisemos la distribución de edades, como la edad es continua, debemos encontrar una forma de agrupar la información.

df["Decade"] = pd.cut(df.Age, 10, labels=range(0,100,10)) # Permite cortar los datos de una serie y agruparlos por partes discretas
df.head()
Timestamp Participant_ID Gender Nationality Age Food Juice Dessert Date Day Decade
0 2019/05/07 2:59:13 PM GMT+8 FPS001 Male Indian 24 Traditional food Fresh Juice Maybe 2019-05-07 14:59:13-08:00 7 20
1 2019/05/07 2:59:45 PM GMT+8 FPS002 Female Indian 22 Western Food Carbonated drinks Yes 2019-05-07 14:59:45-08:00 7 10
2 2019/05/07 3:00:05 PM GMT+8 FPS003 Male Indian 31 Western Food Fresh Juice Maybe 2019-05-07 15:00:05-08:00 7 30
3 2019/05/07 3:00:11 PM GMT+8 FPS004 Female Indian 25 Traditional food Fresh Juice Maybe 2019-05-07 15:00:11-08:00 7 20
4 2019/05/07 3:02:50 PM GMT+8 FPS005 Male Indian 27 Traditional food Fresh Juice Maybe 2019-05-07 15:02:50-08:00 7 20
df.groupby("Decade").agg({"Age": "count"}).plot.bar()
<Axes: xlabel='Decade'>
_images/f805f700a97b30b8b9ee555671f934ea0a1cc8a16762e683b2fd7e07bcf1dc83.png
plt.hist(df.Age, bins=20) # Similar, pero sin tener que realizar tantos filtros
(array([ 4.,  6.,  6., 38., 56., 48., 34., 20., 37.,  6.,  8.,  5.,  5.,
         2.,  3.,  2.,  1.,  0.,  1.,  2.]),
 array([ 8. , 11.6, 15.2, 18.8, 22.4, 26. , 29.6, 33.2, 36.8, 40.4, 44. ,
        47.6, 51.2, 54.8, 58.4, 62. , 65.6, 69.2, 72.8, 76.4, 80. ]),
 <BarContainer object of 20 artists>)
_images/4a5a1ca0495e35c83cf577063390fbc02b0551a3fc15a3fc5034b31eb05aaf1e.png

Seaborn#

Seaborn es otra libreria de visualización que se integra con pandas para crear visualizaciones más intuitivas que matplotlib

Veamos algunas visualizaciones para el mismo set de datos

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns # Esta es la librería,en adelante tendrá el "apodo" de sns
# Tratamiento de datos que hicimos antes

df = pd.read_csv('https://raw.githubusercontent.com/BioAITeamLearning/IntroPython_2024_01_UAI/main/Data/Food_Preference.csv')
df = df.dropna() # Eliminamos los valores nulos
df.head()
Timestamp Participant_ID Gender Nationality Age Food Juice Dessert
0 2019/05/07 2:59:13 PM GMT+8 FPS001 Male Indian 24 Traditional food Fresh Juice Maybe
1 2019/05/07 2:59:45 PM GMT+8 FPS002 Female Indian 22 Western Food Carbonated drinks Yes
2 2019/05/07 3:00:05 PM GMT+8 FPS003 Male Indian 31 Western Food Fresh Juice Maybe
3 2019/05/07 3:00:11 PM GMT+8 FPS004 Female Indian 25 Traditional food Fresh Juice Maybe
4 2019/05/07 3:02:50 PM GMT+8 FPS005 Male Indian 27 Traditional food Fresh Juice Maybe
df['Date'] = pd.to_datetime(df['Timestamp'])
df['Day'] = df.Date.dt.day
df.head()
<ipython-input-77-ad6862c4cc05>:1: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format.
  df['Date'] = pd.to_datetime(df['Timestamp'])
Timestamp Participant_ID Gender Nationality Age Food Juice Dessert Date Day
0 2019/05/07 2:59:13 PM GMT+8 FPS001 Male Indian 24 Traditional food Fresh Juice Maybe 2019-05-07 14:59:13-08:00 7
1 2019/05/07 2:59:45 PM GMT+8 FPS002 Female Indian 22 Western Food Carbonated drinks Yes 2019-05-07 14:59:45-08:00 7
2 2019/05/07 3:00:05 PM GMT+8 FPS003 Male Indian 31 Western Food Fresh Juice Maybe 2019-05-07 15:00:05-08:00 7
3 2019/05/07 3:00:11 PM GMT+8 FPS004 Female Indian 25 Traditional food Fresh Juice Maybe 2019-05-07 15:00:11-08:00 7
4 2019/05/07 3:02:50 PM GMT+8 FPS005 Male Indian 27 Traditional food Fresh Juice Maybe 2019-05-07 15:02:50-08:00 7
sns.set_style("darkgrid") # Seleccionamos un estilo
# Más estilos en:  https://interactivechaos.com/es/manual/tutorial-de-seaborn/estilos-disponibles
sns.set_context("paper") # El contexto es para que la gráfica mejore la presentación y calidad dependiendo de donde la asignemos finalmente
#otras opciones: notebook, talk, poster
sns.set_palette("deep") # Seleccionamos la paleta de colores
# Más opciones en: https://seaborn.pydata.org/tutorial/color_palettes.html
# Gráfico de barras simple para edad vs edad
sns.barplot(x='Food', y='Age', data=df)
<Axes: xlabel='Food', ylabel='Age'>
_images/fff78d939bfd33b0c815275327a821ca89071753314c78e8b90cf95bc8016a06.png
# Usando hue para agregar por género y ci podría cambiarse de mean a sd
ax = sns.barplot(x='Food', y='Age', hue='Gender', data=df)
plt.title('Pedidos de comida por género')
Text(0.5, 1.0, 'Pedidos de comida por género')
_images/d118cc78be089721159079c489f47a1cd74cf816ec187ece0636f77d12e7f06d.png
# las barras verticales son las medidas de dispersión de las columnas y se
# pueden cambiar de mean que es por defecto a desv. std
ax = sns.barplot(x='Food', y='Age', hue='Gender', data=df, ci='sd')
plt.title('Pedidos de comida por género')
plt.show()
<ipython-input-99-6bc0bfea6118>:3: FutureWarning: 

The `ci` parameter is deprecated. Use `errorbar='sd'` for the same effect.

  ax = sns.barplot(x='Food', y='Age', hue='Gender', data=df, ci='sd')
_images/fed98f61897f86ce9ec6de985879e762ffeaf4f498da268ec29a21e159142e28.png
# Histogramas
sns.histplot(df['Age'],kde=True)

## Kde añade la estimación de densidad del histograma
<Axes: xlabel='Age', ylabel='Count'>
_images/ce872501273c1e74a73c08ae874edbbf4c23b0b548c1603a2e0f091c76f2267d.png
# Boxplot
sns.boxplot(x='Food', y='Age', data=df)
<Axes: xlabel='Food', ylabel='Age'>
_images/cfbbffeedef85d9d7493ecbead2296a727fcaccde119f883044550136fc840d8.png
#Violinplot
sns.violinplot(data=df, x='Gender', y='Age')
<Axes: xlabel='Gender', ylabel='Age'>
_images/5f63e98ba1a8238e53e1c28fe9fbe925feda8974506f5d8612f662dc7a7c4cde.png
# Graficos de datos categoricos
sns.catplot(x='Gender', y='Age', data=df, kind='strip')
<seaborn.axisgrid.FacetGrid at 0x79e7b750ef20>
_images/9f8becc0595fc072afb3949ce8031fccb6da894676d8253296643911a3d54861.png
# Agregando una caracterización del color de los puntos
sns.catplot(x='Day', y='Age', hue='Gender', kind="swarm", data=df)
/usr/local/lib/python3.10/dist-packages/seaborn/categorical.py:3398: UserWarning: 13.0% of the points cannot be placed; you may want to decrease the size of the markers or use stripplot.
  warnings.warn(msg, UserWarning)
/usr/local/lib/python3.10/dist-packages/seaborn/categorical.py:3398: UserWarning: 18.6% of the points cannot be placed; you may want to decrease the size of the markers or use stripplot.
  warnings.warn(msg, UserWarning)
<seaborn.axisgrid.FacetGrid at 0x79e7b73ed3f0>
_images/9833da2ac9144af78db1b9f7617ab31f9961fdda61b867f732094d7fc2f4a107.png

Gráficas linea o de series de tiempo#

Los gráficos de líneas muestran cambios a lo largo del tiempo para una entidad. Con nuestro conjunto de datos, un gráfico de líneas podría usarse para mostrar la tendencia de despidos durante el último año o dos. Esto depende de lo que estés tratando de comunicar, pero trabajaremos con un análisis de un año.

import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

df = pd.read_csv("https://raw.githubusercontent.com/fivethirtyeight/data/master/college-majors/recent-grads.csv")
df.head()
Rank Major_code Major Total Men Women Major_category ShareWomen Sample_size Employed ... Part_time Full_time_year_round Unemployed Unemployment_rate Median P25th P75th College_jobs Non_college_jobs Low_wage_jobs
0 1 2419 PETROLEUM ENGINEERING 2339.0 2057.0 282.0 Engineering 0.120564 36 1976 ... 270 1207 37 0.018381 110000 95000 125000 1534 364 193
1 2 2416 MINING AND MINERAL ENGINEERING 756.0 679.0 77.0 Engineering 0.101852 7 640 ... 170 388 85 0.117241 75000 55000 90000 350 257 50
2 3 2415 METALLURGICAL ENGINEERING 856.0 725.0 131.0 Engineering 0.153037 3 648 ... 133 340 16 0.024096 73000 50000 105000 456 176 0
3 4 2417 NAVAL ARCHITECTURE AND MARINE ENGINEERING 1258.0 1123.0 135.0 Engineering 0.107313 16 758 ... 150 692 40 0.050125 70000 43000 80000 529 102 0
4 5 2405 CHEMICAL ENGINEERING 32260.0 21239.0 11021.0 Engineering 0.341631 289 25694 ... 5180 16697 1672 0.061098 65000 50000 75000 18314 4440 972

5 rows × 21 columns

df.plot(x="Rank", y=["P25th", "Median", "P75th"]) # Esta es la forma más fácil de visualizar datos en series de tiempo.
<Axes: xlabel='Rank'>
_images/270a08139b3184d74e0fb4f042f4989ffe67719044e3b4b936f7af73f95e3c16.png

Manejo de series de tiempo#

Manipulación del DF para ajustar fechas#

# Podrás cargar distintos dataset en nuevas variables
d = pd.read_csv("https://raw.githubusercontent.com/BioAITeamLearning/IntroPython_2024_01_UAI/main/Data/comptagevelo2009.csv")
d.head(18)
Date Unnamed: 1 Berri1 Maisonneuve_1 Maisonneuve_2 Brébeuf
0 01/01/2009 00:00 29 20 35 NaN
1 02/01/2009 00:00 19 3 22 NaN
2 03/01/2009 00:00 24 12 22 NaN
3 04/01/2009 00:00 24 8 15 NaN
4 05/01/2009 00:00 120 111 141 NaN
5 06/01/2009 00:00 261 146 236 NaN
6 07/01/2009 00:00 60 33 80 NaN
7 08/01/2009 00:00 24 14 14 NaN
8 09/01/2009 00:00 35 20 32 NaN
9 10/01/2009 00:00 81 45 79 NaN
10 11/01/2009 00:00 318 160 306 NaN
11 12/01/2009 00:00 105 99 170 NaN
12 13/01/2009 00:00 168 94 172 NaN
13 14/01/2009 00:00 145 87 175 NaN
14 15/01/2009 00:00 131 90 172 NaN
15 16/01/2009 00:00 93 49 97 NaN
16 17/01/2009 00:00 25 13 20 NaN
17 18/01/2009 00:00 52 22 55 NaN
# Convertir la columna Date, como los índices del dataframe
d.index = pd.to_datetime(d.Date, format="%d/%m/%Y")
d.index
DatetimeIndex(['2009-01-01', '2009-01-02', '2009-01-03', '2009-01-04',
               '2009-01-05', '2009-01-06', '2009-01-07', '2009-01-08',
               '2009-01-09', '2009-01-10',
               ...
               '2009-12-22', '2009-12-23', '2009-12-24', '2009-12-25',
               '2009-12-26', '2009-12-27', '2009-12-28', '2009-12-29',
               '2009-12-30', '2009-12-31'],
              dtype='datetime64[ns]', name='Date', length=365, freq=None)
del(d['Date']) # Eliminamos la columna Date
del(d["Unnamed: 1"]) # Eliminamos la columna Unnamed: 1
d.head()
Berri1 Maisonneuve_1 Maisonneuve_2 Brébeuf
Date
2009-01-01 29 20 35 NaN
2009-01-02 19 3 22 NaN
2009-01-03 24 12 22 NaN
2009-01-04 24 8 15 NaN
2009-01-05 120 111 141 NaN
# Renombrar todas las columnas
d.columns=["Berri", "Mneuve1", "Mneuve2", "Brebeuf"]
d.head()
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-01-01 29 20 35 NaN
2009-01-02 19 3 22 NaN
2009-01-03 24 12 22 NaN
2009-01-04 24 8 15 NaN
2009-01-05 120 111 141 NaN
for col in d.columns:
    print(col, np.sum(pd.isnull(d[col])))
Berri 0
Mneuve1 0
Mneuve2 0
Brebeuf 187
# Rellenar los valores de las columnas con la media
d.Brebeuf.fillna(d.Brebeuf.mean(), inplace=True)
# Reorganizar el dataframe, organizandolo por orden de fechas
d.sort_index(inplace=True)
d.head(20)
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-01-01 29 20 35 2576.359551
2009-01-02 19 3 22 2576.359551
2009-01-03 24 12 22 2576.359551
2009-01-04 24 8 15 2576.359551
2009-01-05 120 111 141 2576.359551
2009-01-06 261 146 236 2576.359551
2009-01-07 60 33 80 2576.359551
2009-01-08 24 14 14 2576.359551
2009-01-09 35 20 32 2576.359551
2009-01-10 81 45 79 2576.359551
2009-01-11 318 160 306 2576.359551
2009-01-12 105 99 170 2576.359551
2009-01-13 168 94 172 2576.359551
2009-01-14 145 87 175 2576.359551
2009-01-15 131 90 172 2576.359551
2009-01-16 93 49 97 2576.359551
2009-01-17 25 13 20 2576.359551
2009-01-18 52 22 55 2576.359551
2009-01-19 136 127 202 2576.359551
2009-01-20 147 85 176 2576.359551
# Visualizar la cantidad de elementos, ahora que ya se rellenó con la media, se tienen todos los 365 valores
d.count()
Berri      365
Mneuve1    365
Mneuve2    365
Brebeuf    365
dtype: int64
# Seleccionar datos para un rango de fechas
d.loc["2009-10-01":"2009-10-10"]
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-10-01 2643 22 3528 1588.0
2009-10-02 2865 36 3735 1906.0
2009-10-03 993 7 1362 705.0
2009-10-04 1336 0 1620 1116.0
2009-10-05 2935 308 3801 1773.0
2009-10-06 3852 7 4610 2211.0
2009-10-07 2115 3 2825 984.0
2009-10-08 3336 9 4146 1967.0
2009-10-09 1302 482 1686 663.0
2009-10-10 1407 725 1443 1003.0
# Organizar los datos según la columna Berri de menor a mayor
d.sort_values(by="Berri").head(100)
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-04-11 0 0 0 2576.359551
2009-03-31 0 0 0 2576.359551
2009-04-04 0 0 0 2576.359551
2009-04-05 0 0 0 2576.359551
2009-04-06 0 0 0 2576.359551
... ... ... ... ...
2009-12-22 207 107 353 0.000000
2009-01-27 209 119 221 2576.359551
2009-02-13 209 127 222 2576.359551
2009-12-18 214 119 373 0.000000
2009-12-10 219 110 363 0.000000

100 rows × 4 columns

# Organizar los datos según la columna Berri de mayor a menor (parámetro ascending=False), y localidar sólo en rango fechas
d.sort_values(by="Berri",ascending=False).loc["2009-10-01":"2009-10-10"]
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-10-06 3852 7 4610 2211.0
2009-10-08 3336 9 4146 1967.0
2009-10-05 2935 308 3801 1773.0
2009-10-02 2865 36 3735 1906.0
2009-10-01 2643 22 3528 1588.0
2009-10-07 2115 3 2825 984.0
2009-10-10 1407 725 1443 1003.0
2009-10-04 1336 0 1620 1116.0
2009-10-09 1302 482 1686 663.0
2009-10-03 993 7 1362 705.0

Métodos básicos de series de tiempo#

d.head(10)
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-01-01 29 20 35 2576.359551
2009-01-02 19 3 22 2576.359551
2009-01-03 24 12 22 2576.359551
2009-01-04 24 8 15 2576.359551
2009-01-05 120 111 141 2576.359551
2009-01-06 261 146 236 2576.359551
2009-01-07 60 33 80 2576.359551
2009-01-08 24 14 14 2576.359551
2009-01-09 35 20 32 2576.359551
2009-01-10 81 45 79 2576.359551
# El metodo rolling nos proporciona una ventana móvil para operaciones matemáticas. En este caso la media por los 10 primeros meses
d.rolling(2).mean().head(10)
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-01-01 NaN NaN NaN NaN
2009-01-02 24.0 11.5 28.5 2576.359551
2009-01-03 21.5 7.5 22.0 2576.359551
2009-01-04 24.0 10.0 18.5 2576.359551
2009-01-05 72.0 59.5 78.0 2576.359551
2009-01-06 190.5 128.5 188.5 2576.359551
2009-01-07 160.5 89.5 158.0 2576.359551
2009-01-08 42.0 23.5 47.0 2576.359551
2009-01-09 29.5 17.0 23.0 2576.359551
2009-01-10 58.0 32.5 55.5 2576.359551
# Asigna un valor una marca temporal al index
d.index = d.index + pd.Timedelta("5m")
d.head()
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-01-01 00:05:00 29 20 35 2576.359551
2009-01-02 00:05:00 19 3 22 2576.359551
2009-01-03 00:05:00 24 12 22 2576.359551
2009-01-04 00:05:00 24 8 15 2576.359551
2009-01-05 00:05:00 120 111 141 2576.359551
#Visualizamos el mismo rango de tiempo pero esta vez con el metodo .shift observamos este rango un año despues
d.shift(freq=pd.Timedelta(days=365)).head()
Berri Mneuve1 Mneuve2 Brebeuf
Date
2010-01-01 00:05:00 29 20 35 2576.359551
2010-01-02 00:05:00 19 3 22 2576.359551
2010-01-03 00:05:00 24 12 22 2576.359551
2010-01-04 00:05:00 24 8 15 2576.359551
2010-01-05 00:05:00 120 111 141 2576.359551

Downsampling#

La reducción de muestreo, implica reducir el número de instancias en la clase mayoritaria. Esto se hace eliminando aleatoriamente las instancias de la clase mayoritaria hasta que la distribución de la clase esté más equilibrada.

#Visualizamos el primer mes con saltos temporales de 2 días y los primeros valores de esa fecha
d.resample(pd.Timedelta("2d")).first().head()
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-01-01 29 20 35 2576.359551
2009-01-03 24 12 22 2576.359551
2009-01-05 120 111 141 2576.359551
2009-01-07 60 33 80 2576.359551
2009-01-09 35 20 32 2576.359551
# Por otro lado utilizando .mean() podemos observar el valor promedio obtenido entre los datos pero con la particularidad de que estos han sido realizados con el valor del día siguiente
d.resample(pd.Timedelta("2d")).mean().head()
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-01-01 24.0 11.5 28.5 2576.359551
2009-01-03 24.0 10.0 18.5 2576.359551
2009-01-05 190.5 128.5 188.5 2576.359551
2009-01-07 42.0 23.5 47.0 2576.359551
2009-01-09 58.0 32.5 55.5 2576.359551

UpSampling#

El sobremuestreo implica aumentar el número de instancias de la clase minoritaria para equilibrar el conjunto de datos. Por lo general, esto se hace replicando aleatoriamente instancias de la clase minoritaria o generando muestras sintéticas utilizando técnicas como SMOTE (Técnica de sobremuestreo de minorías sintéticas). o como en este caso usando en metodo pad de .fillna que permite rellenar los valores faltantes tipo Nan

#Usamos resample para generar un intervalo de 12 horas en las misma fecha, duplicando así la cantidad de datos, sin embargo encontramos que estan vacios
d.resample(pd.Timedelta("12h")).first().head()
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-01-01 00:00:00 29.0 20.0 35.0 2576.359551
2009-01-01 12:00:00 NaN NaN NaN NaN
2009-01-02 00:00:00 19.0 3.0 22.0 2576.359551
2009-01-02 12:00:00 NaN NaN NaN NaN
2009-01-03 00:00:00 24.0 12.0 22.0 2576.359551
#Para generar el duplicado de los datos con el intervalo de tiempo, sin que haya una ausencia de los datos, realizamos un rellenado con de estas con el metodo fillna
#fillna cuenta con un parametro llamado method que nos permite establecer el metodo de llenado, en este caso usamos 'pad' de Padding, se rellenan los valores faltantes utilizando el último valor válido conocido
d.resample(pd.Timedelta("12h")).first().fillna(method="pad").head()
Berri Mneuve1 Mneuve2 Brebeuf
Date
2009-01-01 00:00:00 29.0 20.0 35.0 2576.359551
2009-01-01 12:00:00 29.0 20.0 35.0 2576.359551
2009-01-02 00:00:00 19.0 3.0 22.0 2576.359551
2009-01-02 12:00:00 19.0 3.0 22.0 2576.359551
2009-01-03 00:00:00 24.0 12.0 22.0 2576.359551

Visualización#

# Graficar usando plot, se grafican las variables numéricas, donde el eje X corresponde al índice con las fechas
d.plot(figsize=(15,3))
<Axes: xlabel='Date'>
_images/814b90a4b8da78381640984e16f2bc383a8be95dc5acfd820a6c7645f5fb3ce6.png
# Graficas sólo una columna (Berri)
d.Berri.plot()
<Axes: xlabel='Date'>
_images/6f2d884c6ea8804d398c69ce5521f73d1fd281b82fd9ce8c496cb436cbc02338.png
# Graficas la columna Berri de forma suma acumulada
d.Berri.cumsum().plot()
<Axes: xlabel='Date'>
_images/d0bace22f6618639d7fc4bd6ca09f5bb0d20a084b091db0416b3b814e363bf50.png
# Graficar puntos para datos de dos columnas
plt.scatter(d.Berri, d.Brebeuf)
<matplotlib.collections.PathCollection at 0x79e7b68a7c10>
_images/9f18e3ddef4835ccfb8d64e0a428fbdfd5d22c3b607050efab5f86a5ecda1174.png
# Graficar puntos todas las columnas contra todas, la diagonal principal corresponde al histograma
pd.plotting.scatter_matrix(d, figsize=(15,15))
array([[<Axes: xlabel='Berri', ylabel='Berri'>,
        <Axes: xlabel='Mneuve1', ylabel='Berri'>,
        <Axes: xlabel='Mneuve2', ylabel='Berri'>,
        <Axes: xlabel='Brebeuf', ylabel='Berri'>],
       [<Axes: xlabel='Berri', ylabel='Mneuve1'>,
        <Axes: xlabel='Mneuve1', ylabel='Mneuve1'>,
        <Axes: xlabel='Mneuve2', ylabel='Mneuve1'>,
        <Axes: xlabel='Brebeuf', ylabel='Mneuve1'>],
       [<Axes: xlabel='Berri', ylabel='Mneuve2'>,
        <Axes: xlabel='Mneuve1', ylabel='Mneuve2'>,
        <Axes: xlabel='Mneuve2', ylabel='Mneuve2'>,
        <Axes: xlabel='Brebeuf', ylabel='Mneuve2'>],
       [<Axes: xlabel='Berri', ylabel='Brebeuf'>,
        <Axes: xlabel='Mneuve1', ylabel='Brebeuf'>,
        <Axes: xlabel='Mneuve2', ylabel='Brebeuf'>,
        <Axes: xlabel='Brebeuf', ylabel='Brebeuf'>]], dtype=object)
_images/6b94c1bd6c594ad65a82a40694b604bec1f84f254212b79d984d3dad5c48844b.png

Reto:#

Como un ejemplo más detallado de trabajo con datos de series temporales, echemos un vistazo a los conteos de bicicletas en el Puente Fremont de Seattle. Estos datos provienen de un contador automático de bicicletas instalado a finales de 2012, que tiene sensores inductivos en las aceras este y oeste del puente. Los conteos horarios de bicicletas se pueden descargar desde http://data.seattle.gov; el conjunto de datos del Contador de Bicicletas del Puente Fremont está disponible en la categoría de Transporte.

Vamos a realizar algunas operaciones con los datos ..

data = pd.read_csv('https://raw.githubusercontent.com/jakevdp/bicycle-data/main/FremontBridge.csv', index_col='Date', parse_dates=True)
data.columns = ['Total', 'East', 'West']
data.head()
<ipython-input-144-220a674f8aa6>:1: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`. To ensure parsing is consistent and as-expected, please specify a format.
  data = pd.read_csv('https://raw.githubusercontent.com/jakevdp/bicycle-data/main/FremontBridge.csv', index_col='Date', parse_dates=True)
Total East West
Date
2019-11-01 00:00:00 12.0 7.0 5.0
2019-11-01 01:00:00 7.0 0.0 7.0
2019-11-01 02:00:00 1.0 0.0 1.0
2019-11-01 03:00:00 6.0 6.0 0.0
2019-11-01 04:00:00 6.0 5.0 1.0
data.dropna().describe()
Total East West
count 147255.000000 147255.000000 147255.000000
mean 110.341462 50.077763 60.263699
std 140.422051 64.634038 87.252147
min 0.000000 0.000000 0.000000
25% 14.000000 6.000000 7.000000
50% 60.000000 28.000000 30.000000
75% 145.000000 68.000000 74.000000
max 1097.000000 698.000000 850.000000
  1. Grafica los datos en bruto del conteo de bicicletas. Asegúrate de etiquetar el eje Y con ‘Conteo de Bicicletas por Hora’.

# Tu codigo va acá
  1. Los datos horarios son demasiado densos. Remuestrea los datos a una cuadrícula semanal y grafica los conteos semanales. Usa estilos de línea diferentes para cada serie.

# Tu codigo va acá
  1. Remuestrea los datos a una cuadrícula diaria y grafica la suma diaria de los conteos de bicicletas. Asegúrate de etiquetar el eje Y con ‘Conteo de Bicicletas por Hora Promedio’.

# Tu codigo va acá
  1. Calcula y grafica la media móvil de 50 días de los datos de bicicletas utilizando una ventana gaussiana con un ancho de 10 días. Usa diferentes estilos de línea para cada serie.

# Tu codigo va acá
  1. Agrupa los datos por tiempo del día y grafica el tráfico promedio de bicicletas. Utiliza groupby y mean, y asegúrate de configurar las marcas de tiempo en el eje X.

# Tu codigo va acá
  1. Agrupa los datos por día de la semana y grafica el tráfico promedio de bicicletas para cada día. Ajusta las etiquetas del índice para representar los días de la semana.

# Tu codigo va acá
  1. Agrupa los datos para diferenciar entre días de semana y fines de semana, y luego calcula la media horaria de los conteos de bicicletas. Usa groupby y mean.

# Tu codigo va acá
  1. Utiliza Matplotlib para crear dos gráficos en paralelo que muestren los conteos horarios de bicicletas durante los días de semana y los fines de semana. Usa subplots y asegúrate de etiquetar los gráficos adecuadamente.

# Tu codigo va acá