Caso Aplicado

image info

EL dataset terremotos.csv contiene la información de los terremotos de los países durante el año 2000 al 2011. Debido a que la información de este dataset es relativamente fácil de trabajar, hemos creado un dataset denominado terremotos_contaminados.csv que posee información contaminada en cada una de sus columnas. De esta forma se podrá ilustrar los distintos inconvenientes que se puede tener en el análisis exploratorio de datos.

Bases del experimento

Lo primero es identificar las variables que influyen en el estudio y la naturaleza de esta.

  • País:

    • Descripción: País del evento sísmico.

    • Tipo de dato: string

    • Limitantes: No pueden haber nombre de ciudades, comunas o pueblos.

  • Año:

    • Descripción: Año del evento sísmico.

    • Tipo de dato: integer.

    • Limitantes: el año debe estar entre al año 2000 y 2011.

  • Magnitud:

    • Descripción: Magnitud del evento sísmico.

    • Tipo de dato: float.

    • Limitantes: la magnitud puede estar entre 5 y 9.6.

¿ Por qué no puede ser un terremoto con una intensidad mayor a 9.6?. Esto se debe a que el terremoto con mayor magnitud registrado por la humanidad es de 9.6, ocurrido en Chile (Valdivia) durante el año 1960. Por lo tanto, entre mayor conocimiento se tenga del fenómeno en estudio, más restrictivo se vulve el análisis exploratorio y más sentido tienen los resultados obtenidos.

Conjunto de datos

El conjunto de datos consta de cuatro columnas:

  • País

  • Año

  • Magnitud

  • Información

Checklist del experimento

Dado que conocemos el fenómeno en estudio, vayamos realizando un checklist de los procesos para hacer un correcto EDA.

1. ¿ Qué pregunta (s) estás tratando de resolver (o probar que estás equivocado)?

El objetivo es encontrar el terremoto de mayor magnitud por país en los distintos años.

2. ¿ Qué tipo de datos tienes ?

Los tipos de variables que tiene el conjunto de datos son:

  • Categóricas: País, Información.

  • Numéricas: Año, Magnitud.

image info

# cargar librerias

import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

pd.set_option('display.max_columns', 500)  # Ver más columnas de los dataframes

# Ver gráficos de matplotlib en jupyter notebook/lab
%matplotlib inline
# cargar datos
terremotos_data = pd.read_csv(os.path.join("data","terremotos_contaminados.csv"), sep=",")
terremotos_data.head()
Año Pais Magnitud Informacion
0 2000 Turkey 6 info no valiosa
1 2000 Turkmenistan 7 info no valiosa
2 2000 Azerbaijan 6.5 info no valiosa
3 2000 Azerbaijan 6.8 info no valiosa
4 2000 Papua New Guinea 8 info no valiosa
# normalizar columnas (minusculas y sin espacios)

terremotos_data.columns = terremotos_data.columns.str.lower().str.strip()
terremotos_data.head()
año pais magnitud informacion
0 2000 Turkey 6 info no valiosa
1 2000 Turkmenistan 7 info no valiosa
2 2000 Azerbaijan 6.5 info no valiosa
3 2000 Azerbaijan 6.8 info no valiosa
4 2000 Papua New Guinea 8 info no valiosa
# resumen de la informacion

def resumen_por_columna(df,cols):
    pd_series = df[cols]
    
    # elementos distintos 
    l_unique = pd_series.unique()
    
    # elementos vacios
    
    l_vacios = pd_series[pd_series.isna()]
    
    df_info = pd.DataFrame({
        'columna': [cols],
        'unicos': [len(l_unique)],
        'vacios': [len(l_vacios)]
    })
    
    return df_info
frames = []

for col in terremotos_data.columns:
    aux_df = resumen_por_columna(terremotos_data,col)
    frames.append(aux_df)
    
df_info = pd.concat(frames).reset_index(drop=True)
df_info['% vacios'] = df_info['vacios']/len(terremotos_data)
df_info
columna unicos vacios % vacios
0 año 17 2 0.008772
1 pais 75 2 0.008772
2 magnitud 46 3 0.013158
3 informacion 4 8 0.035088
plt.style.use('default')
plt.figure(figsize=(7,5))

plotting = sns.barplot(
    x="columna",
    y="unicos",
    data=df_info.sort_values('unicos'),
    palette="Greys_d",
    linewidth=3
)

for container in plotting.containers:
    plotting.bar_label(container,fontsize=12)

plt.title("Valores Distintos")
plt.yticks(fontsize=14)
plt.xticks(fontsize=14)

plt.show()
../../../../_images/eda_12_0.png

¿Qué falta en los datos y cómo los maneja?

En este caso, se tiene la información suficiente para poder realizar el experimento, solo falta ver que los datos de la muestra no esten lo suficientemente contaminados.

¿Qué hacer con los datos faltantes, outliers o información mal inputada?

Este caso es más interesante, y se necesita ir detallando columna por columna los distintos análisis.

Columna: Año

Los años distintos en la muestra son:

terremotos_data['año'].unique()
array(['2000', '2001', 'dos mil uno', '2002', '2003', '2004', '2005',
       '2006', '2007', '2008', '2009', '2010', '2011', '1997', '1990',
       '1999', nan], dtype=object)
# bar plot: año

plt.style.use('default')
plt.figure(figsize=(7,4))

plotting = sns.countplot(
    y="año",
    data=terremotos_data,
    order=terremotos_data['año'].value_counts().index,
    palette="Greys_d",
    linewidth=3
)

for container in plotting.containers:
    plotting.bar_label(container,fontsize=12)


plt.show()
../../../../_images/eda_17_0.png

Se presentan las siguientes anomalidades:

  • Años sin importancia: Se ha establecido que los años de estudios son desde el año 2000 al 2011.

  • Nombres mal escritos: en este caso sabemos que ‘dos mil uno’ corresponde a ‘2001’.

  • Datos vacío

Ahora la pregunta es, ¿ qué debemos hacer primero?. Lo primero es corregir la información, dar un formato estándar a los datos y luego filtrar.

a) Corrección

terremotos_data.loc[terremotos_data['año']=='dos mil uno','año'] = '2001'
terremotos_data.loc[terremotos_data['año'].isnull(),'año'] = '0'

terremotos_data['año'].unique()
array(['2000', '2001', '2002', '2003', '2004', '2005', '2006', '2007',
       '2008', '2009', '2010', '2011', '1997', '1990', '1999', '0'],
      dtype=object)

b) Formato

El formato de los años es integer, por lo tanto se le debe dar ese formato.

terremotos_data['año'] = terremotos_data['año'].astype(int)

c) Filtro

Se ha establecido que los años de estudios son desde el año 2000 al 2011, por lo tanto los años en estudios deberian ser:

anios_estudio = [x for x in range(2000,2011+1)]
print(anios_estudio)
[2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011]

Por lo tanto ya tenemo nuestro primer filtro:

mask_anio = terremotos_data['año'].isin(anios_estudio)

Columna: País

Los paises distintos en la muestra son:

set(terremotos_data['pais'].unique())
{'Afghanistan',
 'Afghanistan ',
 'Algeria',
 'Algeria ',
 'Argentina',
 'Azerbaijan',
 'Azerbaijan ',
 'Bangladesh',
 'Burma ',
 'Chile',
 'Chile ',
 'China',
 'China ',
 'Colombia',
 'Costa Rica',
 'Costa Rica ',
 'Democratic Republic of the Congo',
 'Democratic Republic of the Congo ',
 'Dominican Republic',
 'Ecuador',
 'El Salvador ',
 'Greece',
 'Greece ',
 'Guadeloupe',
 'Guatemala',
 'Haiti ',
 'India',
 'India ',
 'Indonesia',
 'Indonesia ',
 'Iran',
 'Iran ',
 'Iran, 2005 Qeshm earthquake',
 'Italy',
 'Italy ',
 'Japan',
 'Japan ',
 'Kazakhstan',
 'Kyrgyzstan ',
 'Martinique',
 'Mexico ',
 'Morocco',
 'Morocco ',
 'Mozambique',
 'New Zealand',
 'New Zealand ',
 'Nicaragua',
 'Pakistan',
 'Pakistan ',
 'Panama',
 'Papua New Guinea',
 'Peru',
 'Peru ',
 'Philippines',
 'Russian Federation',
 'Rwanda',
 'Samoa ',
 'Serbia',
 'Slovenia',
 'Solomon Islands ',
 'Taiwan',
 'Taiwan ',
 'Tajikistan',
 'Tajikistan ',
 'Tanzania',
 'Tanzania ',
 'Turkey',
 'Turkey ',
 'Turkmenistan',
 'United States ',
 'Venezuela',
 'Vietnam',
 'arica',
 nan,
 'shile'}
# bar plot: pais

plt.style.use('default')
plt.figure(figsize=(14,14))

plotting = sns.countplot(
    y="pais",
    data=terremotos_data,
    order=terremotos_data.pais.value_counts().index,
    palette="Greys_d",
    linewidth=3
)

for container in plotting.containers:
    plotting.bar_label(container,fontsize=12)


plt.show()
../../../../_images/eda_30_0.png

Se presentan las siguientes anomalidades:

  • Formato de los nombres: no se le ha aplicado strip() y lower(), por lo cual tenemos casos como: ‘Turkey’ y ‘Turkey ’ como elementos diferentes.

  • Nombres mal escritos: en este caso sabemos que ‘shile’ corresponde a ‘Chile’ e ‘Iran, 2005 Qeshm earthquake’ corrsponde a ‘Iran’.

  • Datos vacío

Se solucionará cada uno de estos inconvenientes:

Corrección de los nombres

terremotos_data.loc[terremotos_data['pais']=='arica','pais'] = 'chile'
terremotos_data.loc[terremotos_data['pais']=='shile','pais'] = 'chile'
terremotos_data.loc[terremotos_data['pais']=='Iran, 2005 Qeshm earthquake','pais'] = 'iran'
terremotos_data.loc[terremotos_data['pais'].isnull(),'pais'] = 'sin_nombre'

Formato

# correccion formato de nombre 
terremotos_data['pais'] = terremotos_data['pais'].str.lower().str.strip()

Filtro

mask_pais = terremotos_data['pais']!='sin_nombre'

Columna: Magnitud

Las magnitudes distintas en la muestra son:

terremotos_data['magnitud'].unique()
array(['6', '7', '6.5', '6.8', '8', '5.7', '6.4', '5.5', '6.3', '5.4',
       '6.1', '6.7', '7.9', '7.2', '7.5', '5.3', '5.9', '9.7', '5.8',
       '4.7', '7.6', '8.4', '5', '5.6', '6.6', '6.2', '7.1', '7.3', '5.1',
       '5.2', '8.3', '6.9', '9.1', '4.9', '7.8', '8.6', '7.7', '7.4',
       '8.5', '8.1', '8.8', '9', '-10', nan, '2002-Tanzania-5.8',
       '2003-japan-8.5'], dtype=object)
# bar plot: magnitud

plt.style.use('default')
plt.figure(figsize=(10,7))

plotting = sns.countplot(
    y="magnitud",
    data=terremotos_data,
    order=terremotos_data['magnitud'].value_counts().index,
    palette="Greys_d",
    linewidth=3
)

for container in plotting.containers:
    plotting.bar_label(container,fontsize=10)


plt.show()
../../../../_images/eda_39_0.png

Se presentan las siguientes anomalidades:

  • Magnitudes sin importancia: Se ha establecido que las magnitudes de un terremoto son de 5 a 9.6.

  • Datos con información comprimida: Debido a una inputación incorrecta de los datos o a una mala lectura, la información se concentra en una celda.

  • Datos vacío

Corrección de las magnitudes

# caso: tradicional
terremotos_data.loc[terremotos_data['magnitud']=='2002-Tanzania-5.8','magnitud'] = '0'
terremotos_data.loc[terremotos_data['magnitud']=='2003-japan-8.5','magnitud'] = '0'
terremotos_data.loc[terremotos_data['magnitud'].isnull(),'magnitud'] = '0'
# caso: informacion comprimida
terremotos_data.loc[-1] = [2002,'tanzania','5.8','-']
terremotos_data.loc[-2] = [2003,'japan','8.5','-']
terremotos_data = terremotos_data.reset_index(drop=True)
terremotos_data.tail()
año pais magnitud informacion
225 0 sin_nombre 0 NaN
226 0 sin_nombre 0 NaN
227 2005 chile 8 valiosa
228 2002 tanzania 5.8 -
229 2003 japan 8.5 -

Corrección formato de las magnitudes

terremotos_data['magnitud'] = terremotos_data['magnitud'].astype(float)
terremotos_data['magnitud'].unique()
array([  6. ,   7. ,   6.5,   6.8,   8. ,   5.7,   6.4,   5.5,   6.3,
         5.4,   6.1,   6.7,   7.9,   7.2,   7.5,   5.3,   5.9,   9.7,
         5.8,   4.7,   7.6,   8.4,   5. ,   5.6,   6.6,   6.2,   7.1,
         7.3,   5.1,   5.2,   8.3,   6.9,   9.1,   4.9,   7.8,   8.6,
         7.7,   7.4,   8.5,   8.1,   8.8,   9. , -10. ,   0. ])

c) Filtro de las magnitudes

mask_mag_inf =  terremotos_data['magnitud']>=5
mask_mag_sup =  terremotos_data['magnitud']<=9.6

mask_mag = mask_mag_inf & mask_mag_sup

Columna: Información

La cantidad de elementos distintos para la columna información son:

terremotos_data['informacion'].unique()
array(['info no valiosa', 'info valiosa', nan, 'valiosa', '-'],
      dtype=object)

Se observa que esta columna no aporta información valiosa al estudio, quedando excluida para cualquier análisis importante.

terremotos_data.drop('informacion', axis=1, inplace=True)
terremotos_data.head()
año pais magnitud
0 2000 turkey 6.0
1 2000 turkmenistan 7.0
2 2000 azerbaijan 6.5
3 2000 azerbaijan 6.8
4 2000 papua new guinea 8.0

5. ¿Se puede sacar más provecho a los datos ?

Una vez realizado toda la limpieza de datos, debemos filtrar la información que se considere importante.

  • Años: Años desde el 2000 al 2011.

  • País: Paises con nombre distinto de sin_nombre

  • Magnitud: Magnitud entre 5 y 9.6.

# aplicar filtros
terremotos_data_filtrado = terremotos_data[mask_anio & mask_pais & mask_mag]
terremotos_data_filtrado.head()
año pais magnitud
0 2000 turkey 6.0
1 2000 turkmenistan 7.0
2 2000 azerbaijan 6.5
3 2000 azerbaijan 6.8
4 2000 papua new guinea 8.0

Veamos cuánta información se perdio:

print('Cantidad de filas dataset sin filtro:',len(terremotos_data))
print('Cantidad de filas dataset con filtro:',len(terremotos_data_filtrado))
Cantidad de filas dataset sin filtro: 230
Cantidad de filas dataset con filtro: 212

Ahora veamos el el resumen de la información para el nuevo dataset:

frames = []

for col in terremotos_data_filtrado.columns:
    aux_df = resumen_por_columna(terremotos_data_filtrado,col)
    frames.append(aux_df)
    
df_info = pd.concat(frames).reset_index(drop=True)
df_info
columna unicos vacios
0 año 12 0
1 pais 50 0
2 magnitud 39 0

Finalmente, podemos responder la pregunta del inicio:

Solución

# formato wide
terremotos_data_filtrado.pivot_table(index="pais", 
                                     columns="año",
                                     values="magnitud", 
                                     fill_value='', 
                                     aggfunc=pd.np.max)
<ipython-input-31-8c1a5edb19d8>:6: FutureWarning: The pandas.np module is deprecated and will be removed from pandas in a future version. Import numpy directly instead
  aggfunc=pd.np.max)
año 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011
pais
afghanistan 6.3 5.0 7.3 5.8 6.5 6.5
algeria 5.7 6.8 5.2 5.5
argentina 7.2 6.1
azerbaijan 6.8
bangladesh 5.6
burma 6.8
chile 6.3 8.0 7.7 8.8
china 5.9 5.6 5.5 6.3 5.3 5.2 5.0 6.1 7.9 5.7 6.9 5.4
colombia 6.5 5.9
costa rica 6.4 6.1
democratic republic of the congo 6.2 5.9
dominican republic 6.4
ecuador 5.5
el salvador 7.6
greece 6.2 6.4
guadeloupe 6.3
guatemala 6.4
haiti 7.0
india 7.6 6.5 5.1 5.3 5.1 6.9
indonesia 7.9 7.5 6.9 9.1 8.6 7.7 8.5 7.3 7.6
iran 5.3 6.5 6.6 6.3 6.4 6.1
italy 5.9 6.2
japan 6.1 6.8 8.3 6.6 6.6 6.7 6.9 6.4 9.0
kazakhstan 6.0
kyrgyzstan 6.9
martinique 7.4
mexico 7.5
morocco 6.3
mozambique 7.0
new zealand 5.4 6.6 6.3
nicaragua 5.4
pakistan 6.3 5.4 7.6 5.2 6.4
panama 6.5
papua new guinea 8.0 7.6 6.1
peru 8.4 7.5 8.0
philippines 7.5 6.5 7.1 5.3
russian federation 7.3 6.2
rwanda 5.3
samoa 8.1
serbia 5.7
slovenia 5.2
solomon islands 8.1
taiwan 6.4 7.1 5.2 7.0
tajikistan 5.2 5.6 5.2
tanzania 6.4 5.5 6.8
turkey 6.0 6.5 6.3 5.6 5.9 6.1 7.1
turkmenistan 7.0
united states 6.8 6.6
venezuela 5.5
vietnam 5.3

¿ Podemos sacar más información ?. Por supuesto que se puede, no obstante, siempre se debe ser preciso con la información que se

Conclusión del caso

  • El análisis exploratorio de datos (EDA) es una metodología que sirve para asegurarse de la calidad de los datos.

  • A medida que se tiene más expertice en el tema, mejor es el análisis de datos y por tanto, mejor son los resultados obtenidos.

  • No existe un procedimiento estándar para realizar el EDA, pero siempre se debe tener una claridad mental con:

    • Problema que se quiere resolverlo

    • Cómo resolver el problema

    • Posibles problemas de la muestra (datos perdidos, ouliers, etc.)