dlaczego powinienem zrobić kopię ramki danych w pandach

189

Podczas wybierania podrzędnej ramki danych z nadrzędnej ramki danych zauważyłem, że niektórzy programiści wykonują kopię ramki danych za pomocą tej .copy()metody. Na przykład,

X = my_dataframe[features_list].copy()

... zamiast po prostu

X = my_dataframe[features_list]

Dlaczego robią kopię ramki danych? Co się stanie, jeśli nie wykonam kopii?

Elizabeth Susan Joseph
źródło
6
Domyślam się, że podejmują dodatkowe środki ostrożności, aby nie modyfikować ramki danych źródłowych. Prawdopodobnie niepotrzebne, ale kiedy rzucasz coś razem interaktywnie, lepiej bezpiecznie niż przepraszać.
Paul H
8
Zakładam, że nie jest to głupie pytanie, skoro udzielam odpowiedzi przeczącej.
Elizabeth Susan Joseph

Odpowiedzi:

207

To rozszerza odpowiedź Pawła. W Pandas indeksowanie DataFrame zwraca odwołanie do początkowej DataFrame. W związku z tym zmiana podzbioru spowoduje zmianę początkowej ramki DataFrame. Dlatego chcesz użyć kopii, jeśli chcesz mieć pewność, że początkowa ramka DataFrame nie ulegnie zmianie. Rozważ następujący kod:

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

Dostaniesz:

x
0 -1
1  2

Natomiast następujące pozostawia df niezmienione:

df_sub_copy = df[0:1].copy()
df_sub_copy.x = -1
cgold
źródło
6
czy to głęboka kopia?
bikashg
6
Tak. Domyślnym trybem jest „głęboka” kopia! pandas.pydata.org/pandas-docs/stable/reference/api/…
Ambareesh
44

Ponieważ jeśli nie wykonasz kopii, indeksy nadal można manipulować w innym miejscu, nawet jeśli przypiszesz element dataFrame do innej nazwy.

Na przykład:

df2 = df
func1(df2)
func2(df)

func1 może modyfikować df poprzez modyfikację df2, aby tego uniknąć:

df2 = df.copy()
func1(df2)
func2(df)
wróbel
źródło
Czekaj, czekaj, czy możesz wyjaśnić DLACZEGO to się dzieje? To nie ma sensu.
NoName
2
dzieje się tak, ponieważ w pierwszym przykładzie „df2 = df df , both variables reference the same DataFrame instance. So any changes made to ” lub df2zostanie przypisane do tej samej instancji obiektu. Podczas gdy w df2 = df.copy()drugiej instancji obiektu jest tworzona kopia pierwszej instancji, ale teraz dfi df2odniesienie do różnych instancji obiektów i wszelkie zmiany zostaną wprowadzone do ich odpowiednich instancji DataFrame.
Pedro
17

Należy wspomnieć, że zwrócenie kopii lub widoku zależy od rodzaju indeksowania.

Dokumentacja pand mówi:

Zwracanie widoku a kopia

Zasady dotyczące zwracania widoku danych są całkowicie zależne od NumPy. Za każdym razem, gdy w operacji indeksowania bierze udział tablica etykiet lub wektor boolowski, wynikiem będzie kopia. W przypadku indeksowania i dzielenia na pojedyncze etykiety / skalarne, np. Df.ix [3: 6] lub df.ix [:, „A”], zostanie zwrócony widok.

Gusev Slava
źródło
12

Głównym celem jest uniknięcie indeksowania łańcuchowego i wyeliminowanie SettingWithCopyWarning.

Tutaj jest coś w rodzaju indeksowania łańcuchowego dfc['A'][0] = 111

Dokument mówiący o tym, że indeksowanie łańcuchowe powinno być unikane w sekcji Zwracanie widoku a kopia . Oto nieco zmodyfikowany przykład z tego dokumentu:

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

Tutaj aColumnjest to widok, a nie kopia z oryginalnej ramki DataFrame, więc modyfikacja aColumnspowoduje również dfczmodyfikowanie oryginału . Następnie, jeśli najpierw zindeksujemy wiersz:

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

Tym razem zero_rowjest to kopia, więc oryginał dfcnie jest modyfikowany.

Z tych dwóch przykładów powyżej widzimy, że nie jest jednoznaczne, czy chcesz zmienić oryginalną ramkę DataFrame. Jest to szczególnie niebezpieczne, jeśli napiszesz coś takiego:

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

Tym razem w ogóle się nie udało. Tutaj chcieliśmy zmienić dfc, ale tak naprawdę zmodyfikowaliśmy wartość pośrednią, dfc.loc[0]która jest kopią i jest natychmiast odrzucana. Bardzo trudno jest przewidzieć, czy wartość pośrednia, taka jak widok lub kopia, dfc.loc[0]czy też dfc['A']jest widokiem lub kopią, więc nie ma gwarancji, czy oryginalna ramka DataFrame zostanie zaktualizowana. Dlatego należy unikać indeksowania łańcuchowego, a pandy generują SettingWithCopyWarningtego rodzaju aktualizację indeksowania łańcuchowego.

Teraz jest użycie .copy(). Aby usunąć ostrzeżenie, zrób kopię, aby wyraźnie wyrazić swój zamiar:

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

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

Ponieważ modyfikujesz kopię, wiesz, że oryginał dfcnigdy się nie zmieni i nie spodziewasz się, że ulegnie zmianie. Twoje oczekiwania pasują do zachowania, a następnie SettingWithCopyWarningznika.

Uwaga: jeśli chcesz zmodyfikować oryginalną ramkę DataFrame, dokument sugeruje użycie 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
źródło
2

Generalnie bezpieczniej jest pracować na kopiach niż na oryginalnych ramkach danych, z wyjątkiem sytuacji, gdy wiesz, że nie będziesz już potrzebować oryginału i chcesz kontynuować pracę z wersją zmanipulowaną. Normalnie nadal można by było wykorzystać oryginalną ramkę danych do porównania z wersją zmanipulowaną, itp. Dlatego większość ludzi pracuje nad kopiami i na końcu łączy je.

bojax
źródło
0

Załóżmy, że masz ramkę danych, jak poniżej

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

Jeśli chcesz stworzyć inny, df2identyczny z df1, bezcopy

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

Chciałbym zmodyfikować wartość df2 tylko jak poniżej

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

W tym samym czasie zmienia się również df1

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

Ponieważ dwa df są takie same object, możemy to sprawdzić za pomocąid

id(df1)
140367679979600
id(df2)
140367679979600

Czyli jako ten sam obiekt i jedna zmiana na inną, również przekazują tę samą wartość.


Jeśli dodamy copy, i teraz df1i df2uważamy object, że są różne , jeśli dokonamy tej samej zmiany w jednym z nich, drugi się nie zmieni.

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

Warto wspomnieć, że podczas podzbioru oryginalnej ramki danych można bezpiecznie dodać również kopię, aby uniknąć SettingWithCopyWarning

YOBEN_S
źródło