Series e DataFrame — le due strutture di pandas
Cos'è una colonna, cos'è una tabella e perché ogni valore in una colonna deve condividere lo stesso tipo. Include la differenza tra df["col"] e df[["col"]].
Come vuoi imparare?
Il problema che stiamo risolvendo
Nel capitolo precedente è stata scritta una riga, e ha funzionato:
print(sales["quantity"].sum())19La riga è facile da leggere, ma nasconde una domanda. Cosa ha restituito sales["quantity"]? Una lista? Le liste non hanno .sum() — scrivere [12, 5, 2].sum() in Python genera un errore.
Vale la pena conoscere la risposta, perché in pandas hai sempre in mano una di due cose, e quasi ogni confusione inizia con la stessa domanda: quale delle due ho in mano in questo momento? Una colonna o un'intera tabella?
Questo capitolo parla di queste due cose: i loro nomi, le loro forme e cosa le separa.
Alla fine di questo capitolo sarai in grado di
- Spiegare in una sola frase cosa separa una
Seriesda unDataFrame - Costruire una
Seriese ispezionare il suo indice, i suoi valori e il suo tipo separatamente - Spiegare perché ogni valore in una colonna deve condividere lo stesso tipo
- Usare
df["col"]edf[["col"]]sapendo distinguerli - Leggere un
KeyErrore capire esattamente cosa è andato storto con il nome di una colonna
Prerequisiti: installare pandas e la tua prima tabella.
Series — una colonna
Inizia con l'unità più piccola. Una Series è una sequenza di valori, ciascuno associato a un nome:
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity)0 12
1 5
2 2
dtype: int64Sono entrati tre valori e sono uscite tre coppie. I numeri 0, 1, 2 lungo il lato sinistro sono l'indice — un nome per ciascun valore. Non li hai forniti tu; pandas ha contato a partire da zero.
E in basso, dtype: int64 — il tipo dell'intera serie. Un solo tipo, non uno per ciascun valore. È questo che rende pandas veloce, ed è anche il suo vincolo principale.
Le tre parti possono essere esaminate separatamente:
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity.index)
print(quantity.values)
print(quantity.dtype)RangeIndex(start=0, stop=3, step=1)
[12 5 2]
int64RangeIndex significa che l'indice non è altro che un conteggio — inizia da zero e aumenta di uno. values contiene i valori senza i loro nomi. E dtype è il tipo dell'intera colonna.
Un indice non deve per forza essere composto da numeri:
import pandas as pd
quantity = pd.Series([12, 5, 2], index=["pen", "notebook", "bag"])
print(quantity)
print(quantity["notebook"])pen 12
notebook 5
bag 2
dtype: int64
5Ora le righe prendono il nome dai prodotti, e un valore può essere recuperato tramite quel nome. Sembra un dizionario Python, e la somiglianza è reale — con una grande differenza:
import pandas as pd
quantity = pd.Series([12, 5, 2], index=["pen", "notebook", "bag"])
print(quantity * 2)pen 24
notebook 10
bag 4
dtype: int64L'intera colonna è stata moltiplicata per due in una volta sola, senza alcun ciclo. Fare la stessa cosa con un dizionario significa scorrere ogni valore manualmente. Esegui un'operazione su una Series e pandas la applicherà a ogni valore — e tutti i capitoli successivi si basano su questo comportamento.
DataFrame — più colonne, un solo indice
Un DataFrame è composto da più Series affiancate che condividono un solo indice.
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales)
print(sales.shape)
print(sales.columns)product quantity price
0 pen 12 15.0
1 notebook 5 60.0
2 bag 2 850.0
(3, 3)
Index(['product', 'quantity', 'price'], dtype='object')Quell'espressione "condividono un solo indice" è la vera sostanza della questione. Il product, la quantity e il price all'indice 0 parlano tutti della stessa cosa: una penna. L'indice è ciò che unisce tre colonne formando le righe.
Una tabella, più tipi — ma una colonna, un solo tipo
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales.dtypes)product object
quantity int64
price float64
dtype: objectTre colonne, tre tipi. int64 rappresenta i numeri interi, float64 i decimali, e object — che di solito indica testo.
Nota che non c'è str. I tipi di pandas provengono da NumPy, dove ogni valore occupa la stessa quantità di spazio — cosa possibile per i numeri, ma non per il testo di lunghezza variabile. Quindi, per contenere il testo, pandas conserva invece puntatori a oggetti Python, e questa soluzione viene chiamata object.
Ora la regola fondamentale: ogni valore in una colonna condivide un unico tipo. Cosa succede allora quando numeri e testo vengono mescolati insieme?
import pandas as pd
mixed = pd.Series([1, "two", 3.0])
print(mixed)
print(mixed.dtype)0 1
1 two
2 3.0
dtype: objectPandas non genera un errore — imposta l'intera colonna come object, ripiegando sul tipo più permissivo a disposizione. La conseguenza non è eclatante, è silenziosa: la colonna non è più numerica, quindi .mean() fallirà e .sum() inizierà a concatenare testo. Un singolo N/A isolato in una riga di un file CSV fa esattamente questo, ed è l'argomento del capitolo sei.
Quale dei due ho in mano — una Series o un DataFrame?
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
column = sales["quantity"]
print(type(column))
print(type(sales))
print(column.mean())<class 'pandas.core.series.Series'>
<class 'pandas.core.frame.DataFrame'>
6.333333333333333Ed ecco la risposta alla domanda con cui si è aperto questo capitolo. sales["quantity"] non è una lista, è una Series — e una Series possiede .sum(), .mean(), .max() e tutto il resto.
C'è anche una coppia di casi che disorienta chiunque la prima volta:
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(sales["quantity"])
print("---")
print(sales[["quantity"]])0 12
1 5
2 2
Name: quantity, dtype: int64
---
quantity
0 12
1 5
2 2Una parentesi quadra in più, e il risultato è un oggetto di tipo diverso. Il primo è una Series — una colonna, con il suo nome e il suo tipo stampati sotto. Il secondo è un DataFrame — una tabella che per caso ha una sola colonna, quindi l'intestazione della colonna si trova in alto.
La regola è che le parentesi quadre interne indicano una lista di colonne, e chiedere una lista ti restituisce una tabella. Quindi sales[["product", "price"]] produce una tabella a due colonne.
Perché tenere a mente questa distinzione? Perché è qui che i messaggi di errore divergono. UnaSeriesha.mean()ma non i nomi delle colonne; unDataFrameseleziona colonne ma non ti restituisce un singolo numero. Quando un messaggio dice che un oggetto non ha tale attributo, la prima domanda è: quale dei due ho davvero in mano?
Un esempio completo
inventory.py:
import pandas as pd
# Product names become the index, so a row can be reached by name.
stock = pd.DataFrame(
{
"quantity": [12, 5, 2, 30],
"price": [15.0, 60.0, 850.0, 8.0],
},
index=["pen", "notebook", "bag", "eraser"],
)
print(stock)
print()
print("Shape :", stock.shape)
print("Columns :", list(stock.columns))
print("Index :", list(stock.index))
print()
# A column is a Series, so an operation applies to every value at once.
value = stock["quantity"] * stock["price"]
print("Value per product:")
print(value)
print()
print("Total value:", value.sum())quantity price
pen 12 15.0
notebook 5 60.0
bag 2 850.0
eraser 30 8.0
Shape : (4, 2)
Columns : ['quantity', 'price']
Index : ['pen', 'notebook', 'bag', 'eraser']
Value per product:
pen 180.0
notebook 300.0
bag 1700.0
eraser 240.0
dtype: float64
Total value: 2420.0Vale la pena notare due cose.
Innanzitutto, stock["quantity"] * stock["price"] ha moltiplicato due Series e ha prodotto un'altra Series. Pandas le ha associate in base all'indice — la quantità delle penne con il prezzo delle penne, quella delle borse con quello delle borse. In base al nome, non all'ordine delle righe. Questo comportamento è la base per unire due tabelle più avanti.
In secondo luogo, il tipo della colonna risultante è float64, anche se quantity era int64. Moltiplicare un numero intero per un decimale produce un decimale — la stessa regola del normale Python; qui pandas non fa nulla di insolito.
Quando qualcosa va storto
KeyError: 'Quantity' La colonna non esiste. I nomi delle colonne fanno distinzione tra maiuscole e minuscole, e i nomi provenienti da un file CSV contengono spesso spazi iniziali o finali. Esegui print(list(df.columns)) per vedere esattamente cosa c'è — stamparli come lista inserisce le virgolette attorno a ciascun nome, rendendo visibile ogni spazio.
AttributeError: 'DataFrame' object has no attribute 'mean_price' Anche df.column_name estrae una colonna, ma solo per nomi semplici — non quando il nome contiene uno spazio, inizia con una cifra o entra in conflitto con un metodo di pandas come count. df["column_name"] funziona sempre, quindi prendi l'abitudine di scrivere sempre questo.
TypeError: unsupported operand type(s) durante un calcolo su una colonna numerica La colonna probabilmente non è numerica. Controlla df.dtypes; la presenza di object indica che da qualche parte è stato mescolato del testo.
.sum() concatena testo invece di sommare numeri Stessa causa — la colonna è object. Un singolo N/A o una cella vuota in una sola riga è sufficiente per modificare il tipo dell'intera colonna.
ValueError: All arrays must be of the same length Le liste passate a pd.DataFrame({...}) non hanno tutte la stessa lunghezza. Ogni colonna ha bisogno dello stesso numero di valori, perché le righe devono allinearsi su un unico indice condiviso.
Passo 4 di 6 — Prevedi
Verifica cosa hai capito
Sono stati forniti tre valori. Cosa viene stampato esattamente?
import pandas as pd
quantity = pd.Series([12, 5, 2])
print(quantity)- A12 5 2
- B[12, 5, 2]
- C12 5 2 dtype: int64
- D0 12 1 5 2 2 dtype: int64
Le due righe differiscono solo per una coppia di parentesi in più. Cosa restituisce type()?
import pandas as pd
sales = pd.DataFrame({
"product": ["pen", "notebook", "bag"],
"quantity": [12, 5, 2],
"price": [15.0, 60.0, 850.0],
})
print(type(sales["quantity"]))
print(type(sales[["quantity"]]))- AEntrambe una Series
- BEntrambi un DataFrame
- CLa prima una Series, la seconda un DataFrame
- DLa prima un DataFrame, la seconda una Series
Numeri e testo sono stati mescolati in una colonna. Cosa fa pandas?
import pandas as pd
mixed = pd.Series([1, "two", 3.0])
print(mixed.dtype)- ANon segnala nulla — imposta il tipo dell'intera colonna come `object`
- BUn `TypeError` — due tipi non possono coesistere in una colonna
- CElimina il testo e mantiene la colonna come `float64`
- DMantiene un tipo separato per ciascun valore
Per rispondere serve un account
Accedi per verificare le tue risposte
Le domande sono qui sopra, e ragionarci a mente è la parte che conta. Accedi per vedere le risposte, le spiegazioni e i suggerimenti a tre livelli.
Tocca a te
Scrivi un file chiamato library.py che contenga un DataFrame di almeno cinque libri — i titoli come indice e le colonne per il numero di copie (numeri interi), il prezzo (decimali) e il nome dell'autore (testo).
Poi stampa:
- L'intera tabella, il suo
shapee i suoidtypes - La colonna del prezzo da sola — e dimostra con
type()che si tratta di unaSeries - La colonna del prezzo con le doppie parentesi quadre — e dimostra che in questo caso si tratta di un
DataFrame - Il valore totale di ciascun libro (copie × prezzo) e il valore totale complessivo di tutti i libri
Poi fai un esperimento: sostituisci uno dei conteggi delle copie con "unknown" ed esegui di nuovo il file. Cosa indica dtypes adesso, e cosa è successo al totale?
Questi sono i cinque minuti più preziosi di questo capitolo. Una sola parola in una singola cella cambia il tipo di un'intera colonna e rompe i calcoli aritmetici senza segnalare alcun errore — e nei dati reali succede continuamente.
Passo 6 di 6
Sfida — il quiz del capitolo
Dieci domande dalla più facile alla più difficile. Le ultime sono difficili di proposito.
Accedi per fare il quiz