Copies et vues en Pandas¶
Les tableaux, DataFrames, peuvent contenir une quantité importante de données aussi il est bon d'éviter les copies, même temporaires, lorsque c'est possible. Ainsi extraire un sous-tableau pour le consulter ne demande pas de copie. Si maintenant on désire le modifier alors il faut se poser la question de la modification du tableau d'origine.
En pratique Pandas choisit s'il fait une copie ou une vue. S'il a fait une vue, une modification du sous-tableau modifiera le tableau principal ce qui ne sera pas le cas s'il a fait une copie. Aussi Pandas envoie un message d'avertissement pour souligner l'incertitude.
import pandas as pd
from IPython.display import display, HTML
CSS = """
.output {
flex-direction: row;
}
"""
HTML('<style>{}</style>'.format(CSS))
print(pd.__version__) # behaviour can change with the version
2.2.1
df = pd.DataFrame({'A':list('qwer'), 'B':list('asdf')})
df
A | B | |
---|---|---|
0 | q | a |
1 | w | s |
2 | e | d |
3 | r | f |
df2 = df.loc[1:3,:]
df.loc[2,'B'] = 'X' # warning, here 2 is a label (by chance it has the same value than the index)
df2.loc[1,'A'] = 'Z'
/tmp/ipykernel_897/1281438451.py:3: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df2.loc[1,'A'] = 'Z'
df2
est potentiellement une copie d'une partie de df
(mais ce peut être une vue).
from IPython.display import display
display(df, df2)
A | B | |
---|---|---|
0 | q | a |
1 | Z | s |
2 | e | X |
3 | r | f |
A | B | |
---|---|---|
1 | Z | s |
2 | e | X |
3 | r | f |
Nous pouvons donc voir ici que df2
est une vue de df
puisqu'un changement sur l'un d'eux est visible sur l'autre (les deux ont X et Z).
Cependant, si j’ajoute une colonne à df2
, puisque je donne cette colonne à df
alors les colonnes A et B des 2 dataframes sont des vues mais les colonnes C des 2 dataframes sont distinctes donc des copies !
df = pd.DataFrame({'A':list('qwer'), 'B':list('asdf')})
df2 = df.loc[1:3,:]
df2['C'] = df.A + df.B
df.loc[2,'B'] = 'X'
df2.loc[1,'A'] = 'Z'
df['C'] = df2['C']
df2.loc[3,'C'] = 'AB'
/tmp/ipykernel_897/3317636207.py:3: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy df2['C'] = df.A + df.B
display(df, df2)
A | B | C | |
---|---|---|---|
0 | q | a | NaN |
1 | Z | s | ws |
2 | e | X | ed |
3 | r | f | rf |
A | B | C | |
---|---|---|---|
1 | Z | s | ws |
2 | e | X | ed |
3 | r | f | AB |
Notez que le résultat peut être différent puisque vous ne savez jamais si Pandas voit df2 comme une vue ou une copie.
copy
pour être sûr d'avoir une copie¶
Vous voulez être sûr du résultat, faites des copies :
df = pd.DataFrame({'A':list('qwer'), 'B':list('asdf')})
df2 = df.loc[1:3,:].copy()
df.loc[1,'A'] = 'X'
df2.loc[2,'B'] = 'Z'
display(df, df2)
A | B | |
---|---|---|
0 | q | a |
1 | X | s |
2 | e | d |
3 | r | f |
A | B | |
---|---|---|
1 | w | s |
2 | e | Z |
3 | r | f |
Pour résumer :
- en lecteur seule, les vues c'est bien
- en écriture, les copies sont préférables.