Introducció

A la pràctica, les dades sovint s'emmagatzemen en forma de taula, per exemple, un full de càlcul Excel, un fitxer CSV o una base de dades SQL. Imagina’t que necessites analitzar aquestes dades tabulars i obtenir-ne informació útil.

En primer lloc, has de carregar dades de diferents formats conservant la seva estructura tabular i probablement unir diverses taules. Aleshores, per realitzar l'anàlisi de dades real, voldrás accedir a diferents columnes, files i cel·les de les taules, calcular estadístiques generals, crear taules dinàmiques i fer gràfics.

Hi ha una eina a Python que combini totes aquestes funcionalitats? La resposta és sí!

En aquesta activitat treballarem amb pandas, una biblioteca molt potent de codi obert per a la manipulació i anàlisi de dades. Aprendràs a instal·lar la biblioteca i et faràs una idea de la seva funcionalitat principal.

Instal·lació de pandas

pandas no s'inclou a la biblioteca estàndard de Python, per la qual cosa has d'instal·lar pandas amb pip:

$ pip install pandas

Com que pandas fa servir numpy, quan instal.les pandas temabé s’instal.larà numpy. A més, hi ha moltes altres dependències que són opcionals. Per exemple, si vols utilitzar la funcionalitat de visualització de dades de pandas, cal instal·lar la llibreria Matplotlib.

Un cop finalitzada la instal·lació, pots importar la llibreria. Normalment s'abreuja i s'importa com a pd:

import pandas as pd

Tingues en compte que de tant en tant es publiquen noves versions de pandes, amb noves funcionalitats i errors corregits. Per tant, és una bona idea estar atents a les actualitzacions.

Pots actualitzar fàcilment la versió de pandas amb aquesta ordre:

pip install --upgrade pandas.

Creació d'un projecte i un entorn venv amb pandas

Recomanem treballar localment en Python i crear un directori amb el seu venv on hi instal·leu pandas i altres llibreries que necessiteu.

Install virtual environment and create project folder.

$ sudo apt install python3-venv
$ mdkir bio-pandas
$ cd bio-pandas

Activate venv and install dependencies

$ python3 -m venv .venv
$ source .venv/bin/activate
(.venv) $ python3 -m pip install --upgrade pip
(.venv) $ pip install -r requirements-dev.txt

Avantatges de pandas

L’objetiu de pandas és convertir-se en "l'eina d'anàlisi/manipulació de dades de codi obert més potent i flexible disponible en qualsevol idioma".

El nom pandas deriva de "panel data", un terme que s'utilitza en estadístiques i econometria per referir-se a conjunts de dades que contenen observacions de les mateixes persones durant diversos períodes de temps.

Aquesta biblioteca és útil si treballes amb dades tabulars, com ara dades emmagatzemades en fulls de càlcul o bases de dades.

Amb l'ajuda de pandas, pots realitzar fàcilment els passos de processament de dades més típics. En particular, et permet carregar i desar dades de manera molt fàcil, ja que admet la integració immediata amb molts formats tabulars d'ús habitual, com ara .csv, .xlsx, així com bases de dades SQL.

Mirem què més podem obtenir dels pandes!

  • La fusió i unió intuïtives de conjunts de dades permeten combinar fàcilment dades de diferents fonts, mentre que les eines de remodelació flexibles ajuden a construir resums de dades estadístiques.

  • Amb pandas podem editar, ordenar, remodelar i explorar dades tabulars. Per exemple, podeu obtenir tots els valors únics d'una columna només amb una ordre.

  • Els valors que falten a les dades es representen com NaN i es poden manejar fàcilment, per exemple, es poden substituir per algun valor, utilitzant la funcionalitat integrada.

  • bon suport per a sèries temporals i ofereix una àmplia funcionalitat per treballar amb dates i hores indexades.

  • Si instal·leu matplotlib, una biblioteca de traçat de Python, podeu utilitzar el pandas funcionalitat de traçat integrada per fer una trama bàsica a partir de les vostres dades per entendre-les millor.

  • Podeu obtenir informació estadística bàsica sobre les vostres dades amb literalment una línia de codi.

  • Es pot integrar amb altres biblioteques per a l'aprenentatge automàtic, com ara sklearn.

  • Finalment, pandas és un programari de codi obert, que el fa molt popular tant en els dominis acadèmics com comercials.

Fes una ullada a la documentació oficial de Pandas, per descobrir tot el potencial.

Estructures de dades en pandes

Les dues estructures de dades de pandas són Series(1D) i DataFrame(2D).

Series és una matriu unidimensional que emmagatzema elements del mateix tipus de dades.

Cada element emmagatzemat en un objecte Series s'associa amb una etiqueta anomenada index. Per defecte, aquest índex és només una seqüència (0, 1, 2, ...) . Tanmateix, pots utilitzar qualsevol valor personalitzat. Per exemple, quan s'analitzen sèries temporals, les marques de temps (timestamps) normalment s'estableixen com a índexs. Els índexs, així com l'alineació de dades automàtica i explícita basada en ells, són el nucli de pandas.

DataFrame, al seu torn, és una estructura de dades 2D que s'utilitza per representar dades tabulars amb columnes de tipus de dades potencialment diferents. Pots imaginar que un DataFrame és una taula, on cada columna de la taula és un objecte Series. En altres paraules, DataFrame és un contenidor per Series, mentre Series és un contenidor per a elements escalars.

En el exemple que es mostra a continuació, els tres objectes Series emmagatzemen noms, cognoms i edats dels estudiants, mentres que el DataFrame combina aquesta informació en una única taula.

Figura 1: Una il·lustració de com els objectes de la sèrie 3 (nom, cognom i edat) formen un DataFrame amb 3 columnes corresponents

A tenir en compte sobre els DataFrames

  • Cada fila del DataFrame també està associat a un índex.

  • Les taules que representen els DataFrame difereixen lleugerament dels fulls de càlcul amb l’objectiu d’optimitzar els càlculs. Les Series (que equivalen a les columnes del DataFrame) només poden emmagatzemar dades d'un mateix tipus per poder fer operacions molt més ràpid. Això és perquè un objecte Series utilitza una matriu NumPy; per tant, una Series de pandas té la mateixa limitació de tipus de dades que un array de Numpy

  • Tampoc és possible afegir-hi elements a un objecte Series, perquè per poder afegir un elements a un objecte Series s’ha de crear un nou objecte perquè l’array de numpy és de tamany fixe. També si eliminem un element d’un objecte Series es crea un nou objecte. En veritat, de poder es pot, però no s’ha de fer.

  • pandas està dissenyat per preservar la immutabilitat de les dades.

Preguntes tipus test.

1.- Anàlisi del preu de les accions de l'empresa Imagineu que esteu interessats a comprar accions d'una empresa determinada. Abans de prendre la decisió final, vol analitzar el comportament dels preus de les accions d'aquesta empresa al llarg del temps. Heu recopilat les dades següents: data i preu de les accions de l'empresa per a aquest dia. Voleu carregar aquestes dades i analitzar-les amb pandas. Quina estructura de dades de pandas es pot utilitzar per representar-la? Podria haver-hi més d'una resposta correcta. Consell: podeu establir la data com a índex

  1. DataFrame
  2. Series
  3. Array
  4. Aquestes dades no es poden representar en pandes, ja que conté observacions de diferents tipus de dades

2 .- Tipus Series Per què el dataframe Series de pandas només conté valors del mateix tipus?

  1. Les dades de diversos tipus (nombres enters, cadenes i flotants) es poden gestionar simultàniament dins del mateix Series
  2. L’objecte Series no imposa la condició de tenir dades del mateix tipus
  3. Perquè les dades de Series dades s'emmagatzemen en matrius NumPy i el requisit que els elements d'aquestes matrius siguin del mateix tipus de dades

3.- Predicció de preus mòbils Voleu crear un model per predir els preus dels telèfons mòbils segons les seves característiques tècniques. Ara tens dos fulls de càlcul amb característiques tècniques i preus. Quan hauríeu d'utilitzar els pandes?

  1. Combina dos conjunts de dades
  2. Avaluar el rendiment d'un model d'aprenentatge automàtic
  3. Realitzar operacions matemàtiques complicades en matrius
  4. Obteniu estadístiques bàsiques
  5. Corregiu les errades i ompliu tots els valors que falten

4.- Estudiants internacionals Imagineu que treballeu a l'Oficina d'Estudiants Internacionals d'una universitat. La vostra tasca és preparar un breu informe amb dades interessants sobre d'on provenen els estudiants internacionals i quins programes d'estudi van triar. Tens accés a la base de dades de la universitat que conté la següent informació sobre cada estudiant: número d'estudiant, nom, data de naixement, país d'origen i titulació.

Voleu carregar aquestes dades i analitzar-les amb pandas. Quin estructura de dades de pandes utilitzareu per representar-lo?

  1. DataFrame
  2. Aquestes dades no es poden representar en pandes, ja que conté observacions de diferents tipus de dades
  3. Array
  4. Series
  5. Múltiples Series separades

5 .- Què són els pandes ? Quines de les afirmacions següents són certes? Seleccioneu totes les opcions correctes.

  1. pandas proporciona funcionalitat per carregar, manipular i analitzar dades
  2. pandas forma part de la biblioteca estàndard de Python
  3. pandas és una biblioteca gratuïta i de codi obert
  4. pandas està construït a sobre de NumPy
  5. pandas és una biblioteca d'aprenentatge automàtic

6.- Estructures de dades en pandes Quins són els noms de les estructures de dades suportades actualment a pandas? Seleccioneu totes les opcions correctes.

  1. DataFrame
  2. Panel
  3. Series
  4. DataTable

7.- La funcionalitat de pandes Seleccioneu les accions que es poden realitzar amb la biblioteca pandes:

  1. Exploració i edició de les dades tabulars
  2. Neteja de dades (com ara la gestió dels valors que falten)
  3. Combinació de dades de diverses fonts
  4. Realització d'actualitzacions de bases de dades directament

8.- Instal·lació de pandes

Relaciona les ordres i les seves funcions:

  1. pip install pandas
  2. import pandas as pd
  3. pip install --upgrade pandas

A. Instal.lar una nova versió de pandes B. Instal.lar pandes C. Importar pandes al codi

9.- En el seu lloc Per què és més preferible crear un objecte nou que modificar l'objecte original en pandas?

  1. Perquè la creació d'un objecte nou garanteix un processament més ràpid
  2. Perquè ajuda a depurar el codi i evitar comportaments inesperats
  3. Perquè un objecte nou utilitza menys memòria

1: 1, 2;
2: 3; 3: 1, 4, 5 ; 4: 1; 5: 1, 3, 4; 6: 1, 3; 7: 1, 2, 3; 8: 1-B, 2-C, 3-A; 9: 2;


Series

Els DataFrame es construeixen mitjançant un conjunt d’objectes Series, i normalment treballes amb l’objecte DataFrame. Per això no cal que coneguis en profunditat l’objecte Series.

Abans de començar, no oblidis que has d'importar pandas per poder utilitzar totes les seves funcionalitats, inclòs Series. Normalment, el nom de la biblioteca s'abreuja com pd a la declaració d'importació:

Pots notar que cada element emmagatzemat en a Series s'associa amb una etiqueta anomenada index. Per defecte, aquest index és només la seqüència 0, 1, 2, ... . Tanmateix, es pot utilitzar qualsevol valor personalitzat. Per exemple, podem emmagatzemar les edats dels alumnes en un objecte Series, i establir els noms dels estudiants com a identificadors de fila de manera que sabem a quin alumne correspon cada edat:

Està molt bé, però com es crea un objecte Series?

Hi ha diverses maneres de fer-ho.

Conversió d'altres estructures de dades a Sèries

Si les dades ja estan emmagatzemades en una altra estructura de dades (llistes, diciconaris), pots convertir-les fàcilment a un objecte Series com es mostra a continuació:

import pandas as pd

ages_list = [21, 20, 25, 22]
names_list = ['Anna', 'Bob', 'Maria', 'Jack']

ages_series_int8 = pd.Series(ages_list, index=names_list, name='Age', dtype='int8')
print(ages_series_int8)
# Anna     21
# Bob      20
# Maria    25
# Jack     22
# Name: Age, dtype: int8

Aquí, convertim una llista d'edats dels estudiants ages_list en a un objecte Series ages_series_int8. També assignem un índex personalitzat, els noms dels estudiants emmagatzemats a la llista names_list, utilitzant la paraula clau index. Si no proporcionem els valors del index, s'assignarien els valors 0, 1, 2, 3.

Tambè podem especificar el tipus de dades a int8 per estalviar memòria. Com a Numpy, el valor predeterminat que agafa si li posem números és int64. Però si hi hagués una persona de més de 127 anys tindries un error!

Finalment, també podem donar el nostre objecte Series un nom especificant el paràmetre opcional name.

De la mateixa manera, pots convertir un diccionari de Python en un objecte Series. Tingues en compte que les claus del diccionari (els noms dels estudiants a l'exemple següent) es convertiran automàticament en els índexs del nou objecte Series. Genial, oi?

student_ages_dict = {'Anna': 21, 'Bob': 20, 'Maria': 25, 'Jack': 22}
ages_series = pd.Series(student_ages_dict, name='Ages')

print(ages_series)

# Anna     21
# Bob      20
# Maria    25
# Jack     22
# Name: Ages, dtype: int64

Sempre pots canviar més tard l'índex modificant l'atribut de l'índex Series:

ages_series.index = ['A', 'B', 'M', 'J']
print(ages_series)

# A    21
# B    20
# M    25
# J    22
# Name: Ages, dtype: int64

Per descomptat, la longitud de les dades i l'índex que proporcioneu han de coincidir. En cas contrari, es produirà un error.

Sèries amb dates com a índex.

És interessant veure com crear una sèrie amb dates com a índex (index) i strings com a valors (data).

dates = pd.date_range("20240321", periods=6, freq='D')
places = ['Sarria','Portomarín','Palas Do Rei','Arzúa','O Pedrouzo','Santiago']
s2 = pd.Series(data=places,index=dates)
print(dates)
print(s2)

Els dataFrames també permeten els mateixos index que les Series: números, textos, dates ...

Edició de valors d'elements d'un objecte Serie

Pots canviar fàcilment els valors emmagatzemats a un objecte Series, per exemple, accedint-hi per index. Per il·lustrar-ho, actualitzem l'edat de Jack:

student_ages_dict = {'Anna': 21, 'Bob': 20, 'Maria': 25, 'Jack': 22}
ages_series = pd.Series(student_ages_dict, name='Ages', dtype='int16')
ages_series['Jack'] = 23
print(ages_series)

# Anna     21
# Bob      20
# Maria    25
# Jack     23
# Name: Ages, dtype: int64

Al mateix temps, l’objecte Series és immutable respecte la mida; un cop creat, no s'hi poden afegir ni eliminar elements. Es fa d'aquesta manera a propòsit, per emmagatzemar de manera eficient un objecte Series en memòria.

Inserció / esborrat elements d'un objecte Serie

Però què has de fer si necessites afegir o suprimir alguns valors a/des de a Series? No us preocupeu: encara és possible afegir i eliminar elements. Tanmateix, el resultat d'aquestes operacions serà un nou objecte Series.

Per exemple, eliminem el registre de la Maria del nostre Series. Això es pot fer amb el mètode drop:

new_ages_series = ages_series.drop(index='Maria')
print(new_ages_series)
# Anna    21
# Bob     20
# Jack    23
# Name: Ages, dtype: int64

Tingueu en compte que l’objecte original Series no s’ha modificat:

print(ages_series)
# Anna     21
# Bob      20
# Maria    25
# Jack     23
# Name: Ages, dtype: int64

Si vols que enlloc de retornar un nou objecte, es reemplaci l’array de numpy intern, pots utilitzar l’argument opcional inplace:

ages_series.drop(index='Maria', inplace=True)
print(ages_series)

# Anna    21
# Bob     20
# Jack    23
# Name: Ages, dtype: int64

Per afegir nous registres Series, es pot especificar explícitament el valor del nou índex. Afegim el registre de Maria de nou al ages_series:

ages_series['Maria'] = 25 
print(ages_series)

# Anna     21
# Bob      20
# Jack     23
# Maria    25
# Name: Ages, dtype: int64

Si voleu afegir més d'un registre a a Series, serà més ràpid fer-ho amb pd.concat():

new_recs = pd.Series(data={'Jon': 34, 'Peter': 23, 'Karo': 45, 'Abby': 25},dtype='int16')
ages_series = pd.concat([ages_series, new_recs])
print(ages_series)

# Anna     21
# Bob      20
# Jack     23
# Maria    25
# Jon      34
# Peter    23
# Karo     45
# Abby     25
# Name: Ages, dtype: int64

Operacions en sèries

Operacions en sèrie La característica clau de pandas és que podem fer operacions entre diverses Series en que les dades s’alineen automàticament en funció de l'índex.

Imaginem que en tenim dos Series, algebra i calculus, que conté els resultats dels exàmens dels estudiants dels cursos d'Àlgebra i de Càlcul respectivament:

Exam1Serie

Anna 90 Bob 50 Maria 100 Jack 90

Exam2Serie

Anna 100 Bob 90 Jack 70 Maria 80

Suposem que volem calcular la puntuació mitjana que van obtenir els estudiants als dos exàmens.

average = 0.5 * (algebra + calculus)
print(average)

# Anna     75.0
# Bob      90.0
# Jack     80.0
# Maria    90.0
# dtype: float64

Tingueu en compte que l'ordre dels alumnes en aquests dos Series és diferent i, tanmateix, les mitjanes es calculen correctament. Molt convenient!

D'acord, però què passa si alguns índexs d'una Series no existeixen en l'altre Series? Imaginem que també hi havia el tercer examen de Probabilitat, però només el van fer l'Anna i el Bob. Els resultats s'emmagatzemen a la Series probability:

Què passarà si intentem calcular la mitjana dels tres?

average = 0.33 * (algebra + calculus + probability)
print(average)
# Anna     82.5
# Bob      92.4
# Jack      NaN
# Maria     NaN
# dtype: float64

Com pots veure, el resultat d'una operació entre elements no alineats d’un objecte Series és la unió dels índexs implicats. Si no es troba cap etiqueta en un dels operands, el resultat es marcarà com a valor que falta NaN.

Escriure codi sense fer cap alineació explícita de dades us ofereix molta flexibilitat en l'anàlisi de dades. L'alineació de dades integrada en pandas és el que diferencia la biblioteca de la majoria d'eines relacionades per treballar amb dades etiquetades.

Exercicis de sèries.

1.- Resumint dues sèries Imagineu que tenim dues sèries, series_a i series_b. Aquí estan: A 1 B 2 C 3 Nom: series_a, dtype: int64 C 1 B 2 D 3 Nom: series_b, dtype: int64

Ara definim series_c com a suma de series_a i series_b: series_c = series_a + series_b

Quin seria l’objecte series_c resultant?

2.- Eliminació d'un registre de la sèrie Quin dels mètodes següents retorna un objecte Series amb les etiquetes d'índex especificades eliminades? del() delete() drop() remove()

3.- Convertir una llista a Series Suposem que la llista foods conté noms de diferents aliments, i calories és una llista que conté el contingut calòric corresponent per 100 g.

Defineix una funció que crea i retorna un objecte Series que emmagatzema valors del la llista calories fent servir els noms de foods com a índex. A més, defineix el nom de la sèrie com Calorie content.

import pandas as pd
def create_series(foods, calories):
	# write your code here ....

Tingueu en compte que NO cal que imprimiu el resultat. Per exemple, suposem que:

foods = ['bagel', 'pasta', 'rice']
calories = [310, 110, 140]

Aleshores, la vostra funció hauria de retornar el següent Series:

bagel    310
pasta    110
rice     140
Name: Calorie content, dtype: int64

4.- Índex en Series.

Considereu la següent sèrie que conté informació sobre els guanyadors del Festival de la Cançó d'Eurovisió durant els primers cinc anys de la seva existència:

1956 Suïssa 1957 Països Baixos 1958 França 1959 Països Baixos 1960 França dtype: object

Quin és l'índex aquí?

  1. "Suïssa", "Països Baixos", "França", "Països Baixos", "França"
  2. 0, 1, 2, 3, 4
  3. 1956, 1957, 1958, 1959, 1960
  4. Aquest objecte Series no pot existir ja que Series només pot emmagatzemar dades del mateix tipus.

5.- Eliminació d'elements de la sèrie

Imagina que tens un Series amb el nom olympics que emmagatzema els noms de les ciutats que acullen els Jocs Olímpics d'estiu entre el 2000 i el 2020. L'índex s'estableix en l'any dels jocs (com a nombre enter) i els noms de les ciutats es representen com a cadenes.

Aquí teniu un fragment del Series:

2000            Sydney
2004            Athens
...
2020             Tokyo
dtype: object

Tanmateix, els jocs del 2020 es van cancel·lar a causa del brot de COVID-19.

Defineix una funció que suprimeixi la fila corresponent del fitxer olympics i retorna l’objecte resultant Series.

import pandas as pd
def drop_record(olympics):

6.- Convertir un diccionari en sèrie

Suposem que tens un diccionari capitals que emmagatzema capitals de diferents països (els noms dels països són claus i els noms de les capitals són valors).

Defineix una funció que crea i retorna un objecte Series que emmagatzema valors del capitals amb noms de països com a índexs. A més, estableix el nom del Series com Capitals of the world. Al final, retorneu el resultat Series.

Per exemple, suposem que:

capitals = {'Czech Republic': 'Prague', 
            'Russia': 'Moscow', 
            'Australia': 'Canberra'}

Aleshores, la vostra funció hauria de retornar el següent Series:

Czech Republic      Prague
Russia              Moscow
Australia           Canberra
Name: Capitals of the world, dtype: object

7.- Crear una sèrie amb un índex temporal.

Crea una sèrie que tingui com a índex el primer dia de cada mes en format data, durant 6 mesos. Els valors del dataframe seran 6 números reals que representin la temperatura dels 6 mesos de l'any.

1: A NaN B 4 C 4 D NaN; 2: 3; 4: 3;

3.-

import pandas as pd

def create_series(foods, calories):
   series = pd.Series(calories, foods, name="Calorie content")
   return series

foods = ["bagel", "pasta", "rice"]
calories = [310, 110, 140]

df = create_series(foods, calories)
print(df)

5.-

import pandas as pd
def drop_record(olympics, year):
   olympics = olympics.drop(index=year)
   return olympics

years = ["Atenas", "Sydney", "Athens", "Tokyo"]
ages= [1940, 2000, 2004, 2020]
series = pd.Series(data=years, index=ages)
print(series)
series = drop_record(series, 2020)
series = drop_record(series, 1940)
print(series)

6.-

import pandas as pd
capitals = {"Czech Republic": "Prague", "Russia": "Moscow", "Australia": "Canberra"}

series = pd.Series(capitals, name="Capitals of the world")
print(series)

7.-

dates = pd.date_range("20240101", periods=6, freq='M')
temps = [9.5,8.7,14.3,21.6,19.4,25.2]
s2 = pd.Series(data=temps,index=dates)
print(s2)

DataFrames

Ja estàs familiaritzat amb Series, una estructura de dades unidimensional en pandas. En aquest tema, coneixeràs una altra estructura de dades clau de pandas, que s'anomena DataFrame.

DataFrame és una taula amb columnes. De la mateixa manera que cada element d’un objecte Series està etiquetat amb un índex, cada fila d'un DataFrame està etiquetat amb un índex.

Aquí teniu un exemple d'un objecte DataFrame students que emmagatzema informació sobre quatre estudiants:

+----+--------------+---------------+-------+
|    | First Name   | Family Name   |   Age |
|----+--------------+---------------+-------|
|  0 | Anna         | Smith         |    21 |
|  1 | Bob          | Jones         |    20 |
|  2 | Maria        | Williams      |    25 |
|  3 | Jack         | Brown         |    22 |
+----+--------------+---------------+-------+

Aquest DataFrame té tres columnes, és a dir First Name, Family Name, i Age. Les quatre files estan etiquetades amb índexs 0, 1, 2, 3. D'acord, però com el creem?

Creació d'un DataFrame: lectura de dades d'un fitxer

Sovint voldreu utilitzar les dades d'un fitxer emmagatzemat al vostre ordinador. pandas té funcions que et permeten fer-ho.

Un dels formats de text més populars és .csv, que significa valors separats per comes. Aquest format pot emmagatzemar dades tabulars: cada fila d'un fitxer representa una fila d'una taula, i els valors corresponents a diferents columnes estan separats per comes.

Suposem que les dades sobre els estudiants s'emmagatzemen al fitxer students.csv, el contingut del qual es mostra a continuació:

First Name,Family Name,Age
Anna,Smith,21
Bob,Jones,20
Maria,Williams,25
Jack,Brown,22

Per moure les dades dels estudiants a un DataFrame, pots utilitzar la funció read_csv() de pandas. Aquesta funció pren el camí al fitxer i alguns arguments addicionals que poden ser útils per llegir les dades correctament.

Si volem llegir el fitxer tal com és, simplement podem escriure:

import pandas as pd
students = pd.read_csv('students.csv')
print(students)
+----+--------------+---------------+-------+
|    | First Name   | Family Name   |   Age |
|----+--------------+---------------+-------|
|  0 | Anna         | Smith         |    21 |
|  1 | Bob          | Jones         |    20 |
|  2 | Maria        | Williams      |    25 |
|  3 | Jack         | Brown         |    22 |
+----+--------------+---------------+-------+

No enumerarem tots els paràmetres addicionals de read_csv, però aquí hi ha els més essencials:

  • sep— el delimitador que s'utilitza (per defecte ',').
  • header— el número de fila que emmagatzema les capçaleres de columna. Per defecte, pandas intenta inferir-los de la primera fila.
  • names- una llista de noms de columnes. Si voleu utilitzar altres noms de columnes, configureu header=0 i passar una llista de noms de columnes noves amb names.
  • index_col— columnes del fitxer que s'utilitzen com a etiquetes de fila del fitxer DataFrame. Està a punt None per defecte i els números de fila s'utilitzen com a índexs.
  • usecols— una llista de números de columnes o noms de columnes que cal llegir. Per defecte, el DataFrame llegeix totes les columnes.
+----+--------+-------+
|    | name   |   age |
|----+--------+-------|
|  0 | Anna   |    21 |
|  1 | Bob    |    20 |
|  2 | Maria  |    25 |
|  3 | Jack   |    22 |
+----+--------+-------+

Tornem a llegir el mateix fitxer, però aquesta vegada només fem servir la primera i l'última columna, donant-los noms diferents:

students = pd.read_csv('students.csv', usecols=[0,2], header=0, names=['name', 'age'])
print(students)

Per llegir dades d'una base de dades SQL, necessitareu la llibreria pandas i la llibreria sqlalchemy instal·lada. Podeu utilitzar la funció pd.read_sql() amb una consulta SQL per obtenir dades de la base de dades.

import pandas as pd
from sqlalchemy import create_engine

engine = create_engine('database_connection_string')
query = "SELECT * FROM table_name"
df = pd.read_sql(query, engine)

Queda fora de l'abast d'aquest tutorial provar un exemple real. Haurà de ser un posterior i més avançat.

Podeu utilitzar la funció read_excel() per llegir les dades d'un full de càlcul. Té una interfície similar, però només llegeix fitxers .xlsx.

Per llegir un fitxer JSON, utilitzeu read_json() en canvi. El llistat de formats que permet tractar Pandas és força ampli.

Guardar DataFrame a fitxer: escriptura de dades.

Hi ha un (diversos) mètodes anàlegs als que hem vist per guardar els resultats dels nostres dataFrames a fitxers; i comencen per to. Veiem com funciona el to_csv

El fitxer csv contindria:

First Name,Family Name,Age
Anna,Smith,21
Bob,Jones,20
Maria,Williams,25
Jack,Brown,22

El codi que usarem per llegir el fitxer i escriure'n un de nou.

import pandas as pd

# Llegim fitxer csv a dataframe.
df_students: pd.DataFrame = pd.read_csv("students.csv")
print(df_students.info())

# Canviem dades de la primera estudiant.
df_students['Family Name'][0]="Smint"
df_students['Age'][0]=19
print(df_students.head())

# Escrivim dataframe cap a fitxer csv a pandas
df_students.to_csv("new_students.csv",index=False)

En principi s'haura de crear el nou fitxer amb el contingut; i el que es guarda és el següent (hem canviat cognom i edat de la primera estudiant).

El paràmetre index=False l'hem posat perquè no ens cal guardar l'index automàtic del DataFrame.

First Name,Family Name,Age
Anna,Smint,19
Bob,Jones,20
Maria,Williams,25
Jack,Brown,22

Provem altres exemple de fitxer CSV.

Amb tots els tipus de dades, podeu especificar la manera de llegir-los amb paràmetres com ara sep, header i index_col.

Baixem un fitxer csv amb informació sobre diferents cotxes:

from urllib.request import urlretrieve
import os.path
import pandas as pd

url: str = "https://gitlab.com/xtec/bio-pandas/-/raw/main/data/cars.csv"
csv_file: str = "cars.csv"

if not os.path.isfile(csv_file):
   print(f"Descarregant fitxer {csv_file}")
   urlretrieve(url, csv_file)

df_cars: pd.DataFrame = pd.read_csv(csv_file, sep=',')
print(df_cars)
           Car Name  Price Condition  Year Fuel Type
0    Toyota Corolla  25000       New  2023  Gasoline
1       Honda Civic  22000      Used  2021  Gasoline
2      Ford Mustang  35000       New  2023  Gasoline
3  Chevrolet Camaro  40000      Used  2020  Gasoline
4     Tesla Model 3  50000       New  2023  Electric
5            BMW X5  60000      Used  2022  Gasoline
6           Audi A4  30000       New  2023    Diesel

La crida os.path.isfile(csv_file) descarregarà el fitxer només si no existeix al nostre directori.

La crida urlretrieve de la llibreria urllib ens permet descarrgar fitxers penjats públicament a Internet (com el del nostre repositori Gitlab).

Podeu provar que només descarrega el fitxer si no hi ha cap que es digui igual.

Si voleu tenir més informació sobre la lectura de fitxers a Python, consulteu la nostra guia.

sep(delimitador): El paràmetre sep s'utilitza quan es llegeixen fitxers amb delimitadors personalitzats.

Per exemple, si teniu un fitxer CSV amb un delimitador diferent com un punt i coma ';', podeu especificar-ho amb el paràmetre sep. En el nostre exemple, les columnes es separen mitjançant ','. El podem configurar com a paràmetre.

El paràmetre header us permet especificar quina fila del fitxer de dades s'ha de considerar com a capçalera (noms de columnes). De manera predeterminada, està establert a 0, indicant la primera fila com a capçalera.

Utilitzem la tercera fila com a capçalera.

df_cars = pd.read_csv('./cars.csv', header=3)

      Ford Mustang  35000   New  2023  Gasoline
0  Chevrolet Camaro  40000  Used  2020  Gasoline
1     Tesla Model 3  50000   New  2023  Electric

El paràmetre index_col specifica quina columna s'ha d'utilitzar com a índex del DataFrame.

Pot prendre un nombre enter o de columna com a valor. En el nostre exemple, podem configurar el Car Name com a columna d'índex.

df_cars = pd.read_csv('./cars.csv', index_col='Car Name')

               Price Condition  Year Fuel Type
Car Name                                       
Toyota Corolla  25000       New  2023  Gasoline
Honda Civic     22000      Used  2021  Gasoline

Importació i exportació taules HTML.

Importar taules HTML

Una altra utilitat interessant és importar taules HTML; ja que no sempre disposarem les taules en CSV. Anem a veure el següent exemple:

Scraping Wikipedia table with Pandas using read_html() - GeeksforGeeks

import pandas as pd
import numpy as np

dfs = pd.read_html('https://en.wikipedia.org/wiki/Demographics_of_India',
 match='Population distribution by states/union territories')

my_df = dfs[0].head()
print(my_df.dtypes)
print('Total population = ',my_df['Population[59]'].sum())

Per a què us funcioni, instal·leu lxml:

pip install lxml

Aquesta tècnica d’obtenir dades es coneix com a web scrapping. Només s’ha d’utilitzar per a obtenir dades obertes i públiques que no han estat publicades amb formats com CSV o JSON; sinó ens podem trobar amb problemes ètics i legals.

Apart de tenir el compte si les dades de la web són obertes, també cal tenir en compte que l’estat d’aquestes taules no és tan estable que d’altres fonts (CSV, JSON…), ja que el disseny web sol canviar més sovint que el back-end; així que no podem confiar que sempre hi podrem accedir, caldrà decidir guardar-les als nostres servidors o usar altres fonts.

Exercici. Obtenir la taula HTML i guardar-la en un fitxer CSV.

import os.path
import pandas as pd
import numpy as np
from urllib.request import urlretrieve

url = 'https://en.wikipedia.org/wiki/Demographics_of_India'
html_file = "demo-india.html"
csv_file ="demo-india.csv"

if not os.path.isfile(csv_file):
    urlretrieve(url,html_file)
   
    dfs = pd.read_html(html_file,
        match='Population distribution by states/union territories')
    df = dfs[0]
     
    # Using drop() function to delete last row
    df = df.head(df.shape[0] -1)

    df.to_csv(csv_file, index=False)

Informació útil del DataFrame: head(), info(), describe().

Primer, creem un exemple de frame de pandas amb informació sobre els cotxes: els seus noms, preus i si estaven trencats.

import pandas as pd
data = {
    'Car Name': ['Toyota Corolla', 'Honda Civic', 'Ford Mustang', 'Chevrolet Camaro', 'Tesla Model 3', 'BMW X5', 'Audi A4'],
    'Price': [25000, 22000, 35000, 40000, 50000, 60000, 30000],
    'Is Broken': [False, True, True, False, True, False, False]
}

df_cars = pd.DataFrame(data)
print(df_cars)
          Car Name  Price  Is Broken
0   Toyota Corolla  25000      False
1      Honda Civic  22000       True
2     Ford Mustang  35000       True
3  Chevrolet Camaro  40000      False
4    Tesla Model 3  50000       True
5           BMW X5  60000      False
6          Audi A4  30000      False

Quan rebeu el vostre DataFrame per primera vegada, heu d'entendre la informació que hi ha.

Hi ha tres mètodes principals per fer-ho: .head(), .info(), .describe().

El mètode .head() de Pandas és un mètode útil per previsualitzar les primeres files d'un DataFrame. S'utilitza habitualment per obtenir una visió general ràpida de les dades i comprendre la seva estructura, els noms de les columnes i els valors reals del DataFrame.

Podeu passar el nombre de files per mostrar com a paràmetre n. Per defecte, només mostrareu 5 files.

df_cars.head(n=3) # display the first 3 rows

        Car Name  Price  Is Broken
0  Toyota Corolla  25000      False
1     Honda Civic  22000       True
2    Ford Mustang  35000       True

El mètode .info() de Pandas s'utilitza per mostrar metainformació sobre un DataFrame: tipus de dades de cada columna, el nombre de valors no nuls, ús de memòria i molt més.

df_cars.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7 entries, 0 to 6
Data columns (total 3 columns):
 #   Column     Non-Null Count  Dtype 
---  ------     --------------  ----- 
 0   Car Name   7 non-null      object
 1   Price      7 non-null      int64 
 2   Is Broken  7 non-null      bool  
dtypes: bool(1), int64(1), object(1)
memory usage: 247.0+ bytes

Quan cridem df.info(), obtenim la informació següent:

  1. <class 'pandas.core.frame.DataFrame'>: indica que df és un DataFrame
  2. RangeIndex: 7 entries, 0 to 6: Mostra que el DataFrame té un índex d'interval de 0 a 6, que indica 7 files.
  3. Data columns (total 3 columns): Indica que el DataFrame té 3 columnes.
  4. Column: Llista els noms de les columnes ("Nom del cotxe", "Preu" i "Està trencat").
  5. Non-Null Count: mostra el nombre de valors no nuls per a cada columna. En el nostre exemple, no hi ha valors nuls.
  6. Dtype: especifica el tipus de dades de cada columna. 'Car Name'és de tipus 'object'(generalment representa cadenes), 'Price' és de tipus 'int64'(enter), i 'Is Broken' és de tipus bool. Tingueu en compte que, de vegades, les columnes es poden inferir de manera incorrecta, de manera que és possible que sigui necessari una inspecció i una conversió de tipus addicionals. Podeu definir els tipus de columnes canviant el dtype durant la lectura del fitxer. Alternativament, podeu utilitzar df.as_type() després de carregar el DataFrame per especificar els tipus correctes.
  7. memory usage: Mostra l'ús de memòria del DataFrame.

Si només us interessen els dtypes podeu usar l’atribut df.dtypes

El mètode .describe() de Pandas s'utilitza per generar un resum d'estadístiques per a columnes numèriques en un DataFrame. Podeu obtenir la mitjana, recompte, std, primer, segon i tercer quantils i altres estadístiques de les dades.

df_cars.describe()

             Price
count      7.000000
mean   37428.571429
std    13709.572603
min    22000.000000
25%    27500.000000
50%    35000.000000
75%    45000.000000
max    60000.000000

Exercicis introducció als dataframes.

1.- Valors màxims

Baixa el fitxer https://gitlab.com/xtec/bio/-/raw/main/pandas/persons.csv.

Importa el fitxer amb pandes i obten el valor màxim de la columna Age.

2.- paràmetre index_col De quina manera s'utilitza el paràmetre index_col de Pandas? Estableix la primera columna com a índex del DataFrame. Especifiqueu la columna d'índex quan llegiu un fitxer JSON. Establiu una columna d'índex personalitzada per al DataFrame. Especifiqueu la columna d'índex quan llegiu un fitxer CSV.

3.- Mètodes per visualitzar les dades

Relaciona els mètodes següents amb les seves descripcions:

  • head()
  • info()
  • describe()
  1. Genera un resum d’estadístiques per a columnes numèriques del DataFrame, com ara la mitjana, el recompte, la desviació estàndard i els quartils.

  2. Proporciona una vista prèvia ràpida de les primeres files del DataFrame, ajudant a entendre la seva estructura i els noms de les columnes.

  3. Mostra informació sobre el DataFrame, inclosos els tipus de dades de cada columna, el nombre de valors no nuls i l’ús de la memòria.

4.- Valors nuls

Baixa el fitxer https://gitlab.com/xtec/bio/-/raw/main/pandas/persons.csv.

Importa el fitxer amb pandes i digues quin és el nombre de valors no nuls de la columna Address.

5.- Filtra els noms de les files.

Des del mateix fitxer, mostra només les tres primeres files.

Solucions

1: 60; 2: 2; 3: 1-B, 2-C, 3-A; 4: 281;

1.-

import os.path
import pandas as pd
from urllib.request import urlretrieve

filepath = "persons.csv"

if not os.path.isfile(filepath):
   url = "https://gitlab.com/xtec/bio/-/raw/main/pandas/persons.csv"
   urlretrieve(url, filepath)

df = pd.read_csv(filepath)
print(df.describe())

4.-

import os.path
import pandas as pd
from urllib.request import urlretrieve

filepath = "persons.csv"

if not os.path.isfile(filepath):
   url = "https://gitlab.com/xtec/bio/-/raw/main/pandas/persons.csv"
   urlretrieve(url, filepath)

df = pd.read_csv(filepath)
print(df.info())

5.-

...
print(df.head(3))

Creació d'un DataFrame a partir d'altres estructures de dades

També és possible convertir altres estructures de dades, com ara diccionaris, llistes o matrius Numpy, a DataFrame. Heu de passar les dades al constructor DataFrame.

Per exemple, suposem que teniu una llista imbricada que conté informació sobre els estudiants i la volem convertir a DataFrame

students_list = [['Anna', 'Smith', 21],
                 ['Bob', 'Jones', 20],
                 ['Maria', 'Williams', 25],
                 ['Jack', 'Brown', 22]]

students = pd.DataFrame(students_list, columns = ['First Name', 'Family Name', 'Age'])
print(students)
+----+--------------+---------------+-------+
|    | First Name   | Family Name   |   Age |
|----+--------------+---------------+-------|
|  0 | Anna         | Smith         |    21 |
|  1 | Bob          | Jones         |    20 |
|  2 | Maria        | Williams      |    25 |
|  3 | Jack         | Brown         |    22 |
+----+--------------+---------------+-------+

A més, podríem especificar el index en comptes del 0, 1, 2, ... per defecte amb l'argument índex. Provem-ho:

students_index = [100, 200, 300, 400]
students_columns = ['First Name', 'Family Name', 'Age']
students = pd.DataFrame(students_list, 
                        columns = students_columns,
                        index = students_index )
print(students)

Sortida:

+-----+--------------+---------------+-------+
|     | First Name   | Family Name   |   Age |
|-----+--------------+---------------+-------|
| 100 | Anna         | Smith         |    21 |
| 200 | Bob          | Jones         |    20 |
| 300 | Maria        | Williams      |    25 |
| 400 | Jack         | Brown         |    22 |
+-----+--------------+---------------+-------+

En crear un DataFrame d'un diccionari imbricat, els noms d'índex i de columnes s'inferiran automàticament a partir de les claus del diccionari. Mireu l'exemple:

# This is a nested dictionary representing the students table
students_dict = {'First Name': {100: 'Anna', 
                                200: 'Bob', 
                                300: 'Maria',
                                400: 'Jack'},
                 
                 'Family Name': {100: 'Smith', 
                                 200: 'Jones',
                                 300: 'Williams',
                                 400: 'Brown'},
                 'Age': {100: 21, 
                         200: 20, 
                         300: 25,
                         400: 22}}

students = pd.DataFrame(students_dict)
print(students)

Sortida:

+-----+--------------+---------------+-------+
|     | First Name   | Family Name   |   Age |
|-----+--------------+---------------+-------|
| 100 | Anna         | Smith         |    21 |
| 200 | Bob          | Jones         |    20 |
| 300 | Maria        | Williams      |    25 |
| 400 | Jack         | Brown         |    22 |
+-----+--------------+---------------+-------+

Shape --> Forma dels dataframes

Imagineu que acabeu de carregar les vostres dades a un DataFrame i no pots esperar per començar a explorar-lo. Per comprovar quantes files i columnes té un Frame, pots accedir a l’atribut shape.

Conté una tupla amb dos valors, les dimensions al llarg dels dos eixos. Per exemple, en el nostre alumne DataFrame, hi ha quatre files i tres columnes:

students.shape
# (4, 3)

Selecció de primeres(head) i últimes(tail) files.

El DataFrame pot ser massa gran per imprimir-lo. En aquest cas, utilitza els mètodes head() i tail().

Imprimiran la primera o les últimes cinc files del DataFrame respectiu. Si voleu que es mostri un nombre diferent de files, només heu d'especificar-lo entre parèntesis.

Imprimim només les dues primeres files:

students.head(2)
+-----+--------------+---------------+-------+
|     | First Name   | Family Name   |   Age |
|-----+--------------+---------------+-------|
|   0 | Anna         | Smith         |    21 |
|   1 | Bob          | Jones         |    20 |
+-----+--------------+---------------+-------+

Selecció de columnes.

També podeu accedir a cadascuna de les columnes del DataFrame per separat posant el nom de la columna entre claudàtors després del nom del DataFrame. Tingueu en compte que cada columna del DataFrame és un objecte Series:

students['Age']
# 0    21
# 1    20
# 2    25
# 3    22
# Name: Age, dtype: int64

Si necessiteu accedir a diverses columnes alhora, només heu de posar els seus noms en una llista. Mirem només la primera i l'última columnes. Tingueu en compte que la taula resultant és un nou objecte DataFrame:

students[['First Name', 'Age']]
+----+--------------+-------+
|    | First Name   |   Age |
|----+--------------+-------|
|  0 | Anna         |    21 |
|  1 | Bob          |    20 |
|  2 | Maria        |    25 |
|  3 | Jack         |    22 |
+----+--------------+-------+

Tingueu en compte també que si voleu obtenir una sola columna d'un DataFrame com un altre objecte DataFrame enlloc d’un objecte Series, hauríeu de posar el nom de les columnes entre claudàtors dobles:

students[['Age']]
+----+-------+
|    |   Age |
|----+-------|
|  0 |    21 |
|  1 |    20 |
|  2 |    25 |
|  3 |    22 |
+----+-------+

Organització de dades als DataFrames

Ja hem comentat quin tipus de dades es poden emmagatzemar en un DataFrame i com es poden crear.

Ara, anem a saber com podem modificar un DataFrame existent. En aquest tema, parlarem d'algunes operacions bàsiques, com ara canviar el nom, reordenar columnes o canviar l'índex.

Accés als eixos de DataFrame

En primer lloc, hem d'importar pandas i crear un DataFrame d'un diccionari:

import pandas as pd

pets = {
    'species': ['cat', 'dog', 'parrot', 'cockroach'], 
    'name': ['Dr. Mittens Lamar', 'Diesel', 'Peach', 'Richard'], 
    'legs': [4, 4, 2, 6],
    'wings': [0, 0, 2, 4],
    'looking_for_home': ['no', 'no', 'no', 'yes']
}
df = pd.DataFrame(pets)
df.head()

Aquí teniu la sortida:

+----+-----------+-------------------+--------+---------+--------------------+
|    | species   | name              |   legs |   wings | looking_for_home   |
|----+-----------+-------------------+--------+---------+--------------------|
|  0 | cat       | Dr. Mittens Lamar |      4 |       0 | no                 |
|  1 | dog       | Diesel            |      4 |       0 | no                 |
|  2 | parrot    | Peach             |      2 |       2 | no                 |
|  3 | cockroach | Richard           |      6 |       4 | yes                |
+----+-----------+-------------------+--------+---------+--------------------+

Podem canviar els índexs en tots dos DataFrames i Series. Els índexs poden utilitzar diferents tipus de dades, com ara cadenes, objectes Datetime, nombres flotants, valors booleans i altres.

Podeu veure l'índex de files a la primera columna de l'esquerra. Els noms de les columnes ( etiquetes ) es troben a la capçalera. Una altra manera de descriure la indexació és l'etiquetatge d'eixos.

Podeu veure dos eixos al nostre marc de dades, verticals (files) — axis 0 i horitzontal (columnes) — axis 1.

Fem una ullada als eixos del nostre DataFrame accedint a df.axes atribut.

Això és el que obtindrem:

[RangeIndex(start=0, stop=4, step=1),
 Index(['species', 'name', 'legs', 'wings', 'looking_for_home'], dtype='object')]

El primer objecte de la llista és el mètode d'indexació per a files i el segon per a columnes.

La manera predeterminada d'indexar les dades que contenen n files és utilitzant un interval d'enters 0, 1, 2, 3,..., n−1.

Aquest índex reflecteix les posicions dels elements. Com podeu veure més amunt, el nostre DataFrame utilitza només aquest tipus d'indexació de files (interval d'enters): la primera fila té el 0 índex, l'última fila té índex 3.

Comprovem la sortida del mètode df.info():

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4 entries, 0 to 3
Data columns (total 5 columns):
 #   Column            Non-Null Count  Dtype 
---  ------            --------------  ----- 
 0   species           4 non-null      object
 1   name              4 non-null      object
 2   legs              4 non-null      int64 
 3   wings             4 non-null      int64 
 4   looking_for_home  4 non-null      object
dtypes: int64(2), object(3)
memory usage: 288.0+ bytes

Com podeu veure, la primera línia descriu la classe d'objecte ( DataFrame), després el tipus de dades per a la indexació i, a continuació, hi ha una llista de columnes que conté índexs de columnes posicionals, etiquetes de columnes, Non-null Count (el nombre de files no buides) i dtype (un tipus de dades, es detecta automàticament com a object per Pandas).

L'objecte d'índex de fila s'emmagatzema df.index

Podem veure l'índex actual cridant l'atribut corresponent al marc de dades:

df.index
RangeIndex(start=0, stop=4, step=1)

Com que no hi ha etiquetes de fila, l'atribut retornarà un interval d'enters. Podeu aconseguir el mateix resultat utilitzant df.axes[0].

Consell: .info() també us ofereix índexs posicionals. A més de la indexació posicional, de vegades és útil utilitzar etiquetes personalitzades.

Per veure les etiquetes de columna d'a DataFrame, utilitzar df.columns:

Index(['species', 'name', 'legs', 'wings', 'looking_for_home'], dtype='object')

Configurar, canviar i restablir un índex

Una manera de canviar els noms de les columnes és assignar un nou valor a la columns atribut. El nou valor ha de tenir la mateixa longitud que el nombre de columnes.

Canviem el valor d'algunes columnes assignant una llista de nous valors a l'atribut columns:

df.columns = ['col', 'col2', 'col3', 'col4',  'col5']
df.head()

Aquí teniu la sortida:

+----+-----------+-------------------+--------+--------+--------+
|    | col       | col2              |   col3 |   col4 | col5   |
|----+-----------+-------------------+--------+--------+--------|
|  0 | cat       | Dr. Mittens Lamar |      4 |      0 | no     |
|  1 | dog       | Diesel            |      4 |      0 | no     |
|  2 | parrot    | Peach             |      2 |      2 | no     |
|  3 | cockroach | Richard           |      6 |      4 | yes    |
+----+-----------+-------------------+--------+--------+--------+

Com podeu veure, les columnes ara tenen noms diferents. Podem assignar una nova llista d'etiquetes al index atribut:

df.index = ['row', 'row2', 'row3', 'row4']
df.head()

Així serà la taula:

+------+-----------+-------------------+--------+--------+--------+
|      | col       | col2              |   col3 |   col4 | col5   |
|------+-----------+-------------------+--------+--------+--------|
| row  | cat       | Dr. Mittens Lamar |      4 |      0 | no     |
| row2 | dog       | Diesel            |      4 |      0 | no     |
| row3 | parrot    | Peach             |      2 |      2 | no     |
| row4 | cockroach | Richard           |      6 |      4 | yes    |
+------+-----------+-------------------+--------+--------+--------+

També podeu utilitzar qualsevol columna com a índex. Indexem les nostres dades pel nom. Ho podem fer amb el mètode set_index().

Així que podem assignar un nou DataFrame objecte al nostre df variable o utilitzeu un argument opcional inplace=True. Aquesta funció alterarà totalment el Dataframe existent (internament es crearà un nou Dataframe i s'esborrarà l'anterior)

Tornem al nostre DataFrame i restablir el seu índex.

df.set_index('name', inplace=True)  
# is equivalent to df = df.set_index('name')
df.head()

Aquí teniu la sortida:

+-------------------+-----------+--------+---------+--------------------+
|                   | species   |   legs |   wings | looking_for_home   |
|-------------------+-----------+--------+---------+--------------------|
| name              |           |        |         |                    |
|-------------------+-----------+--------+---------+--------------------|
| Dr. Mittens Lamar | cat       |      4 |       0 | no                 |
| Diesel            | dog       |      4 |       0 | no                 |
| Peach             | parrot    |      2 |       2 | no                 |
| Richard           | cockroach |      6 |       4 | yes                |
+-------------------+-----------+--------+---------+--------------------+

Ara, la indexació es basa en la columna name. Si mirem l'atribut index que s'utilitza ara df.index, podem veure que va canviar de rang a la llista de noms:

Index(['Dr. Mittens Lamar', 'Diesel', 'Peach', 'Richard'], dtype='object', name='name')

Consell: Només l’objecte DataFrames té el mètode .set_index().

Podem tornar a restablir la columna d'índex al valor predeterminat (interval sencer) fent servir reset_index().

Com s'ha esmentat anteriorment, utilitzeu inplace=True per guardar els canvis:

df.reset_index(inplace=True)

Sortida:

+----+-------------------+-----------+--------+---------+--------------------+
|    | name              | species   |   legs |   wings | looking_for_home   |
|----+-------------------+-----------+--------+---------+--------------------|
|  0 | Dr. Mittens Lamar | cat       |      4 |       0 | no                 |
|  1 | Diesel            | dog       |      4 |       0 | no                 |
|  2 | Peach             | parrot    |      2 |       2 | no                 |
|  3 | Richard           | cockroach |      6 |       4 | yes                |
+----+-------------------+-----------+--------+---------+--------------------+

Un cop hem restablert l'índex, la columna name passa a ser la primera. Si voleu reindexar les vostres dades i suprimir els índexs existents, feu servir drop=True.

Canviar el nom de columnes

També podeu utilitzar el mètode .rename() per canviar el nom de les columnes. Només cal passar un diccionari amb noms de columnes antics com a claus i noms de columnes noves com a valors:

df.rename(columns={'name': 'pet_name', 'looking_for_home': 'homeless'}, inplace=True)
df.head()

Aquí teniu la sortida:

+----+-------------------+-----------+--------+---------+------------+
|    | pet_name          | species   |   legs |   wings | homeless   |
|----+-------------------+-----------+--------+---------+------------|
|  0 | Dr. Mittens Lamar | cat       |      4 |       0 | no         |
|  1 | Diesel            | dog       |      4 |       0 | no         |
|  2 | Peach             | parrot    |      2 |       2 | no         |
|  3 | Richard           | cockroach |      6 |       4 | yes        |
+----+-------------------+-----------+--------+---------+------------+

Com podeu veure, tot és molt còmode. No cal esmentar totes les columnes si només volem canviar el nom d'algunes d'elles.

També pots utilitzar .rename() per canviar els noms índexs: només cal passar el index = {...} argument en lloc de columns={...}.

Sistemes de coordenades Dataframes: .iloc i .loc

De vegades, és possible que vulguem accedir a una peça d'informació emmagatzemada en una fila o una columna concreta en lloc de treballar amb tota la informació. DataFrame. La bona notícia és que pandas té la solució adequada per a això.

S'anomena indexació i podem seleccionar un subconjunt particular de a DataFrame o a Series per treballar-hi.

En un dataframe, el sistema de coordenades, comença per 0, i la coordenada s'indica primera la fila i després la columna.

Regla nemotècnica (enfonsar-se i nedar)

  1. Primer et tires de cap a la posició de la fila que vols.

  2. Després, vas nedant fins la columna que t'interessa.

Abans de començar, importem pandas (abreujat com pd) i creem un DataFrame d'un diccionari:

import pandas as pd
people = {
    "first_name": ["Michael", "Michael", 'Jane', 'John'], 
    "last_name": ["Jackson", "Jordan", 'Doe', 'Doe'], 
    "email": ["mjackson@email.com", "mjordan@email.com", 
'JaneDoe@email.com', 'JohnDoe@email.com'],
    "birthday": ["29.09.1958", "17.02.1963", "15.03.1978", "12.05.1979"],
    "height": [1.75, 1.98, 1.64, 1.8]
}
df = pd.DataFrame(people)
df.head()

Aquí teniu la sortida:

first_name last_name email birthday height 0 Michael Jackson mjackson@email.com 29.09.1958 1.75 1 Michael Jordan mjordan@email.com 17.02.1963 1.98 2 Jane Doe JaneDoe@email.com 15.03.1978 1.64 3 John Doe JohnDoe@email.com 12.05.1979 1.80

pandas ofereix dues funcions addicionals per seleccionar un subconjunt de files i columnes: .loc i .iloc.

Tingueu en compte que les dues característiques no són mètodes: són propietats de Python i, per això, utilitzen claudàtors. Primer, recordeu que la seva sintaxi bàsica és similar:

.loc[<row selection>, <optional column selection>]
.iloc[<row selection>, <optional column selection>]

.iloc

Comencem amb .loc; el més habitual.

Pot gestionar índexs basats en nombres enters com a etiquetes, però per a més claredat, crearem i anomenarem un índex de text:

df.index = ['first', 'second', 'third', 'fourth']
df.index.name = 'index'
df.head()

Sortida:

      first_name last_name               email    birthday  height
index                                                              
first     Michael   Jackson  mjackson@email.com  29.09.1958    1.75
second    Michael    Jordan   mjordan@email.com  17.02.1963    1.98
third        Jane       Doe   JaneDoe@email.com  15.03.1978    1.64
fourth       John       Doe   JohnDoe@email.com  12.05.1979    1.80

.loc pot prendre:

  • una sola etiqueta de fila;
  • una llista d'etiquetes de files;
  • un tros d'etiquetes de fila;
  • un resultat de declaracions condicionals (una matriu booleana)

També podríem passar columnes com a segon argument d'una manera similar: una sola etiqueta, una llista o una porció.

Si passem un sol argument, pandas tornarà a Series:

df.loc['third']
Sortida:
first_name                 Jane
last_name                   Doe
email         JaneDoe@email.com
birthday             15.03.1978
height                     1.64
Name: third, dtype: object

També podeu seleccionar una sola cel·la:

df.loc['third', 'last_name']

Sortida:

'Doe'

Com podeu veure, hem retornat un valor de cel·la. En aquest cas, és del tipus String.

Per passar una llista d'etiquetes, hem de fer el següent:

df.loc[['first','fourth']]

Obtenim les files amb el primer i el quart índex:

      first_name last_name               email    birthday  height
index                                                              
first     Michael   Jackson  mjackson@email.com  29.09.1958    1.75
fourth       John       Doe   JohnDoe@email.com  12.05.1979    1.80

Afegim una llista de columnes d'etiquetes:

df.loc[['first','fourth'], ['last_name', 'birthday']]

Sortida:

      last_name    birthday
index                       
first    Jackson  29.09.1958
fourth       Doe  12.05.1979
print(df.loc[['first','fourth']]['email'])
index
first 	mjackson@email.com
fourth 	JohnDoe@email.com
Name: email, dtype: object

Tingueu en compte que la primera llista dins del loc els claudàtors defineixen la selecció de fila mentre que la segona llista defineix la selecció de columnes.

Aquí ve una porció d'etiquetes de fila:

df.loc['second':'fourth']

Sortida:

         first_name last_name              email    birthday  height
index                                                             
second    Michael    Jordan  mjordan@email.com  17.02.1963    1.98
third        Jane       Doe  JaneDoe@email.com  15.03.1978    1.64
fourth       John       Doe  JohnDoe@email.com  12.05.1979    1.80

Igual que abans, podem introduir una condició (amb una porció de columna):

df.loc[df.birthday == '12.05.1979', 'last_name':'birthday':2]

Sortida:

      last_name    birthday
index                       
fourth       Doe  12.05.1979

El primer argument aquí pren una fila mentre la columna d'aniversari s'estableix a 12.05.1979. El segon argument pren columnes de last_name a birthday amb un pas de 2. És a dir, triga cada segona columna, començant per la primera seleccionada.

No dubteu a triar qualsevol combinació de valors, llistes i seccions individuals.

result = df.loc[df.height > 1.76]
print(result)

Sortida:

   	first_name last_name          	email	birthday  height
index                                                        	 
second	Michael	Jordan  mjordan@email.com  17.02.1963	1.98
fourth   	John   	Doe  JohnDoe@email.com  12.05.1979	1.80

Pregunta: Com podem obtenir el nom de la persona amb la alçada mínima ?

# Pas 1. Obtenir el mínim
minim = df.height.min()

# Pas 2. Obtenir el nom del mínim, sabent el mínim
result = df.loc[df.height == minim]

.iloc

La sintaxi bàsica d'iloc és la mateixa que a loc, però aquesta se centra en els índexs enters ordinals; aquí no podem utilitzar condicionals.

Per tant, torneu a l'inici DataFrame restablint i deixant anar l'índex d'etiquetes; ja no en necessitem:

df.reset_index(drop=True, inplace=True)
df.head()

Sortida:

  first_name last_name               email    birthday  height
0    Michael   Jackson  mjackson@email.com  29.09.1958    1.75
1    Michael    Jordan   mjordan@email.com  17.02.1963    1.98
2       Jane       Doe   JaneDoe@email.com  15.03.1978    1.64
3       John       Doe   JohnDoe@email.com  12.05.1979    1.80

Al principi, seleccionem el valor de la primera fila i columna:

df.iloc[0, 0]

Retorna la cel·la:

'Michael'

També podem seleccionar quatre cel·les internes:

df.iloc[[1, 2], [1, 2]]

Sortida:

 last_name              email
1    Jordan  mjordan@email.com
2       Doe  JaneDoe@email.com

No t'oblidis del pas! Per definir un pas k dins d'un interval de fila [x,y], utilitzeu la sintaxi següent:

df.iloc[x:y:k, :]

Per exemple, podem enumerar cada segona fila (a partir de zero) amb aquesta línia de codi:

df.iloc[::2, :]

Sortida:

 first_name last_name               email    birthday  height
0    Michael   Jackson  mjackson@email.com  29.09.1958    1.75
2       Jane       Doe   JaneDoe@email.com  15.03.1978    1.64

Genial, no? Aquesta tècnica sembla senzilla si ja esteu familiaritzat amb les llistes de Python.

Tingues en compte que .iloc pren una posició entera. Vol dir que si no tenim una numeració de línia d'extrem a extrem, prendrà les posicions de la fila.

Així que si tenim una indexació fantàstica com aquesta:

   a  b
10  1  4
0   2  5
20  3  6

df.iloc[0] encara seleccionarà la primera fila (amb un índex de 10):

a    1
b    4
Name: 10, dtype: int64

I df.loc[0]seleccionarà la segona fila (amb un índex de 0):

a    2
b    5
Name: 0, dtype: int64

Ús .loc i .iloc quan es vol canviar una part DataFrame.

En resum, anem a veure les principals diferències entre .loci .iloc en una taula:

.loc .iloc
Selecció de fila condicional No
Pren files com Noms d'índex Posició entera de l'índex
Pren les columnes com a Noms de columnes Posició entera de la columna

Modificació d'un DataFrame amb loc & iloc

Tots dos mètodes no només són una manera convenient de seleccionar una part d'a DataFrame, però també ajuden a modificar una part d'a DataFrame només amb una línia de codi.

Imaginem una situació: per desar dades personals en un servidor, els usuaris us han d'enviar un Acord de tractament de dades (DPA).

Suposem que no has rebut el DPA de Jane i John Doe.

Actualitzem les nostres dades:

df.iloc[2:, 2:5] = "no DPA"

Això és el que obtindrem:

 first_name last_name               email    birthday  height
0    Michael   Jackson  mjackson@email.com  29.09.1958    1.75
1    Michael    Jordan   mjordan@email.com  17.02.1963    1.98
2       Jane       Doe              no DPA      no DPA  no DPA
3       John       Doe              no DPA      no DPA  no DPA

Camps categorical -> limitar valors.

Quan ens trobem amb un camp que pot tenir uns pocs valors, que es podrien representar en una llista de selecció (el select d’html) ens interessa crear un camp categorical.

Per crear un categòric de gènere ho podem fer així:

gender = pd.Categorical(["Male", "Female", "Non-Binary", "Transgender", "Intersex", "I prefer not to say"])

Si volem transformar una columna d’strings (dtype object per defecte) a categorical hem d’usar els mètodes astype; suposem el següent exemple:

csv_file:str = "drinks.csv"
if not os.path.isfile(csv_file):
    urlretrieve('http://bit.ly/drinksbycountry',csv_file)
drinks = pd.read_csv(csv_file)

print("Datatype of each column:")
print(drinks.dtypes)
drinks['continent'] = drinks.continent.astype('category')
print("\nDatatype after creating category column:")
print(drinks.dtypes)

Ja tenim el camp continent com a category.

També podem crear camps categòrics quan llegim el dataframe o serie:

[https://www.educative.io/answers/how-do-you-create-a-category-column-while-file-reading-in-pandas](Blog Educative.io - Create category while reading dataFrame)

drinks = pd.read_csv('http://bit.ly/drinksbycountry', 
                    dtype={'continent':'category'})
print("Datatype of each column:")
print(drinks.dtypes)

Recompte d’ocurrències de cada valor en una columna.

value_counts

Pandas té una funció molt interessant que permet comptar el número de vegades que apareix un valor. No cal que sigui categorical, ho pot fer amb camps tipus object i fins i tot númerics.

Tornant a l’’exemple de la oestoporosi, podem saber quantes pacients tenen menopausia o no.

# Quins possibles valors tenim per a la columna ?
print(df_oestop['menop'].value_counts())

Si només ens interessen les pacients que no la tenen podem filtrar-les fàcilment.

# Si només volem saber qui no té menopausia.
print(df_oestop['menop'].value_counts()['NO'])

sort_values

Un cop hem agafat els valors podem ordenar-los amb la funció sort_values. Podem agafar aquest exemple per provar-ho, amb string:

import pandas as pd
df = pd.DataFrame({'team': ['A', 'A', 'B', 'B', 'B', 'B', 'B', 'C'],
                   'points': [15, 12, 18, 20, 22, 28, 35, 40]})
print(df)

Com reemplacem el nom de l’equip per un altre ? Funció replace.

df['team'] = df['team'].replace({'A':'Athletic','B':'Barça','C':'Celta'})

Selecció condicional

De vegades, és possible que vulguem accedir a una informació emmagatzemada en una fila o una columna concreta en lloc de treballar amb un DataFrame sencer.

La bona notícia és que Pandas ens permet fer-ho. S'anomena indexació ; podem seleccionar un subconjunt particular de a DataFrame o a Series per treballar-hi.

Selecció

Abans de començar, importem pandas(abreujat com pd) i creeu un DataFrame a partir d'un diccionari:

import pandas as pd

people = {
    "first_name": ["Michael", "Michael", 'Jane', 'John'], 
    "last_name": ["Jackson", "Jordan", 'Doe', 'Doe'], 
    "email": ["mjackson@email.com", "mjordan@email.com", 'JaneDoe@email.com', 'JohnDoe@email.com'],
    "birthday": ["29.09.1958", "17.02.1963", "15.03.1978", "12.05.1979"],
    "height": [1.75, 1.98, 1.64, 1.8]
}
df = pd.DataFrame(people)
df.head()

Sortida:

+----+--------------+-------------+--------------------+------------+----------+
|    | first_name   | last_name   | email              | birthday   |   height |
|----+--------------+-------------+--------------------+------------+----------|
|  0 | Michael      | Jackson     | mjackson@email.com | 29.09.1958 |     1.75 |
|  1 | Michael      | Jordan      | mjordan@email.com  | 17.02.1963 |     1.98 |
|  2 | Jane         | Doe         | JaneDoe@email.com  | 15.03.1978 |     1.64 |
|  3 | John         | Doe         | JohnDoe@email.com  | 12.05.1979 |     1.8  |
+----+--------------+-------------+--------------------+------------+----------+

Podem seleccionar qualsevol subconjunt del DataFrame, per exemple, una sola columna:

df['email']

Sortida:

0    mjackson@email.com
1     mjordan@email.com
2     JaneDoe@email.com
3     JohnDoe@email.com
Name: email, dtype: object

Ara tenim un Pandas sèrie amb correus electrònics.

També és possible utilitzar-lo df.email. S'anomena sintaxi de punts.

El podem utilitzar per a noms de columnes sense espais.

També podem seleccionar una llista de columnes. Una llista de Python requereix claudàtors addicionals:

df[['first_name', 'last_name']]

Sortida:

+----+--------------+-------------+
|    | first_name   | last_name   |
|----+--------------+-------------|
|  0 | Michael      | Jackson     |
|  1 | Michael      | Jordan      |
|  2 | Jane         | Doe         |
|  3 | John         | Doe         |
+----+--------------+-------------+

Ara tenim un nou DataFrame, format per aquestes dues columnes. Aquesta ordre sempre retorna un DataFrame, de manera que, fins i tot si seleccioneu una llista que consta d'una columna, tornareu un DataFrame:

df[['last_name']]

Sortida:

+----+-------------+
|    | last_name   |
|----+-------------|
|  0 | Jackson     |
|  1 | Jordan      |
|  2 | Doe         |
|  3 | Doe         |
+----+-------------+

Condicionals

Si hem d'introduir una condició per refinar la nostra elecció, podem incloure-la entre claudàtors:

df[df.height < 1.8]

Sortida:

+----+--------------+-------------+--------------------+------------+----------+
|    | first_name   | last_name   | email              | birthday   |   height |
|----+--------------+-------------+--------------------+------------+----------|
|  0 | Michael      | Jackson     | mjackson@email.com | 29.09.1958 |     1.75 |
|  2 | Jane         | Doe         | JaneDoe@email.com  | 15.03.1978 |     1.64 |
+----+--------------+-------------+--------------------+------------+----------+

Com a resultat, hem retornat totes les columnes i files on height el valor és inferior a 1,8.

Tingueu en compte que és possible utilitzar claudàtors dins d'aquesta declaració, per exemple, df[df['height'] < 1.8], però no cal que ho fem aquí, ja que el nom de la columna no conté espais en blanc.

Si necessitem combinar diverses condicions, utilitzem els següents operadors booleans:

  • & per "i"
  • | (línia vertical) per "o"
  • ~ per "no"
  • , <, >=, <=, ==, !=per comparar declaracions.

Si us plau, no us oblideu del parèntesi:

df[(df.first_name == 'Michael') & (df.birthday == '17.02.1963')]

Sortida:

+----+--------------+-------------+-------------------+------------+----------+
|    | first_name   | last_name   | email             | birthday   |   height |
|----+--------------+-------------+-------------------+------------+----------|
|  1 | Michael      | Jordan      | mjordan@email.com | 17.02.1963 |     1.98 |
+----+--------------+-------------+-------------------+------------+----------+

Com a resultat, vam tornar les files que coincideixen amb la nostra condició preestablerta: "el primer nom és Michael, l'aniversari és el 17.02.1963".

Un exemple més amb una condició una mica més complexa. Definim el primer nom com Michael o John, una alçada igual o superior a 1,8, i el cognom no hauria de ser Jordan:

print(df[((df.first_name == 'Michael') | (df.first_name == 'John'))
   & (df.height >= 1.8)
   & (df.last_name != 'Jordan')])

Sortida:

+----+--------------+-------------+-------------------+------------+----------+
|    | first_name   | last_name   | email             | birthday   |   height |
|----+--------------+-------------+-------------------+------------+----------|
|  3 | John         | Doe         | JohnDoe@email.com | 12.05.1979 |      1.8 |
+----+--------------+-------------+-------------------+------------+----------+

Tingueu en compte que no fem servir el caràcter de salt de línia , ja que totes les línies noves es posen dins dels claudàtors.

Si volem fer exclusiu el nostre filtratge , és a dir, seleccionar-ho tot excepte els paràmetres indicats, podem afegir un caràcter de tilde ~i parèntesis addicional:

df[~(((df.first_name == 'Michael') | (df.first_name == 'John'))
   & (df.height >= 1.8)
   & (df.last_name != 'Jordan'))]

Sortida:

+----+--------------+-------------+--------------------+------------+----------+
|    | first_name   | last_name   | email              | birthday   |   height |
|----+--------------+-------------+--------------------+------------+----------|
|  1 | Michael      | Jordan      | mjordan@email.com  | 17.02.1963 |     1.98 |
+----+--------------+-------------+--------------------+------------+----------+

Exercici repàs. Importació i selecció de columnes estudi oestoporosi.

Importa un fitxer que d'un estudi sobre la osteoporosis que es va fer a divereses pacients pacients, hi ha a la URL del nostre Gitlab

Tingueu en compte que si no el tenim el descarregui i si ja el tenim només el llegeixi.

Article i fitxer originals:

Què demanem ?

  1. Fes que el dataframe només contingui les següents columnes:
  • edad (en anys).

  • imc (índex de massa corporal)

  • bua (resultat de l'exploració densitomètrica)

  • classific (normal / osteopenia / osteoporosi)

  • menarqui (edat primera menstruació, en anys)

  • edat_menop (edat inici menopausa, en anys)

  • menop → menopausa (sí, no)

  • tipus de menopausa

  1. Obtén els dtypes i el tamany en memòria, per veure si s’ha carregat bé. → info

  2. Mostra les 10 primeres files.

  3. Obtén la mitjana de l'imc i de la edat de les pacients. → describe

  4. Mostra les dades de la pacient més jove de l'estudi

  5. Mira si hi ha valors NaN en alguna columna. En quines?

  6. Obtén la classificació de les pacients que van des de la posició 100 a la 199. → loc, iloc

  7. Obtén la edat i l’imc de les pacients que van des de la posició 50 al 99. → loc, iloc

  8. Transforma dos camp cap al tipus categorical.

  9. Fes el recompte d’ocurrències d’una variable object i una de categorical; ordenades.

import numpy as np
import pandas as pd
import os.path
from urllib.request import urlretrieve
url: str = "https://marcos-marva.web.uah.es/CursoSanitaria/practicas/datos/osteoporosis.csv"
csv_file:str = "nou_oestoporisi.csv"
if not os.path.isfile(csv_file):
    urlretrieve(url,csv_file)

# 1. Llegim dataframe, separador fitxers \t, acceptem la primera columna com a index.
col_list: list[str] = ['registro','area','edad','imc','bua','clasif','edad_men','menop','tipo_men']
df_oestop: pd.DataFrame = pd.read_csv(url, sep = "\t")

# Sobreescrivim el dataframe, amb només les columnes 
df_oestop = df_oestop.loc[:, col_list]

# Exercici 2, 3, 4 
print(df_oestop.info())
print(df_oestop.head())
print(df_oestop.describe())

# 5. Comptem si hi ha valors no nuls.
print(df_oestop.isna().sum())
   
# 6, 7.
# Obtén la classificació de les pacients que van des del 100 al 199. → loc, iloc
# Obtén la edat i l’imc de les pacients que van des del 50 al 99. → loc, iloc
print(df_oestop.loc[100:199,'clasific'])
print(df_oestop.loc[50:99,['edad','imc']])

# 7. Podem fer la mitjana d'edat.
print('Average age = ',df_oestop['edad'].describe().mean())

# 7. Però l'IMC ens l'ha reconegut com a object i l'hem de convertir a float.  
df_oestop['imc'] = df_oestop['imc'].replace(',', '.')

# Formes de convertir un objecte(string) a float
#df_oestop = df_oestop['imc'].astype(‘float16’)
df_oestop['imc'] = pd.to_numeric(df_oestop['imc'], errors='coerce')

print('Average imc = ',df_oestop['imc'].describe().mean())

# 8. Categòrics
df_oestop['imc'] = df_oestop['imc'].astype('category')
df_oestop['clasific'] = df_oestop['clasific'].astype('category')
print(df_oestop.dtypes)

print(df_oestop['grupedad'].value_counts(ascending=True))
print(df_oestop['clasific'].value_counts())


# 10.
ruta_nou_fitxer: str = "nou_fitxer_oestoporosi.csv"
# Ja podem guardar el nou fitxer si volem, amb ;
df_oestop.to_csv(ruta_nou_fitxer, sep = ";", index=False) 

Filtrem amb queries.

Les versions de Pandas 1.3 i posteriors permeten filtrar amb una altra sintaxi, semblant al SQL.

Aquesta consulta:

 df[((df.first_name == 'Michael') | (df.first_name == 'Jordan'))

És equivalent a:

print(df.query("first_name=='Michael' and last_name=='Jordan'"))
print(df.query("height>1.70 and height<1.86"))

És important no equivocar-se amb la sintaxi, la comparació sense espais.

Teniu més exemples de queries a: https://naps.com.mx/blog/uso-de-query-con-pandas-en-python/

Exercicis loc & iloc i selecció condicional.

Donat aquest codi que crea dades generals sobre alumnes, crea un dataframe i realitza les següents operacions.

student_list=["John","Mary","Lucy","Peter","Ann","Tom", "Oliver", "Luna", "Aria"]
grades_list = [7,9,8,4,10,6,np.nan,np.nan,np.nan] 
wants_fct_list = [False,True,False,True,True,True]
start_date = pd.date_range("20210101", periods=9)
genders = pd.Categorical(["Male", "Female", "Non-Binary", "Male", "Female", "I prefer not to say", "Male", "I prefer not to say", "Female"])

datos: dict[list] = {
      "grade": grades_list,
      "fct": wants_fct_list + 3 * [True], 
      "student_list" : student_list,
      "start_date" : start_date,
      "gender" : genders}

exercicis_frame = pd.DataFrame(
    index=student_list,
    data=datos
)

Operacions.

  1. L'index ha de ser el nom de l'alumne. Apart de ser índex també ha de ser un camp.
  2. Mostra la mitjana de notes de tots els alumnes.
  3. Ordena els alumnes alfabèticament.
  4. Mostra tota la info d'un alumne, a partir del seu nom.
  5. Mostra les notes dels 3 alumnes que tenen una nota més alta.
  6. Filtra els els noms dels alumnes que volen fer FCT.
  7. Mostra els alumnes que tenen una nota superior o igual a 7.
  8. Espai per a que creis una consultes i la seva solució, a partir de les noves consultes que has creat.
  9. Crea un camp de gènere categòric.

Ja ho hem fet al crear el dataframe. Si no ho haguessim fet hauriem d’usar el mètode set_index

# Tots els càlculs.print(exercicis_frame["grade"].describe())
print("Ex3 ", exercicis_frame["grade"].mean())


exercicis_frame3= exercicis_frame.sort_values(by=["student_list"],ascending=True)
print(exercicis_frame3)

# Mostra tota la info d'un alumne, a partir del seu nom.

print("EX4 - Info d'una alumne")
print(exercicis_frame.loc["Mary"])

# Si volguessim info sobre només un atribut d'un alumne.
# students_frame.loc[["Mary"],"grade"]

# Mostra les notes dels 3 alumnes que tenen una nota més alta.
#Possible solució - ordenar els alumnes per nota i mostrar els 3 primers.
exercicis_frame5 = exercicis_frame.sort_values(by=["grade"],axis=0,ascending=False)
print(exercicis_frame6[0:3])

print("Usant una màscara, mostra els noms dels alumnes que volen fer FCT.")
exercicis_frame6 = exercicis_frame.loc[exercicis_frame[‘fct’] == True]
#print(exercicis_frame6.loc['student_list'])

exercicis_frame7 = exercicis_frame.loc[exercicis_frame['grade'] >= 7]
print("Usant una màscara, mostra els alumnes que tenen una nota superior o igual a 7.")
print(exercicis_frame7)

print("Consulta tota la info dels alumnes Ann, Lucy i John.")
print(exercicis_frame.loc[["Ann","Lucy","John"]])

# Consulta les notes de la Mary.
print(exercicis_frame.loc["Mary","grade"])

# Mostra el camp index i la nota dels alumnes, ordenats alfabèticament.
print("11. Mostra el camp index i la nota dels alumnes que tenen nota.")
exercicis_frame11 = exercicis_frame.loc[exercicis_frame['grade'] > 0]
print(exercicis_frame.loc[exercicis_frame11.index,'grade'])

Consulta els alumnes que volen fer FCT i han aprovat (grade >=5) 
print(df1[((df1['fct'] == True) & (df1['grade'] >= 5))])

Agrupació i agregació, molt important per a l'anàlisi de dades

En realitzar l'agregació, transformem les nostres dades en informació.

Per exemple, ens pot interessar agrupar tots els pacients per grups d'edat, o per gènere; per a fer un recompte de quants n'hi ha de cada (pex 11 dones i 14 homes).

També, per fer altres funcions d'agregació un cop agrupades; com la mitjana del sou d'homes i dones.

Exemple: Conjunt de Dades dels Pingüins de Palmer

En aquest tema, utilitzarem el conjunt de dades dels Pingüins de Palmer com a exemple. Pots importar-lo des de GitHub amb les següents línies (es requereix connexió a Internet):

import pandas as pd
df = pd.read_csv(
'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv')

El títol de cada columna parla per si mateix:

df.head(3)

Sortida:

  species    island  bill_length_mm  bill_depth_mm  flipper_length_mm  body_mass_g  sex
0  Adelie  Torgersen          39.1          18.7            181.0       3750.0    MALE
1  Adelie  Torgersen          39.5          17.4            186.0       3800.0  FEMALE
2  Adelie  Torgersen          40.3          18.0            195.0       3250.0  FEMALE

Observació: bill_length_mm i bill_depth_mm són la longitud i profunditat del bec (on mengen) i flipper_lenght_mm la longitud de les aletes.

DataFrame.aggregate

.aggregate() és un mètode de pandas DataFrame i Series que s'utilitza per a l'agregació de dades. Es pot utilitzar per a una o moltes funcions al llarg de qualsevol eix. Per estalviar temps per a coses realment importants (com el Machine Learning o Netflix), potser voldràs utilitzar la versió més curta del mètode — .agg(). Vegem els següents exemples.

Suposem que volem trobar el valor mitjà de la massa corporal del pingüí. Ho pots fer aplicant el mètode .agg() a la sèrie desitjada:

df.body_mass_g.agg('median')

Sortida:

4050.0

Quatre quilos de tendresa antàrtica en blanc i negre!

Consell: Pots passar una funció, el nom de la funció (com a cadena de text), una llista de funcions (o els seus noms) o un diccionari a .agg().

Per exemple, 'unique' en df.body_mass_g.agg('unique') obtindrà els elements únics de la columna body_mass_g, 'nunique' comptarà els elements únics en la columna especificada, i 'sum' en df.body_mass_g.agg('sum') produirà la suma dels valors de body_mass_g.

A més, pots passar una funció així (aquí passem la funció incorporada de Python sum(), però es pot passar qualsevol funció que gestioni un DataFrame):

df.body_mass_g.agg(sum)

També podríem importar numpy, executar df.body_mass_g.agg(numpy.sum) i obtenir el mateix resultat.

Una altra manera de fer-ho és trucant al mètode .median() de la sèrie:

df.body_mass_g.median()

La diferència és que utilitzant el mètode .agg(), també podem aplicar diverses funcions d'agregació a diferents columnes. Per exemple, trobem el bec més curt i el mitjà del pingüí, així com les aletes més llargues i les mitjanes.

Per fer això, hem de cridar la funció agg() i crear un diccionari de Python utilitzant les columnes com a claus del diccionari. A més, necessitem posar les funcions d'agregació en llistes:

df.agg({'bill_length_mm': ['min', 'mean'],
         'flipper_length_mm': ['max', 'mean']
        })

Sortida:

  bill_length_mm  flipper_length_mm
 min     32.10000                NaN
mean     43.92193          200.915205
 max           NaN          231.000000

Recompte de valors NaN.

Consell: La funció retorna NaN per als valors que no hem especificat.

També és possible agregar les dades amb les teves pròpies funcions. L'exemple de la funció a continuació retorna el nombre de valors perduts. Si el conjunt no conté aquests valors, posa 0 (per defecte):

def count_nulls(series, ok_message=0):
    if not series.isna().sum():
        return ok_message
    return len(series) - series.count()

Fem-ho servir en el nostre DataFrame:

df.agg(count_nulls)

Sortida:

species               0
island                0
bill_length_mm        2
bill_depth_mm         2
flipper_length_mm     2
body_mass_g           2
sex                  11
dtype: int64

Si necessites passar un paràmetre a la funció agg(), simplement enumera els seus noms i valors separats per una coma després del nom de la funció:

df.agg(count_nulls, ok_message='Hurray!')

Sortida:

species              Hurray!
island               Hurray!
bill_length_mm             2
bill_depth_mm              2
flipper_length_mm          2
body_mass_g                2
sex                       11
dtype: object

Consell: Si utilitzes funcions no pandas (NumPy o les teves pròpies) — no posis els seus noms entre cometes.

Si vols trobar el valor més gran d'un conjunt específic de característiques per a cada pingüí (suposem que volem veure el valor més gran de 3 característiques — bill_length_mm, bill_depth_mm, i flipper_length_mm), aplica .agg() sobre les columnes establint l'argument axis a 'columns'.

També podem trucar al mètode .agg() per treballar amb un dataframe que només conté valors numèrics. Utilitzem la funció incorporada de Python max(). Resulta en una sèrie de pandas:

df[['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm']].agg(max, 
axis='columns')

Sortida:

0      181.0
1      186.0
2      195.0
3        NaN
4      193.0
...  
339      NaN
340    215.0
341    222.0
342    212.0
343    213.0
Length: 344, dtype: float64

DataFrame.groupby

Podem obtenir una sortida estadística de diferents columnes amb l'agregació, però per entendre les dades en profunditat, necessitem tenir una mirada més propera a diverses parts i combinacions de columnes. Per a aquest propòsit, podem utilitzar groupby(). És una eina molt senzilla, especialment per a aquells que ja estan familiaritzats amb SQL.

Ara comprovem la longitud mitjana del bec per a femelles i mascles. Agrupa tots els pingüins pel seu sexe i agrega'ls amb una línia:

df.groupby(['sex']).agg({'bill_length_mm':'median'})

Sortida:

       bill_length_mm
   sex   
FEMALE           42.8
  MALE           46.8

El df.groupby(['sex']) solament retorna alguna cosa com <pandas.core.groupby.generic.DataFrameGroupBy object at 0x000002A1E7BE8358>. Això és perquè només és un objecte en memòria amb valors dividits per a grups indicats. L'intèrpret no sap què mostrar tret que ho especifiquis directament amb .agg().

Com has notat, la columna "sex" conté 11 valors perduts (alguns pingüins han preferit no compartir el seu gènere). Per incloure'ls en la nostra agrupació, estableix l'argument dropna de groupby a False (es requereix pandas 1.1 o superior):

df.groupby('sex', dropna=False).agg({'bill_length_mm':'median'})

Sortida:

       bill_length_mm
   sex   
FEMALE           42.8
 

 MALE           46.8
   NaN           42.0

Ara podem assumir que els pingüins de sexe desconegut són majoritàriament femelles.

A continuació, suposem que la dieta del pingüí varia per illa, i afecta la longitud del bec. Per comprovar si té sentit o no, agrupem els nostres amics antàrtics per illa i sexe. Tingues en compte que quan necessitem passar diversos arguments, hem de passar una llista:

df.groupby(['island', 'sex']).agg({'bill_length_mm':'median'})

Sortida:

                  bill_length_mm
island    sex                   
Biscoe    FEMALE            44.9
          MALE              48.5
Dream     FEMALE            42.5
          MALE              49.1
Torgersen FEMALE            37.6
          MALE              41.1

L'ordre dels arguments en la llista d'arguments de .groupby() determina l'ordre d'agrupació. Primer vénen els grups, després els subgrups, els subgrups dels subgrups, i així successivament.

Consell: Com has vist fins ara, groupby farà que les etiquetes de grup que li passes es converteixin en columnes d'índex — el paràmetre as_index és responsable d'això i està configurat a True per defecte. Per al nostre últim exemple, si truquem a .index.names, podem veure que tenim 2 índexs — 'island' i 'sex':

df.groupby(['island', 'sex']).agg({'bill_length_mm':'median'}).index.names

La sortida tindrà aquest aspecte: FrozenList(['island', 'sex'])

En cas que vulguis evitar establir l'índex (els índexs) a les etiquetes de grup passades a .groupby(), has d'establir as_index a False:

df.groupby(['island', 'sex'], as_index = False).agg({'bill_length_mm':'median'})

Exercicis dades dels pingüins.

Des del mateix dataset dels pingüins de Palmer, realitza les segúents operacions.

  1. Retorna el nom de les espècies de pingüins agrupades per illes.

Resultat esperat:

                  species
island                     
Biscoe     [Gentoo, Adelie]
Dream           [Chinstrap]
Torgersen          [Adelie]
  1. Compta el número de pingüins que hi ha a cada illa.

Resultat esperat:

      island  species
0     Biscoe      168
1      Dream      124
2  Torgersen       52
  1. The meanest penguin Mostra el pes mitja en quilograms (mean). Arrodoneix la resposta a 2 decimals.

  2. Compta el número de pingüins femella i mascle dins del dataset.

Resultat esperat aproximat:

       species
sex            
FEMALE       X
MALE         Y
  1. Cadena de pingüins en cada illa Mostra la suma de la longitud de les aletes de cada espècie de pingüí de cada illa, així sabrem quanta distancia abarquen tots els pingüins junts.
                     flipper_length_mm
island    species                     
Biscoe    Adelie                 203.0
          Gentoo                1962.0
Dream     Adelie                 559.0
          Chinstrap              398.0
Torgersen Adelie                 936.0

Recorda com carregar el fitxer:

import pandas as pd

df = pd.read_csv(
'https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv')

#print(df.describe())
print(df.head())
print(df.groupby('island', as_index=False).agg({'species': 'count'}))
print(df.groupby('island', as_index=False).agg({'species': 'count'}))
df['body_mass_g'] = df['body_mass_g'] / 1000
print(round(df['body_mass_g'].mean(),2))
print(df.groupby('sex').count())
comp_results = df.groupby(["island","species"]).agg({'flipper_length_mm': 'sum'})

Exemple 2 - Visites eCommerce.

Ja vam veure una funció d’agrupació senzilla i potent: el value_counts, per fer el recompte d’ocurrències dels possibles valors d’un camp.

Però a vegades no és suficient, ens interessa filar més prim. Per exemple, ens interessa sumar valors per gurps d’una categoria concreta, calcular-ne la mitjana, màxim…

agrupació (groupby).

Imaginem-nos que hem muntat un e-Commerce (una botiga digital) i tenim dades de 4 usuaris amb:

  • la data en què van entrar
  • el nom
  • el temps de sessió en minuts (quant de temps van estar a la botiga)
  • el que van gastar quan van comprar.

Els tenim organitzats en aquest dataframe:

df = pd.DataFrame({
    'dia': ['2023-02-22', '2023-02-22', '2023-02-22', '2023-02-22', '2023-02-23', '2023-02-23', '2023-02-23', '2023-02-23'],
    'nom': ['Juan', 'Pepe', 'Carla', 'Cristina', 'Juan', 'Pepe', 'Carla', 'Cristina'],
    'temps_sessio': [10, 32, 7, 15, 2, 5, 6, 7],
    'despesa_total': [20, 39.99, 56.78, 14.24, 14.99, 25.67, 35.17, 8.95]
})
print(df.head(8))

Ja sabem com fer la suma del temps de sessió de tots els usuaris, de fer el recompte d’usuaris per dia i altres.

Però com aconseguim saber el temps de sessió i despesa total de cada dia ?

Molt fàcil: agrupem les dades pel camp dia i fem la funció d’agregació sum

df_suma_temps_despesa_dia = df.groupby('dia').sum()
df_suma_temps_despesa_dia.drop(['nom'],axis=1,inplace=True)
print(df_suma_temps_despesa_dia)

També podem mostrar la despesa total de cada usuari en tots els dies.

print(df.groupby('nom')['despesa_total'].sum().sort_values(ascending=False))

Fixeu-vos com n’és d’interessant fer un gràfic d’aquesta informació.

agregació de files calculades (agg).

Una altra funcionalitat molt potent és crear un dataframe amb diverses dades agrupades de les despeses de cada dia, o per nom.

df_agg = df.groupby('dia').agg(
    total=('despesa_total', sum),
    media=('despesa_total', np.mean))
print(df_agg)
             		total    		media
dia                        
2023-02-22  	131.01  	32.7525
2023-02-23  	114.78  	28.6950

Pregunta: Com ho farem per saber el total, la mitjana i el màxim que ha gastat cada usuari ?

Resposta:

df_agg2 = df.groupby('nom').agg(
    total=('despesa_total', sum),
    mitjana=('despesa_total', np.mean),
    max_dia=('despesa_total', max))
print(df_agg2)
      	total  mitjana  max_dia
nom                         	 
Carla 	91.95   45.975	56.78
Cristina  23.19   11.595	14.24
Josep 	29.00   29.000	29.00
Juan  	34.99   17.495	20.00
Pepe  	65.66   32.830	39.99

Font de l'Exemple e-Commerce

Conclusions agg i groupby

En aquest tema, has après a:

  • Calcular les estadístiques resum de la columna amb l'ajuda de .agg()
  • Separar el conjunt de dades en grups amb l'ajuda de .groupby()

Aquests mètodes de pandas poden ajudar-te a trobar valors atípics en les dades, avaluar una sèrie d'observacions i comparar grups amb la característica estadística desitjada.

No obstant això, tingues en compte el possible resultat quan realitzis una agregació i agrupació complicada de dades reals. Un diagrama dibuixat en paper pot estalviar hores de correcció de codi. Si continues obtenint errors i respostes incorrectes, probablement no tingui res a veure amb l'ús de Python — potser la teva lògica estigui fallant.


Combinació de diversos DataFrames

Com ja sabeu, hi ha dues estructures de dades principals pandas: Series (unidimensional) i DataFrames (bidimensional).

Abans, vas aprendre a crear objectes dels dos tipus. També heu après que podeu transferir dades de diferents fitxers i taules a DataFrames.

Ara, imagineu-vos que heu de treballar amb diversos conjunts de dades similars o relacionats i processar-los de la mateixa manera.

Què és el primer que podríeu fer per facilitar aquesta tasca? Potser us agradaria combinar-los.

Per sort per a tu, pandas proposa diverses maneres de fer-ho. En aquest tema, aprendràs a unir-te DataFrame i Series objectes utilitzant funcions concat() i merge().

Referència:

  • [https://medium.com/analytics-vidhya/python-tip-6-pandas-merge-dev-skrol-bf0be41f29b7]

Concaternació (concat)

La funció concat s'utilitza per concatenar o enganxar diversos objectes junts al llarg d'un eix horitzontal o vertical.

Per fer-ho, hem de passar múltiples Series o DataFrames com a arguments de la funció.

Però primer, crearem dos DataFrame: les taules que emmagatzemen els noms dels estudiants i els seus resultats després d'una competició d'atletisme en proves de 100 metres i 2 quilòmetres respectivament:

junior_class = pd.DataFrame({'Name': ['Ann', 'Kate', 'George', 'Eric'],
'100m (sec.)': ['16.3', '17.1', '14.8', '14.3'],
'2km (min., sec.)': ['9,24', '9,45', '9,17', '8,14']},
index=[1, 2, 3, 4])

senior_class = pd.DataFrame({'Name': ['Jack', 'Alicia', 'Ella', 'James'],
'100m (sec.)': ['15.9', '17.8', '17.0', '15.0'],
'2km (min., sec.)': ['8,18', '9,02', '8,58', '7,58']})

Tingueu en compte que els seus índexs són diferents i independents entre si: el primer DataFrame està indexat en el rang 1-4 i el segon té el valor predeterminat 0, 1, 2, ... .

Es fa amb finalitats il·lustratives: la indexació és important en la concatenació, així que mostrarem com aquestes diferències afecten els resultats i com es poden restablir els valors inicials.

Ara, hauríem de passar el DataFrames a concat() com a seqüència o mapeig:

pd.concat([junior_class, senior_class])
Name 100m (sec.) 2km (min., sec.)
1 Ann 16.3 9,24
2 Kate 17.1 9,45
3 George 14.8 9,17
4 Eric 14.3 8,14
0 Jack 15.9 8,18
1 Alicia 17.8 9,02
2 Ella 17.0 8,58
3 James 15.0 7,58

Com heu pogut notar, el segon DataFrame s'ha afegit a sobre del primer.

Què passa si els vols afegir un al costat de l'altre?

Tot i que té sentit quan es tracta d'índexs significatius (per exemple, anys, dni, cognoms), en el nostre cas probablement ens agradaria tornar-los a calcular.

Aquests i alguns altres problemes es poden resoldre ajustant els paràmetres següents:

axis — l'eix al llarg del qual s'ha de concatenar. Els valors possibles són 0 i 1 ('0' per defecte): axis=0 significa combinar al llarg de les files, i axis=1 és per combinar al llarg de columnes. Ara establim el valor de axis a 1:

pd.concat([junior_class, senior_class], axis=1)
Name 100m (sec.) 2km (min., sec.) Name 100m (sec.) 2km (min., sec.)
0 NaN NaN NaN Jack 15.9 8,18
1 Ann 16.3 9,24 Alicia 17.8 9,02
2 Kate 17.1 9,45 Ella 17.0 8,58
3 George 14.8 9,17 James 15.0 7,58
4 Eric 14.3 8,14 NaN NaN NaN

Us heu adonat que alguns camps estan plens de valors de NaN?

NaN significa "No és un número".

Aquesta és la manera de gestionar els valors que falten pandas i a Numpy; ho hem vist a:

Valors NaN a Numpy

Com ja hem vist, junior_class i senior_class estan indexats de manera diferent.

Per tant, cada vegada que hi hagi aquests desajustos, els camps de dades no coincidents s'ompliran de NaN.

ignore_index— mantenir o restablir els índexs originals en concatenar. Els valors possibles són "True" i "False" (per defecte "False").

Si voleu que el vostre objecte resultant es torni a ordenar, especifiqueu-lo ignore_index=True.

Ara, l'eix s'etiquetarà amb índexs numèrics que comencen per 0:

pd.concat([junior_class, senior_class], ignore_index=True)
Name 100m (sec.) 2km (min., sec.)
0 Ann 16.3 9,24
1 Kate 17.1 9,45
2 George 14.8 9,17
3 Eric 14.3 8,14
4 Jack 15.9 8,18
5 Alicia 17.8 9,02
6 Ella 17.0 8,58
7 James 15.0 7,58

Si li afegim un tercer dataframe també ens el fa bé:

veteran_class = pd.DataFrame({'Name': ['Mike', 'Lisa'],
'100m (sec.)': ['21.9', '18.8'],
'2km (min., sec.)': ['11,18', '10,02']})
concat_pd = pd.concat([junior_class, senior_class, veteran_class], ignore_index=True)

join — combinant-se amb el ' exterior ' o ' interior tipus d'unió ' ('exterior' per defecte). El tipus d'unió exterior retorna la unió de tots els objectes, és a dir, es conservaran totes les seves files originals. Per contra, el tipus intern inclou només les files etiquetades amb índexs presents als dos conjunts de dades, excloent totes les altres files.

Mireu l'exemple següent: hi ha files marcades amb els números 1, 2 i 3, però les files marcades amb 0 (de senior_class) i 4 (des de junior_class) s'eliminen:

pd.concat([junior_class, senior_class], axis=1, join='inner')
Name 100m (sec.) 2km (min., sec.) Name 100m (sec.) 2km (min., sec.)
1 Ann 16.3 9,24 Alicia 17.8 9,02
2 Kate 17.1 9,45 Ella 17.0 8,58
3 George 14.8 9,17 James 15.0 7,58

keys— afegir un nou nivell d'etiquetes per indicar, per exemple, de quines Series o DataFrame els valors provenen o els agrupa d'una altra manera.

Els noms s'han de passar com una seqüència, per exemple, llista:

pd.concat([junior_class, senior_class], keys=['Jun. class', 'Sen. class'])
Name 100m (sec.) 2km (min., sec.)
Jun. class 1 Ann 16.3 9,24
2 Kate 17.1 9,45
3 George 14.8 9,17
4 Eric 14.3 8,14
Sen. class 0 Jack 15.9 8,18
1 Alicia 17.8 9,02
2 Ella 17.0 8,58
3 James 15.0 7,58

Fusió (merge)

Comparat amb concat(), merge()és una eina de combinació més flexible que ofereix possibilitats d'aprofundir una mica més en l'estructura dels objectes. La funció està arrelada en la idea de l'anomenada unió a l'estil de base de dades : unir-se a partir de columnes compartides.

Si esteu familiaritzat amb els tipus d'unions SQL i SQL, la funcionalitat principal de merge() us recordarà les operacions següents: INNER JOIN, LEFT OUTER JOIN, RIGHT OUTER JOIN i FULL OUTER JOIN.

TODO "pd_join.png" (no està imatge)

La funció necessita dos objectes per operar:

Un DataFrame per una banda; i un altre DataFrame o una Series.

Farem servir el DataFrame cap a junior_class de l'apartat anterior.

Suposem que volem fusionar-lo amb un de nou DataFrame que conté informació sobre l'edat dels participants.

Suposem que inclourem només tres membres al dataframe:

age_of_participants = pd.DataFrame({'Name': ['Ann', 'Eric', 'Ella'],
'Age': ['16', '16', '18']})

Ara, passem els dos DataFrame objectes a merge():

junior_class.merge(age_of_participants)
Name 100m (sec.) 2km (min., sec.) Age
0 Ann 16.3 9,24 16
1 Eric 14.3 8,14 16

Com podeu veure, només tenim dues files. Tots dos junior_class i age_of_participants tenen la mateixa columna "Nom".

Així, per defecte, s'uneixen a partir d'aquesta clau coincident.

Només les files que tenen valors superposats de "Nom" en ambdues DataFrames es produeixen: la informació sobre l'edat dels altres participants no està disponible i tindran NaN contra els seus noms a la columna "Edat", de manera que els seus resultats no s'inclouen a la taula final.

Això passa perquè merge() té un paràmetre semblant a join de concat(), i s'estableix per defecte al tipus d'unió interior. Vegem algunes altres opcions i paràmetres per ajustar els resultats:

how- defineix la manera de fusionar. Els valors possibles són ìnner, outer, left i right per defecte inner.

A dalt, vam veure un exemple de combinació interior; si especifiquem 'exterior', obtindríem la unió de totes les dades.

A veure què passa si escrivim how='left':

junior_class.merge(age_of_participants, how='left')
Name 100m (sec.) 2km (min., sec.) Age
0 Ann 16.3 9,24 16
1 Kate 17.1 9,45 NaN
2 George 14.8 9,17 NaN
3 Eric 14.3 8,14 16

Podem veure totes les entrades del primer conjunt de dades, tot i que alguns valors de la columna "Edat" estan absents. De la mateixa manera, how='right' ens aconseguirà totes les files del segon DataFrame.

Per exemple, la fila, corresponent al nom 'Ella', té valors de NaN en '100m (seg.)' i '2km (min., seg.)', ja que junior_class no conté informació sobre les seves puntuacions:

junior_class.merge(age_of_participants, how='right')
Name 100m (sec.) 2km (min., sec.) Age
0 Ann 16.3 9,24 16
1 Eric 14.3 8,14 16
2 Ella NaN NaN 18

on- permet combinar dos dataframes basant-se en una o més columnes que comparteixen alguna relació.

left_on** i right_on s'utilitzen per especificar les columnes en les quals vols basar la unió dels dos dataframes. La diferència principal entre els dos és:

  • left_on: Especifica la columna (o columnes) del dataframe de l'esquerra (el primer dataframe) que utilitzaràs per fer la unió.
  • right_on: Especifica la columna (o columnes) del dataframe de la dreta (el segon dataframe) que utilitzaràs per fer la unió.

Exemple Pràctic funció merge amb paràmetre on

Imagina que tens dos dataframes: df1 i df2; que volem unir utilitzant les columnes id de df1 i identificador de df2.

import pandas as pd

# Dataframes d'exemple
df1 = pd.DataFrame({
    'id': [1, 2, 3],
    'nom': ['Anna', 'Joan', 'Marta'],
    'edat': [28, 34, 22]
})

df2 = pd.DataFrame({
    'identificador': [1, 2, 4],
    'ciutat': ['Barcelona', 'Madrid', 'València'],
    'país': ['Espanya', 'Espanya', 'Espanya']
})

df_merged = pd.merge(df1, df2, left_on='id', right_on='identificador')

print(df_merged)

Resultat:

id nom edat identificador ciutat país
1 Anna 28 1 Barcelona Espanya
2 Joan 34 2 Madrid Espanya

Amb aquests paràmetres, pots unir dataframes fins i tot si les columnes que utilitzes per unir-los tenen noms diferents en cada dataframe.


Diferències entre concat i merge.

Ara que hem revisat els casos d'ús d'ambdues funcions, aclarim les diferències clau entre elles:

  • Principals casos d'ús. En general, concat() s'utilitza simplement per col·locar diversos objectes un al costat de l'altre o un sobre un altre; al mateix temps, merge()s'utilitza principalment per unir-se com a base de dades: el seu conjunt de paràmetres fa que la unió sigui més flexible i més conscient del contingut.
  • El nombre d'objectes que podem unir. concat() pot acceptar una seqüència de diversos objectes, mentre merge() només ens permet unir-ne dos DataFrames o a DataFrame i un nom Series.
  • Tractament d'eixos (axis). Quan s'utilitza concat(), podeu especificar l'eix al llarg del qual necessiteu unir objectes; merge() només admet la unió una al costat de l'altra.
  • Operacions semblants a bases de dades. concat() només pot realitzar una unió interna o externa, mentre merge() pot fer tipus d'unió interior, exterior, esquerra i dreta.

Exercicis.

Fusiona els 3 dataframe de dades mèdiques amb informació addicional sobre els pacients utilitzant la columna "Pacient" com a clau de fusió. Assegura’t que no apareguin files duplicades i que tota la informació existeix.

import pandas as pd
dades_mediques = {
    'Pacient': ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7', 'P8'],
    'Pressió Arterial': [130, 118, 125, 140, 122, 118, 135, 128],
    'Temperatura Corporal': [37.1, 36.8, 37.2, 37.5, 37.0, 36.7, 37.3, 37.2]
}
df_mediques = pd.DataFrame(dades_mediques)
informacio_addicional = {
    'Pacient': ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7'],
    'Edat': [35, 42, 28, 54, 46, 31, 57]
}
df_informacio_addicional = pd.DataFrame(informacio_addicional)
informacio_addicional_2 = {
    'Pacient': ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7', 'P9'],
    'GrupSang': ['A+', 'A-', 'O+', 'A+', 'B+', 'O-', 'O-', 'A+']
}
df_informacio_addicional_2 = pd.DataFrame(informacio_addicional_2)

Sortida esperada:

Pacient Pressió Arterial Temperatura Corporal Pacient Edat Pacient GrupSang
0 P1 130 37.1 P1 35.0 P1 A+
1 P2 118 36.8 P2 42.0 P2 A-
2 P3 125 37.2 P3 28.0 P3 O+
3 P4 140 37.5 P4 54.0 P4 A+
4 P5 122 37.0 P5 46.0 P5 B+
5 P6 118 36.7 P6 31.0 P6 O-
6 P7 135 37.3 P7 58.0 P8 O-
7 P8 128 37.2 NaN NaN P9 A+

Fusiona els tres DataFrames utilitzant "Pacient" com a clau i amb l'opció "how='outer'" per assegurar que tots els pacients estiguin presents.

df_fusionat = df_mediques.merge(df_informacio_addicional, on='Pacient', how='outer')
df_fusionat = df_fusionat.merge(df_informacio_addicional_2, on='Pacient', how='outer')
print(df_fusionat)

Fixeu-vos que amb el concat no és suficient, per molt que li fiquem outer. Per a què el concat funcionés bé totes les files haurien de ser comunes.

Concaterna els tres DataFrames utilitzant "Pacient" com a clau

df_concat2 = pd.concat([df_mediques,df_informacio_addicional,
df_informacio_addicional_2], axis=1, ignore_index=True, join='outer')
print(df_concat2)

Creació de nous camps calculats en pandas.

Crear noves columnes que necessitem en funció d’altres és molt senzill!

Exemples on ens pot interessar:

  • Tenim un camp edat i ens interessa separar per grups d’edat, per a poder fer recomptes i gràfics.
  • Volem juntar nom i cognoms en un mateix camp.
  • Volem calcular l'edat a partir de la data de naixement.
  • Volem calcular l’imc a partir de l’alçada i el pes
  • Volem extreure informació d’un camp string que conté informació rellevant; per exemple ens interessa el primer digit del codi dels ous.

Com crear un nou camp:

Facilissim, si usem el següent dataframe d’exemple:

import pandas as pd
people = {
"first_name": ["Michael", "Michael", 'Jane', 'John'],
"last_name": ["Jackson", "Jordan", 'Doe', 'Doe'],
"birthday": ["29.09.1958", "17.02.1963", "15.03.1978", "12.05.1979"],
"height": [1.75, 1.98, 1.64, 1.8]
}
df = pd.DataFrame(people)

Podem aconseguir un nou camp que es digui full_name amb el cognom i el nom; de la següent manera:

df['full_name'] = df['last_name'] + ', ' + df['first_name']

Sortida:

first_name last_name birthday height full_name
0 Michael Jackson 29.09.1958 1.75 Jackson, Michael
1 Michael Jordan 17.02.1963 1.98 Jordan, Michael
2 Jane Doe 15.03.1978 1.64 Doe, Jane
3 John Doe 12.05.1979 1.80 Doe, John

O bé aconseguir un camp birth_year a partir del birthday:

import datetime
year = datetime.datetime.today().year
df['birth_date'] = df['birthday']
df.head()

Crear un nou camp calculat amb apply:

Això ho aconseguim amb la funció apply. A aquesta li podem passar una funció lambda o una funció pura per a què la apliqui en cada fila (com feiem amb la funció map de Python).

Provem un parell d’exemples amb dades de pacients generades aleatòriament:

import numpy as np
import pandas as pd
import copy

min_value_edat: int = 20
max_value_edat: int = 120
min_value_altura = 1.20
max_value_altura = 2.20
min_value_pes: int = 40
max_value_pes: int = 140

df_pacients = pd.DataFrame({
'edat' : np.random.randint(min_value_edat, max_value_edat ,100),
'altura' : np.random.uniform(min_value_altura, max_value_altura , size=100),
'pes' : np.random.randint(min_value_pes, max_value_pes, size=100)
})

print(df_pacients.head())

** Exemple 1. Nou camp imc (bim).**

El calculem amb la formula

pes (kgs.) / altura (cms.) ^2

def calcula_imc(df_pacients) -> float:
    return round(df_pacients['pes'] / df_pacients['altura'] ** 2, 4)

axis=1, apliquem resultats a les columnes.

df_pacients['imc'] = df_pacients.apply(calcula_imc, axis=1)

Exemple 2. Nou camp que defineix si un pacient és major a 65 o menor.

def equal_or_over_65_years(age: int) -> bool:
    return "Grup65oMes" if age >= 65 else "GrupMenor65"
    df_pacients['grup_edat'] = df_pacients['edat'].apply(equal_or_over_65_years)
    print(df_pacients.head())

Amb funcions lambda:

df_pacients['grup_edat'] = df_pacients['edat'].apply(lambda age: "Grup65oMes" if age >= 65 else "GrupMenor65")

Sortida:

Fixeu-vos que ha creat correctament els 2 camps.

edat altura pes imc grup_edat
0 95 1.448017 58 27.6618 Grup65oMes
1 20 1.696707 115 39.9470 GrupMenor65
2 115 1.904625 71 19.5722 Grup65oMes
3 63 2.069863 125 29.1761 GrupMenor65
4 50 1.788875 119 37.1867 GrupMenor65

Exemple 3. Classificació de grups d’edat més avançada:

import pandas as pd
import numpy as np
data = {
'Edat': [25, 42, 18, 60, 35, np.nan, np.nan, np.nan],
'Gènere': ['Home', 'Dona', 'Home', 'Dona', 'Home', 'Dona', 'Home', 'Dona']
}
df = pd.DataFrame(data)

Solució

def clasificar_grup_edat(edat):
    if pd.isna(edat):
        return 'Edat desconeguda'
    elif edat < 20:
        return '0-19'
    elif 20 <= edat < 30:
        return '20-29'
    elif 30 <= edat < 40:
        return '30-39'
    elif 40 <= edat < 50:
        return '40-49'
    elif edat >= 50:
        return '50+'
df['grup_edat'] = df['Edat'].apply(clasificar_grup_edat)
print(df)

Sortida:

Edat Gènere Grup d'Edat
0 25.0 Home 20-29
1 42.0 Dona 40-49
2 18.0 Home 0-19
3 60.0 Dona 50+
4 35.0 Home 30-39
5 NaN Dona Edat desconeguda
6 NaN Home Edat desconeguda
7 NaN Dona Edat desconeguda

Si tenim molts intervals d’edat val la pena usar la funció cut i llistes:

Defineix els intervals d'edat i les etiquetes dels grups

bins = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
labels = ['0-10', '11-20', '21-30', '31-40', '41-50', '51-60', '61-70', '71-80', '81-90', '91-100']

Utilitza la funció 'cut' per classificar les edats als grups

df['grup_edat'] = pd.cut(df['Edat'], bins=bins, labels=labels, right=False)

Codi de l’exemple 3 (les 2 funcions):

data = {
'Edat': [25, 42, 18, 60, 35, np.nan , 64, 81, 28, 53],
'Gènere': ['Home', 'Dona', 'Home', 'Dona', 'Home', 'Dona', 'Home', 'Dona', '', 'Dona']
}
df = pd.DataFrame(data)
def clasificar_grup_edat(edat):
if pd.isna(edat):
return 'Edat desconeguda'
elif edat < 20:
return '0-19'
elif 20 <= edat < 30:
return '20-29'
elif 30 <= edat < 40:
return '30-39'
elif 40 <= edat < 50:
return '40-49'
elif edat >= 50:
return '50+'

df['Grup Edat'] = df['Edat'].apply(clasificar_grup_edat)
print(df)

#Defineix els intervals d'edat i les etiquetes dels grups

bins = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200]
labels = ['0-10', '11-20', '21-30', '31-40', '41-50', '51-60', '61-70', '71-80', '81-90', '91-100', '100+']

Utilitza la funció 'cut' per classificar les edats als grups
df['Grup Edat'] = pd.cut(df['Edat'], bins=bins, labels=labels, right=False)
print(df)
```py

**Exemple 4. Agafar una data i extreure’n informació:** 

Fixeu-vos en aquest exemple, en el que a partir d’una data de naixement agafem només l’any i després calculem la data. 

Fixeu-vos que cal garantir que la data sigui format datetime.

```py
import datetime as dt
data = {
'DataNaixement': ['1990-05-15', '1985-10-20', '2000-03-08', '1978-12-30', '1995-07-04'],
'Gènere': ['Home', 'Dona', 'Home', 'Dona', 'Home']
}

df = pd.DataFrame(data)

tipus datetime
df['DataNaixement'] = pd.to_datetime(df['DataNaixement'])
df['AnyNaixement'] = df['DataNaixement'].dt.year
año_actual = dt.datetime.now().year
df['EdatActual'] = año_actual - df['AnyNaixement']
print(df)

Sortida:

DataNaixement Gènere AnyNaixement EdatActual
0 1990-05-15 Home 1990 33
1 1985-10-20 Dona 1985 38
2 2000-03-08 Home 2000 23
3 1978-12-30 Dona 1978 45
4 1995-07-04 Home 1995 28

També funciona per formats de data com el nostre (dd/mm/yyyy)

data2 = {
'DataNaixement': ['15/05/1990', '20/10/1985'],
'Gènere': ['Home', 'Dona']
}
DataNaixement Gènere AnyNaixement EdatActual
0 1990-05-15 Home 1990 33
1 1985-10-20 Dona 1985 38

Exercici pendent


Referències

Fusió i concaternació (no tant pero qui sap)

Encara no està acabat! El documents complets estàn a: