Hamburger Icon
Trazando el camino con pandas (II): Simplificando el Análisis de Datos Tabulares en Python

Trazando el camino con pandas (II): Simplificando el Análisis de Datos Tabulares en Python

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.