---
# System prepended metadata

title: Big Data. Intro a Pandas.
tags: [' python', ' pandas', DAM]

---

---
title: Big Data. Intro a Pandas.
tags: DAM, python, pandas
---

<div style="width: 30%; margin-left: auto;">
    
![](https://hackmd.io/_uploads/HJiR4eGJT.png)    
    
</div>



<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Llicència de Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br />Aquesta obra està subjecta a una llicència de <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Reconeixement-CompartirIgual 4.0 Internacional de Creative Commons</a>

# BigData. Intro a Pandas.
[Pandas](https://pandas.pydata.org/) és una eina d'analisi i manipulació de dades desenvolupada per a ser utilitzada des del llenguatge de programació Python.

## 10 minutes to pandas
[10 minutes to pandas](https://pandas.pydata.org/docs/user_guide/10min.html) és el tutorial d'introducció oficial per als no iniciats.
### Importació de biblioteques.
Normalment, importarem pandas amb l'alias 'pd' i habitualment també importarem NumPy.
```python=
import numpy as np
import pandas as pd
```
### Estructures de dades en pandas.
Les estructures bàsiques de dades en pandes són les sèries i els dataframes.
Les sèries són vectors unidimensionals i els dataframes són taules bidimensionals.

#### Creació d'objectes.
Per a més informació veure la [introducció a les estructures de dades](https://pandas.pydata.org/docs/user_guide/dsintro.html#dsintro).

Creació d'una sèrie passant-li una llista de dades i deixant que pandas generi un RangeIndex genèric.
```python=
#!/bin/usr/python3

import numpy as np
import pandas as pd

def main():
    print("Crea serie a partir d'una llista:")
    serie = pd.Series([1, 3, 5, np.nan, 6, 8])
    print(f"serie =\n{serie}")
    print("\n" * 2)
    print("Crea DataFrame a partir de diccionari:")
    dades = {
        "Columna A": pd.Timestamp(year=2023, month= 11, day= 20),
        "Columna B": [12, 24, 36, 48, 60],
        "Columna C": 1.0,
        "Altra informació": ["foo", "bar", "foo", "bar", "bar"]
    }
    data_frame = pd.DataFrame(dades)
    print(f" data_frame = \n{data_frame}")
    print(f"{data_frame.dtypes=}")
    
if __name__ == "__main__":
    main()
```

#### Llegint dades de fitxers.
Pandas ens permet obrir fitxers i crear datasets directament desde les seves dades. Per exemple, podem crear un dataset directament des d'un csv amb la funció 'read_csv':
```python=
df = pd.read_csv("./dades/2022_accidents_causa_conductor_gu_bcn_.csv")
```

#### Visualització bàsica de dades.
A continuació es mostra un exemple on utilitzem head i tail per mostrar el començament i el final d'un dataframe, mostrem per pantalla les propietats index i columns, utilitzem la funció 'describe' per obtenir informació estadística bàsica de les diferents columnes, ordenem per un dels camps i seleccionem un rang determinat de files.
```python=
#!/bin/usr/python3

import numpy as np
import pandas as pd

def main():
    df = pd.read_csv("./dades/2022_accidents_causa_conductor_gu_bcn_.csv")
    print(df)
    print("\n"*2)

    print("head")
    print(df.head())
    print("\n"*2)

    print("tail --> 2 línies")
    print(df.tail(2))

    print(f"{df.index=}")
    print(f"{df.columns=}")
    print(f"{df.describe()}")
    print("Ordenació")
    print(f"{df.sort_values(by='Nom_carrer').head(30)}")
    print("Selecció")
    print(df["Nom_carrer"])
    print("Selecció de rang")
    print(df[10:13])
    
if __name__ == "__main__":
    main()
```

#### Selecció
Al exemple anterior hem vist com seleccionar pel nom d'una columna 'df["Nom_carrer"]' i també per un rang de files: 'df[10:13]'. Però la capacitat de selecció de pandas va molt més enllà. Podem realitzar una selecció combinant un rang de files amb una llista de columnes:

```python=
#!/bin/usr/python3

import numpy as np
import pandas as pd

def main():
    df = pd.read_csv("./dades/2022_accidents_causa_conductor_gu_bcn_.csv")
    print(df)
    print("\n"*2)

    print("Selecció")
    print(df["Nom_carrer"])
    print("Selecció de rang")
    print(df[10:13])
    print("Selecció combinada de columnes i files")
    print(df.loc[100:105, ["Nom_districte", "Nom_carrer"]])
if __name__ == "__main__":
    main()
```
Les seleccions també es poden fer per posició de les files i columnes amb la funció 'iloc'.
Per exemple, la comanda: 'df.iloc[300]' per al dataframe que estem tractant, ens donaria la següent sortida:
```
Numero_expedient                                              2022S007721    
Codi_districte                                                              3
Nom_districte                                                  Sants-Montjuïc
Codi_barri                                                                 12
Nom_barri                                          la Marina del Prat Vermell
Codi_carrer                                                            701265
Nom_carrer                  Litoral (Besòs)                               ...
Num_postal                                                            1300000
Descripcio_dia_setmana                                              Divendres
NK_Any                                                                   2022
Mes_any                                                                    12
Nom_mes                                                              Desembre
Dia_mes                                                                    16
Hora_dia                                                                   21
Descripcio_causa_mediata                      Canvi de carril sense precaució
Descripcio_torn                                                         Tarda
Coordenada_UTM_X_ED50                                               430303.25
Coordenada_UTM_Y_ED50                                              4579227.37
Longitud                                                             2.165611
Latitud                                                             41.359692
Name: 300, dtype: object
```
En aquest cas no imprimeix la informació en forma de fila, sino que ho fa en format vertical amb el nom de les columnes a la part esquerra i els valors a la part dreta.

També podem realitzar seleccións basades en rangs de posicions i en llistes de posicions:
```python=
#!/bin/usr/python3

import numpy as np
import pandas as pd

def main():
    df = pd.read_csv("./dades/2022_accidents_causa_conductor_gu_bcn_.csv")
    print(df.head(2))
    print("\n"*2)

    print("Selecció d'una fila.")
    print(df.iloc[300])
    
    print("Selecció de rangs de posicions")
    print(df.iloc[500:503, 8:11])
    print("Selecció de llistes de posicions")
    print(df.iloc[[500, 600, 700], [0, 8, 15]])
    
if __name__ == "__main__":
    main()
```

#### Indexació booleana
Pandas posa al nostra abast funcions per filtrar per condicions que es puguin avaluar a True o False:
```python=
#!/bin/usr/python3

import numpy as np
import pandas as pd

def main():
    df = pd.read_csv("./dades/2022_accidents_causa_conductor_gu_bcn_.csv")
    print(df.head(2))
    print("\n"*2)

    print("Selecció dels accidents de la segona meitat de l'any.")
    print(df[df["Mes_any"] > 6])
    print("Mostrar accidents del messos de Març i de Novembre")
    print(df[df["Mes_any"].isin([3, 11])])
    
if __name__ == "__main__":
    main()
```

#### Combinar
##### Concat
Permet combinar dataframes juntant les files de tots:
```python=
>>> df = pd.DataFrame(np.random.randn(10, 4))
>>> print(df)
          0         1         2         3
0  0.250334 -0.443551  0.988513 -0.787132
1 -1.465765  0.956211 -0.276900  0.411752
2 -0.177937 -1.197951  0.386362 -0.800747
3  1.116690  2.332147 -1.216588  0.319217
4  0.660634 -0.176374  0.319602 -0.780145
5  0.047625  1.350357 -0.417497  0.825456
6  1.483693  1.294452  1.208015  0.389316
7 -0.505037  0.997385  0.782625 -1.533021
8  0.119392  0.510399  1.607561 -0.003989
9  1.013316  0.752841 -0.120283  1.800011
>>> 
>>> pieces = [df[:3], df[3:7], df[7:]]
>>> print(pieces[0])
          0         1         2         3
0  0.250334 -0.443551  0.988513 -0.787132
1 -1.465765  0.956211 -0.276900  0.411752
2 -0.177937 -1.197951  0.386362 -0.800747
>>> print(pieces[1])
          0         1         2         3
3  1.116690  2.332147 -1.216588  0.319217
4  0.660634 -0.176374  0.319602 -0.780145
5  0.047625  1.350357 -0.417497  0.825456
6  1.483693  1.294452  1.208015  0.389316
>>> 
```
##### Merge. [Més informació](https://pandas.pydata.org/docs/user_guide/merging.html#merging-join).
merge() combina per columnes:
```python=
>>> left = pd.DataFrame({"key": ["foo", "foo"], "lval": [1, 2]})
>>> right = pd.DataFrame({"key": ["foo", "foo"], "rval": [4, 5]})
>>> print(left)
   key  lval
0  foo     1
1  foo     2
>>> print(right)
   key  rval
0  foo     4
1  foo     5
>>> print(pd.merge(left, right, on="key"))
   key  lval  rval
0  foo     1     4
1  foo     1     5
2  foo     2     4
3  foo     2     5
>>> 
```
```python=
>>> left = pd.DataFrame({"key": ["foo", "bar"], "lval": [1, 2]})
>>> right = pd.DataFrame({"key": ["foo", "bar"], "rval": [4, 5]})
>>> print(pd.merge(left, right, on="key"))
   key  lval  rval
0  foo     1     4
1  bar     2     5
>>> 

```

#### Agrupar. [Més informació](https://pandas.pydata.org/docs/user_guide/groupby.html#groupby).
El procés d'agrupar implica com a mínim un dels següents processos:
* Separar les dades en diferents grups.
* Aplicar alguna funció a tots els grups de manera independent.
* Combinar els resultats en una única estructura.

```python=
>>> df = pd.DataFrame(
... {
... "A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
... "B": ["one", "one", "two", "three", "two", "two", "one", "three"],
... "C": np.random.randn(8),
... "D": np.random.randn(8),
... })
>>> print (df)
     A      B         C         D
0  foo    one -0.165858  2.053846
1  bar    one  0.020635  1.053649
2  foo    two -0.761409  0.251071
3  bar  three  0.268283 -0.758702
4  foo    two  0.318896 -0.195485
5  bar    two  0.362911 -1.844101
6  foo    one -1.009939  0.325332
7  foo  three  1.714997 -1.549752
>>> 
```
Agrupació per una única columna:
```python=
>>> print(df.groupby("A")[["C", "D"]].sum())
            C         D
A                      
bar  0.651829 -1.549154
foo  0.096687  0.885012
>>> 

```
Agrupant per diverses columnes formant un multi-índex:
```python=
>>> print(df.groupby(["A", "B"]).sum())
                  C         D
A   B                        
bar one    0.020635  1.053649
    three  0.268283 -0.758702
    two    0.362911 -1.844101
foo one   -1.175797  2.379178
    three  1.714997 -1.549752
    two   -0.442513  0.055586
>>> 

```

## Format Parket per a fitxers
Els fitxers Parquet són un format de emmagatzematge de dades columnar. És un format de fitxer dissenyat per emmagatzemar dades de manera eficient, especialment en entorns de big data, gràcies al seu emmagatzematge columnar, compressió i esquema ric.
Un bon lloc per a conèixer millor el format parquet per a fitxers és la [pàgina oficial](https://parquet.apache.org/docs/).

Característiques del format parquet:

* Emmagatzematge Columnar: En lloc d'emmagatzemar les dades per files, com ho fa un format de fitxer típic com CSV, Parquet emmagatzema les dades per columnes. Això significa que els valors d'una columna es guarden junts en lloc dels valors d'una fila.

* Eficiència en Lectura: Aquest enfocament columnar és eficient per a operacions que impliquen llegir només certes columnes de les dades, ja que només s'accedeix a les columnes necessàries, la qual cosa pot reduir el temps de lectura.

* Compressió: Parquet també inclou tècniques de compressió, el que significa que les dades s'emmagatzemen de manera més eficient en termes d'espai en disc.

* Esquema Ric: Parquet emmagatzema metadades sobre les dades en sí, el que significa que té un esquema ric. Això és útil perquè permet emmagatzemar informació sobre el tipus de dades a cada columna, la qual cosa facilita el procés de lectura i anàlisi.

* Optimitzat per a Big Data: Parquet és especialment popular en entorns de processament de dades a gran escala, com Hadoop i Spark. Al ser columnar i comprimit, redueix significativament el temps de lectura i l'espai d'emmagatzematge, la qual cosa és crucial en entorns on es manejen grans quantitats de dades.

Fins i tot el '*Ministerio de Asustos Económicos y Transformación Digital*' té publicada [una pàgina](https://datos.gob.es/es/blog/por-que-deberias-de-usar-ficheros-parquet-si-procesas-muchos-datos) on ens insta a utilitzar aquest tipus de fitxers i ens explica el per què.

Amb pandas podem escriure directament a un fitxer de tipus parquet amb la funció:
```python=
df.to_parquet("./dades/2023_pad_mdbas_edat-1.parquet")
```

I omplir un DataFrame a partir d'un fitxer parquet amb read_parquet:
```python=
df = pd.read_parquet('./dades/2023_pad_mdbas_edat-1.parquet')
```
```
