introduzione
L’introduzione di Copy-on-Write (CoW) è una modifica rivoluzionaria che avrà un certo impatto sul codice Panda esistente. Esamineremo come adattare il nostro codice per evitare errori quando CoW sarà abilitato per impostazione predefinita. Questo è attualmente previsto per il rilascio di Pandas 3.0, previsto per aprile 2024. Il primo articolo in questa serie è stato spiegato il comportamento di Copy-on-Write while il secondo post si è tuffato nelle ottimizzazioni delle prestazioni relative al Copy-on-Write.
Stiamo pianificando di aggiungere una modalità di avviso che avviserà per tutte le operazioni che cambieranno il comportamento con CoW. L’avviso sarà molto rumoroso per gli utenti e quindi deve essere trattato con una certa attenzione. Questo post spiega i casi comuni e come adattare il codice per evitare cambiamenti nel comportamento.
Assegnazione concatenata
L’assegnazione concatenata è una tecnica in cui un oggetto viene aggiornato attraverso 2 operazioni successive.
import pandas as pddf = pd.DataFrame({"x": (1, 2, 3)})
df("x")(df("x") > 1) = 100
La prima operazione seleziona la colonna "x"
mentre la seconda operazione limita il numero di righe. Esistono molte combinazioni diverse di queste operazioni (ad esempio combinate con loc
O iloc
). Nessuna di queste combinazioni funzionerà con CoW. Invece, lanceranno un avvertimento ChainedAssignmentError
per rimuovere questi schemi invece di non fare nulla in silenzio.
In generale, puoi usare loc
Invece:
df.loc(df("x") > 1, "x") = 100
La prima dimensione di loc
corrisponde sempre a row-indexer
. Ciò significa che puoi selezionare un sottoinsieme di righe. La seconda dimensione corrisponde a column-indexer
che consente di selezionare un sottoinsieme di colonne.
In genere è più veloce da usare loc
quando desideri impostare valori in un sottoinsieme di righe, quindi questo ripulirà il tuo codice e fornirà un miglioramento delle prestazioni.
Questo è il caso ovvio in cui CoW avrà un impatto. Avrà un impatto anche sulle operazioni sul posto concatenate:
df("x").replace(1, 100)
Lo schema è lo stesso di sopra. La selezione della colonna è la prima operazione. IL replace
Il metodo tenta di operare sull’oggetto temporaneo, che non riuscirà ad aggiornare l’oggetto iniziale. Puoi anche rimuovere questi modelli abbastanza facilmente specificando le colonne su cui desideri operare.
df = df.replace({"x": 1}, {"x": 100})
Modelli da evitare
Il mio post precedente spiega come funziona il meccanismo CoW e come i DataFrames condividono i dati sottostanti. Verrà eseguita una copia difensiva se due oggetti condividono gli stessi dati mentre si modifica un oggetto sul posto.
df2 = df.reset_index()
df2.iloc(0, 0) = 100
IL reset_index
L’operazione creerà una visualizzazione dei dati sottostanti. Il risultato viene assegnato a una nuova variabile df2
ciò significa che due oggetti condividono gli stessi dati. Questo vale fino al df
è la raccolta dei rifiuti. IL setitem
l’operazione attiverà quindi una copia. Questo è completamente inutile se non hai bisogno dell’oggetto iniziale df
più. La semplice riassegnazione alla stessa variabile invaliderà il riferimento mantenuto dall’oggetto.
df = df.reset_index()
df.iloc(0, 0) = 100
Riassumendo, la creazione di più riferimenti nello stesso metodo mantiene vivi i riferimenti non necessari.
I riferimenti temporanei creati quando si concatenano metodi diversi vanno bene.
df = df.reset_index().drop(...)
Ciò manterrà vivo solo un riferimento.
Accesso all’array NumPy sottostante
pandas attualmente ci dà accesso all’array NumPy sottostante tramite to_numpy
O .values
. L’array restituito è una copia, se il tuo DataFrame è costituito da diversi dtype, ad esempio:
df = pd.DataFrame({"a": (1, 2), "b": (1.5, 2.5)})
df.to_numpy()((1. 1.5)
(2. 2.5))
Il DataFrame è supportato da due array che devono essere combinati in uno solo. Questo attiva la copia.
L’altro caso è un DataFrame supportato solo da un singolo array NumPy, ad esempio:
df = pd.DataFrame({"a": (1, 2), "b": (3, 4)})
df.to_numpy()((1 3)
(2 4))
Possiamo accedere direttamente all’array e ottenere una vista invece di una copia. Questo è molto più veloce della copia di tutti i dati. Ora possiamo operare sull’array NumPy e potenzialmente modificarlo sul posto, il che aggiornerà anche il DataFrame e potenzialmente tutti gli altri DataFrame che condividono dati. Ciò diventa molto più complicato con Copy-on-Write, poiché abbiamo rimosso molte copie difensive. Molti più DataFrame ora condivideranno la memoria tra loro.
to_numpy
E .values
restituirà un array di sola lettura per questo motivo. Ciò significa che l’array risultante non è scrivibile.
df = pd.DataFrame({"a": (1, 2), "b": (3, 4)})
arr = df.to_numpy()arr(0, 0) = 1
Ciò attiverà un ValueError
:
ValueError: assignment destination is read-only
Puoi evitarlo in due modi diversi:
- Attiva una copia manualmente se vuoi evitare di aggiornare DataFrames che condividono memoria con il tuo array.
- Rendi l’array scrivibile. Questa è una soluzione più performante ma elude le regole Copy-on-Write, quindi dovrebbe essere usata con cautela.
arr.flags.writeable = True
Ci sono casi in cui ciò non è possibile. Un evento comune è, se si accede a una singola colonna supportata da PyArrow:
ser = pd.Series((1, 2), dtype="int64(pyarrow)")
arr = ser.to_numpy()
arr.flags.writeable = True
Questo restituisce a ValueError
:
ValueError: cannot set WRITEABLE flag to True of this array
Gli array di frecce sono immutabili, quindi non è possibile rendere scrivibile l’array NumPy. In questo caso la conversione da Arrow a NumPy è a copia zero.
Conclusione
Abbiamo esaminato le modifiche più invasive relative al Copy-on-Write. Queste modifiche diventeranno il comportamento predefinito in Pandas 3.0. Abbiamo anche studiato come possiamo adattare il nostro codice per evitare di romperlo quando la funzione Copy-on-Write è abilitata. Il processo di aggiornamento dovrebbe essere abbastanza fluido se riesci a evitare questi schemi.
Fonte: towardsdatascience.com