Data Cleaning con Pandas. Parte 1.
Ésta es la primera parte sobre Data Cleaning. En este artículo vas a encontrar métodos básicos para el Data Cleaning. En la segunda parte, te presento procedimientos más avanzados para la limpieza.
Primeros pasos para limpiar un DataFrame
Data Cleaning es un procedimiento para revisar la estructura de datos del DataFrame, organizar las columnas, sustituir los valores nulos, separar en columnas aquellas que sean necesarias y eliminar aquello que no aporte información útil, para finalmente obtener un DataFrame con el que poder trabajar de forma fácil y sencilla gracias a una buena infraestructura de datos.
En este post te presento algunos pasos básicos para conocer la info del DataFrame y métodos para hacer esa limpieza y que te quede un DataFrame muy usable.
Algunos de los métodos que aquí utilizo son básicos en pandas y puedes encontrar una explicación detallada en el post Data Analysis con pandas. Primeros pasos.
Ponlo en práctica con un dataset de Kaggle
Para ponerlo en práctica, nada como montarte tu propio DataFrame y hacer un Data Cleaning. Kaggle es una plataforma donde hay multitud de datasets listos para descargar y que puedas trastear tu mismo. Aquí te recomiendo alguno que tiene muchas posibilidades para hacer esa limpieza.
Y puedes encontrar muchos más datasets en Kaggle clasificados por área. Recuerda descargar el archivo .csv.
Yo en este caso te muestro este ejemplo utilizando el dataset Shark attack. Mi proyecto ‘Sharkies’ es público en GitHub y fue el primer proyecto que presenté en IronHack.
¿Por dónde empezar?
Conoce el dataset y tras echarle un vistazo, plantea una hipótesis y redacta el README. Esto guiará bien lo que tienes que hacer, ya que las posibilidades de limpieza y filtrado del dataset son infinitas, así que esto te ayudará a delimitar tu trabajo.
import pandas as pd #Importar la librería
df = pd.read_csv('GSAF5.csv') #Importar el dataset df.columns #Conocer las columnasdf.head() #Ver los primeros resultados
Con estas consultas rápidas puedes conocer qué información contiene el DataFrame y cómo está organizada.
Mi hipótesis: “El mayor número de muertes por ataques a surfistas se produjeron en la costa oeste de Estados Unidos.” Esto delimita la información que buscar en el dataset.
Reducir los datos que se muestran
Si tienes un dataset con muchos registros, lo ideal es que cuando ejecutes una celda no llames al DataFrame al completo, si no que llames a los 5 primeros registros para comprobar que todo ejecuta bien. Así Jupyter Notebook ejecuta mucho más rápido y no se sobrecarga.
Es ideal hacer el Data Cleaning en un documento .ipynb para que puedas ir haciendo comprobaciones visuales de los resultados que obtienes. Más tarde, puedes pasar el código a un archivo .py, pero para checkear los resultados de la aplicación de diferentes métodos, te recomiendo trabajar antes en un Jupyter Notebook. Ayuda mucho a debuggear, es decir, a encontrar errores en el código.
Filtrar el DataFrame
Una hipótesis planteada ayuda a saber qué información buscar y con esto, puedes seleccionar las series del DataFrame que te proporcionarán información para probar o desmontar la hipótesis.
Puedes crear un nuevo DataFrame o sobreescribir el df que ya tenemos.
df = df[['Case Number','Type', 'Country', 'Area', 'Activity', 'Injury', 'Fatal (Y/N)']]
En este caso, opto por la segunda opción sustituyendo al anterior, y que solo contará con la información de las series del original que necesito: Case Number, Type, Country, Area, Activity, Injury y Fatal (Y/N). Así reescribo df seleccionando las columnas que me interesan, tras haberlas explorado.
Este DataFrame es más usable que el inicial, ya que he reducido el número de series de 24 a 7. No solo he reducido el tamaño, también, las posibilidades de tener mucha información disponible que desvíe nuestro foco de estudio.
Los registros duplicados
El objetivo del análisis de datos es hacer un estudio de la información de la que disponemos y se presupone que la recogida de datos o la codificación de los mismos ha sido correcta. Sin embargo, en ocasiones existen registros duplicados, lo que influye directamente en el resultado que vayamos a obtener de nuestro análisis.
Antes de decidir qué hacer, revisa bien los duplicados ¿Son registros duplicados en su totalidad? Comprueba que los registros están o no están duplicados en todas las celdas.
El método .duplicated() devuelve si un registro está duplicado o no (True o False), para conocer cuántos hay, haz una suma o conteo de los mismos. Esto puedes aplicarlo al DataFrame al completo o solo a una serie.
df.duplicated().sum() #Duplicados del DataFrame
df['Case Number'].duplicated().sum() #Duplicados de la serie
Seleccioné la serie Case Number porque es la única serie donde tener registros duplicados puede afectar al estudio.
Para conocer cuáles son los registros duplicados, filtro el DataFrame con los duplicados, selecciono la columna Case Number y lo transformo a una lista con el método .tolist().
duplicates = df[df['Case Number'].duplicated(keep=False)]['Case Number'].tolist()duplicates[:10] #duplicates es una lista no un DataFrame
Como tengo una lista con 16 registros duplicados, la última línea de código es la selección de los 10 primeros elementos de la lista duplicates.
La opción fácil es eliminar un registro. Déjalo como última opción.
Sabiendo que hay 16 Case Numbers repetidos, ahora compruebo que información de esas filas son iguales. En caso de que no sean iguales, ese registro me interesa mantenerlo, por lo que modifico el Case Number.
El proceso es crear un set de la lista duplicates, así solo selecciono un registro. y con un for loop compruebo si en el DataFrame dos registros con el mismo Case Number son iguales. Los métodos .iloc() y .loc() son métodos para localización de registros. El primero funciona sobre el índice; el segundo, sobre etiquetas. Te lo cuento más adelante.
set_duplicates = set(duplicates)for r in set_duplicates:
dup = df[df['Case Number'] == r]
df.loc[dup.iloc[1,:].name, 'Case Number'] = df.loc[dup.iloc[1,:].name]['Case Number'] + 'z'
# Comprobación de que no hay registros duplicadosdf.duplicated().sum()# Devuelve 0
Localizar valores nulos. Eliminar o sustituir
Los valores nulos o ‘NaN’ indican que no hay registro. Ante esto tienes varias opciones: deducir el valor del registro de otra información, completarlo con otro valor o eliminarlo. Eliminar un registro debe ser siempre la última opción. En ocasiones solo falta un registro en una celda, para lo que te recomiendo que revises la fila, para saber si puedes deducir la información. Por ejemplo, si falta el registro del país en una fila, pero tiene la localización del área, puedes deducir en qué país fue. O si falta el registro de actividad, pero en lesión aparece ‘surfista’ o ‘tabla de surf’ puedes saber que la actividad era surf.
df.isnull().sum()
Puesto que estamos buscando los sucesos a surfistas en la costa oeste de Estados Unidos, es importante intentar deducir la Activity para la que no hay registro, para saber si alguna de ellas puede ser surf, antes de volver a filtrar el DataFrame.
La serie Injury puede proporcionar información si indica algo sobre surfer o board. Vemos qué valores toma la serie y qué información ha registrado cuando la Actitivy es nula.
df['Injury'].value_counts()df[(df['Activity'].isnull())]['Injury'].value_counts()
Para bucear en el contenido de una serie que es de tipo string, el método .str() tiene múltiples funciones aplicables útiles para trabajar con el contenido en este formato.
En este caso creo unos filtros, buscando si contiene ‘surfer’, ‘board’ y dejamos fuera los casos en los que se mencione ‘on board’ y ‘off board’.
filter_1 = (df['Injury'].str.contains(' board ', na=False, regex=False))filter_2 = (df['Injury'].str.contains('on board ', na=False, regex=False))filter_3 = (df['Injury'].str.contains('off board ', na=False, regex=False))filter_4 = (df['Injury'].str.contains(' surfer ', na=False, regex=False))newdf = df[filter_1 & ~filter_2 & ~filter_3 & filter_4]# ~se utiliza para negar una condición
También se puede conocer si allá donde el país no está declarado alguna de las áreas son de la costa oeste de Estados Unidos.
df[(df['Country'].isnull()) & (df['Area'].isin(('Alaska', 'Hawai', 'California', 'Oregon', 'Washington')))]
Si ninguno de estos filtros devuelve información útil, puedes completar el registro con el método .fillna(). Y en última instancia si el registro no te aporta información útil, puedes eliminarlo con el método .dropna().
Reducir el DataFrame
Una vez exploradas las series, los registros duplicados, los valores nulos y limpiado en unos pasos básicos el DataFrame, te recomiendo reducirlo a los valores que te serán útiles para el análisis. En este caso, filtro de nuevo por la actividad surf y sobreescribo el DataFrame.
Entre paréntesis se incluye una condición, la columna ‘Activity’ debe ser igual a ‘Surfing’. Puedes encadenar todos los condicionales que necesites con ‘&’ (and) y ‘|’ (or).
df = df[(df['Activity'] == 'Surfing')]
De esta forma reduzco el tamaño del DataFrame, centrándome en información útil y obviando aquello que no es necesario para estudiar la hipótesis.
Con estos pasos ya tenemos un DataFrame listo y usable para comenzar a hacer un análisis en mayor profundidad. En la segunda parte del post, te muestro cómo ordenar, castear, agrupar, crear bins, explorar y separar valores en series. ¡Seguimos!