Selezionare colonne e righe — per nome o per posizione
Puntare a una parte di una tabella: colonne con parentesi quadre, righe per etichetta con loc e per posizione con iloc, singole celle con at — e sapere quale dei due metodi stai usando, perché dopo un taglio o un ordinamento smettono di coincidere.
Come vuoi imparare?
Il problema che stiamo risolvendo
Hai letto il file degli ordini del negozio e l'hai verificato, come insegnava il capitolo tre. Ora le persone iniziano a farti domande.
Il team del magazzino dice: «Mandaci solo il prodotto e la quantità di ogni ordine — nient'altro.» Il contabile dice: «Cosa conteneva esattamente l'ordine 1006 e quanto è costato?» E il tuo manager dice: «Mostrami gli ultimi tre ordini arrivati.»
Nessuna di queste richieste è un calcolo sull'intera tabella. Ognuna riguarda il puntare: su alcune colonne, su una riga, sulle ultime righe, su una singola cella. Stampare tutto e leggere con il dito funziona con otto righe. Non funziona con ottomila, e non funziona all'interno di un programma, che non ha nessun dito.
Pandas ha diversi modi di puntare, e il motivo per cui ce ne sono diversi è il vero argomento di questo capitolo. Una riga può essere indicata in due modi diversi: con il suo nome (la sua etichetta nell'indice) o con la sua posizione (prima, seconda, ultima). Quando una tabella viene appena letta, i due coincidono con gli stessi numeri, e i principianti non si accorgono che esistono due sistemi distinti. Nel momento in cui prendi le ultime tre righe, o ordini, o filtri, smettono di coincidere — e il codice che li confonde inizia a restituire la riga sbagliata senza nessun errore.
Quindi questo capitolo parla di selezione, ma soprattutto parla di sapere sempre quale dei due stai usando.
Alla fine di questo capitolo sarai in grado di
- Selezionare una colonna e ottenere una
Series, oppure più colonne e ottenere unDataFrame— e sapere quale dei due hai tra le mani - Selezionare righe per etichetta con
.loce per posizione con.iloc, e spiegare perché le loro slice terminano in modo diverso - Selezionare righe e colonne insieme con
.loc[rows, columns]e prevedere la forma del risultato — fino a un singolo valore con.loco.at - Assegnare etichette significative alle righe con
set_index(), e vedere come cambia il significato di.loc - Spiegare perché modificare una selezione non modifica la tabella originale in pandas 3, e modificare l'originale nel modo corretto; leggere e correggere i messaggi di
KeyErroreIndexErrorche la selezione produce
Prerequisiti: Leggere un CSV e verificare cosa è arrivato.
Crea prima il file
Nella cartella del progetto, crea orders.csv con esattamente queste nove righe. Questo file verrà usato per il resto del corso, quindi salvalo in un posto che ritroverai:
order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0Ogni riga è un ordine: un numero d'ordine, il giorno, la filiale del negozio che lo ha venduto, cosa è stato venduto, la categoria del prodotto, la quantità e il prezzo di un singolo articolo.
Prima di scrivere il codice
La selezione sembra così semplice che si inizia subito a digitare le parentesi. Dedicaci prima un minuto seguendo questi passaggi, perché ogni errore di selezione in questo capitolo nasce dal saltarne uno.
1. Esprimi la domanda in una frase e sottolinea i sostantivi. I sostantivi ti dicono su cosa stai puntando:
- «il prodotto e la quantità di ogni ordine» — due colonne, tutte le righe
- «ordine 1006» — una riga, identificata dal numero d'ordine
- «gli ultimi tre ordini» — righe per posizione, contate dalla fine
- «il prezzo dell'ordine 1006» — una cella: una riga e una colonna insieme
2. Guarda la tabella prima di puntarci dentro. Puoi puntare solo su nomi che esistono. Tre cose contano per la selezione: la dimensione, i nomi esatti delle colonne e l'indice — le etichette sulla sinistra.
import pandas as pd
orders = pd.read_csv("orders.csv")
print(orders.shape)
print(list(orders.columns))
print(orders.index)(8, 7)
['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
RangeIndex(start=0, stop=8, step=1)I nomi delle colonne tornano come lista, tra virgolette — così puoi vedere che 'price' è in minuscolo e senza spazi indesiderati. L'indice è un RangeIndex da 0 a 7: le etichette sono semplicemente un contatore. Tienilo a mente, perché è la coincidenza attorno a cui ruota l'intero capitolo. In questo momento, la riga etichettata 2 è anche la riga in posizione 2. Non sarà sempre così.
3. Abbozza la risposta prima di produrla. Cosa dovrebbe uscire — un numero, una colonna o una tabella più piccola? Quante righe e colonne? Per la richiesta del team del magazzino la risposta è una tabella di 8 righe e 2 colonne. Per «ordine 1006» è una riga. Per «il prezzo dell'ordine 1006» è un numero, 850.0. Se sai la forma che ti aspetti, una selezione sbagliata è evidente nel momento in cui vedi il risultato.
4. Scegli lo strumento in base a ciò che sai. Questa lista è il capitolo in un'unica immagine:
- Conosci i nomi delle colonne → parentesi quadre:
orders["quantity"],orders[["product", "quantity"]] - Conosci l'etichetta di una riga, il suo nome nell'indice →
.loc:orders.loc[2] - Conosci la posizione di una riga — prima, ultima, quinta →
.iloc:orders.iloc[-3:] - Hai bisogno di righe e colonne insieme →
.loc[rows, cols]o.iloc[rows, cols]:orders.loc[2:4, ["product", "price"]] - Hai bisogno di esattamente una cella →
.at[row, col]o.loc[row, col]:orders.at[5, "price"]
5. Decidi come verificherai. Dopo ogni selezione, stampa .shape o type() del risultato e confrontalo con il tuo abbozzo. È una riga sola, e intercetta la maggior parte degli errori qui sotto prima che diventino numeri sbagliati.
Il resto del capitolo illustra questi strumenti uno alla volta, ciascuno con un esempio risolto.
Una colonna: un nome tra parentesi quadre
Inserisci un nome di colonna tra parentesi quadre e ottieni quella colonna:
quantity = orders["quantity"]
print(type(quantity))
print(quantity)<class 'pandas.Series'>
0 12
1 5
2 2
3 7
4 30
5 1
6 20
7 4
Name: quantity, dtype: int64Ciò che torna è una Series — esattamente l'oggetto del capitolo due: valori, un indice sulla sinistra, un Name (la colonna da cui proviene) e un dtype. Nota che l'indice è venuto con essa. I numeri da 0 a 7 non sono decorativi: indicano ancora a quale riga appartiene ciascun valore.
Poiché è una Series, tutti i metodi di Series funzionano direttamente su di essa:
print(orders["quantity"].sum())81Questo è il motivo tipico per cui si seleziona una colonna: per farle una domanda.
Più colonne: una lista tra parentesi quadre
Per il team del magazzino hai bisogno di due colonne. Inserisci una lista di nomi tra le parentesi quadre:
stock = orders[["product", "quantity"]]
print(type(stock))
print(stock)<class 'pandas.DataFrame'>
product quantity
0 pen 12
1 notebook 5
2 bag 2
3 bottle 7
4 eraser 30
5 bag 1
6 pen 20
7 bottle 4Le doppie parentesi non sono sintassi speciale, ed è utile vederlo chiaramente. La coppia esterna è la selezione; la coppia interna è una normale lista Python, ["product", "quantity"]. Potresti costruire prima la lista e passarla — wanted = ["product", "quantity"] poi orders[wanted] — e significherebbe esattamente la stessa cosa. Le colonne tornano nell'ordine della lista, quindi questo è anche il modo per riordinare le colonne.
Il risultato è un DataFrame, perché una tabella con due colonne è ancora una tabella. Ed ecco una distinzione che confonde le persone per mesi: una lista con un solo nome restituisce comunque un DataFrame.
print(orders["product"].shape)
print(orders[["product"]].shape)(8,)
(8, 1)(8,) è una Series — una dimensione, otto valori. (8, 1) è un DataFrame con otto righe e una colonna. Si stampano in modo diverso, hanno metodi diversi, e quando li salvi su file, il DataFrame mantiene l'intestazione della colonna. La regola è semplice: un nome dà una Series; una lista dà un DataFrame — indipendentemente da quanti nomi ci sono nella lista.
Perché non orders.quantity?
Vedrai l'accesso alle colonne scritto con il punto in molti tutorial:
print(orders.branch.head(2))0 north
1 south
Name: branch, dtype: strQui funziona, ed è più corto. Ma funziona solo quando il nome della colonna è un nome Python valido che non è già usato per qualcos'altro — e un DataFrame ha centinaia di metodi e attributi. Ecco una piccola tabella le cui colonne si chiamano count e size, due nomi perfettamente ragionevoli per un negozio:
d = pd.DataFrame({"count": [1, 2], "size": [3, 4]})
print(d.size)
print(d["size"])4
0 3
1 4
Name: size, dtype: int64d.size non ti ha restituito la colonna. Ti ha restituito l'attributo size del DataFrame — il numero di celle, 2 × 2 = 4 — senza avvertirti. Nessun errore, solo una risposta sbagliata. Un nome con uno spazio, come unit price, non si può scrivere con il punto. Le parentesi quadre non hanno nessuno di questi problemi, quindi la regola per il codice che mantieni è: usa df["col"]. Il punto va bene per un'occhiata rapida nel terminale, nient'altro.
Righe per etichetta: .loc
Le righe non si selezionano con le semplici parentesi quadre. (Le parentesi quadre con un nome significano «colonna», e pandas non indovina quale delle due intendi.) Le righe hanno i propri due strumenti, e il primo è .loc — localizzazione basata sull'etichetta (label). Gli fornisci l'etichetta dell'indice:
print(orders.loc[2])order_id 1003
date 2024-03-02
branch north
product bag
category accessories
quantity 2
price 850.0
Name: 2, dtype: objectUna riga torna come Series, girata di lato: i nomi delle colonne sono diventati il suo indice, e il suo Name è l'etichetta della riga, 2.
Guarda dtype: object. Una colonna ha un tipo solo, ma una riga attraversa le colonne — un numero, del testo, un decimale — quindi l'unico tipo che può contenere tutto è il generico object. Questo è uno dei motivi per cui pandas ragiona per colonne: una riga mescola i tipi, una colonna no.
Ora la parte importante. Chiedi a .loc un intervallo di etichette:
print(orders.loc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.0
4 1005 2024-03-03 north eraser stationery 30 8.0Tre righe: 2, 3 e 4. Una slice di .loc include la fine. Questo sorprende chiunque conosca Python, dove range(2, 4) e list[2:4] si fermano prima del 4. Non è un'incoerenza; c'è un motivo.
Le etichette non sono sempre numeri. Se le etichette fossero nomi di prodotti, scriveresti qualcosa come loc["bag":"eraser"]. Per escludere la fine, dovresti nominare l'etichetta successiva a "eraser" — e con i nomi non c'è un «prossimo» che puoi conoscere senza guardare. Quindi con le etichette indichi la prima che vuoi e l'ultima che vuoi, e le ottieni entrambe. Le posizioni sono diverse: la posizione successiva al 4 è sempre 5, quindi la regola Python del «fermati prima» non costa nulla.
Righe per posizione: .iloc
Il secondo strumento è .iloc — localizzazione basata su interi (integer). Ignora completamente le etichette e conta le posizioni, da 0, esattamente come una lista Python:
print(orders.iloc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.0Due righe, posizioni 2 e 3; la posizione 4 è esclusa, come in qualsiasi slice Python. Metti i due output affiancati: loc[2:4] ha dato tre righe e iloc[2:4] ne ha date due, sulla stessa tabella, con gli stessi numeri tra parentesi.
Poiché .iloc è posizionale, i numeri negativi contano dalla fine — il che risponde direttamente alla domanda del tuo manager:
print(orders.iloc[-3:])order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0«Gli ultimi tre» è una domanda sulla posizione, quindi .iloc è lo strumento adatto. .loc non sa cosa significa «ultimo»; conosce solo i nomi.
Etichette e posizioni non sono la stessa cosa
Finora, l'etichetta 2 e la posizione 2 erano la stessa riga, quindi non potevi distinguere i due strumenti dai loro risultati. Prendi gli ultimi tre ordini come tabella a sé, e i due si separano:
last3 = orders.tail(3)
print(last3)order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0Le righe hanno mantenuto le loro etichette: 5, 6, 7. Pandas non le rinumera, di proposito — l'etichetta è il modo in cui una riga rimane la stessa riga dopo che hai tagliato la tabella. Ora chiedi «la prima riga» in entrambi i modi:
print(last3.iloc[0])order_id 1006
date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 5, dtype: objectlast3.loc[0]KeyError: 0.iloc[0] significa «la riga al primo posto», che è l'ordine con etichetta 5. .loc[0] significa «la riga la cui etichetta è 0» — e in questa tabella non esiste, quindi pandas solleva KeyError. Questo è l'errore che si presenta più avanti nel corso: dopo aver filtrato o ordinato le righe — entrambi argomenti futuri — le posizioni cambiano e le etichette no. La regola da portarsi dietro:
- Stai chiedendo della posizione — primo, ultimo, i primi tre dopo un ordinamento? Usa
.iloc. - Stai chiedendo dell'identità — questo ordine, questo prodotto? Usa
.loc.
E il caso peggiore non è il KeyError. Il caso peggiore è quando l'etichetta 0 esiste da qualche altra parte nella tabella: allora .loc[0] restituisce silenziosamente una riga diversa da quella che intendevi.
.locchiede un nome,.ilocchiede una posizione. Su una tabella appena letta i due coincidono per pura casualità, quindi il codice che li confonde supera tutti i controlli del primo giorno — e restituisce la riga sbagliata, senza errori, la prima volta che la tabella è stata tagliata, filtrata o ordinata.
Assegnare alle righe etichette significative: set_index
Il contabile ha chiesto dell'ordine 1006. Con l'indice predefinito dovresti sapere che l'ordine 1006 si trova sotto l'etichetta 5 — un fatto sul file, non sull'ordine. Il vero nome di una riga è il suo numero d'ordine, quindi rendi il numero d'ordine l'indice:
by_id = orders.set_index("order_id")
print(by_id.head(3))
print(by_id.shape)date branch product category quantity price
order_id
1001 2024-03-01 north pen stationery 12 15.0
1002 2024-03-01 south notebook stationery 5 60.0
1003 2024-03-02 north bag accessories 2 850.0
(8, 6)Tre cose sono cambiate. I numeri d'ordine si sono spostati a sinistra, nell'indice. L'indice ha ora un nome, order_id, stampato su una riga a sé sopra di esso. E la forma è (8, 6) invece di (8, 7) — order_id non è più una colonna, è l'indice. Nota anche che set_index ha restituito una nuova tabella, che abbiamo chiamato by_id; orders è rimasto invariato.
Ora .loc parla il linguaggio della domanda:
print(by_id.loc[1006])date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 1006, dtype: objectE una slice per etichetta è ora un intervallo di numeri d'ordine, con entrambe le estremità incluse:
print(by_id.loc[1003:1005, ["product", "quantity"]])product quantity
order_id
1003 bag 2
1004 bottle 7
1005 eraser 30.iloc non è cambiato per nulla — le posizioni sono posizioni, qualunque siano le etichette:
print(by_id.iloc[0:2])date branch product category quantity price
order_id
1001 2024-03-01 north pen stationery 12 15.0
1002 2024-03-01 south notebook stationery 5 60.0Qui i due strumenti si sono chiaramente separati: by_id.loc[0] ora darebbe KeyError, perché 0 non è un numero d'ordine, mentre by_id.iloc[0] è l'ordine 1001. Se mai dovessi rimettere il numero d'ordine come colonna, by_id.reset_index() lo riporta indietro.
Scegli una colonna i cui valori siano unici. Un indice serve a dare un nome alle righe, e un nome condiviso da due righe non ne indica una sola. Guarda cosa succede con product, che si ripete:
by_product = orders.set_index("product")
print(by_product.index.is_unique)
print(by_product.loc["bag"])False
order_id date branch category quantity price
product
bag 1003 2024-03-02 north accessories 2 850.0
bag 1006 2024-03-03 south accessories 1 850.0Nessun errore — ma loc["bag"] ha restituito un DataFrame di due righe invece di una singola riga come Series. Il codice che si aspettava una riga (ad esempio per leggere il suo price come numero singolo) ora ne ottiene due. Ecco perché index.is_unique vale una riga di verifica ogni volta che imposti un indice che intendi usare per cercare le righe.
Righe e colonne insieme
Sia .loc che .iloc accettano due elementi separati da una virgola: prima le righe, poi le colonne. Ciascuna parte può essere una singola etichetta, una lista o una slice:
print(orders.loc[2:4, ["product", "price"]])product price
2 bag 850.0
3 bottle 120.0
4 eraser 8.0print(orders.loc[[0, 3], ["branch", "product"]])branch product
0 north pen
3 east bottleUna slice funziona anche per le colonne, con la regola inclusiva di .loc — «ogni colonna da branch a category». Un semplice : significa «tutte le righe»:
print(orders.loc[:, "branch":"category"])branch product category
0 north pen stationery
1 south notebook stationery
2 north bag accessories
3 east bottle accessories
4 north eraser stationery
5 south bag accessories
6 east pen stationery
7 north bottle accessories.iloc fa lo stesso per posizione. Anche le colonne si contano da 0, quindi branch è la colonna 2 e product è la colonna 3:
print(orders.iloc[[0, 3], [2, 3]])branch product
0 north pen
3 east bottleStesso risultato della versione con .loc due blocchi sopra — ma nota quanto è più difficile da leggere. [2, 3] non dice nulla; ["branch", "product"] dice tutto, e continua a funzionare anche se qualcuno aggiunge una colonna all'inizio del file. Preferisci i nomi quando li conosci.
Quale forma verrà restituita?
Che tu ottenga un singolo valore, una Series o un DataFrame dipende solo da cosa metti su ciascun lato della virgola. Una singola etichetta elimina quella dimensione; una lista o slice la mantiene:
print(type(by_id.loc[1006, "price"]).__name__)
print(type(by_id.loc[1006, ["product", "price"]]).__name__)
print(type(by_id.loc[[1003, 1006], "price"]).__name__)
print(type(by_id.loc[[1003, 1006], ["product", "price"]]).__name__)float64
Series
Series
DataFrame- un'etichetta, un'etichetta → un singolo valore
- un'etichetta, una lista o slice → una
Series(una riga) - una lista o slice, un'etichetta → una
Series(una colonna) - una lista o slice, una lista o slice → un
DataFrame
Questo è il passaggio 3 della pianificazione — abbozza la risposta — trasformato in una regola. Se vuoi una tabella, anche di una sola riga, metti una lista su entrambi i lati: by_id.loc[[1006], ["price"]].
Un singolo valore: .loc e .at
La domanda del contabile — quanto è costato l'ordine 1006? — è una cella: un'etichetta di riga e un nome di colonna.
print(by_id.loc[1006, "price"])
print(by_id.at[1006, "price"])850.0
850.0Entrambi danno lo stesso numero. .at accetta solo una singola riga e una singola colonna, nient'altro, il che lo rende leggermente più veloce e rende evidente l'intenzione: questa è una cella sola. (.iat è il gemello posizionale, by_id.iat[5, 5].) Usa quello che si legge meglio; .loc va benissimo.
Un singolo valore è un numero ordinario, quindi puoi calcolarci sopra direttamente. Il valore dell'ordine 1004 — quantità per prezzo:
print(by_id.at[1004, "quantity"] * by_id.at[1004, "price"])840.0Una selezione è un oggetto a sé stante
C'è ancora una cosa da capire prima di usare le selezioni in programmi reali: cosa succede quando ne modifichi una.
import pandas as pd
orders = pd.read_csv("orders.csv")
prices = orders["price"]
prices[0] = 999.0
print(prices.head(2))
print(orders.loc[0, "price"])0 999.0
1 60.0
Name: price, dtype: float64
15.0prices è cambiato. orders no. In pandas 3, ogni selezione si comporta come una copia separata: puoi modificarla liberamente e la tabella originale non viene mai toccata. (Internamente pandas evita di copiare effettivamente i dati finché non ci scrivi — questo è il nome del meccanismo, Copy-on-Write — ma la regola di cui hai bisogno è il comportamento, non il meccanismo.)
Lo stesso vale per una selezione di più colonne:
stock = orders[["product", "price"]]
stock["price"] = 0
print(orders["price"].head(2))0 15.0
1 60.0
Name: price, dtype: float64Questo è il comportamento sicuro: una funzione che riceve una selezione e la modifica non può danneggiare la tua tabella principale a tua insaputa. Ma ha una conseguenza che devi conoscere. Se vuoi modificare l'originale, devi farlo sull'originale, in un solo passaggio, con .loc:
orders.loc[0, "price"] = 16.0
print(orders.loc[0, "price"])16.0La versione in due passaggi — selezionare una colonna, poi impostare una cella in essa — non funziona, e pandas 3 te lo dice:
orders["price"][0] = 1.0ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignmentprint(orders.loc[0, "price"])16.0Ancora 16.0 — l'assegnazione è finita in una copia temporanea ed è stata scartata. orders["price"] ha prodotto una selezione, e [0] = 1.0 ha modificato quella, non orders. Questo si chiama chained assignment (assegnazione a catena): due parentesi di fila a sinistra di =. La correzione è sempre quella indicata dall'avviso: un singolo .loc con sia la riga che la colonna, orders.loc[0, "price"] = 1.0.
Se leggi tutorial più vecchi, troverai questo territorio descritto con un SettingWithCopyWarning e consigli in cui a volte l'originale cambiava e a volte no. Era pandas 1 e 2. In pandas 3 la regola è semplice e sempre uguale: una selezione non modifica mai l'originale; .loc sull'originale sì.
Un esempio completo
Tutte e tre le richieste dall'inizio del capitolo trovano risposta in un unico programma. select_orders.py:
import pandas as pd
orders = pd.read_csv("orders.csv")
# 1. Check what arrived before pointing into it
print("Shape :", orders.shape)
print("Columns:", list(orders.columns))
# 2. Name the rows by what they are
by_id = orders.set_index("order_id")
print("Order ids unique:", by_id.index.is_unique)
print()
# Stock team: two columns, every row
stock = orders[["product", "quantity"]]
print("Stock view:", stock.shape)
print(stock.head(3))
print()
# Accountant: one order by its number, then one cell
print(by_id.loc[1006, ["product", "quantity", "price"]])
print("Order 1006 total:", by_id.at[1006, "quantity"] * by_id.at[1006, "price"])
print()
# Manager: the last three orders, a question about position
print(by_id.iloc[-3:, [1, 2, 4]])Shape : (8, 7)
Columns: ['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
Order ids unique: True
Stock view: (8, 2)
product quantity
0 pen 12
1 notebook 5
2 bag 2
product bag
quantity 1
price 850.0
Name: 1006, dtype: object
Order 1006 total: 850.0
branch product quantity
order_id
1006 south bag 1
1007 east pen 20
1008 north bottle 4Perché è scritto in questo modo:
- Guarda prima di puntare.
shapeelist(columns)vengono stampati per primi, così un nome di colonna scritto male o con spazi in più sarebbe visibile prima che qualsiasi selezione fallisca. - Verifica che l'indice sia unico prima di usarlo per cercare gli ordini, così
by_id.loc[1006]è garantito essere una riga eby_id.at[...]un numero. - Ogni richiesta usa lo strumento adatto alla domanda. Colonne per nome con le parentesi; un ordine per la sua identità con
.loce.at; «gli ultimi tre» per posizione con.iloc. - Stampa la forma della vista del magazzino —
(8, 2)è esattamente l'abbozzo: ogni ordine, due colonne. - L'ultima selezione è quella da migliorare.
[1, 2, 4]corrisponde per caso abranch,product,quantityinby_id— nota che le posizioni si sono spostate di uno perchéorder_idè diventato l'indice. Le posizioni sono fragili. Nel tuo codice,by_id.iloc[-3:][["branch", "product", "quantity"]]oby_id.loc[by_id.index[-3:], ["branch", "product", "quantity"]]direbbero la stessa cosa con i nomi.
Quando qualcosa va storto
KeyError: 'Price' Non esiste nessuna colonna con quel nome esatto. I nomi delle colonne distinguono maiuscole e minuscole: price e Price sono nomi diversi. Stampa list(df.columns) e copia il nome da lì. Con una lista di nomi, orders[["product", "Price"]], il messaggio diventa KeyError: "['Price'] not in index" e indica esattamente quale manca; con il punto, orders.Price, è AttributeError: 'DataFrame' object has no attribute 'Price'. Stessa causa, stessa correzione.
KeyError: 'price' — sebbene price sia chiaramente nel file L'intestazione probabilmente ha spazi dopo le virgole, order_id, product, price, e quegli spazi sono diventati parte dei nomi:
from io import StringIO
import pandas as pd
raw = "order_id, product, price\n1001, pen, 15.0\n"
padded = pd.read_csv(StringIO(raw))
print(list(padded.columns))
padded["price"]['order_id', ' product', ' price']
KeyError: 'price'La lista mostra lo spazio iniziale in ' price', che è esattamente il motivo per cui il capitolo tre ti ha detto di stampare le colonne come lista. Correggilo durante la lettura, con pd.read_csv("file.csv", skipinitialspace=True), o in seguito con df.columns = df.columns.str.strip().
KeyError: ('product', 'price') Hai scritto orders["product", "price"] — una sola coppia di parentesi. Senza la lista interna, Python passa i due nomi come una singola tupla, e pandas cerca una colonna chiamata ('product', 'price'). Scrivi orders[["product", "price"]].
KeyError: 0 da .loc Hai usato una posizione con .loc. O la tabella è stata tagliata (tail, un filtro, un ordinamento) e l'etichetta 0 non c'è più, oppure hai impostato un indice e le etichette sono ora numeri d'ordine. Se intendi «prima riga», scrivi .iloc[0]. Su una tabella il cui indice è testuale, lo stesso errore commesso con una slice — orders.set_index("product").loc[0:2] — solleva invece TypeError: cannot do slice indexing on Index with these indexers [0] of type int; la correzione è la stessa, .iloc[0:2].
IndexError: single positional indexer is out-of-bounds A .iloc è stata fornita una posizione che non esiste — orders.iloc[8] su una tabella di otto righe, le cui posizioni vanno da 0 a 7. Controlla df.shape; l'ultima riga è sempre df.iloc[-1]. Lo stesso messaggio appare per una posizione di colonna troppo grande, orders.iloc[:, 7].
ValueError: Location based indexing can only have [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types Hai dato a .iloc un nome di colonna: orders.iloc[0, "branch"]. .iloc accetta solo posizioni. Usa .loc con un'etichetta — orders.loc[0, "branch"] — oppure, se hai davvero bisogno della posizione, cercala: orders.columns.get_loc("branch") restituisce 2.
Passo 4 di 6 — Prevedi
Verifica cosa hai capito
Stessa tabella, stessi numeri tra le parentesi quadre. Cosa viene stampato?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
print(len(orders.loc[2:5]), len(orders.iloc[2:5]))- A4 4
- B3 4
- C3 3
- D4 3
L'obiettivo è stampare il prodotto del primo degli ultimi tre ordini. Cosa succede in realtà, e perché?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
last3 = orders.tail(3)
# Wanted: the product of the first of these three orders
print(last3.loc[0, "product"])- AStampa `bag`, il prodotto della prima delle tre righe
- B`IndexError` — la tabella ha solo tre righe
- C`KeyError: 0` — `tail(3)` ha conservato le etichette 5, 6 e 7, e `.loc` cerca un'etichetta 0
- DStampa `pen`, il prodotto nella prima riga della tabella originale
Ti serve un DataFrame che contenga solo la colonna price, non una Series. Quale riga te lo restituisce?
- Aorders.loc[:, "price"]
- Borders.price
- Corders["price"]
- Dorders[["price"]]
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
Usando lo stesso orders.csv, scrivi select.py. Prima di qualsiasi codice, scrivi in un commento cosa deve restituire ogni selezione — un valore, una Series o un DataFrame — e quante righe. Il programma deve soddisfare quattro condizioni:
- Le colonne sono sempre selezionate per nome: stampa la
shapedidate,branchequantityinsieme. - Gli ordini vengono cercati per identità su una tabella indicizzata per
order_id, dopo aver verificato che l'indice sia unico: stampa l'intera riga per l'ordine 1007, poiproductebranchper gli ordini da 1002 a 1005 — tutti e quattro. - A «primo» e «ultimo» si risponde per posizione: stampa
productepriceper i primi due e per gli ultimi due ordini; poi, con.at, la quantità dell'ordine 1005 e il valore totale dell'ordine 1003 (quantità × prezzo). - Modifica il prezzo dell'ordine 1001 a
16.0nella tabella indicizzata con un singolo.loc, e stampalo per dimostrare che la modifica è avvenuta.
Il tuo output deve essere esattamente questo:
(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0Poi prevedi, scrivi la tua previsione, e solo dopo esegui ognuna di queste tre righe:
by_id.loc[0]orders.iloc[8]by_id["price"][1001] = 99.0— e subito dopo, stampa di nuovo il prezzo dell'ordine 1001
Soluzione
Prima pianifica. Il blocco di commento è l'abbozzo del passaggio 3 di «Prima di scrivere il codice» — cosa deve restituire ogni selezione:
- Tre colonne →
orders[[...]]→ unDataFramedi forma(8, 3) - Un ordine, per identità →
by_id.loc[1007]→ unaSeries(una riga) - Quattro ordini per identità, due colonne →
by_id.loc[1002:1005, [...]]→ unDataFrame4 × 2, perché.locinclude 1005 - I primi due e gli ultimi due, per posizione →
.iloc[:2]e.iloc[-2:]→ dueDataFrame2 × 2 - Celle singole →
.at→ numeri normali - Modifica l'originale → un
.locsuby_id→ la tabella stessa cambia
select.py:
import pandas as pd
orders = pd.read_csv("orders.csv")
# Columns by name -> a DataFrame of 8 rows, 3 columns
print(orders[["date", "branch", "quantity"]].shape)
# Rows named by their order number; check the names are unique first
by_id = orders.set_index("order_id")
print("unique:", by_id.index.is_unique)
# One order by identity -> a Series; four orders -> 4 x 2 (loc includes 1005)
print(by_id.loc[1007])
print(by_id.loc[1002:1005, ["product", "branch"]])
# First two and last two by place -> .iloc for rows, names for columns
print(by_id.iloc[:2][["product", "price"]])
print(by_id.iloc[-2:][["product", "price"]])
# Single cells -> plain numbers
print("qty of 1005:", by_id.at[1005, "quantity"])
print("total of 1003:", by_id.at[1003, "quantity"] * by_id.at[1003, "price"])
# Change the original, in one step
by_id.loc[1001, "price"] = 16.0
print("price of 1001:", by_id.at[1001, "price"])(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0Ogni risultato corrisponde al piano: (8, 3) per tre colonne, quattro ordini per 1002:1005 perché .loc include la fine, e due righe ciascuna per le selezioni posizionali. Nella condizione 3 le righe escono per posizione e le colonne per nome — .iloc per le righe, poi una lista di nomi — così il codice dice esattamente quello che intende.
Ora le tre previsioni:
by_id.loc[0]KeyError: 0Le etichette sono ora numeri d'ordine; non esiste nessun ordine 0. La prima riga per posizione sarebbe by_id.iloc[0].
orders.iloc[8]IndexError: single positional indexer is out-of-boundsOtto righe significano posizioni da 0 a 7. L'ultima riga è orders.iloc[7], o meglio, orders.iloc[-1].
by_id["price"][1001] = 99.0ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignmentprint(by_id.at[1001, "price"])16.0Ancora 16.0: by_id["price"] ha creato una selezione separata e il 99.0 è finito in essa, non in by_id. La condizione 4 ha funzionato e questo no, e la differenza è un singolo .loc contro due parentesi di fila.
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