Si necesitas trabajar con datos en tablas, ya sean hojas de cálculo, bases de datos o documentos en formato CSV, pandas es tu herramienta. Pandas llama a las tablas DataFrame, si quieres puedes consultar esta Introducción a pandas donde te explico un poco más al respecto.
Pandas se presenta como una herramienta esencial para manipular datos tabulares, ya sea en hojas de cálculo, bases de datos o archivos CSV. Al utilizar la estructura de datos DataFrame, pandas simplifica la gestión y análisis de información.
Desde la creación sencilla de tablas hasta la lectura de extensos conjuntos de datos, veremos cómo pandas facilita tareas comunes como filtrar datos, realizar estadísticas descriptivas y trabajar con subconjuntos específicos. Con ejemplos, se exploran diversas funcionalidades, desde los primeros pasos hasta la agrupación inteligente de información.
Primeros pasos con un DataFrame
Supón que quieres guardar la información sobre las personas que forman un equipo. Inicialmente, puedes crear la estructura de datos de forma manual así de fácil:
import pandas as pd people = pd.DataFrame( { "name": [ "Human 1", "Human 2", "Human 3", ], "age": [ "20", "35", "50", ], "has_pets": [ False, False, True ], } ) print(people) """ name age has_pets 0 Human 1 20 False 1 Human 2 35 False 2 Human 3 50 True """
¿Fácil no? Solo un diccionario de listas, donde las claves del diccionario serán las cabeceras de las columnas y los valores de cada lista serán los valores en cada celda.
Como ya se comentó en el anterior post, cada columna en el DataFrame es una Serie. Para acceder a los datos de una Serie podemos hacer esto:
print(people["name"]) """ 0 Human 1 1 Human 2 2 Human 3 """
También puedes crear una Serie de forma directa:
names = pd.Series(["Human 1", "Human 2", "Human 3"], name="Name") print(names) """ 0 Human 1 1 Human 2 2 Human 3 """
Aunque pueda paracer algo inútil ahora, verás que veces te interesará trabajar directamente con Series.
Continuando con el DataFrame que hemos creado, podemos obtener datos básicos como el máximo y mínimo en alguna de sus columnas:
people["age"].max() # 50 people["age"].min() # 20
Y también a datos estadísticos básicos de los datos numéricos que haya en la tabla:
people.describe() """ name age has_pets count 3 3 3 unique 3 3 2 top Human 1 20 False freq 1 1 2 """
Si te das cuenta, el método describe devuelve información en formato tabular. Este y muchos otros métodos en pandas devuelven DataFrames o Series con información.
Lectura y escritura de datos tabulares
Una vez visto lo básico, vamos a subir de nivel. Vamos a analizar datos de crímenes en EEUU entre el 2001 y 2023. Se ha usado este dataset como fuente.
Pandas dispone del método read_csv() que nos permite transformar los datos en un archivo de este tipo a un DataFrame. Se puede conseguir lo mismo con otros formatos mediante el método correspondiente: _read_formato() sustituyendo formato por "excel", "json", "sql"...
Cargaremos el dataset de este modo:
crimes = pd.read_csv("dataset.csv") print(crimes) """ ID Case Number Date Block IUCR ... Year Updated On Latitude Longitude Location 0 11037294 JA371270 03/18/2015 12:00:00 PM 0000X W WACKER DR 1153 ... 2015 08/01/2017 03:52:26 PM NaN NaN NaN 1 11646293 JC213749 12/20/2018 03:00:00 PM 023XX N LOCKWOOD AVE 1154 ... 2018 04/06/2019 04:04:43 PM NaN NaN NaN 2 11645836 JC212333 05/01/2016 12:25:00 AM 055XX S ROCKWELL ST 1153 ... 2016 04/06/2019 04:04:43 PM NaN NaN NaN 3 11645959 JC211511 12/20/2018 04:00:00 PM 045XX N ALBANY AVE 2820 ... 2018 04/06/2019 04:04:43 PM NaN NaN NaN 4 11645601 JC212935 06/01/2014 12:01:00 AM 087XX S SANGAMON ST 1153 ... 2014 04/06/2019 04:04:43 PM NaN NaN NaN ... ... ... ... ... ... ... ... ... ... ... ... 7950971 12131776 JD327752 08/10/2020 08:30:00 PM 066XX S SACRAMENTO AVE 0620 ... 2020 08/17/2020 03:41:32 PM 41.772671 -87.698104 (41.772671069, -87.698104059) 7950972 12082414 JD269218 06/18/2020 09:00:00 PM 093XX S DR MARTIN LUTHER KING JR DR 0820 ... 2020 06/25/2020 03:41:40 PM 41.724546 -87.614211 (41.724546436, -87.614210743) 7950973 12118237 JD311791 07/27/2020 03:02:00 PM 033XX W POLK ST 0486 ... 2020 08/03/2020 03:41:51 PM 41.870921 -87.709461 (41.870920735, -87.709461362) 7950974 12142591 JD340297 08/14/2020 03:00:00 PM 023XX W ROSEMONT AVE 0910 ... 2020 08/26/2020 03:40:41 PM 41.995927 -87.688929 (41.995927389, -87.688928533) 7950975 12002171 JD177406 03/06/2020 02:00:00 PM 033XX N LAKEWOOD AVE 1150 ... 2020 03/13/2020 03:41:29 PM 41.942112 -87.661459 (41.942112495, -87.661458579) """
Como estamos cargando un archivo con mucha información, por defecto, cuando imprimamos el DataFrame pandas nos mostrará las primeras 5 files y columnas junto a las últimas 5. Como comprenderás, es complicado mostrar 8 millones de filas y 22 columnas por consola.
Para facilitar el tutorial, he hecho limpieza y he elimiando las columnas que no me interesan y solo he mantenido 200 filas. Si queremos ver por ejemplo las primeras 6 filas, podemos hacer esto:
crimes = pd.read_csv("dataset.csv", delimiter=";") print(crimes.head(6)) """ Date Block Primary Type Description Location Description Arrest Domestic 0 03/18/2015 12:00:00 PM 0000X W WACKER DR DECEPTIVE PRACTICE FINANCIAL IDENTITY THEFT OVER $ 300 BANK False False 1 12/20/2018 03:00:00 PM 023XX N LOCKWOOD AVE DECEPTIVE PRACTICE FINANCIAL IDENTITY THEFT $300 AND UNDER APARTMENT False False 2 05/01/2016 0:25 055XX S ROCKWELL ST DECEPTIVE PRACTICE FINANCIAL IDENTITY THEFT OVER $ 300 NaN False False 3 12/20/2018 04:00:00 PM 045XX N ALBANY AVE OTHER OFFENSE TELEPHONE THREAT RESIDENCE False False 4 06/01/2014 0:01 087XX S SANGAMON ST DECEPTIVE PRACTICE FINANCIAL IDENTITY THEFT OVER $ 300 RESIDENCE False False 5 09/01/2018 0:01 082XX S INGLESIDE AVE THEFT OVER $500 RESIDENCE False True """
Fíjate como en este caso he especificado el delimitador, ya que he tratado el CSV original con Excel y al guardarlo me ha cambiado el formato (gracias Microsoft).
Si queremos saber el tipo de datos que se almacena en cada columna, usaremos la propiedad dtypes del DataFrame:
crimes = pd.read_csv("dataset.csv", delimiter=";") print(crimes.dtypes) """ Date object Block object Primary Type object Description object Location Description object Arrest bool Domestic bool """
Ahora, imagina que quieres guardar estos datos en otro formato, cualquiera de los que soporta pandas, podrías hacer esto:
crimes = pd.read_csv("dataset.csv", delimiter=";") crimes.to_json("dataset.json")
Esto generará el archivo dataset.json.
Si queremos hacer limpieza de datos, antes quizá nos interesa saber alguna información técnica del DataFrame. Esto lo podemos hacer mediante la función info():
crimes = pd.read_csv("dataset.csv", delimiter=";") print(crimes.info()) """ <class 'pandas.core.frame.DataFrame'> RangeIndex: 198 entries, 0 to 197 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Date 198 non-null object 1 Block 198 non-null object 2 Primary Type 198 non-null object 3 Description 198 non-null object 4 Location Description 180 non-null object 5 Arrest 198 non-null bool 6 Domestic 198 non-null bool dtypes: bool(2), object(5) memory usage: 8.2+ KB None """
Podemos ver por ejemplo los valores no nulos que tenemos, así podemos evaluar la calidad de los datos de forma superficial.
Trabajando con subconjuntos de datos
Puede ser interesante filtrar información en el DataFrame. Imagina que queremos conocer el tipo de crimen y si fue doméstico o no:
crimes = pd.read_csv("dataset.csv", delimiter=";") print(crimes[["Primary Type", "Domestic"]]) """ Primary Type Domestic 0 DECEPTIVE PRACTICE False 1 DECEPTIVE PRACTICE False 2 DECEPTIVE PRACTICE False 3 OTHER OFFENSE False 4 DECEPTIVE PRACTICE False .. ... ... 193 DECEPTIVE PRACTICE False 194 CRIM SEXUAL ASSAULT False 195 DECEPTIVE PRACTICE False 196 BATTERY False 197 OTHER OFFENSE False """
Ahora, imagina que quiero filtrar y quedarme con los crímenes que han conllevado arresto:
crimes = pd.read_csv("dataset.csv", delimiter=";") print(crimes[crimes["Arrest"] == True]) """ Date Block Primary Type ... Location Description Arrest Domestic 12 04/16/2020 05:00:00 AM 005XX W 32ND ST BATTERY ... APARTMENT True False 13 07/01/2020 10:16 081XX S COLES AVE ASSAULT ... STREET True False 19 08/04/2020 20:28 081XX S LOOMIS BLVD WEAPONS VIOLATION ... STREET True False 27 06/02/2020 22:00 042XX S EMERALD AVE THEFT ... STREET True False 28 08/12/2005 23:00 063XX S COTTAGE GROVE AVE INTERFERENCE WITH PUBLIC OFFICER ... SIDEWALK True False 34 09/11/2020 22:44 008XX N TRUMBULL AVE BATTERY ... RESIDENCE True True 44 05/22/2020 10:29:00 PM 0000X N LOREL AVE NARCOTICS ... STREET True False 46 08/30/2018 07:45:00 PM 031XX W HARRISON ST NARCOTICS ... POLICE FACILITY/VEH PARKING LOT True False 47 11/27/2018 08:43:00 PM 0000X E CERMAK RD LIQUOR LAW VIOLATION ... TAVERN/LIQUOR STORE True False 49 08/17/2018 10:15:00 AM 038XX W MONROE ST NARCOTICS ... VEHICLE NON-COMMERCIAL True False 65 08/05/2020 1:00 025XX S MICHIGAN AVE BATTERY ... HOSPITAL BUILDING / GROUNDS True False 72 07/05/2020 17:18 007XX N CENTRAL AVE BATTERY ... STREET True True 117 07/27/2018 02:15:22 PM 069XX S ROCKWELL ST NARCOTICS ... RESIDENCE True False 126 04/08/2014 14:47 050XX W LAWRENCE AVE NARCOTICS ... RESIDENCE True False 147 03/30/2020 11:45:00 AM 100XX S CALUMET AVE BATTERY ... STREET True True 149 08/05/2020 8:06 070XX S GREEN ST CRIMINAL DAMAGE ... STREET True False 169 10/22/2020 05:00:00 AM 045XX S JUSTINE ST CRIMINAL DAMAGE ... RESIDENCE True False 191 05/01/2003 0:01 059XX W WRIGHTWOOD AVE OFFENSE INVOLVING CHILDREN ... APARTMENT True True """
Como puedes ver, podemos filtrar mediante una expresión entre "[" y "]". Podemos unir varias condiciones mediante los operadores lógicos & y | para AND y OR respectivamente, por ejemplo, si queremos los crímenes que han conllevado arresto y además se han producido en la calle:
crimes = pd.read_csv("dataset.csv", delimiter=";") conditions = (crimes["Arrest"] == True) & (crimes["Location Description"] == "STREET") print(crimes[conditions]) """ Date Block Primary Type Description Location Description Arrest Domestic 13 07/01/2020 10:16 081XX S COLES AVE ASSAULT AGGRAVATED - HANDGUN STREET True False 19 08/04/2020 20:28 081XX S LOOMIS BLVD WEAPONS VIOLATION UNLAWFUL POSSESSION - HANDGUN STREET True False 27 06/02/2020 22:00 042XX S EMERALD AVE THEFT $500 AND UNDER STREET True False 44 05/22/2020 10:29:00 PM 0000X N LOREL AVE NARCOTICS POSSESS - HEROIN (WHITE) STREET True False 72 07/05/2020 17:18 007XX N CENTRAL AVE BATTERY AGGRAVATED P.O. - HANDS, FISTS, FEET, NO / MIN... STREET True True 147 03/30/2020 11:45:00 AM 100XX S CALUMET AVE BATTERY DOMESTIC BATTERY SIMPLE STREET True True 149 08/05/2020 8:06 070XX S GREEN ST CRIMINAL DAMAGE TO VEHICLE STREET True False """
Date cuenta que en este caso hemos englobado cada condición entre paréntesis, esto es necesario para que pandas procese correctamente las condiciones.
Agrupando la información
Otra de las muchas funciones que nos proporciona pandas es la capacidad de agrupar los datos por alguna categoría que le indiquemos. Si quisiéramos agrupar los crímenes por su tipo, haríamos esto:
crimes = pd.read_csv("dataset.csv", delimiter=";") print(crimes.groupby("Primary Type")[["Description", "Arrest", "Domestic"]].describe()) """ Description Arrest Domestic count unique top freq count unique top freq count unique top freq Primary Type ASSAULT 7 4 SIMPLE 4 7 2 False 6 7 2 False 5 BATTERY 26 8 DOMESTIC BATTERY SIMPLE 10 26 2 False 21 26 2 True 14 BURGLARY 4 2 FORCIBLE ENTRY 3 4 1 False 4 4 1 False 4 CRIM SEXUAL ASSAULT 3 2 NON-AGGRAVATED 2 3 1 False 3 3 2 False 2 CRIMINAL DAMAGE 12 2 TO VEHICLE 7 12 2 False 10 12 2 False 11 DECEPTIVE PRACTICE 76 15 FINANCIAL IDENTITY THEFT OVER $ 300 48 76 1 False 76 76 2 False 75 INTERFERENCE WITH PUBLIC OFFICER 1 1 OBSTRUCTING JUSTICE 1 1 1 True 1 1 1 False 1 LIQUOR LAW VIOLATION 1 1 LIQUOR LICENSE VIOLATION 1 1 1 True 1 1 1 False 1 MOTOR VEHICLE THEFT 4 1 AUTOMOBILE 4 4 1 False 4 4 1 False 4 NARCOTICS 5 5 POSSESS - HEROIN (WHITE) 1 5 1 True 5 5 1 False 5 OFFENSE INVOLVING CHILDREN 4 4 CRIM SEX ABUSE BY FAM MEMBER 1 4 2 False 3 4 1 True 4 OTHER OFFENSE 19 6 TELEPHONE THREAT 6 19 1 False 19 19 2 False 15 SEX OFFENSE 1 1 CRIMINAL SEXUAL ABUSE 1 1 1 False 1 1 1 True 1 THEFT 33 4 $500 AND UNDER 11 33 2 False 32 33 2 False 32 WEAPONS VIOLATION 2 2 UNLAWFUL POSSESSION - HANDGUN 1 2 2 True 1 2 1 False 2 """
Como puedes ver, aquí usamos varios mecanismos vistos anteriormente para mostrar la información.
Conclusiones
Pandas emerge como una herramienta poderosa y versátil para el análisis de datos tabulares en Python. Desde la creación intuitiva de DataFrames hasta la lectura y escritura de archivos en diferentes formatos, el tutorial destaca la capacidad de pandas para simplificar tareas complejas.
La manipulación eficiente de subconjuntos de datos, la aplicación de condiciones lógicas y la agrupación inteligente ofrecen a los usuarios un control total sobre la información. Con pandas, explorar y analizar datos se convierte en una experiencia accesible, facilitando el camino hacia el descubrimiento de patrones y la toma de decisiones informada.
En el próximo post analizaremos datos de interés a nivel europeo.