Pandas y pitones
Tags: spanish, python, pandas, tutorial
Published: 2024-04-18 12:47:13

---
title: Pandas y pitones
series: Tratamiento de datos con Python
published: true
description: La diferencia entre Pandas y Python puro.
tags: #spanish #python #pandas #tutorial
cover_image: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Grosser_Panda.JPG/1280px-Grosser_Panda.JPG
# Use a ratio of 100:42 for best results.
# published_at: 2024-04-18 10:08 +0000
---

Indirectamente, a través de un vídeo de Youtube, me enteré de un concurso llamado 1brc, es decir, [un billón de filas (*one billion row count*), un concurso sobre programar con Java](https://github.com/gunnarmorling/1brc) la forma más rápida de procesar un archivo de datos CSV.

El concurso ya había terminado, pero me pareció simpático tratar de hacerlo con Python. Y la forma más sencilla sin duda es utilizar Pandas.

Para instalarlo, basta con un `python -m pip install -U pandas` en la línea de comandos.

![Python y Pandas](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lf6niwmldnenqrlql9rf.jpg)

Mediante Pandas, podemos incluso cargar directamente el archivo CSV de datos en el directorio *data/* del repo, creando un **DataFrame**.

```python
import pandas as pd
from datetime import datetime


url = "https://github.com/gunnarmorling/1brc/raw/main/data/weather_stations.csv"
t1 = datetime.now()

# Compile the data
df_temperatures_by_city = pd.read_csv(url)
print(df_temperatures_by_city)
```

Si observamos la salida del programa, tenemos varios pequeños problemas...

```
      # Adapted from https://simplemaps.com/data/world-cities
0      # Licensed under Creative Commons Attribution ...     
1                                          Tokyo;35.6897     
2                                        Jakarta;-6.1750     
3                                          Delhi;28.6100     
4                                      Guangzhou;23.1300     
...                                                  ...     
44687                                      Numto;63.6667     
44688                                       Nord;81.7166     
44689                                Timmiarmiut;62.5333     
44690                                San Rafael;-16.7795     
44691                                    Nordvik;74.0165     

[44692 rows x 1 columns]
```

Si hacemos un pequeño recuento:
1. Existen filas con comentarios que empiezan por el carácter '#'
2. El separador es ';' en lugar del esperado ',' (recordemos que se trata de un archivo CSV o valores separados por comas (comma-separated values).
3. El número de filas no es un billón. Supongo que se trata solo de una muestra del archivo. Bueno, en realidad esto no me afecta, porque solo quiero hacer este pequeño experimento.

Si consultamos la [documentación de Pandas para `read_csv()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html), veremos que para solventar el cambio de delimitador tenemos el parámetro *delimiter*; para evitar los comentarios, podemos establecer '#' como indicador de comentario con `comment='#'`. Finalmente, leyendo la documentación encontraremos adecuado también evitar las líneas en blanco con `skip_blank_lines=True`.

Además, podemos indicarle que queremos utilizar ciertos nombres para las columnas, como *city* para la primera (de tipo cadena de caracteres), y *temperatures* (de tipo número real o **float**), para la segunda. Esto podemos indicarlo con  el parámetro *names* para bautizar las columnas, y con *dtype* para indicar los tipos de dichas columnas. Así, nombraremos las columnas con `names=("city", "temperature")` por un lado, y `dtype={"city": str, "temperature": float}` por el otro.


```python
df_temperatures_by_city = pd.read_csv(url,
                                sep=';',
                                names=("city", "temperature"),
                                dtype={"city": str, "temperature": float},
                                comment='#',
                                skip_blank_lines=True)
print(df_temperatures_by_city)
```

La salida ahora tiene mucha mejor pinta.

```
              city  temperature
0            Tokyo      35.6897
1          Jakarta      -6.1750
2            Delhi      28.6100
3        Guangzhou      23.1300
4           Mumbai      19.0761
...            ...          ...
44686        Numto      63.6667
44687         Nord      81.7166
44688  Timmiarmiut      62.5333
44689   San Rafael     -16.7795
44690      Nordvik      74.0165
```

Nos interesa agrupar estos datos por ciudades, de manera que tengamos todos sus datos de temperatura juntos. Para ello, podemos utilizar el método `groupby("nombre_de_columna")`, que alternativamente puede tomar una lista de columnas si quisiéramos agrupar los datos por varias de ellas.

```python
# Compile the data
df_temperatures_by_city = pd.read_csv(url,
                                sep=';',
                                names=("city", "temperature"),
                                dtype={"city": str, "temperature": float},
                                comment='#',
                                skip_blank_lines=True).groupby("city")
print(df_temperatures_by_city.groups)
```
Tenemos ahora la siguiente lista, aunque no es demasiado autoexplicativa.

```
{'A Coruña': [2689], 'A Yun Pa': [15026], 'Aabenraa': [28671], 'Aachen': [2698], [...]
```

Si queremos mostrar los datos específicos de una ciudad, podemos hacerlo con el método `get_group(city)`. En este caso, tiramos de patria y vamos a mostrar los datos de Coruña, y después los de París.

```python
print(df_temperatures_by_city.get_group("A Coruña"))
print(df_temperatures_by_city.get_group("Paris"))
```

```
          city  temperature
2689  A Coruña      43.3667
        city  temperature
36     Paris      48.8567
23091  Paris      33.6688
39778  Paris      36.2933
40147  Paris      38.2016
43521  Paris      39.6148
```

Mmmm... sé lo que te estás preguntando. Claramente son grados *farenheit*, y convertidos a Celsius serían... Pero mejor que lo haga **Pandas**. Para convertir a grados Celsius desde Farenheit, debemos restar 32, multiplicar por 5, y dividir entre 9, es decir: `(x - 32) * 5) / 9`.

Lo único que tenemos que hacer es decirle a **Pandas** que tome un grupo (`get_group("nombre")`), y de ese grupo se centre en la columna de temperaturas (notación con corchetes: `["temperature"]`, o bien directamente como atributo; `.temperature`), y aplique la función de más arriba (esto se puede hacer con *apply()*, a la que podemos pasarle una lambda, como en `apply(lambda x: (x - 32) * 5) / 9)`.

```python
print(df_temperatures_by_city.get_group("A Coruña").temperature.apply(lambda x: (x - 32) * 5) / 9)
print(df_temperatures_by_city.get_group("Paris").temperature.apply(lambda x: (x - 32) * 5) / 9)
```
Y así obtenemos la salida...

```
2689    6.314833
Name: temperature, dtype: float64

36       9.364833
23091    0.927111
39778    2.385167
40147    3.445333
43521    4.230444
Name: temperature, dtype: float64
```

Nótese que solo estamos viendo la columna de temperatures, el número a la izquierda de cada valor es el número de fila.

Bueno, ya hemos jugado un tanto con **Pandas**. El problema pedía mostrar la temperatura mínima, la media y la máxima con un solo decimal para cada ciudad (ordenado alfabéticamente), en el formato `{A Coruña=43.4/43.4/43.4, Paris=33.7/39.3/48.9,...}`.

Por el momento está claro que debemos construir un diccionario que asocie cada ciudad con el mínimo de temperatura, la media y el máximo. Podemos ya guardarlos como texto con el formato pedido, para ahorrar pasos.

```python
# Build a dictionary with the data
temperatures_by_city = {}
for city, df_group in df_temperatures_by_city:
    temperatures_by_city[city] = f"{df_group.temperature.min(): 3.1f}/" \
                                 f"{df_group.temperature.mean(): 3.1f}/" \
                                 f"{df_group.temperature.max(): 3.1f}"
```

Así, por ejemplo, para la entrada *A Coruña* obtendremos `43.4/43.4/43.4` con `temperatures_by_city["A Coruña"]`, mientras que para *Paris* obtendremos `33.7/39.3/48.9` mediante `temperatures_by_city["Paris"]`.

Ya solo queda mostrar los datos en el formato pedido. Para ello, debemos ordenar las ciudades alfabéticamente, separarlas de sus datos con '=', y separar cada ciudad de la siguiente con una coma (',').

Podemos crear fácilmente una lista de ciudades ordenadas mediante la función `sorted()`: `cities = sorted(temperatures_by_city.keys())`.

De acuerdo, empecemos por formatear ciudades y temperaturas: `"f"{city}={temperatures_by_city[city]}"`. De acuerdo, pero necesitamos recorrer todas las ciudades y crear una cadena de caracteres. Esto podemos hacerlo con comprensión de listas.

```python
str.join(", ",
            (f"{city}={temperatures_by_city[city]}" for city in cities))
```

En lugar de utilizar los corchetes para la comprensión de listas, empleamos un generador con los paréntesis para evitar precisamente crear una lista en memoria, ya que realmente solo necesitamos crear la cadena de caracteres, lo cuál ya es suficientemente memoria empleada.

Así, la parte final del [código completo utilizando Pandas para calcular datos medios de temperaturas](https://ideone.com/I0X0VB) sería:

```python
# Show
cities = sorted(temperatures_by_city.keys())

print("{",
      str.join(", ",
            (f"{city}={temperatures_by_city[city]}" for city in cities)),
      "}",
      sep="")

```

Empleamos el parámetro `sep=""`, que indica qué separador se utilizará entre los valores a mostrar, para conseguir que no se realice ninguna separación.

¡Pandas es muy útil! ¿Qué te ha parecido?