Jak radzić sobie SettingWithCopyWarning
w Pandach?
Ten post jest przeznaczony dla czytelników, którzy:
- Chciałby zrozumieć, co oznacza to ostrzeżenie
- Chciałby zrozumieć różne sposoby tłumienia tego ostrzeżenia
- Chciałby zrozumieć, jak ulepszyć swój kod i postępować zgodnie z dobrymi praktykami, aby uniknąć tego ostrzeżenia w przyszłości.
Ustawiać
np.random.seed(0)
df = pd.DataFrame(np.random.choice(10, (3, 5)), columns=list('ABCDE'))
df
A B C D E
0 5 0 3 3 7
1 9 3 5 2 4
2 7 6 8 8 1
Co to jest SettingWithCopyWarning
?
Aby wiedzieć, jak sobie poradzić z tym ostrzeżeniem, ważne jest, aby zrozumieć, co to znaczy i dlaczego jest ono podnoszone.
Podczas filtrowania ramek danych istnieje możliwość wycinania / indeksowania ramki w celu zwrócenia widoku lub kopii , w zależności od układu wewnętrznego i różnych szczegółów implementacji. „Widok” to, jak sugeruje to termin, widok oryginalnych danych, więc modyfikacja widoku może modyfikować oryginalny obiekt. Z drugiej strony „kopia” jest replikacją danych z oryginału, a modyfikacja kopii nie ma wpływu na oryginał.
Jak wspomniano w innych odpowiedziach, SettingWithCopyWarning
utworzono go w celu oznaczenia operacji „przypisania łańcuchowego”. Rozważ df
powyższą konfigurację. Załóżmy, że chcesz wybrać wszystkie wartości w kolumnie „B”, gdzie wartości w kolumnie „A” to> 5. Panda pozwala to robić na różne sposoby, niektóre bardziej poprawne niż inne. Na przykład,
df[df.A > 5]['B']
1 3
2 6
Name: B, dtype: int64
I,
df.loc[df.A > 5, 'B']
1 3
2 6
Name: B, dtype: int64
Zwracają ten sam wynik, więc jeśli czytasz tylko te wartości, nie ma to znaczenia. Więc o co chodzi? Problem z połączeniem łańcuchowym polega na tym, że zazwyczaj trudno jest przewidzieć, czy widok lub kopia zostaną zwrócone, więc w dużej mierze staje się to problemem, gdy próbujesz przypisać wartości z powrotem. Aby skorzystać z wcześniejszego przykładu, zastanów się, w jaki sposób ten kod jest wykonywany przez interpretera:
df.loc[df.A > 5, 'B'] = 4
# becomes
df.__setitem__((df.A > 5, 'B'), 4)
Za pomocą jednego __setitem__
połączenia z df
. OTOH, rozważ ten kod:
df[df.A > 5]['B'] = 4
# becomes
df.__getitem__(df.A > 5).__setitem__('B", 4)
Teraz, w zależności od tego, czy __getitem__
zwrócił widok, czy kopię, __setitem__
operacja może nie działać .
Zasadniczo należy używać loc
do przypisywania opartego na etykietach i przypisywania iloc
liczb całkowitych / pozycyjnych, ponieważ specyfikacja gwarantuje, że zawsze działają one na oryginale. Dodatkowo do ustawienia pojedynczej komórki należy użyć at
i iat
.
Więcej można znaleźć w dokumentacji .
Uwaga
Wszystkie operacje indeksowania logicznego wykonane za pomocą loc
można również wykonać za pomocą iloc
. Jedyna różnica polega na tym, że iloc
oczekuje się liczb całkowitych / pozycji dla indeksu lub tablicy liczbowej wartości boolowskich oraz indeksów liczb całkowitych / pozycji dla kolumn.
Na przykład,
df.loc[df.A > 5, 'B'] = 4
Można napisać nas
df.iloc[(df.A > 5).values, 1] = 4
I,
df.loc[1, 'A'] = 100
Można zapisać jako
df.iloc[1, 0] = 100
I tak dalej.
Po prostu powiedz mi, jak ukryć ostrzeżenie!
Rozważ prostą operację na kolumnie „A” w df
. Wybranie „A” i podzielenie przez 2 spowoduje wyświetlenie ostrzeżenia, ale operacja będzie działać.
df2 = df[['A']]
df2['A'] /= 2
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/IPython/__main__.py:1: 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
df2
A
0 2.5
1 4.5
2 3.5
Istnieje kilka sposobów bezpośredniego wyciszenia tego ostrzeżenia:
Zrobić deepcopy
df2 = df[['A']].copy(deep=True)
df2['A'] /= 2
Zmianapd.options.mode.chained_assignment
może być ustawiony None
, "warn"
albo "raise"
. "warn"
jest wartością domyślną. None
całkowicie wyciszy to ostrzeżenie i "raise"
rzuci SettingWithCopyError
, uniemożliwiając przejście operacji.
pd.options.mode.chained_assignment = None
df2['A'] /= 2
@Peter Cotton w komentarzach wymyślił fajny sposób nieinwazyjnej zmiany trybu (zmodyfikowany z tej treści ) za pomocą menedżera kontekstu, aby ustawić tryb tylko tak długo, jak jest to wymagane, i zresetować go z powrotem do stan oryginalny po zakończeniu.
class ChainedAssignent:
def __init__(self, chained=None):
acceptable = [None, 'warn', 'raise']
assert chained in acceptable, "chained must be in " + str(acceptable)
self.swcw = chained
def __enter__(self):
self.saved_swcw = pd.options.mode.chained_assignment
pd.options.mode.chained_assignment = self.swcw
return self
def __exit__(self, *args):
pd.options.mode.chained_assignment = self.saved_swcw
Wykorzystanie jest następujące:
# some code here
with ChainedAssignent():
df2['A'] /= 2
# more code follows
Lub, aby podnieść wyjątek
with ChainedAssignent(chained='raise'):
df2['A'] /= 2
SettingWithCopyError:
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
„Problem XY”: Co robię źle?
Przez większość czasu użytkownicy próbują znaleźć sposoby na wyeliminowanie tego wyjątku, nie w pełni rozumiejąc, dlaczego został zgłoszony. To dobry przykład problemu XY , w którym użytkownicy próbują rozwiązać problem „Y”, który w rzeczywistości jest objawem głębiej zakorzenionego problemu „X”. Pytania będą zadawane w oparciu o typowe problemy, które napotykają to ostrzeżenie, a następnie zostaną przedstawione rozwiązania.
Pytanie 1
Mam ramkę danych
df
A B C D E
0 5 0 3 3 7
1 9 3 5 2 4
2 7 6 8 8 1
Chcę przypisać wartości w kolumnie „A”> 5 do 1000. Moje oczekiwane wyniki to
A B C D E
0 5 0 3 3 7
1 1000 3 5 2 4
2 1000 6 8 8 1
Niewłaściwy sposób to zrobić:
df.A[df.A > 5] = 1000 # works, because df.A returns a view
df[df.A > 5]['A'] = 1000 # does not work
df.loc[df.A 5]['A'] = 1000 # does not work
Właściwy sposób za pomocą loc
:
df.loc[df.A > 5, 'A'] = 1000
Pytanie 2 1
Próbuję ustawić wartość w komórce (1, „D”) na 12345. Moje oczekiwane wyjście to
A B C D E
0 5 0 3 3 7
1 9 3 5 12345 4
2 7 6 8 8 1
Próbowałem różnych sposobów dostępu do tej komórki, takich jak
df['D'][1]
. Jak najlepiej to zrobić?
1. To pytanie nie jest ściśle związane z ostrzeżeniem, ale dobrze jest zrozumieć, jak prawidłowo wykonać tę konkretną operację, aby uniknąć sytuacji, w których ostrzeżenie mogłoby się pojawić w przyszłości.
Aby to zrobić, możesz użyć dowolnej z następujących metod.
df.loc[1, 'D'] = 12345
df.iloc[1, 3] = 12345
df.at[1, 'D'] = 12345
df.iat[1, 3] = 12345
Pytanie 3
Próbuję rozdzielić wartości w oparciu o jakiś warunek. Mam DataFrame
A B C D E
1 9 3 5 2 4
2 7 6 8 8 1
Chciałbym przypisać wartości w „D” do 123 tak, aby „C” == 5. Próbowałem
df2.loc[df2.C == 5, 'D'] = 123
Co wydaje się w porządku, ale wciąż dostaję
SettingWithCopyWarning
! Jak to naprawić?
Prawdopodobnie jest to prawdopodobnie spowodowane kodem znajdującym się wyżej w potoku. Czy stworzyłeś df2
z czegoś większego, takiego jak
df2 = df[df.A > 5]
? W takim przypadku indeksowanie logiczne zwróci widok, więc df2
odniesie się do oryginału. To, co musisz zrobić, to przypisać df2
do kopii :
df2 = df[df.A > 5].copy()
# Or,
# df2 = df.loc[df.A > 5, :]
Pytanie 4
Próbuję usunąć z miejsca kolumnę „C”
A B C D E
1 9 3 5 2 4
2 7 6 8 8 1
Ale za pomocą
df2.drop('C', axis=1, inplace=True)
Zgłasza SettingWithCopyWarning
. Dlaczego to się dzieje?
Wynika to z faktu, że df2
musiał zostać utworzony jako widok z innej operacji krojenia, takiej jak
df2 = df[df.A > 5]
Rozwiązaniem tego problemu jest albo zrobić copy()
z df
lub wykorzystania loc
, jak poprzednio.