por que eu deveria fazer uma cópia de um quadro de dados nos pandas

188

Ao selecionar um sub-quadro de dados de um quadro-pai pai, notei que alguns programadores fazem uma cópia do quadro de dados usando o .copy()método Por exemplo,

X = my_dataframe[features_list].copy()

... em vez de apenas

X = my_dataframe[features_list]

Por que eles estão fazendo uma cópia do quadro de dados? O que acontecerá se eu não fizer uma cópia?

Elizabeth Susan Joseph
fonte
6
Meu palpite é que eles estão tomando precauções extras para não modificar o quadro de dados de origem. Provavelmente desnecessário, mas quando você está jogando algo juntos interativamente, é melhor prevenir do que remediar.
Paul H
8
Suponho que essa não seja uma pergunta estúpida para dar uma negativa.
Elizabeth Susan Joseph

Respostas:

207

Isso se expande na resposta de Paulo. No Pandas, a indexação de um DataFrame retorna uma referência ao DataFrame inicial. Assim, alterar o subconjunto alterará o DataFrame inicial. Portanto, você deseja usar a cópia se quiser garantir que o DataFrame inicial não seja alterado. Considere o seguinte código:

df = DataFrame({'x': [1,2]})
df_sub = df[0:1]
df_sub.x = -1
print(df)

Você terá:

x
0 -1
1  2

Por outro lado, o seguinte deixa df inalterado:

df_sub_copy = df[0:1].copy()
df_sub_copy.x = -1
cgold
fonte
6
esta é uma cópia profunda?
bikashg
6
Sim. O modo padrão é a cópia "profunda"! pandas.pydata.org/pandas-docs/stable/reference/api/…
Ambareesh
44

Como se você não fizer uma cópia, os índices ainda poderão ser manipulados em outro local, mesmo se você atribuir o dataFrame a um nome diferente.

Por exemplo:

df2 = df
func1(df2)
func2(df)

func1 pode modificar o df modificando o df2, para evitar que:

df2 = df.copy()
func1(df2)
func2(df)
pardal
fonte
Espera espera espera, você pode explicar por que isso ocorre? Não faz sentido.
NoName
2
é porque no primeiro exemplo, `df2 = df , both variables reference the same DataFrame instance. So any changes made to df` ou df2será feito na mesma instância do objeto. Enquanto na df2 = df.copy()segunda instância do objeto é criada, uma cópia da primeira, mas agora, dfe a df2referência a diferentes instâncias do objeto e quaisquer alterações serão feitas na respectiva instância do DataFrame.
Pedro
17

É necessário mencionar que o retorno da cópia ou exibição depende do tipo de indexação.

A documentação do pandas diz:

Retornando uma visualização versus uma cópia

As regras sobre quando uma exibição dos dados são retornadas dependem inteiramente do NumPy. Sempre que uma matriz de rótulos ou um vetor booleano estiver envolvido na operação de indexação, o resultado será uma cópia. Com indexação e faturamento de etiqueta / escalar únicos, por exemplo, df.ix [3: 6] ou df.ix [:, 'A'], uma exibição será retornada.

Gusev Slava
fonte
12

O objetivo principal é evitar a indexação encadeada e eliminar o SettingWithCopyWarning.

Aqui a indexação encadeada é algo como dfc['A'][0] = 111

O documento dizia que a indexação encadeada deve ser evitada em Retornando uma exibição versus uma cópia . Aqui está um exemplo ligeiramente modificado desse documento:

In [1]: import pandas as pd

In [2]: dfc = pd.DataFrame({'A':['aaa','bbb','ccc'],'B':[1,2,3]})

In [3]: dfc
Out[3]:
    A   B
0   aaa 1
1   bbb 2
2   ccc 3

In [4]: aColumn = dfc['A']

In [5]: aColumn[0] = 111
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

In [6]: dfc
Out[6]:
    A   B
0   111 1
1   bbb 2
2   ccc 3

Aqui aColumnestá uma visualização e não uma cópia do DataFrame original; portanto, a modificação também aColumnfará com que o original dfcseja modificado. Em seguida, se indexarmos a linha primeiro:

In [7]: zero_row = dfc.loc[0]

In [8]: zero_row['A'] = 222
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

In [9]: dfc
Out[9]:
    A   B
0   111 1
1   bbb 2
2   ccc 3

Desta vez zero_rowé uma cópia, portanto o original dfcnão é modificado.

A partir desses dois exemplos acima, vemos que é ambíguo se você deseja ou não alterar o DataFrame original. Isso é especialmente perigoso se você escrever algo como o seguinte:

In [10]: dfc.loc[0]['A'] = 333
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

In [11]: dfc
Out[11]:
    A   B
0   111 1
1   bbb 2
2   ccc 3

Desta vez, não funcionou. Aqui queríamos mudar dfc, mas na verdade modificamos um valor intermediário dfc.loc[0]que é uma cópia e é descartado imediatamente. É muito difícil prever se o valor intermediário como dfc.loc[0]ou dfc['A']é uma visão ou uma cópia, por isso não é garantido ou não trama de dados original será atualizado. É por isso que a indexação em cadeia deve ser evitada, e o pandas gera o SettingWithCopyWarningpara esse tipo de atualização de indexação em cadeia.

Agora é o uso de .copy(). Para eliminar o aviso, faça uma cópia para expressar sua intenção explicitamente:

In [12]: zero_row_copy = dfc.loc[0].copy()

In [13]: zero_row_copy['A'] = 444 # This time no warning

Como você está modificando uma cópia, sabe que o original dfcnunca será alterado e não espera que seja alterado. Sua expectativa corresponde ao comportamento e depois SettingWithCopyWarningdesaparece.

Nota, se você deseja modificar o DataFrame original, o documento sugere que você use loc:

In [14]: dfc.loc[0,'A'] = 555

In [15]: dfc
Out[15]:
    A   B
0   555 1
1   bbb 2
2   ccc 3
Cosyn
fonte
2

Em geral, é mais seguro trabalhar em cópias do que em quadros de dados originais, exceto quando você sabe que não precisará mais do original e deseja continuar com a versão manipulada. Normalmente, você ainda teria algum uso para o quadro de dados original para comparar com a versão manipulada, etc. Portanto, a maioria das pessoas trabalha com cópias e mescla no final.

bojax
fonte
0

Supondo que você tenha um quadro de dados como abaixo

df1
     A    B    C    D
4 -1.0 -1.0 -1.0 -1.0
5 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0

Quando você deseja criar outro df2que seja idêntico df1, semcopy

df2=df1
df2
     A    B    C    D
4 -1.0 -1.0 -1.0 -1.0
5 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0
6 -1.0 -1.0 -1.0 -1.0

E gostaria de modificar o valor df2 apenas como abaixo

df2.iloc[0,0]='changed'

df2
         A    B    C    D
4  changed -1.0 -1.0 -1.0
5       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0

Ao mesmo tempo, o df1 também é alterado

df1
         A    B    C    D
4  changed -1.0 -1.0 -1.0
5       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0

Como dois df são iguais object, podemos verificá-lo usando oid

id(df1)
140367679979600
id(df2)
140367679979600

Portanto, eles têm o mesmo objeto e um muda outro, também passa o mesmo valor.


Se adicionarmos o copy, e agora, df1e formos df2considerados diferentes object, se fizermos a mesma alteração em um deles, o outro não será alterado.

df2=df1.copy()
id(df1)
140367679979600
id(df2)
140367674641232

df1.iloc[0,0]='changedback'
df2
         A    B    C    D
4  changed -1.0 -1.0 -1.0
5       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0
6       -1 -1.0 -1.0 -1.0

É bom mencionar que, quando você define o quadro de dados original, também é seguro adicionar a cópia para evitar a SettingWithCopyWarning

YOBEN_S
fonte