3. Manipulació de Dades amb Pandas
Introducció¶
Pandas¶
Pandas és la llibreria més popular de Python per a l'anàlisi de dades.
No podeu treballar amb dades si no les podeu llegir. En esta secció aprendrem a crear dades de forma estructurada, així com treballar amb dades preexistents.
Per començar a treballar amb pandas, el primer pas serà importar la llibrería. Al no formar part de les llibreries estàndar de Python, l'hauràs d'instal·lar (sobre l'entorn desitjat) amb:
conda install pandas
Normalment, al igual que amb numpy, es comença amb:
import pandas as pd
pd.set_option('display.max_rows', 5) # Per a que ens mostre 5 files com a màxim
pd.DataFrame({'Yes': [50, 21], 'No': [131, 2]})
| Yes | No | |
|---|---|---|
| 0 | 50 | 131 |
| 1 | 21 | 2 |
A diferència de numpy, pot contindre valors no numèrics:
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'], 'Sue': ['Pretty good.', 'Bland.']})
| Bob | Sue | |
|---|---|---|
| 0 | I liked it. | Pretty good. |
| 1 | It was awful. | Bland. |
Estem utilitzant pd.DataFrame() per generar els objectes DataFrame. La sintaxi per declarar-los és un diccionari de Python on les claus seran el nom de les columnes i els valors són les llistes d'entrada de cada clau.
El constructor de tious diccionari assigna a les files els valors 0, 1, 2,... per a les etiquetes de fila. En la majoria de casos pot ser útil, però podem assignar etiquetes a cada fila.
La llista d'etiquetes de fila s'anomena Index, i és el paràmetre que podem utilitzar per assignar els seus valors:
pd.DataFrame({'Bob': ['I liked it.', 'It was awful.'],
'Sue': ['Pretty good.', 'Bland.']},
index=['Product A', 'Product B'])
| Bob | Sue | |
|---|---|---|
| Product A | I liked it. | Pretty good. |
| Product B | It was awful. | Bland. |
Series¶
Una Serie, per contra, és una seqüència de valors. Si un DataFrame és una taula, una Serie és una llista. I en realitat, pots crear una serie passant una llista de valors:
pd.Series([1, 2, 3, 4, 5])
0 1 1 2 2 3 3 4 4 5 dtype: int64
Una Serie és, bàsicament, una columna d'un DataFrame. Igual que al DataFrame, pots assignar etiquetes a les files de cada Serie passant el paràmetre index. No obstant, una Serie no té un nom de columna, sinó simplement un nom general:
pd.Series([30, 35, 40], index=['2015 Sales', '2016 Sales', '2017 Sales'], name='Product A')
2015 Sales 30 2016 Sales 35 2017 Sales 40 Name: Product A, dtype: int64
Com hauràs pogut deduir, les Series i els DataFrame estan molt relacionats, podent pensar que un DataFrame és un conjunt de Series unides. Ho veurem més endavant.
Llegint dades d'arxius¶
La majoria de vegades no volem crear les dades manualment, sinó que volem treballar amb dades que ja existeixen.
Les dades poden estar allotjades en diferents formats, però una de les formes més freqüents i bàsiques és en format d'arxiu separat per comes o CSV.
Product A,Product B,Product C,
30,21,9,
35,34,1,
41,11,11
Per crear un DataFrame a partir d'un CSV utilitzarem la funció pd.read_csv(). El dataset que es carrega pots trobar-lo ací
import os
directory = os.path.abspath('')
resource_path = os.path.join(directory,"data","winemag-data-130k-v2.csv")
reviews = pd.read_csv(resource_path, index_col=0)
reviews.country
0 Italy
1 Portugal
...
129969 France
129970 France
Name: country, Length: 129971, dtype: object
També podem accedir fent ús de la notació pròpia dels diccionaris de Python, és a dir, a través de les claus:
reviews['country']
0 Italy
1 Portugal
...
129969 France
129970 France
Name: country, Length: 129971, dtype: object
Les dues formes d'accedir són igualment vàlides. Però accedir a través de la clau sempre funcionarà, mentre que en l'altre cas no. Si algun nom de columna té un nom no vàlid com a nom de variable, no funcionarà.
Per exemple, si tenim un objecte DataFrame persones amb una columna principal ocupació, persona.principal ocupació no funcionarà, mentre que, persones['principal ocupació'] si que funcionaria.
Com podeu observar, una Serie de pandas és com un diccionari, així que no és d'estranyar que, per llegir un únic valor específic, només necessitem utilitzar l'operador d'indexació [] una vegada més.
reviews['country'][0]
'Italy'
L'operador d'indexació i la selecció d'atributs són molt útils i fàcils d'utilitzar perquè funcionen igual que a la resta de l'ecosistema Python. No obstant, pandas té els seus propis operadors d'accés, loc i iloc, que les utilitzarem per a operacions més avançades.
Selecció basada en índexs¶
La indexació de Pandas funciona en un dels dos paradigmes. El primer és la selecció basada en índexs, que utilitza iloc: seleccionar dades en funció de la seva posició numèrica a les dades. iloc segueix aquest paradigma. Per seleccionar la primera fila de dades en un DataFrame, podem utilitzar el següent:
reviews.iloc[0]
country Italy
description Aromas include tropical fruit, broom, brimston...
...
variety White Blend
winery Nicosia
Name: 0, Length: 13, dtype: object
Tant si utilitzem loc com si utilitzem iloc hem d'indicar primer la fila i després la columna, igual que a numpy. Això implica que és més fàcil recuperar files i més difícil recuperar columnes. Per obtenir una columna amb iloc, podem fer el següent:
reviews.iloc[:, 0]
0 Italy
1 Portugal
...
129969 France
129970 France
Name: country, Length: 129971, dtype: object
Pregunta
Què estem seleccionant en el següent codi `reviews.iloc[:3, 0]`?
El mateix resultat el podem obtindre utilitzant el Fancy Indexing.
reviews.iloc[[0, 1, 2], 0]
0 Italy 1 Portugal 2 US Name: country, dtype: object
Finalment, també podem utilitzar números negatius per a seleccionar utilitzant índex. Començaran a indexar des del final en sentit ascendent.
reviews.iloc[-5:]
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 129966 | Germany | Notes of honeysuckle and cantaloupe sweeten th... | Brauneberger Juffer-Sonnenuhr Spätlese | 90 | 28.0 | Mosel | NaN | NaN | Anna Lee C. Iijima | NaN | Dr. H. Thanisch (Erben Müller-Burggraef) 2013 ... | Riesling | Dr. H. Thanisch (Erben Müller-Burggraef) |
| 129967 | US | Citation is given as much as a decade of bottl... | NaN | 90 | 75.0 | Oregon | Oregon | Oregon Other | Paul Gregutt | @paulgwine | Citation 2004 Pinot Noir (Oregon) | Pinot Noir | Citation |
| 129968 | France | Well-drained gravel soil gives this wine its c... | Kritt | 90 | 30.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Gresser 2013 Kritt Gewurztraminer (Als... | Gewürztraminer | Domaine Gresser |
| 129969 | France | A dry style of Pinot Gris, this is crisp with ... | NaN | 90 | 32.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Marcel Deiss 2012 Pinot Gris (Alsace) | Pinot Gris | Domaine Marcel Deiss |
| 129970 | France | Big, rich and off-dry, this is powered by inte... | Lieu-dit Harth Cuvée Caroline | 90 | 21.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Schoffit 2012 Lieu-dit Harth Cuvée Car... | Gewürztraminer | Domaine Schoffit |
Selecció basada en etiquetes¶
El segon paradigma per a selecció d'atributs és l'utilitzat per loc, que utilitza la selecció basada en etiquetes. En este paradigma s'utilitza el valor de les claus o etiquetes, no la seua posició, per seleccionar valors.
Per exemple:
reviews.loc[0, 'country']
'Italy'
iloc és conceptualment més senzill que loc perquè ignora els índexs del conjunt de dades. Quan utilitzem iloc, tractem el conjunt de dades com una gran matriu (una llista de llistes) que hem indexat per posició. loc, en canvi, utilitza la informació dels índexs, és a dir el valor de les etiquetes o claus, per fer la indexació. Com que el vostre conjunt de dades sol tenir índexs significatius, normalment és més fàcil fer les coses amb loc.
Per exemple, hi ha una operacions que és molt més fàcil amb loc:
reviews.loc[:, ['taster_name', 'taster_twitter_handle', 'points']]
| taster_name | taster_twitter_handle | points | |
|---|---|---|---|
| 0 | Kerin O’Keefe | @kerinokeefe | 87 |
| 1 | Roger Voss | @vossroger | 87 |
| ... | ... | ... | ... |
| 129969 | Roger Voss | @vossroger | 90 |
| 129970 | Roger Voss | @vossroger | 90 |
129971 rows × 3 columns
Comprovar resposta
reviews.iloc[:, [8, 9, 3]]
Per a accedir mitjançant índex necessitem conèixer millor l'estructura de com està organitzada la informació.
Escollim entre loc i iloc¶
A l'hora de triar o fer el canvi entre loc i iloc, cal tindre en compte que els dos mètodes utilitzen esquemes d'indexació lleugerament diferents.
iloc utilitza l'esquema d'indexació stdlib de Python, on s'inclou el primer element de l'interval i s'exclou l'últim. Així, 0:10 seleccionarà les entrades 0,...,9.
loc, per la seva banda, utilitza índexs inclusivament. Així, 0:10 seleccionarà les entrades 0,...,10.
Per què el canvi? Recordeu que loc pot indexar qualsevol tipus stdlib: cadenes, per exemple. Si tenim un DataFrame amb valors d'índex Apples, ..., Potatoes, ..., i volem seleccionar "totes les opcions de fruita alfabètiques entre Apples i Potatoes", és molt més convenient indexar amb df.loc['Apples':'Potatoes'] que indexar alguna cosa com df.loc['Apples', 'Potatoet'] (t ve després de s a l'alfabet).
Això és molt confús quan l'índex és una llista numèrica simple, p. 0,...,1000. En aquest cas, df.iloc[0:1000] retornarà 1000 entrades, mentre que df.loc[0:1000] en retornarà 1001. Per obtindre 1000 elements amb loc, haureu d'anar un més avall i utilitzar df.loc[0:999]. En cas contrari, la semàntica d'utilitzar loc és la mateixa que la d'iloc.
Manipulant l'índex¶
La selecció basada en etiquetes obté el seu poder de les etiquetes a l'índex. Este índex, per sort, el podem manipular. Per fer-ho utilitzarem el mètode set_index().
Mira què passa quan executem el següent codi:
reviews.set_index("title")
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | variety | winery | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| title | ||||||||||||
| Nicosia 2013 Vulkà Bianco (Etna) | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | White Blend | Nicosia |
| Quinta dos Avidagos 2011 Avidagos Red (Douro) | Portugal | This is ripe and fruity, a wine that is smooth... | Avidagos | 87 | 15.0 | Douro | NaN | NaN | Roger Voss | @vossroger | Portuguese Red | Quinta dos Avidagos |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| Domaine Marcel Deiss 2012 Pinot Gris (Alsace) | France | A dry style of Pinot Gris, this is crisp with ... | NaN | 90 | 32.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Pinot Gris | Domaine Marcel Deiss |
| Domaine Schoffit 2012 Lieu-dit Harth Cuvée Caroline Gewurztraminer (Alsace) | France | Big, rich and off-dry, this is powered by inte... | Lieu-dit Harth Cuvée Caroline | 90 | 21.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Gewürztraminer | Domaine Schoffit |
129971 rows × 12 columns
Això és molt útil per a crear un índex per al conjunt de dades que siga millor que l'actual.
Aquesta operació de set_index retorna una còpia modificada del DataFrame sense modificar la original. Si la volem modificar hauriem de fer alguna de les dos opcions següents.
reviews.set_index("title",inplace=True)
reviews=reviews.set_index("title")
Selecció condicional¶
Fins ara hem estat indexant utilitzant propietats estructurals del propi DataFrame. Per fer coses interessants amb les dades, no obstant, hem de fer seleccions en funció de determinades condicions.
Per exemple, suposem que estem interessats específicament en vins millors que la mitjana produïts a Itàlia. Podem començar per comprovar si cada vi és italià o no:
reviews.country == 'Italy'
0 True
1 False
...
129969 False
129970 False
Name: country, Length: 129971, dtype: bool
L'execució ens torna una sèrie de booleans True/False basat en el país de cada registre. Aquest resultat es pot utilitzar dins de loc per seleccionar les dades rellevants:
reviews.loc[reviews.country == 'Italy']
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Nicosia 2013 Vulkà Bianco (Etna) | White Blend | Nicosia |
| 6 | Italy | Here's a bright, informal red that opens with ... | Belsito | 87 | 16.0 | Sicily & Sardinia | Vittoria | NaN | Kerin O’Keefe | @kerinokeefe | Terre di Giurfo 2013 Belsito Frappato (Vittoria) | Frappato | Terre di Giurfo |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129961 | Italy | Intense aromas of wild cherry, baking spice, t... | NaN | 90 | 30.0 | Sicily & Sardinia | Sicilia | NaN | Kerin O’Keefe | @kerinokeefe | COS 2013 Frappato (Sicilia) | Frappato | COS |
| 129962 | Italy | Blackberry, cassis, grilled herb and toasted a... | Sàgana Tenuta San Giacomo | 90 | 40.0 | Sicily & Sardinia | Sicilia | NaN | Kerin O’Keefe | @kerinokeefe | Cusumano 2012 Sàgana Tenuta San Giacomo Nero d... | Nero d'Avola | Cusumano |
19540 rows × 13 columns
Aquest DataFrame té uns 20.000 registres, mentre que l'original tenia uns 130.000. Això vol dir que al voltant del 15% dels vins són originaris d'Itàlia.
També volíem saber quins són millors que la mitjana. Els vins es classifiquen en una escala de 80 a 100 punts, de manera que això podria significar vins que hagin acumulat almenys 90 punts. Podem utilitzar el signe & (and) per unir les dues condicions:
reviews.loc[(reviews.country == 'Italy') & (reviews.points >= 90)]
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 120 | Italy | Slightly backward, particularly given the vint... | Bricco Rocche Prapó | 92 | 70.0 | Piedmont | Barolo | NaN | NaN | NaN | Ceretto 2003 Bricco Rocche Prapó (Barolo) | Nebbiolo | Ceretto |
| 130 | Italy | At the first it was quite muted and subdued, b... | Bricco Rocche Brunate | 91 | 70.0 | Piedmont | Barolo | NaN | NaN | NaN | Ceretto 2003 Bricco Rocche Brunate (Barolo) | Nebbiolo | Ceretto |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129961 | Italy | Intense aromas of wild cherry, baking spice, t... | NaN | 90 | 30.0 | Sicily & Sardinia | Sicilia | NaN | Kerin O’Keefe | @kerinokeefe | COS 2013 Frappato (Sicilia) | Frappato | COS |
| 129962 | Italy | Blackberry, cassis, grilled herb and toasted a... | Sàgana Tenuta San Giacomo | 90 | 40.0 | Sicily & Sardinia | Sicilia | NaN | Kerin O’Keefe | @kerinokeefe | Cusumano 2012 Sàgana Tenuta San Giacomo Nero d... | Nero d'Avola | Cusumano |
6648 rows × 13 columns
Igual que hem utilitzat l'operador condicional & (and) podriem utilitzar altres, com | (or) o ~ (not).
Pandas inclou uns quants selectors condicionals integrats, dos dels quals destacarem.
El primer és isin. isin us permet seleccionar dades el valor de les quals "es troba a" una llista de valors.
Per exemple, a continuació es mostra com el podem utilitzar per seleccionar vins només d'Itàlia o França:
reviews.loc[reviews.country.isin(['Italy', 'France'])]
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Nicosia 2013 Vulkà Bianco (Etna) | White Blend | Nicosia |
| 6 | Italy | Here's a bright, informal red that opens with ... | Belsito | 87 | 16.0 | Sicily & Sardinia | Vittoria | NaN | Kerin O’Keefe | @kerinokeefe | Terre di Giurfo 2013 Belsito Frappato (Vittoria) | Frappato | Terre di Giurfo |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129969 | France | A dry style of Pinot Gris, this is crisp with ... | NaN | 90 | 32.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Marcel Deiss 2012 Pinot Gris (Alsace) | Pinot Gris | Domaine Marcel Deiss |
| 129970 | France | Big, rich and off-dry, this is powered by inte... | Lieu-dit Harth Cuvée Caroline | 90 | 21.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Schoffit 2012 Lieu-dit Harth Cuvée Car... | Gewürztraminer | Domaine Schoffit |
41633 rows × 13 columns
Pregunta
Com obtindries el mateix resultat però utilitzant l'operador `|` (or)?
Comprovar resposta
reviews.loc[(reviews.country == 'Italy') | (reviews.country == 'France')]
El segon és isnull (i el seu company notnull). Aquests mètodes ens permeten recuperar valors que són (o no) buits (NaN).
Per exemple, per saber els vins que no tenen una etiqueta de preu, és a dir, dels quals no tenim la informació del seu preu, faríem:
reviews.loc[reviews.price.isnull()]
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Nicosia 2013 Vulkà Bianco (Etna) | White Blend | Nicosia |
| 13 | Italy | This is dominated by oak and oak-driven aromas... | Rosso | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Masseria Setteporte 2012 Rosso (Etna) | Nerello Mascalese | Masseria Setteporte |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129893 | Italy | Aromas of passion fruit, hay and a vegetal not... | Corte Menini | 91 | NaN | Veneto | Soave Classico | NaN | Kerin O’Keefe | @kerinokeefe | Le Mandolare 2015 Corte Menini (Soave Classico) | Garganega | Le Mandolare |
| 129964 | France | Initially quite muted, this wine slowly develo... | Domaine Saint-Rémy Herrenweg | 90 | NaN | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Ehrhart 2013 Domaine Saint-Rémy Herren... | Gewürztraminer | Domaine Ehrhart |
8996 rows × 13 columns
Assignació de valors¶
Per altra banda, podem assignar valors al DataFrame és senzill.
Per a incloure una nova columna, utilitzem una nova etiqueta i assignem valor.
reviews['critic'] = 'everyone'
reviews
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | critic | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Nicosia 2013 Vulkà Bianco (Etna) | White Blend | Nicosia | everyone |
| 1 | Portugal | This is ripe and fruity, a wine that is smooth... | Avidagos | 87 | 15.0 | Douro | NaN | NaN | Roger Voss | @vossroger | Quinta dos Avidagos 2011 Avidagos Red (Douro) | Portuguese Red | Quinta dos Avidagos | everyone |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129969 | France | A dry style of Pinot Gris, this is crisp with ... | NaN | 90 | 32.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Marcel Deiss 2012 Pinot Gris (Alsace) | Pinot Gris | Domaine Marcel Deiss | everyone |
| 129970 | France | Big, rich and off-dry, this is powered by inte... | Lieu-dit Harth Cuvée Caroline | 90 | 21.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Schoffit 2012 Lieu-dit Harth Cuvée Car... | Gewürztraminer | Domaine Schoffit | everyone |
129971 rows × 14 columns
reviews['index_backwards'] = range(len(reviews), 0, -1)
reviews['index_backwards']
0 129971
1 129970
...
129969 2
129970 1
Name: index_backwards, Length: 129971, dtype: int64
Si volem assignar un valor concret a una cel·la concreta, seleccionem la cel·la (amb loc o iloc) i assignem el valor desitjat.
reviews.loc[0,'critic'] = "Ferran"
reviews
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | critic | index_backwards | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Nicosia 2013 Vulkà Bianco (Etna) | White Blend | Nicosia | Ferran | 129971 |
| 1 | Portugal | This is ripe and fruity, a wine that is smooth... | Avidagos | 87 | 15.0 | Douro | NaN | NaN | Roger Voss | @vossroger | Quinta dos Avidagos 2011 Avidagos Red (Douro) | Portuguese Red | Quinta dos Avidagos | everyone | 129970 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129969 | France | A dry style of Pinot Gris, this is crisp with ... | NaN | 90 | 32.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Marcel Deiss 2012 Pinot Gris (Alsace) | Pinot Gris | Domaine Marcel Deiss | everyone | 2 |
| 129970 | France | Big, rich and off-dry, this is powered by inte... | Lieu-dit Harth Cuvée Caroline | 90 | 21.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Schoffit 2012 Lieu-dit Harth Cuvée Car... | Gewürztraminer | Domaine Schoffit | everyone | 1 |
129971 rows × 15 columns
Funcions de resum¶
Les dades no sempre les tenim en el format en què les volem. De vegades hem de reformatar les dades per poder fer una tasca.
Pandas ofereix funcions de resum que reestructuren les dades de forma que siguen més útils. Per exemple, fixem-nos en la funció describe():
reviews.points.describe()
count 129971.000000
mean 88.447138
...
75% 91.000000
max 100.000000
Name: points, Length: 8, dtype: float64
Aquest mètode genera un resum dels atributs de la columna donada.
És conscient del tipus, és a dir, la seua ixida canvia en funció del tipus de dades que conté la columna.
L'eixida anterior només té sentit per a dades numèriques; per a les dades de text, obtindriem:
reviews.country.describe()
count 129908 unique 43 top US freq 54504 Name: country, dtype: object
Si voleu obtenir alguna estadística senzilla sobre una columna en un DataFrame o una sèrie, normalment hi ha una funció pandas útil que ho fa possible.
Per exemple, per veure la mitjana dels punts assignats (per exemple, comparar un vi amb una puntuació mitjana), podem utilitzar la funció mean().
Una vegada obtinguda la mitjana, podem veure si un vi està per dalt o per baix de la mitjana.
reviews.points.mean()
88.44713820775404
Per a veure una llista de valors únics (tots els valors diferents), podem utilitzar la funció unique():
reviews.country.unique()
array(['Italy', 'Portugal', 'US', 'Spain', 'France', 'Germany',
'Argentina', 'Chile', 'Australia', 'Austria', 'South Africa',
'New Zealand', 'Israel', 'Hungary', 'Greece', 'Romania', 'Mexico',
'Canada', nan, 'Turkey', 'Czech Republic', 'Slovenia',
'Luxembourg', 'Croatia', 'Georgia', 'Uruguay', 'England',
'Lebanon', 'Serbia', 'Brazil', 'Moldova', 'Morocco', 'Peru',
'India', 'Bulgaria', 'Cyprus', 'Armenia', 'Switzerland',
'Bosnia and Herzegovina', 'Ukraine', 'Slovakia', 'Macedonia',
'China', 'Egypt'], dtype=object)
Per veure una llista de valors únics i amb quina freqüència es produeixen al conjunt de dades, podem utilitzar el mètode value_counts():
reviews.country.value_counts()
country
US 54504
France 22093
...
China 1
Egypt 1
Name: count, Length: 43, dtype: int64
Funcions de mapatge¶
Un mapa és un terme, agafat de les matemàtiques, per a una funció que pren un conjunt de valors i els mapetja a un altre conjunt de valors.
En ciència de dades sovint tenim la necessitat de crear noves representacions a partir de dades existents o de transformar les dades del format en què es troben ara al format en què necessitem.
Hi ha dos mètodes de mapeig que utilitzareu molt a sovint.
map() és el primer, i és més senzill d'utilitzar.
Per exemple, suposeu que volem ressetejar les puntuacions que van rebre els vins a 0. Podem fer-ho de la següent manera:
review_points_mean = reviews.points.mean()
reviews.points.map(lambda p: 0)
0 0
1 0
..
129969 0
129970 0
Name: points, Length: 129971, dtype: int64
La funció que passeu a map() hauria d'esperar un valor únic de la sèrie (la puntuació a l'exemple anterior) i retornar una versió transformada d'aquest valor.
map() aplica la funció que reb com a argunment a cadascun dels elements i li assigna el seu valor de retorn, transformant la serie original en una nova sèrie on tots els valors han canviat de valor segons la funció.
apply() és el mètode equivalent a map aplicat a tot un DataFrame. Utilitza un mètode per cada fila.
def remean_points(row):
row.points = row.points - review_points_mean
return row
reviews.apply(remean_points, axis='columns')
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | critic | index_backwards | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | -1.447138 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Nicosia 2013 Vulkà Bianco (Etna) | White Blend | Nicosia | Ferran | 129971 |
| 1 | Portugal | This is ripe and fruity, a wine that is smooth... | Avidagos | -1.447138 | 15.0 | Douro | NaN | NaN | Roger Voss | @vossroger | Quinta dos Avidagos 2011 Avidagos Red (Douro) | Portuguese Red | Quinta dos Avidagos | everyone | 129970 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129969 | France | A dry style of Pinot Gris, this is crisp with ... | NaN | 1.552862 | 32.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Marcel Deiss 2012 Pinot Gris (Alsace) | Pinot Gris | Domaine Marcel Deiss | everyone | 2 |
| 129970 | France | Big, rich and off-dry, this is powered by inte... | Lieu-dit Harth Cuvée Caroline | 1.552862 | 21.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Schoffit 2012 Lieu-dit Harth Cuvée Car... | Gewürztraminer | Domaine Schoffit | everyone | 1 |
129971 rows × 15 columns
Truc
Si haverem cridat reviews.apply() amb axis='index' en compte de axis='column', aleshores en lloc de passar una funció per transformar cada fila, hauriem de donar una funció per transformar cada columna.
Tingueu en compte que map() i apply() retornen sèries i DataFrames nous i transformats, respectivament. No modifiquen les dades originals que reben. Si mirem la primera fila de ressenyes, podem veure que encara té el seu valor de punts original.
reviews.head()
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | critic | index_backwards | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Nicosia 2013 Vulkà Bianco (Etna) | White Blend | Nicosia | Ferran | 129971 |
| 1 | Portugal | This is ripe and fruity, a wine that is smooth... | Avidagos | 87 | 15.0 | Douro | NaN | NaN | Roger Voss | @vossroger | Quinta dos Avidagos 2011 Avidagos Red (Douro) | Portuguese Red | Quinta dos Avidagos | everyone | 129970 |
| 2 | US | Tart and snappy, the flavors of lime flesh and... | NaN | 87 | 14.0 | Oregon | Willamette Valley | Willamette Valley | Paul Gregutt | @paulgwine | Rainstorm 2013 Pinot Gris (Willamette Valley) | Pinot Gris | Rainstorm | everyone | 129969 |
| 3 | US | Pineapple rind, lemon pith and orange blossom ... | Reserve Late Harvest | 87 | 13.0 | Michigan | Lake Michigan Shore | NaN | Alexander Peartree | NaN | St. Julian 2013 Reserve Late Harvest Riesling ... | Riesling | St. Julian | everyone | 129968 |
| 4 | US | Much like the regular bottling from 2012, this... | Vintner's Reserve Wild Child Block | 87 | 65.0 | Oregon | Willamette Valley | Willamette Valley | Paul Gregutt | @paulgwine | Sweet Cheeks 2012 Vintner's Reserve Wild Child... | Pinot Noir | Sweet Cheeks | everyone | 129967 |
Pandas ofereix moltes operacions de mapes comunes ja integrades.
Per exemple, ací teniu una manera més pràctica de reassignar la nostra columna de punts i assignar-li la diferència amb el valor mitjà:
review_points_mean = reviews.points.mean()
reviews.points - review_points_mean
0 -1.447138
1 -1.447138
...
129969 1.552862
129970 1.552862
Name: points, Length: 129971, dtype: float64
En aquest codi estem realitzant una operació entre molts valors a la part esquerra (tota la Sèrie) i un sol valor a la dreta (el valor mitjà).
Pandas mira aquesta expressió i descobreix que hem de voler restar aquest valor mitjà de tots els valors del conjunt de dades (és com la difusió als arrays de numpy).
Panda també enten què fer si realitzem aquestes operacions entre Sèries d'igual longitud.
Per exemple, una manera senzilla de combinar la informació del país i la regió al conjunt de dades seria fer el següent:
reviews.country + " - " + reviews.region_1
0 Italy - Etna
1 NaN
...
129969 France - Alsace
129970 France - Alsace
Length: 129971, dtype: object
Aquests operadors són més ràpids que map() o apply() perquè utilitzen les funcions universals de nunmpy als datasets de panda.
Qualsevol funció universal de numpy (>, <, == i així successivament) funciona d'aquesta manera.
Tanmateix, no són tan flexibles com map() o apply().
Agrupaments i ordenació¶
Els mapes ens permeten transformar les dades en un DataFrame o Series a un valor per a una columna sencera. Tanmateix, sovint volem agrupar les nostres dades i després fer alguna cosa específica al grup en què es troben les dades. Per fer açò, utilitzem la funció groupby().
Anàlisi grupal¶
Una funció que hem estat utilitzant molt fins ara és la funció value_counts(). Podem replicar el que fa value_counts() fent el següent:
reviews.groupby('points').points.count()
points
80 397
81 692
...
99 33
100 19
Name: points, Length: 21, dtype: int64
groupby() ens agrupa els vins per puntuació.Per a cadascun d'aquests grups, agafem points() els comptem amb count().
value_counts() és només una drecera a aquesta operació groupby().
Podem utilitzar qualsevol de les funcions de resum que hem utilitzat abans amb aquestes dades. Per exemple, per obtenir el vi més barat de cada categoria de valor de punts, podem fer el següent:
reviews.groupby('points').price.min()
points
80 5.0
81 5.0
...
99 44.0
100 80.0
Name: price, Length: 21, dtype: float64
Podeu pensar que cada grup que generem és una part del nostre DataFrame que només conté dades amb valors que coincideixen. Aquest DataFrame és accessible per nosaltres directament mitjançant el mètode apply(), i llavors podem manipular les dades de la manera que creguem adequada.
Per exemple, ací teniu una manera de seleccionar el nom del primer vi revisat de cada celler del conjunt de dades:
reviews.groupby('winery').apply(lambda df: df.title.iloc[0])
winery
1+1=3 1+1=3 NV Rosé Sparkling (Cava)
10 Knots 10 Knots 2010 Viognier (Paso Robles)
...
àMaurice àMaurice 2013 Fred Estate Syrah (Walla Walla V...
Štoka Štoka 2009 Izbrani Teran (Kras)
Length: 16757, dtype: object
Per a un control encara més detallat, també podeu agrupar-los per més d'una columna. Per exemple, així és com escolliríem el millor vi per país i província:
reviews.groupby(['country', 'province']).apply(lambda df: df.loc[df.points.idxmax()])
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | critic | index_backwards | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| country | province | |||||||||||||||
| Argentina | Mendoza Province | Argentina | If the color doesn't tell the full story, the ... | Nicasia Vineyard | 97 | 120.0 | Mendoza Province | Mendoza | NaN | Michael Schachner | @wineschach | Bodega Catena Zapata 2006 Nicasia Vineyard Mal... | Malbec | Bodega Catena Zapata | everyone | 47217 |
| Other | Argentina | Take note, this could be the best wine Colomé ... | Reserva | 95 | 90.0 | Other | Salta | NaN | Michael Schachner | @wineschach | Colomé 2010 Reserva Malbec (Salta) | Malbec | Colomé | everyone | 51668 | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| Uruguay | San Jose | Uruguay | Baked, sweet, heavy aromas turn earthy with ti... | El Preciado Gran Reserva | 87 | 50.0 | San Jose | NaN | NaN | Michael Schachner | @wineschach | Castillo Viejo 2005 El Preciado Gran Reserva R... | Red Blend | Castillo Viejo | everyone | 90073 |
| Uruguay | Uruguay | Cherry and berry aromas are ripe, healthy and ... | Blend 002 Limited Edition | 91 | 22.0 | Uruguay | NaN | NaN | Michael Schachner | @wineschach | Narbona NV Blend 002 Limited Edition Tannat-Ca... | Tannat-Cabernet Franc | Narbona | everyone | 90610 |
425 rows × 15 columns
Un altre mètode groupby() que val la pena estudiar és agg(), que us permet executar moltes funcions diferents al vostre DataFrame simultàniament.
Per exemple, podem generar un resum estadístic senzill del conjunt de dades de la següent manera:
reviews.groupby(['country']).price.agg([len, min, max])
/tmp/ipykernel_3496/4122224884.py:1: FutureWarning: The provided callable <built-in function min> is currently using SeriesGroupBy.min. In a future version of pandas, the provided callable will be used directly. To keep current behavior pass the string "min" instead. reviews.groupby(['country']).price.agg([len, min, max]) /tmp/ipykernel_3496/4122224884.py:1: FutureWarning: The provided callable <built-in function max> is currently using SeriesGroupBy.max. In a future version of pandas, the provided callable will be used directly. To keep current behavior pass the string "max" instead. reviews.groupby(['country']).price.agg([len, min, max])
| len | min | max | |
|---|---|---|---|
| country | |||
| Argentina | 3800 | 4.0 | 230.0 |
| Armenia | 2 | 14.0 | 15.0 |
| ... | ... | ... | ... |
| Ukraine | 14 | 6.0 | 13.0 |
| Uruguay | 109 | 10.0 | 130.0 |
43 rows × 3 columns
L'ús efectiu de groupby() us permetrà fer moltes coses realment potents amb el vostre conjunt de dades.
Múltiples índexs¶
En tots els exemples que hem vist fins ara, hem estat treballant amb objectes DataFrame o Series amb un índex d'etiqueta única. groupby() és lleugerament diferent pel fet que, depenent de l'operació que executem, de vegades donarà lloc al que s'anomena multiíndex.
Un índex múltiple difereix d'un índex normal perquè té diversos nivells. Per exemple:
countries_reviewed = reviews.groupby(['country', 'province']).description.agg([len])
countries_reviewed
| len | ||
|---|---|---|
| country | province | |
| Argentina | Mendoza Province | 3264 |
| Other | 536 | |
| ... | ... | ... |
| Uruguay | San Jose | 3 |
| Uruguay | 24 |
425 rows × 1 columns
Els índexs múltiples tenen diversos mètodes per tractar la seva estructura escalonada que no es troben en els índexs d'un sol nivell. També requereixen dos nivells d'etiquetes per recuperar un valor. Tractar l'eixida multi-índex és un problema comú per als usuaris nous a Panda.
Els casos d'ús d'un índex múltiple es detallen juntament amb les instruccions per utilitzar-los a la secció MultiIndex/Selecció avançada de la documentació de pandas.
Tanmateix, en general, el mètode multiíndex que utilitzareu amb més freqüència és el de tornar a convertir a un índex normal, el mètode reset_index():
countries_reviewed.reset_index()
| country | province | len | |
|---|---|---|---|
| 0 | Argentina | Mendoza Province | 3264 |
| 1 | Argentina | Other | 536 |
| ... | ... | ... | ... |
| 423 | Uruguay | San Jose | 3 |
| 424 | Uruguay | Uruguay | 24 |
425 rows × 3 columns
Ordenació¶
Si tornem a mirar countries_reviewed, podem veure que l'agrupació retorna dades per ordre d'índex, no per ordre de valor. És a dir, quan s'emet el resultat d'un "groupby", l'ordre de les files depèn dels valors de l'índex, no de les dades.
Per obtenir les dades en l'ordre en què les volem, les podem ordenar nosaltres mateixos. El mètode sort_values() és útil per a això.
countries_reviewed = countries_reviewed.reset_index()
countries_reviewed.sort_values(by='len')
| country | province | len | |
|---|---|---|---|
| 179 | Greece | Muscat of Kefallonian | 1 |
| 192 | Greece | Sterea Ellada | 1 |
| ... | ... | ... | ... |
| 415 | US | Washington | 8639 |
| 392 | US | California | 36247 |
425 rows × 3 columns
sort_values() per defecte és una ordenació ascendent, on els valors més baixos van primer. Tanmateix, la majoria de les vegades volem una ordenació descendent, on els nombres més alts van primer:
countries_reviewed.sort_values(by='len', ascending=False)
| country | province | len | |
|---|---|---|---|
| 392 | US | California | 36247 |
| 415 | US | Washington | 8639 |
| ... | ... | ... | ... |
| 63 | Chile | Coelemu | 1 |
| 149 | Greece | Beotia | 1 |
425 rows × 3 columns
Per ordenar per valors d'índex, utilitzeu el mètode complementari sort_index(). Aquest mètode té els mateixos arguments i ordre per defecte:
countries_reviewed.sort_index()
| country | province | len | |
|---|---|---|---|
| 0 | Argentina | Mendoza Province | 3264 |
| 1 | Argentina | Other | 536 |
| ... | ... | ... | ... |
| 423 | Uruguay | San Jose | 3 |
| 424 | Uruguay | Uruguay | 24 |
425 rows × 3 columns
Finalment, sabeu que podeu ordenar per més d'una columna alhora:
countries_reviewed.sort_values(by=['country', 'len'])
| country | province | len | |
|---|---|---|---|
| 1 | Argentina | Other | 536 |
| 0 | Argentina | Mendoza Province | 3264 |
| ... | ... | ... | ... |
| 424 | Uruguay | Uruguay | 24 |
| 419 | Uruguay | Canelones | 43 |
425 rows × 3 columns
Tipus de dades en Pandas (dtype)¶
El tipus de dades d'una columna en un DataFrame o una sèrie es coneix com a dtype.
Podem utilitzar la propietat dtype per saber el tipus d'una columna específica. Per exemple, podem obtindre el tipus d de la columna "preu" al DataFrame "revisions":
reviews.price.dtype
dtype('float64')
Si el que volem és averiguar els tipus de dades de totes les columnes d'un DataFrame, podem utilitzarla propietat dtypes, que retorna el dtype de totes les columnes al DataFrame:
reviews.dtypes
country object
description object
...
critic object
index_backwards int64
Length: 15, dtype: object
Els tipus de dades ens diuen alguna cosa sobre com els pandas emmagatzemen les dades internament. float64 significa que està utilitzant un nombre de coma flotant de 64 bits; "int64" significa un nombre enter de mida similar, i així successivament.
Una peculiaritat a tenir en compte (i aquí s'exposa molt clarament) és que les columnes que consisteixen completament en cadenes no tenen el seu propi tipus; en canvi, se'ls dóna el tipus d'objecte.
És possible convertir una columna d'un tipus en un altre sempre que aquesta conversió tingui sentit utilitzant la funció astype(). Per exemple, podem transformar la columna "punts" del seu tipus de dades "int64" existent en un tipus de dades "float64":
reviews.points
0 87
1 87
..
129969 90
129970 90
Name: points, Length: 129971, dtype: int64
reviews.points.astype('float64')
0 87.0
1 87.0
...
129969 90.0
129970 90.0
Name: points, Length: 129971, dtype: float64
L'índex d'un DataFrame o una Series també té el seu propi "dtype":
reviews.index.dtype
dtype('int64')
Ens falten dades!!¶
Les entrades a les què falten valors reben el valor NaN, abreviatura de Not a Number. Per raons tècniques, aquests valors NaN sempre són del tipus float64.
Pandas ofereix alguns mètodes específics per a les dades que falten. Per seleccionar entrades NaN podeu utilitzar pd.isnull() (o el seu complementari pd.notnull()).
Per exemple, per seleccionar els vins que no tenen la informació del seu país de procedència fariem:
reviews[pd.isnull(reviews.country)]
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | critic | index_backwards | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 913 | NaN | Amber in color, this wine has aromas of peach ... | Asureti Valley | 87 | 30.0 | NaN | NaN | NaN | Mike DeSimone | @worldwineguys | Gotsa Family Wines 2014 Asureti Valley Chinuri | Chinuri | Gotsa Family Wines | everyone | 129058 |
| 3131 | NaN | Soft, fruity and juicy, this is a pleasant, si... | Partager | 83 | NaN | NaN | NaN | NaN | Roger Voss | @vossroger | Barton & Guestier NV Partager Red | Red Blend | Barton & Guestier | everyone | 126840 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129590 | NaN | A blend of 60% Syrah, 30% Cabernet Sauvignon a... | Shah | 90 | 30.0 | NaN | NaN | NaN | Mike DeSimone | @worldwineguys | Büyülübağ 2012 Shah Red | Red Blend | Büyülübağ | everyone | 381 |
| 129900 | NaN | This wine offers a delightful bouquet of black... | NaN | 91 | 32.0 | NaN | NaN | NaN | Mike DeSimone | @worldwineguys | Psagot 2014 Merlot | Merlot | Psagot | everyone | 71 |
63 rows × 15 columns
La substitució dels valors que falten és una operació habitual abans d'entrenar models d'IA. Pandas ofereix un mètode molt útil per a aquest problema: fillna().
fillna() ofereix algunes estratègies diferents per mitigar aquestes dades. Per exemple, simplement podem substituir cada NaN per un "Desconegut":
reviews.region_2.fillna("Unknown")
0 Unknown
1 Unknown
...
129969 Unknown
129970 Unknown
Name: region_2, Length: 129971, dtype: object
O podríem omplir cada valor que falta amb el primer valor no nul que apareix en algun moment després del registre donat a la base de dades. Això es coneix com a backfill strategy. Altres possibilitats són, omplir el valor amb el valor mitja de la resta de dades de la columna per intentar influir el menys possible amb el model.
També, podem tindre un valor no nul que voldríem substituir. Per exemple, suposem que des que es va publicar aquest conjunt de dades, la revisora Kerin O'Keefe ha canviat el seu identificador de Twitter de @kerinokeefe a @kerin. Una manera de reflectir-ho al conjunt de dades és utilitzar el mètode replace():
reviews.taster_twitter_handle.replace("@kerinokeefe", "@kerino")
0 @kerino
1 @vossroger
...
129969 @vossroger
129970 @vossroger
Name: taster_twitter_handle, Length: 129971, dtype: object
El mètode replace() és útil per reemplaçar les dades que falten a les quals se'ls ha donat algun valor tipus: Desconegut, No vàlid...
Canvi de nom¶
Sovint, les dades ens arribaran amb noms de columnes o noms d'índex que no ens convencen i volem canviar. En aquest cas, podrem utilitzar les funcions de pandas per canviar els noms de les entrades.
La primera funció que estudiarem és rename(), que ens permet canviar noms d'índex i/o noms de columnes.
Per exemple, per canviar la columna points del nostre conjunt de dades a score, faríem:
reviews.rename(columns={'points': 'score'})
| country | description | designation | score | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | critic | index_backwards | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Nicosia 2013 Vulkà Bianco (Etna) | White Blend | Nicosia | Ferran | 129971 |
| 1 | Portugal | This is ripe and fruity, a wine that is smooth... | Avidagos | 87 | 15.0 | Douro | NaN | NaN | Roger Voss | @vossroger | Quinta dos Avidagos 2011 Avidagos Red (Douro) | Portuguese Red | Quinta dos Avidagos | everyone | 129970 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129969 | France | A dry style of Pinot Gris, this is crisp with ... | NaN | 90 | 32.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Marcel Deiss 2012 Pinot Gris (Alsace) | Pinot Gris | Domaine Marcel Deiss | everyone | 2 |
| 129970 | France | Big, rich and off-dry, this is powered by inte... | Lieu-dit Harth Cuvée Caroline | 90 | 21.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Schoffit 2012 Lieu-dit Harth Cuvée Car... | Gewürztraminer | Domaine Schoffit | everyone | 1 |
129971 rows × 15 columns
rename() ens permet canviar el nom dels valors de l'índex o de la columna especificant un paràmetre index o column, respectivament. Admet una varietat de formats d'entrada, però normalment un diccionari Python és el més convenient.
Ací teniu un exemple que es pot utilitzar per canviar el nom d'alguns elements de l'índex.
reviews.rename(index={0: 'firstEntry', 1: 'secondEntry'})
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | critic | index_backwards | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| firstEntry | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Nicosia 2013 Vulkà Bianco (Etna) | White Blend | Nicosia | Ferran | 129971 |
| secondEntry | Portugal | This is ripe and fruity, a wine that is smooth... | Avidagos | 87 | 15.0 | Douro | NaN | NaN | Roger Voss | @vossroger | Quinta dos Avidagos 2011 Avidagos Red (Douro) | Portuguese Red | Quinta dos Avidagos | everyone | 129970 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129969 | France | A dry style of Pinot Gris, this is crisp with ... | NaN | 90 | 32.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Marcel Deiss 2012 Pinot Gris (Alsace) | Pinot Gris | Domaine Marcel Deiss | everyone | 2 |
| 129970 | France | Big, rich and off-dry, this is powered by inte... | Lieu-dit Harth Cuvée Caroline | 90 | 21.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Schoffit 2012 Lieu-dit Harth Cuvée Car... | Gewürztraminer | Domaine Schoffit | everyone | 1 |
129971 rows × 15 columns
Canviar el nom de comlumnes sol ser més freqüent que l'índex, per això, set_index() sol utilitzar-se més.
Tant l'índex de fila com l'índex de columna poden tindre el seu propi nom. El mètode rename_axis() es pot utilitzar per canviar aquests noms.
Per exemple:
reviews
| country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | critic | index_backwards | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Nicosia 2013 Vulkà Bianco (Etna) | White Blend | Nicosia | Ferran | 129971 |
| 1 | Portugal | This is ripe and fruity, a wine that is smooth... | Avidagos | 87 | 15.0 | Douro | NaN | NaN | Roger Voss | @vossroger | Quinta dos Avidagos 2011 Avidagos Red (Douro) | Portuguese Red | Quinta dos Avidagos | everyone | 129970 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129969 | France | A dry style of Pinot Gris, this is crisp with ... | NaN | 90 | 32.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Marcel Deiss 2012 Pinot Gris (Alsace) | Pinot Gris | Domaine Marcel Deiss | everyone | 2 |
| 129970 | France | Big, rich and off-dry, this is powered by inte... | Lieu-dit Harth Cuvée Caroline | 90 | 21.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Schoffit 2012 Lieu-dit Harth Cuvée Car... | Gewürztraminer | Domaine Schoffit | everyone | 1 |
129971 rows × 15 columns
reviews.rename_axis("wines", axis='rows').rename_axis("fields", axis='columns')
| fields | country | description | designation | points | price | province | region_1 | region_2 | taster_name | taster_twitter_handle | title | variety | winery | critic | index_backwards |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| wines | |||||||||||||||
| 0 | Italy | Aromas include tropical fruit, broom, brimston... | Vulkà Bianco | 87 | NaN | Sicily & Sardinia | Etna | NaN | Kerin O’Keefe | @kerinokeefe | Nicosia 2013 Vulkà Bianco (Etna) | White Blend | Nicosia | Ferran | 129971 |
| 1 | Portugal | This is ripe and fruity, a wine that is smooth... | Avidagos | 87 | 15.0 | Douro | NaN | NaN | Roger Voss | @vossroger | Quinta dos Avidagos 2011 Avidagos Red (Douro) | Portuguese Red | Quinta dos Avidagos | everyone | 129970 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 129969 | France | A dry style of Pinot Gris, this is crisp with ... | NaN | 90 | 32.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Marcel Deiss 2012 Pinot Gris (Alsace) | Pinot Gris | Domaine Marcel Deiss | everyone | 2 |
| 129970 | France | Big, rich and off-dry, this is powered by inte... | Lieu-dit Harth Cuvée Caroline | 90 | 21.0 | Alsace | Alsace | NaN | Roger Voss | @vossroger | Domaine Schoffit 2012 Lieu-dit Harth Cuvée Car... | Gewürztraminer | Domaine Schoffit | everyone | 1 |
129971 rows × 15 columns
Combinant dades¶
Quan realitzem operacions en un conjunt de dades, de vegades haurem de combinar diferents DataFrames i/o Sèries.
Pandas té tres mètodes bàsics per fer-ho. Per ordre creixent de complexitat, són concat(), join() i merge().
Concat¶
El mètode de combinació més senzill és concat().
Donada una llista d'elements, aquesta funció agruparà aquests elements al llarg d'un eix.
Això és útil quan tenim dades en diferents objectes DataFrame o Series però amb els mateixos camps (columnes).
Un exemple: el conjunt de dades de vídeos de YouTube que divideix les dades segons el país d'origen (per exemple, el Canadà i el Regne Unit, en aquest exemple). Si volem estudiar diversos països simultàniament, podem utilitzar concat() per combinar-los:
directory = os.path.abspath('')
canadian_videos_path = os.path.join(directory,"data","CAvideos.csv")
british_videos_path = os.path.join(directory,"data","GBvideos.csv")
canadian_youtube = pd.read_csv(canadian_videos_path)
british_youtube = pd.read_csv(british_videos_path)
canadian_youtube
| video_id | trending_date | title | channel_title | category_id | publish_time | tags | views | likes | dislikes | comment_count | thumbnail_link | comments_disabled | ratings_disabled | video_error_or_removed | description | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | n1WpP7iowLc | 17.14.11 | Eminem - Walk On Water (Audio) ft. Beyoncé | EminemVEVO | 10 | 2017-11-10T17:00:03.000Z | Eminem|"Walk"|"On"|"Water"|"Aftermath/Shady/In... | 17158579 | 787425 | 43420 | 125882 | https://i.ytimg.com/vi/n1WpP7iowLc/default.jpg | False | False | False | Eminem's new track Walk on Water ft. Beyoncé i... |
| 1 | 0dBIkQ4Mz1M | 17.14.11 | PLUSH - Bad Unboxing Fan Mail | iDubbbzTV | 23 | 2017-11-13T17:00:00.000Z | plush|"bad unboxing"|"unboxing"|"fan mail"|"id... | 1014651 | 127794 | 1688 | 13030 | https://i.ytimg.com/vi/0dBIkQ4Mz1M/default.jpg | False | False | False | STill got a lot of packages. Probably will las... |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 40879 | lbMKLzQ4cNQ | 18.14.06 | Trump Advisor Grovels To Trudeau | The Young Turks | 25 | 2018-06-13T04:00:05.000Z | 180612__TB02SorryExcuse|"News"|"Politics"|"The... | 115225 | 2115 | 182 | 1672 | https://i.ytimg.com/vi/lbMKLzQ4cNQ/default.jpg | False | False | False | Peter Navarro isn’t talking so tough now. Ana ... |
| 40880 | POTgw38-m58 | 18.14.06 | 【完整版】遇到恐怖情人該怎麼辦?2018.06.13小明星大跟班 | 我愛小明星大跟班 | 24 | 2018-06-13T16:00:03.000Z | 吳宗憲|"吳姍儒"|"小明星大跟班"|"Sandy"|"Jacky wu"|"憲哥"|"中天... | 107392 | 300 | 62 | 251 | https://i.ytimg.com/vi/POTgw38-m58/default.jpg | False | False | False | 藝人:李妍瑾、玉兔、班傑、LaLa、小優、少少專家:陳筱屏(律師)、Wendy(心理師)、羅... |
40881 rows × 16 columns
british_youtube
| video_id | trending_date | title | channel_title | category_id | publish_time | tags | views | likes | dislikes | comment_count | thumbnail_link | comments_disabled | ratings_disabled | video_error_or_removed | description | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Jw1Y-zhQURU | 17.14.11 | John Lewis Christmas Ad 2017 - #MozTheMonster | John Lewis | 26 | 2017-11-10T07:38:29.000Z | christmas|"john lewis christmas"|"john lewis"|... | 7224515 | 55681 | 10247 | 9479 | https://i.ytimg.com/vi/Jw1Y-zhQURU/default.jpg | False | False | False | Click here to continue the story and make your... |
| 1 | 3s1rvMFUweQ | 17.14.11 | Taylor Swift: …Ready for It? (Live) - SNL | Saturday Night Live | 24 | 2017-11-12T06:24:44.000Z | SNL|"Saturday Night Live"|"SNL Season 43"|"Epi... | 1053632 | 25561 | 2294 | 2757 | https://i.ytimg.com/vi/3s1rvMFUweQ/default.jpg | False | False | False | Musical guest Taylor Swift performs …Ready for... |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 38914 | -DRsfNObKIQ | 18.14.06 | Eleni Foureira - Fuego - Cyprus - LIVE - First... | Eurovision Song Contest | 24 | 2018-05-08T20:32:32.000Z | Eurovision Song Contest|"2018"|"Lisbon"|"Cypru... | 14317515 | 151870 | 45875 | 26766 | https://i.ytimg.com/vi/-DRsfNObKIQ/default.jpg | False | False | False | Eleni Foureira represented Cyprus at the first... |
| 38915 | 4YFo4bdMO8Q | 18.14.06 | KYLE - Ikuyo feat. 2 Chainz & Sophia Black [A... | SuperDuperKyle | 10 | 2018-05-11T04:06:35.000Z | Kyle|"SuperDuperKyle"|"Ikuyo"|"2 Chainz"|"Soph... | 607552 | 18271 | 274 | 1423 | https://i.ytimg.com/vi/4YFo4bdMO8Q/default.jpg | False | False | False | Debut album 'Light of Mine' out now: http://ky... |
38916 rows × 16 columns
pd.concat([canadian_youtube, british_youtube])
| video_id | trending_date | title | channel_title | category_id | publish_time | tags | views | likes | dislikes | comment_count | thumbnail_link | comments_disabled | ratings_disabled | video_error_or_removed | description | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | n1WpP7iowLc | 17.14.11 | Eminem - Walk On Water (Audio) ft. Beyoncé | EminemVEVO | 10 | 2017-11-10T17:00:03.000Z | Eminem|"Walk"|"On"|"Water"|"Aftermath/Shady/In... | 17158579 | 787425 | 43420 | 125882 | https://i.ytimg.com/vi/n1WpP7iowLc/default.jpg | False | False | False | Eminem's new track Walk on Water ft. Beyoncé i... |
| 1 | 0dBIkQ4Mz1M | 17.14.11 | PLUSH - Bad Unboxing Fan Mail | iDubbbzTV | 23 | 2017-11-13T17:00:00.000Z | plush|"bad unboxing"|"unboxing"|"fan mail"|"id... | 1014651 | 127794 | 1688 | 13030 | https://i.ytimg.com/vi/0dBIkQ4Mz1M/default.jpg | False | False | False | STill got a lot of packages. Probably will las... |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 38914 | -DRsfNObKIQ | 18.14.06 | Eleni Foureira - Fuego - Cyprus - LIVE - First... | Eurovision Song Contest | 24 | 2018-05-08T20:32:32.000Z | Eurovision Song Contest|"2018"|"Lisbon"|"Cypru... | 14317515 | 151870 | 45875 | 26766 | https://i.ytimg.com/vi/-DRsfNObKIQ/default.jpg | False | False | False | Eleni Foureira represented Cyprus at the first... |
| 38915 | 4YFo4bdMO8Q | 18.14.06 | KYLE - Ikuyo feat. 2 Chainz & Sophia Black [A... | SuperDuperKyle | 10 | 2018-05-11T04:06:35.000Z | Kyle|"SuperDuperKyle"|"Ikuyo"|"2 Chainz"|"Soph... | 607552 | 18271 | 274 | 1423 | https://i.ytimg.com/vi/4YFo4bdMO8Q/default.jpg | False | False | False | Debut album 'Light of Mine' out now: http://ky... |
79797 rows × 16 columns
Join¶
També podem utilitzar el combinador join() que es situa al mig en termes de complexitat.
join() ens permet combinar diferents objectes DataFrame que tenen un índex en comú.
Per exemple, per treure vídeos que van ser tendències el mateix dia al Canadà i al Regne Unit, podríem fer el següent:
left = canadian_youtube.set_index(['title', 'trending_date'])
right = british_youtube.set_index(['title', 'trending_date'])
left.join(right, lsuffix='_CAN', rsuffix='_UK')
| video_id_CAN | channel_title_CAN | category_id_CAN | publish_time_CAN | tags_CAN | views_CAN | likes_CAN | dislikes_CAN | comment_count_CAN | thumbnail_link_CAN | ... | tags_UK | views_UK | likes_UK | dislikes_UK | comment_count_UK | thumbnail_link_UK | comments_disabled_UK | ratings_disabled_UK | video_error_or_removed_UK | description_UK | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| title | trending_date | |||||||||||||||||||||
| !! THIS VIDEO IS NOTHING BUT PAIN !! | Getting Over It - Part 7 | 18.04.01 | PNn8sECd7io | Markiplier | 20 | 2018-01-03T19:33:53.000Z | getting over it|"markiplier"|"funny moments"|"... | 835930 | 47058 | 1023 | 8250 | https://i.ytimg.com/vi/PNn8sECd7io/default.jpg | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| #1 Fortnite World Rank - 2,323 Solo Wins! | 18.09.03 | DvPW66IFhMI | AlexRamiGaming | 20 | 2018-03-09T07:15:52.000Z | PS4 Battle Royale|"PS4 Pro Battle Royale"|"Bat... | 212838 | 5199 | 542 | 11 | https://i.ytimg.com/vi/DvPW66IFhMI/default.jpg | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 🚨 BREAKING NEWS 🔴 Raja Live all Slot Channels Welcome 🎰 | 18.07.05 | Wt9Gkpmbt44 | TheBigJackpot | 24 | 2018-05-07T06:58:59.000Z | Slot Machine|"win"|"Gambling"|"Big Win"|"raja"... | 28973 | 2167 | 175 | 10 | https://i.ytimg.com/vi/Wt9Gkpmbt44/default.jpg | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 🚨Active Shooter at YouTube Headquarters - LIVE BREAKING NEWS COVERAGE | 18.04.04 | Az72jrKbANA | Right Side Broadcasting Network | 25 | 2018-04-03T23:12:37.000Z | YouTube shooter|"YouTube active shooter"|"acti... | 103513 | 1722 | 181 | 76 | https://i.ytimg.com/vi/Az72jrKbANA/default.jpg | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
40900 rows × 28 columns
Els paràmetres lsuffix i rsuffix són necessaris en este exemple perquè les dades tenen els mateixos noms de columnes tant en conjunts de dades britànics com canadencs.
Si no fos així (perquè, per exemple, els hem reanomenat abans) no els necessitariem.
Merge¶
El tercer combinador que podem utilitzar serà merge.
Aquest exemple utilitzarà la columnavideo_id en ambdós conjunts de dades per a fer la combinació. A més, amb l'argunment how, indicarem com fer la combinació.
Els possibles valors de how seran inner, left, right o outer.
inner és l'opció per defecte i indica que només s'han de conservar les files que tenen coincidències en ambdós conjunts de dades.
merged_data = pd.merge(canadian_youtube, british_youtube, on='video_id', how='inner')
merged_data
| video_id | trending_date_x | title_x | channel_title_x | category_id_x | publish_time_x | tags_x | views_x | likes_x | dislikes_x | ... | tags_y | views_y | likes_y | dislikes_y | comment_count_y | thumbnail_link_y | comments_disabled_y | ratings_disabled_y | video_error_or_removed_y | description_y | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | n1WpP7iowLc | 17.14.11 | Eminem - Walk On Water (Audio) ft. Beyoncé | EminemVEVO | 10 | 2017-11-10T17:00:03.000Z | Eminem|"Walk"|"On"|"Water"|"Aftermath/Shady/In... | 17158579 | 787425 | 43420 | ... | Eminem|"Walk"|"On"|"Water"|"Aftermath/Shady/In... | 17158579 | 787420 | 43420 | 125882 | https://i.ytimg.com/vi/n1WpP7iowLc/default.jpg | False | False | False | Eminem's new track Walk on Water ft. Beyoncé i... |
| 1 | n1WpP7iowLc | 17.14.11 | Eminem - Walk On Water (Audio) ft. Beyoncé | EminemVEVO | 10 | 2017-11-10T17:00:03.000Z | Eminem|"Walk"|"On"|"Water"|"Aftermath/Shady/In... | 17158579 | 787425 | 43420 | ... | Eminem|"Walk"|"On"|"Water"|"Aftermath/Shady/In... | 20539417 | 840642 | 47715 | 124236 | https://i.ytimg.com/vi/n1WpP7iowLc/default.jpg | False | False | False | Eminem's new track Walk on Water ft. Beyoncé i... |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 47340 | -QPdRfqTnt4 | 18.14.06 | Dumbo Official Teaser Trailer | Disney Movie Trailers | 1 | 2018-06-13T07:00:00.000Z | Disney|"Walt Disney Studios"|"Dumbo"|"Flying E... | 4427381 | 96391 | 5508 | ... | Disney|"Walt Disney Studios"|"Dumbo"|"Flying E... | 4427381 | 96391 | 5508 | 12726 | https://i.ytimg.com/vi/-QPdRfqTnt4/default.jpg | False | False | False | Watch the teaser trailer for Tim Burton’s all-... |
| 47341 | r63VBOagGAo | 18.14.06 | Shawn Mendes x Portugal (FPF Official World Cu... | FPFutebolOficial | 17 | 2018-06-13T13:11:56.000Z | Canal|"Oficial"|"da"|"Federação"|"Portuguesa"|... | 653114 | 65635 | 547 | ... | Canal|"Oficial"|"da"|"Federação"|"Portuguesa"|... | 653114 | 65624 | 547 | 3819 | https://i.ytimg.com/vi/r63VBOagGAo/default.jpg | False | False | False | Portugal's Football Team Official SongLyrics: ... |
47342 rows × 31 columns
Dades categòriques i no categòriques (numèriques)¶
Les dades es poden classificar en dues categories principals: categòriques i no categòriques (o numèriques).
Dades Categòriques:
- Aquest tipus de dades es divideixen en categories o grups.
- Les categories no tenen un ordre entre elles.
- Exemples comuns inclouen el color dels cotxes (roig, blau, groc) o el tipus de combustible (gasolina, dièsel, elèctric).
Dades No Categòriques (Numèriques):
- Aquestes dades es representen amb valors numèrics i tenen un ordre inherent.
- Podem realitzar operacions matemàtiques com ara sumar, restar, multiplicar i dividir amb aquestes dades.
- Exemples inclouen edat, altura, pes, i altres mesures numèriques.
Exemple:
Suposem que tenim dades sobre estudiants, i algunes de les variables són:
Nom (Categòrica): Ana, Jordi, Marc.
Edat (Numèrica): 20, 22, 19.
Gènere (Categòric): Femení, Masculí, Altres.
En aquest exemple, "Nom" i "Gènere" són dades categòriques, ja que són categories discretes i no tenen un ordre específic. "Edat" és una dada numèrica, ja que és una quantitat que es pot manipular matemàticament i té un ordre inherent.
Transformació de dades categòriques¶
La transformació de dades categòriques en un format que puga ser utilitzat per algorismes d'intel·ligència artificial és un pas important en el processament de les dades. Alguns algorismes necessiten aquesta transformació per diverses raons:
Format Numèric: Molts algorismes d'IA estan dissenyats per treballar amb dades numèriques. Convertir les dades categòriques a valors numèrics permet als algorismes processar-les i trobar patrons.
Operacions Matemàtiques: Els algorismes realitzen moltes operacions matemàtiques, com ara càlculs de distància o optimització. Això és més senzill de fer amb dades numèriques.
Millora de l'Aprenentatge Automàtic: Alguns models d'aprenentatge automàtic, com les xarxes neuronals, requereixen dades numèriques per ajustar els pesos i les connexions entre les neurones.
Millora del Rendiment: En molts casos, la transformació de dades categòriques pot millorar el rendiment dels models, ja que permet als algorismes trobar patrons de manera més eficient.
Label encoding¶
Un mètode senzill per a transformar les dades categòriques en numèriques és el label encoding. El que fem és assignar un nombre enter únic a cada categoria. Per a fer aquestes transformacions, podem utilitzar la classe LabelEncoder de la llibreria sklearn.preprocessing.
Aquestes llibreries les estudiarem més endavant en més detall en el curs.
import pandas as pd
from sklearn.preprocessing import LabelEncoder
# Creem un DataFrame d'exemple
data = {'Color': ['Verd', 'Blau', 'Groc', 'Verd', 'Blau', 'Groc', 'Verd']}
df = pd.DataFrame(data)
# Creem un objecte LabelEncoder
le = LabelEncoder()
# Aplicar Label Encoding a la columna 'Color'
df['Color_Encoded'] = le.fit_transform(df['Color'])
# Mostrar el DataFrame original i el codificat
print(df)
Pandas també disposa de la seus llibreria pròpia per a fer-ho:
import pandas as pd
# Creem un DataFrame d'exemple
data = {'Color': ['Verd', 'Blau', 'Groc', 'Verd', 'Blau', 'Groc', 'Verd']}
df = pd.DataFrame(data)
# Convertim la columna 'Color' a categoria
df['Color_Encoded'] = pd.Categorical(df['Color']).codes
# Mostrar el DataFrame
print(df)
Si volem recuperar despres la traducció feta, podem utilitzar el mètode inverse_transform() de l'objecte LabelEncoder o be la funcionalitat pròpia de pandas from_codes():
# Recuperar les categories originals
df['Color_Original'] = le.inverse_transform(df['Color_Encoded'])
# o be en la versió de pandas
df['Color_Original'] = pd.Categorical.from_codes(df['Color_Encoded'], categories=df['Color'].unique())
Als dos casos, l'eixida serà:
| Color | Color_Encoded | Color_Original |
|---|---|---|
| Verd | 2 | Verd |
| Blau | 0 | Blau |
| Groc | 1 | Groc |
| Verd | 2 | Verd |
| Blau | 0 | Blau |
| Groc | 1 | Groc |
Ja veureem que aqusta tècnica té els seus inconvenients, especialment quan les categories no tenen un ordre natural, ja que poden assumir-se errors per distàncies que realment no existeixen. Per exemple, en l'exemple anterior, el model podria interpretar que la distància entre Blau (0) i Groc (1) és menor que la distància entre Blau (0) i Verd (2), quan en realitat no hi ha cap relació d'ordre entre els colors.
One-hot encoding¶
Un dels mètodes més utilitzats per a transformar les dades categòriques en numèriques és el one-hot encoding.
Este mètode crea columnes addicionals per a cada categoria i utilitza 0 o 1 per indicar la presència o absència de cada categoria.
Suposem que tenim una variable categòrica "Color" amb les categories: Roig, Blau i Verd. Utilitzarem l'encodificació one-hot per transformar aquestes categories en vectors binaris:
| Color | Roig | Blau | Verd |
|---|---|---|---|
| Roig | 1 | 0 | 0 |
| Blau | 0 | 1 | 0 |
| Verd | 0 | 0 | 1 |
S'ha convertit la característica color en tres categories numèriques. Cada fila de color es converteix en un vector binari que indica la presència o absència de cada categoria. Com podem veure la solució és crer una base vectorial que matemàticament tots els vectors són ortogonals entre ells i existeeix la mateixa distància entre qualsevol parell de vectors.
Si tenim un cotxe de color blau, la representació amb one-hot encoding seria: [0, 1, 0].
Exemple amb pandas¶
import pandas as pd
# Creem un DataFrame d'exemple
data = {'Color': ['Verd', 'Blau', 'Groc', 'Verd', 'Blau', 'Groc', 'Verd']}
df = pd.DataFrame(data)
# Aplicar One-Hot Encoding
df_one_hot = pd.get_dummies(df['Color'], prefix='Color')
# Afegir les columnes One-Hot Encoding al DataFrame original
df = pd.concat([df, df_one_hot], axis=1)
# Mostrar el DataFrame resultant
print(df)
L'eixida serà:
| Color | Color_Blau | Color_Groc | Color_Verd |
|---|---|---|---|
| Verd | 0 | 0 | 1 |
| Blau | 1 | 0 | 0 |
| Groc | 0 | 1 | 0 |
| Verd | 0 | 0 | 1 |
| Blau | 1 | 0 | 0 |
| Groc | 0 | 1 | 0 |
Exmemple amb sklearn¶
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
# Creem un DataFrame d'exemple
data = {'Color': ['Verd', 'Blau', 'Groc', 'Verd', 'Blau', 'Groc', 'Verd']}
df = pd.DataFrame(data)
# Creem un objecte OneHotEncoder
ohe = OneHotEncoder(sparse=False)
# Aplicar One-Hot Encoding a la columna 'Color'
color_encoded = ohe.fit_transform(df[['Color']])
# Convertir l'array numpy resultant a DataFrame
df_one_hot = pd.DataFrame(color_encoded, columns=ohe.get_feature_names_out(['Color']))
# Afegir les columnes One-Hot Encoding al DataFrame original
df = pd.concat([df, df_one_hot], axis=1)
# Mostrar el DataFrame resultant
print(df)
L'eixida serà:
| Color | Color_Blau | Color_Groc | Color_Verd |
|---|---|---|---|
| Verd | 0.0 | 0.0 | 1.0 |
| Blau | 1.0 | 0.0 | 0.0 |
| Groc | 0.0 | 1.0 | 0.0 |
| Verd | 0.0 | 0.0 | 1.0 |
| Blau | 1.0 | 0.0 | 0.0 |
| Groc | 0.0 | 1.0 | 0.0 |
Per a reecuperar les categories originals, podem utilitzar el mètode inverse_transform() de l'objecte OneHotEncoder o si hem feet servir get_dummies() de pandas, podem utilitzar el mètode idxmax() per a cada fila:
# Recuperar les categories originals utilitzant sklearn
df['Color_Original'] = ohe.inverse_transform(df_one_hot)
# Recuperar les categories originals utilitzant pandas
df['Color_Original'] = df_one_hot.idxmax(axis=1).str.replace('Color_', '')