Pandas Práctico(8): Limpieza de Datos

En un mundo ideal, todos los datos que necesitas estarían disponibles en el formato correcto y con contenido completo.

En el mundo real, probablemente necesitarás obtener datos de muchas fuentes diferentes e incompletas. Por eso es importante aprender a limpiar tus datos antes de analizarlos o pasarlos a un algoritmo de ML.

La limpieza de datos puede no ser la parte más glamorosa de tu trabajo, pero es una parte crucial del desarrollo de productos basados en datos. No solo es importante para todo el pipeline, sino que también es una de las tareas que más tiempo consume en un proyecto (algunos estiman que consume alrededor del 80% del tiempo en un proyecto de aprendizaje automático).

En este artículo, aprenderemos algunas técnicas básicas de limpieza de datos que te permitirán manejar las situaciones más comunes.

¡Genial, comencemos!

Manejo de valores faltantes

El manejo de valores faltantes es una técnica básica (y extremadamente útil). Los datos a los que tendrás acceso probablemente carezcan de atributos de algunas entradas, y es mejor dedicar algo de tiempo a manejar explícitamente cada caso.

Imagina que tienes un archivo csv con el siguiente contenido:

Name,Type,Color,Evolves,HP
Abra,Psychic,,True,
Pikachu,Electric,,True,35
Ekans,,Purple,,35
Dratini,,Blue,,41
Ditto,Normal,Pink,False,48

Como puedes ver, faltan entradas para varias filas. Si lo cargas usando read_csv, el dataframe resultante se verá así:

import pandas as pd
frame = pd.read_csv('./sample_data/pokes_missing.csv')
frame
Name Type Color Evolves HP
0 Abra Psychic NaN True NaN
1 Pikachu Electric NaN True 35.0
2 Ekans NaN Purple NaN 35.0
3 Dratini NaN Blue NaN 41.0
4 Ditto Normal Pink False 48.0

Pandas llena cada entrada sin valor con el valor centinela predeterminado NaN. En muchas situaciones, simplemente quieres eliminar las filas con valores faltantes y dejar solo las que tienen exclusivamente campos no-NaN. Para esto, puedes usar la función dropna:

# dropna no afecta el dataframe original, devuelve uno nuevo.
frame.dropna()
Name Type Color Evolves HP
4 Ditto Normal Pink False 48.0
#Si quieres realizar el dropna en el lugar, especifica el atributo inplace
frame.dropna(inplace=True)
frame
Name Type Color Evolves HP
4 Ditto Normal Pink False 48.0
# Recarguemos los datos:
frame = pd.read_csv('./sample_data/pokes_missing.csv')

Dropna puede usarse de manera diferente: Puedes decirle a la función que elimine solo las filas que están compuestas exclusivamente de valores NaN. De esta manera puedes deshacerte de entradas que no proporcionan información alguna. Para hacer esto, proporciona el parámetro opcional * how='all' al llamar dropna.

Eliminar todas las filas con valores NaN puede no ser la mejor opción. Muy a menudo, la mejor opción es llenar las entradas faltantes con valores que proporcionas o con valores calculados del propio dataframe. Pandas proporciona una función llamada fillna que te permite manejar valores faltantes:

# Llenemos cada entrada faltante con el valor 0
frame.fillna(10)
Name Type Color Evolves HP
0 Abra Psychic 10 True 10.0
1 Pikachu Electric 10 True 35.0
2 Ekans 10 Purple 10 35.0
3 Dratini 10 Blue 10 41.0
4 Ditto Normal Pink False 48.0

¿Notas algo malo con el dataframe? Bueno, para empezar, el valor 10 podría funcionar razonablemente bien para HP, pero no tiene sentido para Type, Color o Evolves. fillna te permite especificar cómo llenar valores NaN para cada columna si pasas un diccionario en lugar de un solo valor:

frame.fillna({'Type': 'Unknown',
              'Color': 'Yellow',
              'Evolves': True,
              'HP': 30})
Name Type Color Evolves HP
0 Abra Psychic Yellow True 30.0
1 Pikachu Electric Yellow True 35.0
2 Ekans Unknown Purple True 35.0
3 Dratini Unknown Blue True 41.0
4 Ditto Normal Pink False 48.0

¡Ahora el dataframe tiene un poco más de sentido! Como con dropna, el cambio realizado por fillna no ocurre en el dataframe original, solo crea un nuevo dataframe con nuevos valores. Si quieres cambiar el dataframe original proporciona el argumento inplace:

frame.fillna({'Type': 'Unknown',
              'Color': 'Yellow',
              'Evolves': True,
              'HP': 30},
             inplace=True)
frame
Name Type Color Evolves HP
0 Abra Psychic Yellow True 30.0
1 Pikachu Electric Yellow True 35.0
2 Ekans Unknown Purple True 35.0
3 Dratini Unknown Blue True 41.0
4 Ditto Normal Pink False 48.0

Manejo de duplicados

A veces tus datos contendrán filas duplicadas o valores con los mismos campos. Echa un vistazo a este dataframe:

frame = pd.read_csv('./sample_data/pokes_duplicates.csv')
frame
Name Type Color Evolves HP
0 Abra Psychic Yellow True 25
1 Pikachu Electric Yellow True 35
2 Ekans Poison Purple True 35
3 Dratini Dragon Blue True 41
4 Ditto Normal Pink False 48
5 Abra Psychic Yellow True 25
6 Dratini Dragon Blue True 41

El frame tiene dos Dratinis y dos Abras, así que eliminémoslos usando drop_duplicates:

frame.drop_duplicates()
Name Type Color Evolves HP
0 Abra Psychic Yellow True 25
1 Pikachu Electric Yellow True 35
2 Ekans Poison Purple True 35
3 Dratini Dragon Blue True 41
4 Ditto Normal Pink False 48

A veces quieres eliminar duplicados basándote en el valor de un atributo. En este caso, puedes proporcionar un argumento extra (o lista de argumentos si quieres usar múltiples atributos) para especificar la columna a tomar en consideración. Por ejemplo, la siguiente llamada asegura que tengamos solo filas con colores únicos:

# Abra y Pikachu son ambos amarillos, así que es hora de elegir Pikachu
frame.drop_duplicates('Color')
Name Type Color Evolves HP
0 Abra Psychic Yellow True 25
2 Ekans Poison Purple True 35
3 Dratini Dragon Blue True 41
4 Ditto Normal Pink False 48

Mapeos y otras transformaciones

En esta sección, aprenderemos sobre algunas otras técnicas para modificar tus datos. La primera técnica es usar la función map para alterar una columna en un dataframe:

frame = frame = pd.read_csv('./sample_data/pokes.csv')
frame
Name Type Color Evolves HP
0 Abra Psychic Yellow True 25
1 Pikachu Electric Yellow True 35
2 Ekans Poison Purple True 35
3 Dratini Dragon Blue True 41
4 Ditto Normal Pink False 48
# Transformemos el tipo en una cadena completamente en mayúsculas
frame['Type'] = frame['Type'].map(lambda x: x.upper())
frame
Name Type Color Evolves HP
0 Abra PSYCHIC Yellow True 25
1 Pikachu ELECTRIC Yellow True 35
2 Ekans POISON Purple True 35
3 Dratini DRAGON Blue True 41
4 Ditto NORMAL Pink False 48

Lo siguiente que podemos hacer es usar la función replace para cambiar valores. En el siguiente ejemplo reemplazaremos los valores booleanos de la columna Evolves por las cadenas ‘Yes’ y ‘No’:

frame['Evolves'] = frame['Evolves'].replace([True, False], ['Yes','No'])
frame
Name Type Color Evolves HP
0 Abra PSYCHIC Yellow Yes 25
1 Pikachu ELECTRIC Yellow Yes 35
2 Ekans POISON Purple Yes 35
3 Dratini DRAGON Blue Yes 41
4 Ditto NORMAL Pink No 48

También podemos usar selección condicional para obtener solo los datos que necesitamos. Supongamos que estamos interesados solo en los elementos con HP menor a 40:

frame[frame['HP'] < 40]
Name Type Color Evolves HP
0 Abra PSYCHIC Yellow Yes 25
1 Pikachu ELECTRIC Yellow Yes 35
2 Ekans POISON Purple Yes 35

Lo último con lo que trataremos es transformar datos categóricos en columnas codificadas one-hot. Si quieres una explicación más detallada de la codificación one-hot puedes leer este artículo:

# get_dummies transforma datos categóricos en un dataframe codificado one-hot
# concatenaremos el dataframe original y las columnas de tipo codificadas one-hot

pd.concat([frame, pd.get_dummies(frame['Type'])], axis=1)
Name Type Color Evolves HP DRAGON ELECTRIC NORMAL POISON PSYCHIC
0 Abra PSYCHIC Yellow Yes 25 0 0 0 0 1
1 Pikachu ELECTRIC Yellow Yes 35 0 1 0 0 0
2 Ekans POISON Purple Yes 35 0 0 0 1 0
3 Dratini DRAGON Blue Yes 41 1 0 0 0 0
4 Ditto NORMAL Pink No 48 0 0 1 0 0

Limpia y limpia

Obtener datos perfectos al comienzo del proyecto es extremadamente improbable. Probablemente, necesitarás juntar un conjunto de datos de muchas fuentes, y en el proceso, podría haber algunos datos malformados, erróneos o faltantes.

Eso está bien siempre y cuando sepas cómo limpiar tus datos y ponerlos en forma adecuada para tus análisis/algoritmos. A menudo se dice que probablemente pasarás el 80% de tu tiempo limpiando y manipulando datos, así que es una buena idea aprender uno o dos trucos.

En el próximo artículo, aprenderemos cómo juntar datos de diferentes fuentes en una sola colección.

¡Gracias por leer!

Qué hacer a continuación

Juan Luis Orozco Villalobos

¡Hola! Soy Juan, ingeniero de software y consultor en Budapest. Me especializo en computación en la nube e IA, y me encanta ayudar a otros a aprender sobre tecnología e ingeniería