Colaboratory logo Alternative text

Los datos hablan III#

En esta sección aprenderás a hacer un análisis de tus datos a través de gráficas y valores númericos de correlación de variables. Aprenderás a distinguir cuando una variable depende de otra de manera proporcional y cómo modelar matemáticamente dicha relación. En la última sección tienes ejercicios para que puedas practicar.

#Nota: Si esta trabajando en  Colab necesitarás instalar la Api de Makesens
#!pip install APIMakeSens
## Importamos las librerías
import random                      # Para números aleatorios
import pandas as pd                # Para conjuntos de datos
import numpy as np                 # Para cálculos matemáticos con matrices y vectores
import seaborn as sns              # Para visualización de datos estadistícos
import scipy as sp                 # Para cálculo estadistícos como regresión lineal
import matplotlib.pyplot as plt    # Para hacer gráficas
import matplotlib.dates as mdates  # Para manejar datos de fechas
from MakeSens import MakeSens      # Para descargar los datos de las estaciones
## Establecemos el estilo de grafico
lista_estilos = plt.style.available       # Lista de los estilos de graficos en python
tamaño = len(lista_estilos)               # Número de estilos en la lista
print(lista_estilos,tamaño)

## Seleccionamos un estilo aleatorio de la lista
N = np.random.randint(0,tamaño)           # Número aleatorio
plt.style.use(lista_estilos[N])           # Pedimos usar el estilo de la componente N de la lista
print('Estás usando el estilo: '+lista_estilos[N])
['Solarize_Light2', '_classic_test_patch', '_mpl-gallery', '_mpl-gallery-nogrid', 'bmh', 'classic', 'dark_background', 'fast', 'fivethirtyeight', 'ggplot', 'grayscale', 'seaborn-v0_8', 'seaborn-v0_8-bright', 'seaborn-v0_8-colorblind', 'seaborn-v0_8-dark', 'seaborn-v0_8-dark-palette', 'seaborn-v0_8-darkgrid', 'seaborn-v0_8-deep', 'seaborn-v0_8-muted', 'seaborn-v0_8-notebook', 'seaborn-v0_8-paper', 'seaborn-v0_8-pastel', 'seaborn-v0_8-poster', 'seaborn-v0_8-talk', 'seaborn-v0_8-ticks', 'seaborn-v0_8-white', 'seaborn-v0_8-whitegrid', 'tableau-colorblind10'] 28
Estás usando el estilo: tableau-colorblind10
#plt.style.use('seaborn-v0_8-ticks')

1. Recopilación de datos#

Primero recopilamos los datos y los guardamos en una variable DataFrame a la que llamaremos data.

Nota: Cuando la columna de índices se convierte a una variable Datetime python puede leer más rápido los datos y esto permite que podamos hacer graficas sin que transcurra demasiado tiempo.

estacion = 'mE1_00007'           # Nombre estacion miniEva
start = '2023-04-30 00:00:00'    # Fecha de inicio: año-mes-día hora:minuto:segundo
end   = '2023-05-07 23:00:00'    # Fecha de fin:    año-mes-día hora:minuto:segundo

data = MakeSens.download_data(estacion, start, end,'1T') # Descargar los datos
data.index = pd.DatetimeIndex(data.index)               # Convertir a tipo datetime el índice

Nota: data.head() nos permite hechar un vistazo a las columnas de datos que tenemos. En total tenemos 32 columnas, en este enlace puedes conocer acerca de cada una de ellas: Campo de datos

data.head(5)
ts humedad humedad2 iluminancia pm10_1 pm10_1_AE pm10_2 pm10_2_AE pm1_1 pm1_1_AE ... pm_n_10_0_2 pm_n_1_0_1 pm_n_1_0_2 pm_n_2_5_1 pm_n_2_5_2 pm_n_5_0_1 pm_n_5_0_2 presion temperatura temperatura2
1970-01-01 00:00:00.000000000 2023-04-30 00:00:00 61.184834 56.005188 9.0 14.0 14.0 15.0 15.0 11.0 11.0 ... 0.0 71.0 66.0 6.0 4.0 0.0 2.0 919.924316 30.379448 31.838713
1970-01-01 00:00:00.000000001 2023-04-30 00:02:00 61.408108 56.545357 9.0 19.0 19.0 17.0 17.0 12.0 12.0 ... 0.0 122.0 113.0 4.0 6.0 0.0 0.0 919.008606 30.414761 31.812008
1970-01-01 00:00:00.000000002 2023-04-30 00:04:00 62.050137 57.218281 9.0 15.0 15.0 22.0 22.0 12.0 12.0 ... 0.0 93.0 122.0 2.0 10.0 0.0 4.0 919.394836 30.404671 31.755932
1970-01-01 00:00:00.000000003 2023-04-30 00:06:00 62.739666 57.489891 9.0 36.0 36.0 36.0 36.0 29.0 25.0 ... 0.0 159.0 184.0 8.0 4.0 0.0 0.0 918.058350 30.460163 31.937515
1970-01-01 00:00:00.000000004 2023-04-30 00:08:00 62.585793 57.978180 9.0 23.0 23.0 20.0 20.0 16.0 16.0 ... 0.0 139.0 126.0 6.0 6.0 0.0 2.0 920.309998 30.374403 31.601053

5 rows × 31 columns

2. Limpieza de datos#

Para esta parte trabajaremos con los datos de material particulado: PM1, PM2.5 y PM10. Vamos a limpiar nuestro datos, pero primero, exploremos como se ven nuestros datos

## Listamos las columnas que deseamos limpiar
columnas = ['pm25_1','pm25_2','pm25_1_AE','pm25_2_AE','pm10_1','pm10_2','pm10_1_AE','pm10_2_AE', 'pm1_1', 'pm1_1_AE', 'pm1_2','pm1_2_AE']

## Hacemos una gráfica de datos sin limpieza
colors = ['blue','red','orange','cyan','purple','green','pink','blue','red','orange','cyan','purple'] # Lista de colores

plt.figure(figsize=(16,22))          # Creamos la figura

for i in range(len(columnas)):       # Íteramos entre las columnas de PM
    
    ax = plt.subplot(6,2,i+1)        # Graficamos en subfiguras
    
    plt.scatter(data.index,          # Datos eje x 
                data[columnas[i]],   # Datos eje y
                label=columnas[i],   # Etiqueta de los datos
                s=15,                # Tamaño de los puntos
                color=colors[i],     # Color de los puntos
                edgecolors='white')  # Color borde de los puntos
    
    plt.legend()  # Mostramos leyendas
    
    ax.xaxis.set_major_formatter(mdates.DateFormatter('%b-%d'))  # Cambiamos el formato de las fechas en el eje x
    ax.tick_params(which='major',      # Seleccionamos el eje mayor (eje x)
                    pad=5,             # Distanica de la etiqueta a los ticks
                    labelsize=10,      # Tamaño de letra de la etiqueta
                    direction="inout") # Pone a la barra de los ticks a la mita del eje x 
    
plt.show()        # Mostramos grafica 
../_images/46048a806360604cf66fcc58efb360ee776d42beabd18ee5df6646c0dd813f1b.png

Nota: El formato de fechas '%b-%d' significa que sobre el eje x las fechas están dispuestas en la forma mes-día. Dónde el mes esta puesto con su respectivo nombre abreviado. Otras posibilidades son: %m-%d para mes-día en formato decimal, %d-%m-%y para día-mes-año en formato decimal.

Directiva

Significado

Ejemplo

%w

Día de la semana como número decimal. 0 es domingo y 6 es sábado

0, …, 6

%d

Día del mes como número decimal

01, 02, …, 31

%m

Mes como número decimal

01, 02, …, 12

%y

Año sin las centenas como número decimal

00, 01, …, 99

%b

Mes como su nombre abreviado (en Inglés)

Jan, Feb, …, Dec

Tabla de formato de fechas

Como en la sección anterior (Los datos hablan II), vamos a eliminar los valores por encima de 120 \(\mu g/m^3\). Recuerda que elegimos 120 \(\mu g/m^3\) como un valor de referencia pero que esta sujeto a cambios. Por ejemplo los sensores de la estaciones pueden medir correctamente concentración de material particulado entre 0-500 \(\mu g/m^3\) [*], pero para Bucaramanga los valores típicos de PM suelen estar por debajo de 100 \(\mu g/m^3\) (Inferior a la alerta naranja).

## Hacemos limpieza de datos y volvemos a graficas nuentro datos
for columna in columnas:                                             # Iteramos entre cada columna
    data[columna] = data[columna].where(
                                        data[columna]<120,  # Condición para conservar el dato
                                        np.nan              # Valor por el que se reemplaza el dato si se incumple la condición anterior
                                        )   
    
## Graficamos los datos luego de la limpieza
plt.figure(figsize=(16,22))         # Creamos la figura

for i in range(len(columnas)):      # Iteramos entre las columnas de PM 
    
    ax = plt.subplot(6,2,i+1)       # Graficamos en subfiguras
    
    plt.scatter(data.index,         # Datos eje x
                data[columnas[i]],  # Datos eje y
                label=columnas[i],  # Etiqueta de los datos
                s=15,               # Tamaño de los puntos
                color=colors[i],    # Color de los puntos
                edgecolors='white') # Color del borde de los puntos
    
    plt.legend()  # Mostramos leyendas
    
    ax.xaxis.set_major_formatter(mdates.DateFormatter('%b-%d'))  #Formato para el eje x
    ax.tick_params(which='major',      # Seleccionamos el eje mayor (eje x)
                    pad=5,             # Distanica de la etiqueta a los ticks
                    labelsize=10,      # Tamaño de letra de la etiqueta
                    direction="inout") # Pone a la barra de los ticks a la mita del eje x 
    
plt.show()        # Mostramos grafica 
../_images/2b3253dc8cd9ee8df6140649d05e852c163dea9abfb2bd6a6038a0001de92990.png

3. Variación de datos entre sensores#

  • ¿Podemos promediar los datos del sensor 1 con los del sensor 2?

    Sí pero bajo ciertas condiciones. Necesitamos que los datos entre ambos sensores sean ceranos. El valor que mide la cercanía de datos es el coeficiente de variación (CV). Tomamos fila de datos y en ella promediamos el valor del sensor 1 y del sensor 2, calculamos el coeficiente CV en esta fila. Si el promedio del coeficiente de variación de una columna de datos es menor a CV<0.3, entonces es válido usar el promedio; caso contrario no lo es.

El coeficiente de variación es la división entre la desviación estándar y el promedio. Este mide qué tanto se dispersan los datos comparados con el valor medio.

# Cambiamos el nombre de las columnas de 'temperatura' y 'humedad' por 'temperatura1' y 'humedad1' para
# incluirlas en el análisi del coeficiente de variación
data = data.rename(index=str, columns={'temperatura':'temperatura1', 'humedad':'humedad1'})
data.columns  # Muestra las nuevas columnas
Index(['ts', 'humedad1', 'humedad2', 'iluminancia', 'pm10_1', 'pm10_1_AE',
       'pm10_2', 'pm10_2_AE', 'pm1_1', 'pm1_1_AE', 'pm1_2', 'pm1_2_AE',
       'pm25_1', 'pm25_1_AE', 'pm25_2', 'pm25_2_AE', 'pm_n_0_3_1',
       'pm_n_0_3_2', 'pm_n_0_5_1', 'pm_n_0_5_2', 'pm_n_10_0_1', 'pm_n_10_0_2',
       'pm_n_1_0_1', 'pm_n_1_0_2', 'pm_n_2_5_1', 'pm_n_2_5_2', 'pm_n_5_0_1',
       'pm_n_5_0_2', 'presion', 'temperatura1', 'temperatura2'],
      dtype='object')

Nota: Las variables que tienen dos valores de medición son: ‘pm25’, ‘pm10’, ‘pm1’, ‘humedad’ y ‘temperatura’. Calcularemos el coeficiente de variación para ellas.

## Definimos columnas de datos por separado
cols_sensor1 = ['pm25_1','pm25_1_AE','pm10_1','pm10_1_AE','pm1_1', 'pm1_1_AE', 'humedad1', 'temperatura1']
cols_sensor2 = ['pm25_2','pm25_2_AE','pm10_2','pm10_2_AE','pm1_2', 'pm1_2_AE', 'humedad2', 'temperatura2']
cols_new = ['pm25','pm25_AE','pm10','pm10_AE','pm1','pm1_AE','humedad','temperatura']

## Calculamos coeficiente de variación
mean = pd.DataFrame({})     # Promedios
std = pd.DataFrame({})      # Desviaciones estandar
CV = pd.DataFrame({})       # Coeficiente de variación
for i in range(len(cols_sensor1)):             # Iteramos entre cada columnas
    col1 = cols_sensor1[i]                     # Nombre primera columna (sensor 1)
    col2 = cols_sensor2[i]                     # Nombre segunda columna (sensor 2)
    mean[col1] = (data[col1] + data[col2])/2   # Promedio
    std[col1] = np.sqrt( (mean[col1]-data[col1])**2 + (mean[col1]-data[col2])**2 ) # Desviación estandar
    CV[col1] = std[col1]/mean[col1]            # Coeficiente de variación

## Analizamos si los promedios del coeficiente de variación son menores a 0.3
CV_mean = CV.mean()
for i in range(len(cols_sensor1)):
    col1 = cols_sensor1[i]       # Nombre primera columna (sensor 1)
    col2 = cols_sensor2[i]       # Nombre segunda columna (sensor 2)
    col_new = cols_new[i]        # Nombre columna nueva
    if CV_mean[col1]<0.3:         # Si CV_mean < 0.3 entonces guarda el promedio en el dataset
        data[col_new] = mean[col1]

Analicemos con detalle el los DataFrame del código:

mean.head()
pm25_1 pm25_1_AE pm10_1 pm10_1_AE pm1_1 pm1_1_AE humedad1 temperatura1
1970-01-01 00:00:00 14.0 14.0 14.5 14.5 11.0 11.0 58.595011 31.109080
1970-01-01 00:00:00.000000001 18.0 18.0 18.0 18.0 11.5 11.5 58.976732 31.113384
1970-01-01 00:00:00.000000002 17.5 17.5 18.5 18.5 13.0 13.0 59.634209 31.080302
1970-01-01 00:00:00.000000003 36.0 33.0 36.0 36.0 29.0 25.0 60.114778 31.198839
1970-01-01 00:00:00.000000004 21.0 21.0 21.5 21.5 15.0 15.0 60.281987 30.987728
std.head()
pm25_1 pm25_1_AE pm10_1 pm10_1_AE pm1_1 pm1_1_AE humedad1 temperatura1
1970-01-01 00:00:00 0.000000 0.000000 0.707107 0.707107 0.000000 0.000000 3.662563 1.031856
1970-01-01 00:00:00.000000001 1.414214 1.414214 1.414214 1.414214 0.707107 0.707107 3.438484 0.988003
1970-01-01 00:00:00.000000002 3.535534 3.535534 4.949747 4.949747 1.414214 1.414214 3.416638 0.955486
1970-01-01 00:00:00.000000003 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 3.712152 1.044646
1970-01-01 00:00:00.000000004 2.828427 2.828427 2.121320 2.121320 1.414214 1.414214 3.258074 0.867373
CV.head()
pm25_1 pm25_1_AE pm10_1 pm10_1_AE pm1_1 pm1_1_AE humedad1 temperatura1
1970-01-01 00:00:00 0.000000 0.000000 0.048766 0.048766 0.000000 0.000000 0.062506 0.033169
1970-01-01 00:00:00.000000001 0.078567 0.078567 0.078567 0.078567 0.061488 0.061488 0.058302 0.031755
1970-01-01 00:00:00.000000002 0.202031 0.202031 0.267554 0.267554 0.108786 0.108786 0.057293 0.030742
1970-01-01 00:00:00.000000003 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.061751 0.033483
1970-01-01 00:00:00.000000004 0.134687 0.134687 0.098666 0.098666 0.094281 0.094281 0.054047 0.027991
print(CV_mean)
pm25_1          0.087460
pm25_1_AE       0.083024
pm10_1          0.097723
pm10_1_AE       0.095782
pm1_1           0.075705
pm1_1_AE        0.070989
humedad1        0.059643
temperatura1    0.031303
dtype: float64

4. Correlación de datos#

Correlación lineal
#

El objetivo de esta sección es analizar el grado de la relación existente entre variables usando modelos matemáticos. Por ejemplo, ¿en qué medida un aummento de los gastos en publicidad hace aumentar las ventas de un determinado producto?, ¿cómo cambia la humudad con el aumento de la presión o temperatura? El estudio de relación diferente de una relación funcional entre dos variables se llama e análisis de correlación.

Definición:#

Tenemos la tarea de estudiar si existe o no algún tipo de relación entre dos variables. Así, por ejemplo, podemos preguntarnos si hay alguna relación entre la medición de \(PM2.5\) y la de \(PM10\). Una primera aproximación al problema consistiría en dibujar en el plano un punto por cada medición: la primera coordenada de cada punto sería la medición de \(PM2.5\), mientras que la segunda coordenada sería la medición respectiva de \(PM10\). Así, tendríamos una nube de puntos en la cual se haría evidente si existe algún tipo de relación (lineal, parabólica, exponensial, etc.) entre ambos PM. En partícular, queremos cuantificar que tan parecida a una línea recta es la nube de puntos de las dos variables. El parámetro que nos da tal cuantificación es el coeficiente de correlación lineal de Pearson, \(r\), cuyo valor oscila entre \(–1\) y \(+1\).

Para hacer correctamente la correlación de datos y evitar errores vamos a copiar el data_set y vamos a cambiar la columna de índices por una de números enteros.

data_ = data.copy()                  # Copiamos el dataset
data_ = data_[sorted(data_.columns)] # Ordenamos columnas

## Lista de columnas redundantes
lista_cols = ['pm_n_2_5_2','pm_n_2_5_1','pm_n_10_0_2', 'pm_n_10_0_1', 'pm_n_0_5_2','pm_n_0_3_2','pm_n_0_5_1', 'pm_n_0_3_1', 'pm_n_5_0_1', 'pm_n_5_0_2', 'pm_n_1_0_1', 'pm_n_1_0_2', 'latitud', 'longitud']

## Analizamos si hay columnas de promedio y las agregamos a lista_cols
for i, col in enumerate(cols_new):  # Iteramos sobre cada columna
    col1 = cols_sensor1[i]          # Nombre primera columna (sensor 1)
    col2 = cols_sensor2[i]          # Nombre segunda columna (sensor 2)
    if col in data.columns:         # Verificamos si col ya existe en el dataset
        lista_cols.append(col1)     # Agregamos col1 y col2 a la lista de columnas redundantes
        lista_cols.append(col2)
    else:
        data_[col] = data[col1]     # Creamos una nueva columna de nombre col, con los valores del sensor 1 por defecto

## Eliminamos columnas redundantes
for columna in lista_cols:          # Iteramos sobre cada columna redundante
    del data_[columna]

## Cambiamos la columna de índices por una de números enteros empezando desde 0
data_['index_new'] = np.arange(0,len(data_.index))  # Arreglo de números enteros desde 0 hasta el número de filas del dataset menos uno
data_ = data_.set_index('index_new')                # Cambiamos el índice del dataset
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File /usr/local/lib/python3.9/site-packages/pandas/core/indexes/base.py:3791, in Index.get_loc(self, key)
   3790 try:
-> 3791     return self._engine.get_loc(casted_key)
   3792 except KeyError as err:

File index.pyx:152, in pandas._libs.index.IndexEngine.get_loc()

File index.pyx:181, in pandas._libs.index.IndexEngine.get_loc()

File pandas/_libs/hashtable_class_helper.pxi:7080, in pandas._libs.hashtable.PyObjectHashTable.get_item()

File pandas/_libs/hashtable_class_helper.pxi:7088, in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'latitud'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
Cell In[15], line 19
     17 ## Eliminamos columnas redundantes
     18 for columna in lista_cols:          # Iteramos sobre cada columna redundante
---> 19     del data_[columna]
     21 ## Cambiamos la columna de índices por una de números enteros empezando desde 0
     22 data_['index_new'] = np.arange(0,len(data_.index))  # Arreglo de números enteros desde 0 hasta el número de filas del dataset menos uno

File /usr/local/lib/python3.9/site-packages/pandas/core/generic.py:4441, in NDFrame.__delitem__(self, key)
   4436             deleted = True
   4437 if not deleted:
   4438     # If the above loop ran and didn't delete anything because
   4439     # there was no match, this call should raise the appropriate
   4440     # exception:
-> 4441     loc = self.axes[-1].get_loc(key)
   4442     self._mgr = self._mgr.idelete(loc)
   4444 # delete from the caches

File /usr/local/lib/python3.9/site-packages/pandas/core/indexes/base.py:3798, in Index.get_loc(self, key)
   3793     if isinstance(casted_key, slice) or (
   3794         isinstance(casted_key, abc.Iterable)
   3795         and any(isinstance(x, slice) for x in casted_key)
   3796     ):
   3797         raise InvalidIndexError(key)
-> 3798     raise KeyError(key) from err
   3799 except TypeError:
   3800     # If we have a listlike key, _check_indexing_error will raise
   3801     #  InvalidIndexError. Otherwise we fall through and re-raise
   3802     #  the TypeError.
   3803     self._check_indexing_error(key)

KeyError: 'latitud'
data_.head()
humedad iluminancia pm1 pm10 pm10_AE pm1_AE pm25 pm25_AE presion temperatura
index_new
0 58.595011 9.0 11.0 14.5 14.5 11.0 14.0 14.0 919.924316 31.109080
1 58.976732 9.0 11.5 18.0 18.0 11.5 18.0 18.0 919.008606 31.113384
2 59.634209 9.0 13.0 18.5 18.5 13.0 17.5 17.5 919.394836 31.080302
3 60.114778 9.0 29.0 36.0 36.0 25.0 36.0 33.0 918.058350 31.198839
4 60.281987 9.0 15.0 21.5 21.5 15.0 21.0 21.0 920.309998 30.987728

¿Qué esperamos obtener de los datos?#

Sabemos que el material particulado puede cambiar con factores como la lluvia o con la temperatura, por eso esperaríamos obtener una buena correlación entre el \(PM\) y las demás variables ambientales. Igualmente, sabemos que los diferentes valores de \(PM\) están ligados entre sí, cuando uno aumenta los demás también tienden a aumentar. Una posible causa de esto es que los diferentes tamaños de material particulado provengan de una misma fuente. En todo caso, debería existir una correlación entre los valores de material particulado. Adicionalmente, no sería extraño que exista una correlación entre algunas variables ambientales debido a que todas ellas caracterizan al clima.

Siempre es util preguntarse que esperamos obtener de los datos antes de hacer el análisis de datos. Nos ayuda a interpretar los resultados y a darnos cuenta de eventos inesperados. Así, hacer un pre-análisis es bastante recomendado en ciencia de datos.

Matriz de correlación#

La matriz de correlación cálcula el coeficiente de correlación de Pearson entre cada pareja de datos del data set. Por ejemplo, entre temperatura y presión, temperatura y \(PM2.5\), temperatura y \(PM10\), etc.

El coeficiente de Pearson \(r\) estima si los datos presentan alguna relación de proporcionalidad lineal entre dos variables. Es decir, si una variable aumenta la otra aumenta o disminuye siempre en la misma proporción, por ejemplo, \(r=0.5\) implica que cuando la varible \(X\) aumenta, la otra \(Y\) aumenta el doble. En general, si \(X\) aumenta en una cantidad, \(Y\) aumenta en un factor de \(1/r\) de ella.

Un valor positivo del coeficiente de Pearson indica que si uno aumenta, el otro también lo hace. Un valor negativo indica que si uno aumenta, el otro disminuye.

Aquí vemos ejemplos del valor del coeficiente de Pearson para diversas nubes de datos. Note que \(r\) también es cero cuando existe una relación entre los datos pero que no es líneal.

La matriz de correlación se cálcula con la librería pandas desde un dataset. Más información aquí

## Calculamos la matriz de Correlación de datos
corr = data_.corr() 
corr
humedad iluminancia pm1 pm10 pm10_AE pm1_AE pm25 pm25_AE presion temperatura
humedad 1.000000 -0.606786 -0.128160 -0.110593 -0.119536 -0.162285 -0.107973 -0.137888 0.837698 -0.832282
iluminancia -0.606786 1.000000 0.180771 0.164006 0.166771 0.175716 0.162001 0.174082 -0.642040 0.627405
pm1 -0.128160 0.180771 1.000000 0.984745 0.978206 0.979509 0.991543 0.981797 -0.157911 0.182994
pm10 -0.110593 0.164006 0.984745 1.000000 0.991792 0.961274 0.996859 0.979230 -0.145167 0.171870
pm10_AE -0.119536 0.166771 0.978206 0.991792 1.000000 0.979559 0.988987 0.993017 -0.154332 0.181070
pm1_AE -0.162285 0.175716 0.979509 0.961274 0.979559 1.000000 0.967141 0.991765 -0.183065 0.208297
pm25 -0.107973 0.162001 0.991543 0.996859 0.988987 0.967141 1.000000 0.982935 -0.145114 0.172328
pm25_AE -0.137888 0.174082 0.981797 0.979230 0.993017 0.991765 0.982935 1.000000 -0.169158 0.195559
presion 0.837698 -0.642040 -0.157911 -0.145167 -0.154332 -0.183065 -0.145114 -0.169158 1.000000 -0.992926
temperatura -0.832282 0.627405 0.182994 0.171870 0.181070 0.208297 0.172328 0.195559 -0.992926 1.000000

Exploración de los datos#

Tomemos como ejemplo al \(PM2.5\). Tenemos la intuición de que el material particulado dependa de las condiciones ambientales como temperatura, presión y humedad. Así, en los datos podemos encontrar si esta hipotesis es válida o no. La columna de la matriz asociada a ‘pm25’ es la que cuantifica dicha relación de variables. Cuanto mayor sea la correlación en valor absoluto, mayor será la relación lineal.

## Datos de correlación de 'pm25' con las otras variables
corr['pm25']
humedad       -0.107973
iluminancia    0.162001
pm1            0.991543
pm10           0.996859
pm10_AE        0.988987
pm1_AE         0.967141
pm25           1.000000
pm25_AE        0.982935
presion       -0.145114
temperatura    0.172328
Name: pm25, dtype: float64

¿Cuáles son las variables más correlacionadas con pm25? Revisémolos visualmente a continuación con un gráfico de barras.

Visualización de los datos#

A continuación presentamos distintas maneras de visualizar y analizar los datos de la matriz de correlación. Podemos sacarle buen jugo a los datos de diversas maneras. Cada una tiene un propósito diferente.

Gráfico de Barras#

Un grafico de barras nos ayuda a ver visualmente los valores del coeficiente de Pearson para cada variable en comparación con PM2.5. Antes de graficar, tenemos que pm25 puede estar correlacionado con las variables ambientales como presión, temperatura, humedad, y con los demás valores de material particulado.

El gráfico de barras o bar plot se realiza con la librería pandas desde una matriz de correlación. Más información aquí

fig, ax = plt.subplots(figsize=(7,7))    # Creamos figura
corr['pm25'].plot.barh(ax=ax,            # Eje de la figura
                       color='blue',     # Color de las barras
                       width=0.8)        # Ancho de las barras (entre 0-1)
plt.show()                               # Mostramos la figura
../_images/40fb5f7825f41968fd43519cd845c7389b79069268ce1c92cd868bd33c87d21a.png

Nota: vemos que para el caso de pm25 la mayor correlación se encuentra entre los demás valores de material particulado ¿por qué pasa esto?

Mapa de calor#

Un mapa de calor permite visualizar los datos en una matriz. Cada celda de la matriz se colorea de acuerdo al valor que tiene. Los valores cercanos a \(+1\) se colorean con un color claro y los valores cercanos a \(-1\) con un color oscuro. Esto se cumple para el coloreado por defecto, en general la barra de colores es la que nos indica a qué valor corresponde cada tonalidad.

El mapa de calor o heat map se grafica con la librería de visualización de datos seaborn. Más información aquí

plt.subplots(figsize=(25,15))  # Creamos la figura
sns.heatmap(corr,              # Matriz de correlación 
            annot=True,        # True para que aparezcan los valores en cada celda. False para que no aparezcan.
            fmt=".2f",         # Formato de los valores númericos de cada celda
            annot_kws={        # Caracteristícas del texto de las anotaciones
                'fontsize':20} # Tamaño de letra de las anotaciones
            )
plt.show()
../_images/c572ec2d13a6e44e9da76cb71289b079f27328feb61ac61d0fe567e43fd724bb.png

Nota: Podemos usar el Traductor de Google para traducir una página web del inglés al español y esto puede ayudarte en la búsqueda de información de una librería.

Gráfico de pares#

En el gráfico de pares se toma cada par de variables del dataset y se hace un gráfico de puntos para cada una. Sobre la diagonal del grafico de pares está la distribución de los datos en un histograma.

Dado que el grafico de pares sería muy grande si elegimos todas las variables, entonces elegimos un subcojunto de variables representativas.

El gráfico de pares o pair plot se realiza con la librería de visualización de datos seaborn. Más información aquí

# Subconjunto de variables representativas
subconjunto_variables = ['humedad', 'iluminancia','pm10_AE', 'pm1_AE', 'pm25_AE', 'presion','temperatura']
sns.pairplot(
            data=data_[subconjunto_variables],  # Subconjunto de datos en forma de dataset
            plot_kws = {                        # Caracteristícas graficos fuera de la diagonal
                        'marker':'o',           # Tipo de puntos
                        'size':10},             # Tamaño de puntos
            diag_kind='hist'                    # Tipo de graficos en la diagonal
            )
plt.show()
../_images/b6e8cf67b7cd9889005d2cb80c0def88828c2795f5f86c6b79d94f9603944e8c.png

Gráfico de unión#

Este gráfico toma dos variables y analiza la correlación entre ellas por medio de líneas de ajuste y de histogramas.

El gráfico de unión o joint plot se realiza con la librería de visualización de datos seaborn. Más información aquí

sns.jointplot(x="pm25_AE",  # Datos en el eje x
              y="pm10_AE",  # Datos en el eje y
              data=data_,   # Conjunto de datos
              color="r",    # Color gráfico
              kind='reg',   # Tipo de gráfico. Las opciones son { 'scatter' | 'kde' | 'hist' | 'hex' | 'reg' | 'resid' }
              height=8,     # Tamaño de la figura (análogo de figsize)
              ratio=2,      # Relación de tamaño entre el gráfico principal y los gráficos adyacentes (a los lados)
              scatter_kws={ # Caracteristicas grafico de puntos
                's':50,                # Tamaño de puntos
                'edgecolors':'white',  # Color del borde de los puntos
                'linewidth': 1.2,      # Ancho de la línea de borde
                'alpha':1              # Transparencia línea de borde (0: transparente, 1: visible)
              },
              line_kws={           # Caracteristícas línea de regresión
                'linewidth': 1.5,  # Ancho línea de regresión
                'color':'blue'     # Color línea de regresión
              },
              marginal_kws={       # Caracteristícas de los gráficos adyacentes (a los lados)
                'linewidth': 0.5,  # Ancho del borde del gráfico de barras
                'line_kws':{                  # Caracteristícas línea de regresión 
                            'linewidth':2,    # Ancho línea de regresión
                            'linestyle':'-'   # Estilo línea de regresión
                }
              }
)
plt.show()
../_images/127327be4bb8e3d1399067776f5db5757d4bd759d62c8762f4eac61baab753f4.png
sns.jointplot(x="pm10",  # Datos en el eje x
              y="pm10_AE",  # Datos en el eje y
              data=data_,   # Conjunto de datos
              color='b',    # Color gráfico
              kind='reg',   # Tipo de gráfico. Las opciones son { 'scatter' | 'kde' | 'hist' | 'hex' | 'reg' | 'resid' }
              height=8,     # Tamaño de la figura (análogo de figsize)
              ratio=2,      # Relación de tamaño entre el gráfico principal y los gráficos adyacentes (a los lados)
              scatter_kws={ # Caracteristicas grafico de puntos
                's':50,                # Tamaño de puntos
                'edgecolors':'white',  # Color del borde de los puntos
                'linewidth': 1.2,      # Ancho de la línea de borde
                'alpha':1              # Transparencia línea de borde (0: transparente, 1: visible)
              },
              line_kws={           # Caracteristícas línea de regresión
                'linewidth': 1.5,  # Ancho línea de regresión
                'color':'red'     # Color línea de regresión
              },
              marginal_kws={       # Caracteristícas de los gráficos adyacentes (a los lados)
                'linewidth': 0.5,  # Ancho del borde del gráfico de barras
                'line_kws':{                  # Caracteristícas línea de regresión 
                            'linewidth':2,    # Ancho línea de regresión
                            'linestyle':'-'   # Estilo línea de regresión
                }
              }
)
plt.show()
../_images/f8995ac6d0d42073ff92643936fc4286d561f160c60eb760ef3cafe3779ca9da.png

Ecuación de ajuste líneal#

Ecuación línea recta#

Primero consideremos la ecuación de la línea recta. La línea se caracteriza por dos parámetros, la pendiente indica la inclinación de la recta y la ordenada indica la ubicación de la línea con respecto al plano \(xy\). La pendiente indica qué tan empinada está la línea, entre mayor sea, mayor es su inclinación. Por su parte, la ordenada marca el punto de intersección de la línea con el eje x.

Consideremos la ecuación de la línea recta: $\(y=mx+b\)\( A \)m\( se le conoce como pendiente y a \)b$ como ordenada. Hagamos una tabla de datos para entenderla mejor.

m = 2
b = 0.3

x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
y = m*x+b

tabla = pd.DataFrame({'x':x,'y':y})
tabla
x y
0 0 0.3
1 1 2.3
2 2 4.3
3 3 6.3
4 4 8.3
5 5 10.3
6 6 12.3
7 7 14.3
8 8 16.3
9 9 18.3
10 10 20.3

Note que para el valor \(x=0\), \(y\) es igual a la ordenada \(b=0.3\). Observe también que si tomamos el valor de \(y\) para \(x=2\) y su valor para \(x=1\), por ejemplo, tendremos que al dividir la diferencia en \(y\) entre la diferencia en \(x\) se obtiene \(\frac{4.3-2.3}{2-1}=2\) que es la pendiente \(m\), esto se cumple lal tomar cualquier \(m\). Los datos de \(y\) dependen de esta manera de los valores de \(x\).

Los datos en \(x\) y \(y\) pueden graficarse en un scatter plot

plt.scatter(x,y)
<matplotlib.collections.PathCollection at 0x7f5d38762650>
../_images/87daf6023d4752d00f91b4c5a32010dc803ef517b962e81ca450ee28c5fa66ed.png

Como vemos, los datos se parecen a una línea recta. Por ello es la ecuación de la línea recta.

Ajuste de datos#

En el ejemplo anterior disponíamos de la ecuación de la línea recta y con ella obteniamos la grafica de puntos. A veces estamos interesados en el problema inverso: dado una nube de puntos cuál es la línea recta que pasa por ella o cuál es la línea que más se ajusta a la nube de puntos.

Veamos un ejemplo, supongamos que se dispone de este conjunto de datos:

x = np.array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16,
              17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29])
y = np.array([   0.3,  2.09,  3.90,  5.05, 11.99,
               14.57,  8.86, 17.28, 21.01, 10.57,
               27.27, 20.77, 16.56, 20.68, 18.76,
               40.13, 33.56, 30.83, 53.32, 36.48,
               23.41, 61.17, 41.62, 40.89, 59.79,
               33.70, 59.02, 48.58, 71.03, 71.40])
plt.scatter(x,y)
<matplotlib.collections.PathCollection at 0x7f5d36310710>
../_images/8398d8f17005ac68284d03a91b7a3fb66f7a921e317fd8b70f4835dcc40c6635.png

Claramente, los puntos no están alineados en una línea recta. Pero podemos preguntarnos por cuál sería la mejor línea recta que describe los datos, dicha recta describiría de mejor manera la tendencia que siguen esos datos.

En el ejemplo anterior, la ecuación que mejor se ajusta es \(y=2x+0.3\)

# Para la ecuación de ajuste
xt = np.arange(0,30)  # Puntos en x
yt = 2*xt+0.3         # Puntos en y

plt.scatter(x,y)       # Graficamos nube de puntos
plt.plot(xt,yt, 'red') # Graficamos línea recta de ajuste
[<matplotlib.lines.Line2D at 0x7f5d361cd390>]
../_images/2f1271793d77071c0a2ece74c975a9be469be01647d3c4e6561697a21cc6d007.png

A la ecuación de la línea recta que ajusta mejor los datos se le llama ecuación de ajuste. En general, un conjunto de datos puede ajustarse por distintas ecuaciones como la cuadrática, por ejemplo.

Ejemplo: datos de una masa deslizandose sobre una rampa

En aplicaciones practicas deseamos ajustar los datos de una variable física que varia en el tiempo. Aún cuando sabemos que la grafica de los datos debe ser una línea recta, los datos muestran un esparcimiento en forma de una nube de puntos. Esto se debe a que al hacer una medición física siempre tenemos errores aleatorios asociados al instrumento de medición o a la interacción de agentes externos. A continuación se presenta un ejemplo de una toma y ajuste líneal de los datos de un laboratorío de la carrera de Física, que relacionan la velocidad de una masa (un libro) que se desliza sobre una rampa.

# Tiempo medido en segundos
t = np.array([0.017, 0.033, 0.050, 0.067, 0.083, 0.100, 0.117, 0.133, 0.150, 0.167, 0.183, 0.200, 0.217, 0.233, 
              0.250, 0.267, 0.283, 0.300, 0.317, 0.333, 0.350, 0.367, 0.383, 0.400, 0.417, 0.433, 0.450, 0.467, 
              0.483, 0.500, 0.517, 0.533, 0.550, 0.567, 0.583, 0.600, 0.617, 0.633, 0.650, 0.667, 0.683, 0.700])
     
# Distancia medida en metros    
v = np.array([0.083, 0.121, 0.141, 0.157, 0.213, 0.238, 0.281, 0.330, 0.344, 0.394, 0.454, 0.478, 0.510, 0.514, 
              0.560, 0.588, 0.610, 0.625, 0.623, 0.692, 0.734, 0.799, 0.804, 0.763, 0.829, 0.862, 0.848, 0.909, 
              0.937, 0.951, 0.975, 1.034, 1.033, 1.015, 1.087, 1.143, 1.164, 1.187, 1.224, 1.305, 1.312, 1.330])

m = 1.7840
b = 0.0817
tt = t
vt = m*tt + b


plt.title('$v(t)$ vs $t$')
plt.scatter(t,v)
plt.plot(tt,vt,'red', label = 'Ecuación de Ajuste: $y=%.3f x + %.3f$'%(m,b))
plt.legend()
<matplotlib.legend.Legend at 0x7f5d34b73ed0>
../_images/e71798aa8ca7e1baa09ad7bba0447fdff1b6e6e369bfee2445d1ea41c9cefdda.png

Ajuste líneal de algunos datos de la MiniEva#

A continuación hallamos la ecuación de ajuste de la regresión lineal. Como los datos continen filas con None debemos eliminarlas para que no haya error al usar linregress. Para esto usamos el comando dropna()

1. Usando curve_fit#

from scipy.optimize import curve_fit

data_xy = data_[['pm10','pm10_AE']].dropna() # Creamos un dataset con las variables que deseamos comparar y eliminamos las filas con None
def linea_recta(x,m,b):  # Función de ajuste
    y = m*x + b          # m: pendiente, b: ordenada
    return y
params, var = curve_fit(                     # params: parámetros de ajuste, var: varianza
                        linea_recta,         # Función de ajuste que le aplicaremos a los datos
                        data_xy['pm10'],     # Datos en x
                        data_xy['pm10_AE']   # Datos en y
                        )
cov = np.sqrt(np.diag(var))    # Covarinza

print('params =',params)
print('cov    =', cov)
print('Ecuación de la recta: Y = %.3f X + (%.3f)'%(params[0],params[1]))
params = [0.90522915 1.63121427]
cov    = [0.00159891 0.04152468]
Ecuación de la recta: Y = 0.905 X + (1.631)

2. Usando linregress#

data_XY = data_[['pm10','pm10_AE']].dropna() # Creamos un dataset con las variables que deseamos comparar y eliminamos las filas con None

# Hallamos datos con linregress
pendiente, intercepto, r_value, p_value, std = sp.stats.linregress(                           # r_value: coeficiente Pearson, p_value: probabilidad de obtener datos por fuera de la recta de ajuste, std: desviación estándar
                                                                   data_XY['pm10'].values,    # Datos eje x
                                                                   data_XY['pm10_AE'].values  # Datos eje y
                                                                  )
print('Ecuación de la recta: Y = %.3f X + (%.3f)'%(pendiente, intercepto))
Ecuación de la recta: Y = 0.905 X + (1.631)

Otros gráficos#

Implot

data.index = pd.DatetimeIndex(data.index) # Convierte a tipo datetime el índice
data_['h'] = data.index.hour              # Columna que asigna la hora a cada fila del conjunto de datos
data_h = data_.groupby('h').mean()        # Agrupamos todos los datos de una misma hora usando promedio

sns.lmplot(x='pm25_AE',   # Datos eje x
           y='pm1_AE',    # Datos eje y
           data=data_h,   # Conjunto de datos
           height=7       # Tamaño de gráfica (análogo a figsize) 
) 
<seaborn.axisgrid.FacetGrid at 0x7fc91e2bd710>
../_images/6eddd72d10419192f7809ba77854f220698d252676af5355be752873e8355b30.png

Jointplot con datos por horas

sns.jointplot(x="pm25_AE",  # Datos en el eje x
              y="pm1_AE",  # Datos en el eje y
              data=data_h,   # Conjunto de datos
              color="r",    # Color gráfico
              kind='reg',   # Tipo de gráfico. Las opciones son { 'scatter' | 'kde' | 'hist' | 'hex' | 'reg' | 'resid' }
              height=8,     # Tamaño de la figura (análogo de figsize)
              ratio=2,      # Relación de tamaño entre el gráfico principal y los gráficos adyacentes (a los lados)
              scatter_kws={ # Caracteristicas grafico de puntos
                's':50,                # Tamaño de puntos
                'edgecolors':'white',  # Color del borde de los puntos
                'linewidth': 1.2,      # Ancho de la línea de borde
                'alpha':1              # Transparencia línea de borde (0: transparente, 1: visible)
              },
              line_kws={           # Caracteristícas línea de regresión
                'linewidth': 1.5,  # Ancho línea de regresión
                'color':'blue'     # Color línea de regresión
              },
              marginal_kws={       # Caracteristícas de los gráficos adyacentes (a los lados)
                'linewidth': 0.5,  # Ancho del borde del gráfico de barras
                'line_kws':{                  # Caracteristícas línea de regresión 
                            'linewidth':2,    # Ancho línea de regresión
                            'linestyle':'-'   # Estilo línea de regresión
                }
              }
)
plt.show()
../_images/41520055b8f0564cd71df475e9d291982f9269301b973026acaf5ad69bb78d5f.png

Ahora que ya has visto como analizar tus datos, puedes probar con diferentes variables y datos. Juega un poco con los datos realizando los siguientes ejercicios.

  1. Elige dos variables del set de datos data_ o data_h y gráfica la correlación entre los datos ¿Qué puedes decir acerca de las dos variables, una depende de la otra?

  1. Elige dos variables fuertemente correlacionadas (\(|r|>0.8\)). Ahora toma los datos de tres colegios en el mismo rango de tiempo y analiza si la correlación es la misma en las tres estaciones.

  1. Para este ejercicio debes comparar dos regresiones líneales entre valores de PM y PM_AE.

    a) Crea un nuevo datset llamado data_pm_mayor con los datos de la estacion mE1_00007 entre las fechas 2023-04-30 00:00:00 y 2023-05-07 23:00:00. Elimina los datos por debajo de \(60\) y los datos por encima de \(120\) (Ayuda: revisa el contenido de la sección 2. Limpieza de datos). Haz una regresión líneal entre PM10 y PM10_AE.

    b) Ahora crea un nuevo dataset llamado data_pm_menor con los datos de la misma estación y entre las mismas fechas. En este caso, elimina los datos por encima de \(60\). Nuevamente haz una regresión líneal entre PM10 y PM10_AE.

    c) Finalmente, compara las pendientes obtenidas en las regresiones líneales anteriores. ¿Cuál de esas pendientes es mayor que la otra? ¿Cuál crees que sea el aporte del número \(60\) en los resultados de las pendientes? ¿Cómo interpretas tus resultados?