¿Cómo limpiar datos de un proyecto de transparencia pública?
Enfrentar la limpieza de datos es un paso inevitable y esencial para cualquier profesional de Data Science. Es un desafío constante con el que te cruzarás a lo largo de tu carrera, así que ¡mejor disfrutarlo! En este artículo, trabajaremos con datos provenientes del municipio de Monterrey y utilizaremos herramientas como Python, Pandas y Jupyter Notebook para llevar a cabo este proceso.
¿Cómo comenzamos con la preparación del entorno?
Lo primero es contar con el entorno adecuado para el procesamiento de datos. Aquí tienes algunas opciones:
Jupyter Notebook: Ideal si estás usando Anaconda o una instalación estándar de Python.
Google Colab: Una excelente alternativa si prefieres trabajar en la nube sin configurar un entorno local.
Después de seleccionar tu entorno, crea una carpeta llamada notebooks y dentro de ella inicia un archivo de texto para registrar las librerías necesarias, facilitando la replicación del proyecto en el futuro.
¿Por qué organizar la información en Pandas?
La organización es clave para mantener un flujo de trabajo ordenado. Iniciamos importando las librerías necesarias:
import pandas as pd
import os
Con Pandas, cargaremos las hojas de cálculo que contienen los datos necesarios. Para ello, utilizamos la función read_excel, especificando las hojas requeridas como “gastos varios”, “servicios profesionales”, “comunicación” y “servicios personales”.
Cada hoja se carga en un DataFrame separado, permitiendo un manejo más controlado de la información:
Si notas acciones repetitivas durante la carga y el procesamiento de datos, valdría la pena crear funciones para simplificar y automatizar el proceso, aunque inicialmente optamos por manejar cada hoja manualmente por la baja cantidad de observaciones.
¿Cómo concatenamos los datos en un solo lugar?
Una vez que hemos cargado todas las hojas necesarias, es momento de unificarlas en un solo DataFrame:
Uno de los principales retos durante la limpieza de datos es identificar las filas innecesarias, como aquellos totales que suelen encontrarse en ciertas hojas de cálculo. Usamos dropna() de Pandas para eliminar filas que contengan datos faltantes:
df_completo = df_completo.dropna(how='any')
Esto reduce el conjunto de datos a las entradas realmente válidas.
¿Cómo verificamos la limpieza de datos y tipos de información?
Verificar que los datos estén con el tipo apropiado es crucial para evitar errores en el análisis:
print(df_completo.dtypes)
Verifica también que los meses correspondan al periodo de estudio para confirmar la calidad de los datos. En este caso, confirmamos que todos los registros pertenecen al mes de diciembre.
¿Cómo almacenamos el conjunto de datos limpio?
Guardamos el conjunto de datos procesado y limpio en un formato legible y común como CSV:
Ahora, tu conjunto de datos está listo para análisis futuros. Continúa explorando y modelando la información, abordando cualquier reto que se presente, y comparte tus experiencias con otros aprendices de Data Science. ¡El descubrimiento de conocimientos está a un paso más cerca!
Super. Voy a robarte esta función para mis limpiezasa futuras. Muy chévere
Me tomó un par de días lograrlo (tenía data de gobiernos). Estos fueron algunos retos que tuve y aprendizajes:
Fechas: la mayoría de veces hay que pasar del tipo object al tipo datetime. Un reto especial que encontré fue que se debe usar el parámetro dayfirst=True en el método pd.to_datetime() cuando el formato tiene el día primero. Si no se realiza, las fechas se volverán locas y todo saldrá mal (estuve 3 horas trabajando con la data incorrecta). 😂
Datos negativos y datos erróneos: una buena práctica si se tiene muchos datos es visualizarlos para ver si todo está bien (tip: usa figsize=[12, 8] o más para que puedas visualizar correctamente y no se te escape nada). Aquí encontré datos que no debían ser negativos como negativos y tuve que transformarlos. Además, encontré datasets que en un solo día tenían la mayoría de registros, así que tuve que buscar otros que tengan la información tomada correctamente.
Trasponer: me encontré registros que me hubieran servido mucho como filas, pero estaban como columnas. .transpose() me salvó acá.
Crear nuevos datos: había muchas cosas como datos acumulados y datos agrupados que me servirían mucho, no estaban en la data original, pero hice agregaciones y los fui creando para mi análisis futuro.
Eliminar columnas: así mismo, muchas columnas no me servirían, así que las eliminé y luego exporté los DataFrames procesados. Funciones como .diff() me ayudaron para agregar con la misma columna pero en diferentes filas.
Dividir el trabajo: por último, separé el trabajo en 3 notebooks de limpieza y carga. No sé qué tan buena práctica sea, pero me pareció correcto separarlos por tema (sino hubiera quedado un notebook gigante).
Dios Mio, las fechas son una locura.
Ok Ok, bien hecho.
Cosas de estándares de datos, las fuentes deben de mejorar, pero bien por salir adelante.
Correcto con las operaciones de columnas, me sirvió la forma en la que lo acomodaste para un proyecto personal: Generar, Eliminar y Dividir
He tomado los datasets del año 2019, vienen en archivos CSV para cada mes:
Utilicé la siguiente función para tener un solo DF (para mí fue un reto):
'''
Recibe como parámetro de entrada una lista de archivos CSV,
hace la lectura y concatena los dataframes, siendo esta concatenación el retorno.
Los datos en los archivos CSV deben tener la misma estructura.
'''defdf_builder(data_list): n_files =len(data_list)-1 df_full = pd.read_csv(data_list[n_files])for i inrange(n_files -2): df_i = pd.read_csv(data_list[i]) df_full = pd.concat([df_full, df_i])return df_full
La función toma 1 min y medio aprox en su ejecución, entrega un dataframe de 16.2 M filas x 23 columnas, la cantidad de filas depende del listado de archivos CSV que se tiene.
Realicé una comprobación de la columna AÑO, para asegurar que todos los registros sean del 2019, como sorpresa se encontraron 1072981 veces el año 2020:
Creo que el mio fue un poco mas basico pero es lo que me gusta, es con respecto a las ventas en el mercadona de españa, lo cual le añadi la columna ganancia y la columna mes, porque lo que deseo es saber es comparar mes a mes las ventas de estos para poder ver cuanto es el % que va aumentando o disminuyendo y a partir de ello crear planes de accion para las distintas categorias, como tbn me gusta mas el español, puse todas las columnas en español.
import pandas as pd
df= pd.read_csv('C:/Users/felix/Proyecto/mercadona.csv')# Convertir la columna 'insert_date' en un objeto datetime
df['insert_date']= pd.to_datetime(df['insert_date'])# Crear nuevas columnas para la fecha y la hora
df['Fecha']= df['insert_date'].dt.datedf['Hora']= df['insert_date'].dt.time# Eliminar la columna original 'insert_date'df.drop(columns=['insert_date'], inplace=True)nueva_column ={'id':'id','supermarket':'Mercado','category':'Categoria','name':'Descripcion','price':'Precio_Costo','reference_price':'Precio_Venta','reference_unit':'Unidad','Fecha':'Fecha','Hora':'Hora'}df = df.rename(columns=nueva_column)# Suponiendo que tienes un DataFrame df y quieres agregar la columna "Ganancia" entre las columnas "Precio Costo" y "Precio Venta"ganancia = df['Precio_Venta']- df['Precio_Costo']df.insert(loc=df.columns.get_loc('Unidad'), column='Ganancia', value=ganancia)# Supongamos que tienes un DataFrame df con una columna llamada 'fecha'# Asegúrate de que 'fecha' esté en formato datetime
df['Fecha']= pd.to_datetime(df['Fecha'], format='%d/%m/%Y')# Agregar una nueva columna 'Mes' que contenga el nombre del mes
df['Mes']= df['Fecha'].dt.strftime('%B')df.head()```import pandas as pd
Para no complicarnos depaso convertir en una funcion el proceso de captura y concatenacion de la informaciond de las hojas de excel
import pandas as pd
import os
##guardamos en un dataframe cada hoja de excel y le agregamos una columna sheets con el nombre de la hola
def concadenar_hojas_en_dataframe(path, hojas, skiprow): datas =[]for hoja inhojas: df_data = pd.read_excel(path,sheet_name=hoja,skiprows=skiprow) df_data['SHEETS']=hoja
datas.append(df_data.copy())return pd.concat(datas)##Unimos todas las dataframes
hojas =['GASTOS VARIOS','CONTRATISTAS Y FDO FED','SERV PPROF','COMUNICACION','SERV. PERS.']df_compras_dic =concadenar_hojas_en_dataframe(path='./12_DICIEMBRE_2020.xlsx', hojas=hojas, skiprow=5)print(df_compras_dic.head(5))#print(df_compras_dic.tail(5))print(len(df_compras_dic))df_compras_dic_com = df_compras_dic.dropna(how='any')print(len(df_compras_dic_com))#print(list(df_compras_dic_com['NUM BEN']))print(df_compras_dic_com.dtypes)print(df_compras_dic_com['FECHA'].apply(lambda x: x.month).value_counts())df_compras_dic_com.to_csv('data/clean_compras.csv',index=None)
Nota: lo estoy trabajando en VScode en mi pc
Tendrian que ajustar los path para trabajar en google colab o jupiter etc etc.
Me parece excelente! Gracias por contribuir!!
muchas gracias! por ti pude solucionar los problemas que tenia para avanzar
Estoy trabajando con una base de clientes de una empresa, los features son sus montos de compra y cantidad de transacciones en las diferentes categorías y canal.
Quiero saber, ¿qué clientes comprarán la categoría belleza?
Me estoy enfrentando a una base con muchas variables, tengo muchos outliers y ceros, analizando el problema me doy cuenta que los ceros no deben ser 0 sino nulos ya que son compras y 0 quiere decir que compró cero pero en realidad tiene que ser nulo, por otro lado tener muchos ceros afectan mucho a la distribución de los datos y a las medidas de tendencia central, así que tocó hacer la transformación:
df = df.replace(0, np.nan)
Excluí los outliers con el método de las cajitas, excluyendo a todos los datos por debajo de p25-1.5IQR y por encima de p75+1.5IQR:
new_df = df.copy() # Copiar el dataframe original a uno nuevo
for variable invariables: q25 = np.percentile(df[variable].dropna(),25) q75 = np.percentile(df[variable].dropna(),75) iqr =1.5*(q75 - q25) lower_bound = q25 - iqr
upper_bound = q75 + iqr
exclude_values =(new_df[variable]< lower_bound)|(new_df[variable]> upper_bound) new_df = new_df[~exclude_values]
Solucionado el problema de outliers y ceros. Ahora el detalle es que ya no tengo muchos ceros sino muchos nulos, debido a que cada cliente compra sólo entre 1 y 3 categorías.
He creado el feature 'Belleza' que tendrá las clases (1 y 0) que servirá para hacer los modelos de clasificación.
Ahora me acabo de dar cuenta que las clases están desbalanceadas:
Belleza=1 20%
Belleza=0 80%
¿Cómo podría balancear las clases de belleza con valores nulos?
Utilicé el oversampling, replicando la clase minoritaria de manera aleatoria:
# Separar características y etiquetas
X= muestra_df.drop(['Belleza','CUST_ID'], axis=1)y = muestra_df['Belleza']# Unir características y etiquetas en un único DataFramedf =X.copy()df['Belleza']= y
# Dividir los datos en clases mayoritaria y minoritaria
mayoritaria = df[df['Belleza']==0]minoritaria = df[df['Belleza']==1]# Realizar el sobremuestreo de la clase minoritaria
minoritaria_sobremuestreada =resample(minoritaria, replace=True, n_samples=len(mayoritaria), random_state=42)# Combinar las clases mayoritaria y minoritaria sobremuestreada
df_sobremuestreado = pd.concat([mayoritaria, minoritaria_sobremuestreada])# Separar características y etiquetas del DataFrame sobremuestreado
X_resampled= df_sobremuestreado.drop('Belleza', axis=1)y_resampled = df_sobremuestreado['Belleza']
Y por fin, tengo mis datos listos, sin outliers, sin ceros y balanceados, listos para correr nuestros modelos de clasificación.
Este es mi segundo día tratando de dejar lista esta base, al principio eran sólo números y pensé que iba ser más sencillo pero valió la pena.
Ahora el reto será encontrar los modelos adecuados de clasificación que puedan trabajar con valores nulos. Si tienen alguna sugerencia, les agradecería.
Me emociono mucho tu post, has realizado un buen trabajo al abordar los desafíos de tu base de datos y lograste prepararla para correr modelos de clasificación.
Te propondria, como siguiente paso, trabajar en métodos de feature engineering y selection para expander las combinaciones con las que puedes traerte más información. Inclusive, puedes pensar en enriquecer tu base de datos, te propongo el método de "hypothesis vetting" que puedes encontrar en una búsqueda.
También, puede ser divertido plantear una busqueda de hiperparámetros, e inclusive, porque no, probar deep learning en el!
Muy buen trabajo! espero que encuentres éxito en tus próximos pasos y que sigas disfrutando del proceso. ¡Sigue así y no dudes en buscar más recursos y técnicas para mejorar tus resultados! Si tienes alguna pregunta adicional o necesitas más orientación, no dudes en preguntar. ¡Mucho éxito en tu proyecto!
En mi base de datos encontre los siguientes elementos que tuve que trabajar en su proceso de limpieza:
1. Habian unas columnas que se llamaban departamentos pero estaban vacias y solo aparecian uno que otro número.
Entre la columna de Edad habian varios registros que se llamaban "Total" y tuvieron que ser eliminados a partir de un drop
3. En la columna edad habia un dato llamado sin información que tenia valores nulos y se elimino nuevamente a través de dro.
Varios datos de la edad se repetian y tuveron que ser agrupados para entregar toda la información consolidada.
La limpieza de datos en Power Query es conocida por su interfaz intuitiva y visual, lo que facilita la manipulación de datos sin necesidad de programar. Sin embargo, Python y Pandas ofrecen un mayor control y flexibilidad para realizar tareas más complejas y automatizadas, especialmente en proyectos de ciencia de datos. Cada herramienta tiene sus ventajas dependiendo del contexto del proyecto y las necesidades específicas del análisis. Ambas son útiles y pueden coexistir en tu flujo de trabajo.
Presente la de problemas al comento de crear el objeto pd, y al final le di solución este es mi código por si presentan inconvenientes:
# Encontramos nuestra ruta
ruta_cuaderno = os.getcwd()# Realizamos un join con el path que tenemos más el archivo
ruta_xlsx = os.path.join(ruta_cuaderno,'12_DICIEMBRE_2020.xlsx')# Confirmamos sí esta excelente sino iteramos hasta dar con la ruta
if os.path.exists(ruta_xlsx):print("El archivo existe en la ruta especificada.")else:print("El archivo no existe en la ruta especificada.")# Ahora lo convertimos a un objeto de pandas
df = pd.read_excel(ruta_xlsx)# Visualizamosdf
gracias por compartir! depende de donde tenias el archivo con respecto a tu kernel corriendo!
yo cree una funcion que me devuelva todos los dataframes de una vez en un diccionario (la hice con comprehentions porque ando tambien practicando este tema :D)
Data es la ruta donde estan los datos, skip_rows es la cantidad de filas que debe saltar para leer esos datos y sheets_list es una lista con los nombres de todas las hojas de calculo que quiero transformar en dataframes, de esta forma puedo tener toda la data llamando una sola vez la funcion
#Listwith the name of the sheets on the data file
sheet_names =["GASTOS VARIOS","CONTRATISTAS Y FDO FED","SERV PPROF","COMUNICACION","SERV. PERS."]#Using a function to read and add all sheets into one dictionary
dataframes =read_sheets("./data/raw/12_DICIEMBRE_2020.xlsx",5, sheet_names)
Gracias por compartir!
La lipieza que realice en mi dataset
Manipulación de Valores Numéricos:
Corrección del formato de valores numéricos:La división de las columnas de tasas de cobertura bruta y neta por 100(`data['tasa_cobertura_bruta']= data['tasa_cobertura_bruta']/data['tasa_cobertura_bruta']= data['tasa_cobertura_bruta']/100) asegura que los porcentajes estén representados correctamente si
Normalización de Nombres de Columnas:
Renombrado de columnas:Se utilizó data.rename()para renombrar columnas a nombres más descriptivos y claros, lo que facilita su comprensión y el análisis posterior.Por ejemplo, cambie de 'tasa_cobertura_bruta' a 'cobertura_bruta'.
Otras Acciones:
Verificación y ajuste de variables clave:
Se realizó una revisión para asegurar que las variables clave para el análisis estén en el formato correcto y tengan la precisión necesaria.
Guardar los Datos Limpios:
Guardado de datos limpios:Finalmente,data.to_csv().Este paso es crucial para conservar los datos preparados para análisis poster
AÑOCÓDIGO_DEPARTAMENTODEPARTAMENTOPOBLACIÓN_5_16 \
020115Antioquia1288473120118Atlántico5239352201111Bogotá,D.C.14793343201113Bolívar4966764201115Boyacá300501..............391202291Amazonas23239392202294Guainía16612393202295Guaviare22538394202297Vaupés15585395202299Vichada33949TASA_MATRICULACIÓN_5_16COBERTURA_NETACOBERTURA_NETA_TRANSICIÓN \
094.0193.8570.28199.3299.0550.59290.7090.2968.63391.5791.4059.74486.1686.1163.36...........39177.2577.2454.7339267.6067.6052.0239380.1480.1345.7439455.5155.5137.1939566.9966.9848.58...3944.200.55513955.190.6698[396 rows x 38 columns]Output is truncated.Viewas a scrollable element or open in a text editor.Adjust cell output settings...
Excelente, gracias por compartir!
Anotar valor encima de un gráfico
Les dejo esta función que me sirvió para visualizar los datos exactos en los gráficos de barras. Además de una manera de implementarlo.
def annotate_bars(ax):"""
Anota el valor exacto encima de cada barra en un gráfico de barras."""
for p in ax.patches: ax.annotate(f'{int(p.get_height())}',(p.get_x()+ p.get_width()/2., p.get_height()), ha='center', va='center', xytext=(0,10), textcoords='offset points')
Implementación, ejemplo:
#Distribución de los contratos por año actualizado hasta agosto 9 de 2023(AnnoCargueSECOP):plt.figure(figsize=(10,6))ax = sns.countplot(data=df, x='Anno Cargue SECOP', palette='viridis')plt.title('Distribución de Contratos por Año')plt.ylabel('Número de Contratos')annotate_bars(ax)plt.show()
Resultado:
Gracias por compartir!
No he visto muchos cursos en los que se use jupyter lab, me alegra que este lo tenga
Gracias Diego! seguramente vas a ver varios en la escuela de data science!
!ls no me funcionó ya que estaba manejando windows y se usa !dir
Gracias por el tip!
Yo creé mi base de datos usando web scraping para obtener información de vehículos en venta en MercadoLibre, en mi caso, los retos que encontré a la hora de limpiar los datos fueron:
Cambiar los tipos de datos: variables como el precio, el año y los kilómetros recorridos además de números, tenían otros caracteres, esto lo pude solucionar fácilmente con una función que me eliminaba dichos caracteres y convertía el tipo de dato a entero.
Datos duplicados: Inspeccionando la base de datos encontré que habían registros que tenían los mismos valores para todas las variables, excepto el link de la publicación, a partir de esto descubrí que efectivamente aunque los links fueran diferentes, llevaban a la misma publicación, por lo cual decidí eliminar los duplicados.
Datos Faltantes: la base de datos tenía valores nulos para el color, el tipo de transmisión, la capacidad del motor y el tipo de carrocería. Para la parte de la transmisión y la carrocería les asigné el valor a partir de el modelo de cada registro, el color decidí dejarlo así ya que por ahora no lo considero relevante para mi análisis, (pero lo voy a comprobar a medida que continuo con el proyecto) y la capacidad del motor también quiero corroborar que tan determinante es, ya que la gran mayoría de los registros no la contienen y no puedo determinarla a partir del modelo.
Gracias por compartir! Definitivamente cada problema es un mundo!
siempre me había preguntado que factores determinan en que momentos del año nacen mas personas, entonces estoy limpiando datos del dane que tienen varias variables como el estrato, edad y demás.
Por esto tube que hacer:
descargar los archivos txt con los datos y pasarlos a encoding utf-8
importarlos a pandas donde no pude unirlos porque los títulos de las columnas eran unos con mayúsculas y otros con minúsculas
para que el dataset no fuera tan grande elimine las filas que no tuvieran fecha de nacimiento ya que eran irrelevances para este caso
también me falta trasformar los datos categóricos de números a un valor mas intuitivo
Excelente, Thomas! Te enfrentaste a varios problemas comunes que surgen al trabajar con data real. Genial tu entusiasmo!