Jak radzić sobie z SettingWithCopyWarning w Pandy?

Tło

Właśnie uaktualniłem moje pandy z 0.11 do 0.13. 0rc1. Teraz aplikacja wyskakuje wiele nowych ostrzeżeń. Jeden z nich taki:

E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TVol']   = quote_df['TVol']/TVOL_SCALE
Chcę wiedzieć, co to dokładnie znaczy? Czy muszę coś zmienić?

Jak mam zawiesić ostrzeżenie, jeśli nalegam na użycie quote_df['TVol'] = quote_df['TVol']/TVOL_SCALE?

Funkcja, która daje błędy

def _decode_stock_quote(list_of_150_stk_str):
    """decode the webpage and return dataframe"""

    from cStringIO import StringIO

    str_of_all = "".join(list_of_150_stk_str)

    quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
    quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
    quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]
    quote_df['TClose'] = quote_df['TPrice']
    quote_df['RT']     = 100 * (quote_df['TPrice']/quote_df['TPCLOSE'] - 1)
    quote_df['TVol']   = quote_df['TVol']/TVOL_SCALE
    quote_df['TAmt']   = quote_df['TAmt']/TAMT_SCALE
    quote_df['STK_ID'] = quote_df['STK'].str.slice(13,19)
    quote_df['STK_Name'] = quote_df['STK'].str.slice(21,30)#.decode('gb2312')
    quote_df['TDate']  = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])

    return quote_df

Więcej komunikatów o błędach

E:\FinReporter\FM_EXT.py:449: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TVol']   = quote_df['TVol']/TVOL_SCALE
E:\FinReporter\FM_EXT.py:450: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TAmt']   = quote_df['TAmt']/TAMT_SCALE
E:\FinReporter\FM_EXT.py:453: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  quote_df['TDate']  = quote_df.TDate.map(lambda x: x[0:4]+x[5:7]+x[8:10])
Author: Brad Solomon, 0000-00-00

4 answers

SettingWithCopyWarning został stworzony w celu oznaczania potencjalnie mylących "przykutych" zadań, takich jak poniższe, które nie zawsze działają zgodnie z oczekiwaniami, szczególnie gdy pierwsza selekcja zwraca kopię. [zobacz GH5390 i GH5597 do dyskusji w tle.]

df[df['A'] > 2]['B'] = new_val  # new_val not set in df

Ostrzeżenie proponuje przepisanie w następujący sposób:

df.loc[df['A'] > 2, 'B'] = new_val
Jednak to nie pasuje do twojego użycia, co jest równoważne:
df = df[df['A'] > 2]
df['B'] = new_val
/ Align = "left" / zapisuje wracając do oryginalnej ramki (ponieważ przesadziłeś odniesienie do niej), niestety tego wzorca nie można odróżnić od pierwszego przykładzie przypisania łańcuchowego, stąd Ostrzeżenie (fałszywie dodatnie). Możliwość wystąpienia fałszywych alarmów jest omówiona w docs on indexing , Jeśli chcesz przeczytać dalej. Możesz bezpiecznie wyłączyć to nowe ostrzeżenie za pomocą następującego przypisania.
pd.options.mode.chained_assignment = None  # default='warn'
 415
Author: Garrett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-15 07:54:48

Ogólnie rzecz biorąc, celem SettingWithCopyWarning jest pokazanie użytkownikom (i nowym użytkownikom esp), że mogądziałać na kopii, a nie na oryginale, jak myślą. Nie fałszywych pozytywnych (IOW wiesz, co robisz, więc ok ). Jedną z możliwości jest po prostu wyłączenie (domyślnie warn) ostrzeżenia, jak sugeruje @Garrett.

Oto inna opcja.

In [1]: df = DataFrame(np.random.randn(5,2),columns=list('AB'))

In [2]: dfa = df.ix[:,[1,0]]

In [3]: dfa.is_copy
Out[3]: True

In [4]: dfa['A'] /= 2
/usr/local/bin/ipython:1: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
  #!/usr/local/bin/python

Można ustawić flagę is_copy na False, co skutecznie wyłączy sprawdź ,* dla tego obiektu "

In [5]: dfa.is_copy = False

In [6]: dfa['A'] /= 2

Jeśli będziesz kopiować to wiesz co robisz , więc żadne dalsze Ostrzeżenie się nie wydarzy.

In [7]: dfa = df.ix[:,[1,0]].copy()

In [8]: dfa['A'] /= 2

Kod pokazany powyżej, chociaż uzasadniony, i prawdopodobnie coś, co ja również robię, jest technicznie argumentem za tym ostrzeżeniem, a nie fałszywym wynikiem. Innym sposobem, aby Nie mieć Ostrzeżenie, byłoby wykonanie operacji wyboru przez reindex, np.

quote_df = quote_df.reindex(columns=['STK',.......])

Lub

quote_df = quote_df.reindex(['STK',.......], axis=1) # v.0.21
 106
Author: Jeff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-27 10:18:45

Pandy dataframe copy warning

Kiedy pójdziesz i zrobisz coś takiego:

quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]

pandas.ix w tym przypadku zwraca nową, samodzielną ramkę danych.

Jakiekolwiek wartości, które zdecydujesz się zmienić w tej ramce danych, nie zmieni oryginalnej ramki danych.

To właśnie pandy próbują was ostrzec.

Dlaczego .ix to zły pomysł

Obiekt .ix próbuje zrobić więcej niż jedną rzecz, a dla każdego, kto przeczytał cokolwiek o czystym kodzie, to silny zapach.

Biorąc pod uwagę ten dataframe:

df = pd.DataFrame({"a": [1,2,3,4], "b": [1,1,2,2]})

Dwa zachowania:

dfcopy = df.ix[:,["a"]]
dfcopy.a.ix[0] = 2

Zachowanie pierwsze: dfcopy jest teraz samodzielną ramką danych. Zmiana nie zmieni df

df.ix[0, "a"] = 3

Zachowanie drugie: zmienia to oryginalną ramkę danych.

Użyj .loc zamiast

Twórcy pandas uznali, że obiekt .ix był dość śmierdzący [spekulatywnie] i w ten sposób stworzyli dwa nowe obiekty, które pomagają w przystąpieniu i przypisaniu danych. (The inne byty .iloc)

.loc jest szybszy, ponieważ nie próbuje utworzyć kopii danych.

.loc ma na celu modyfikację istniejącej ramki danych w miejscu, co jest bardziej wydajne pamięci.

.loc jest przewidywalny, ma jedno zachowanie.

Rozwiązanie

To, co robisz w swoim przykładzie kodu, to Ładowanie dużego pliku z dużą ilością kolumn, a następnie modyfikowanie go, aby był mniejszy.

Funkcja pd.read_csv może Ci w tym wiele pomóc i spraw, aby ładowanie pliku było znacznie szybsze.

Więc zamiast robić to

quote_df = pd.read_csv(StringIO(str_of_all), sep=',', names=list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefg')) #dtype={'A': object, 'B': object, 'C': np.float64}
quote_df.rename(columns={'A':'STK', 'B':'TOpen', 'C':'TPCLOSE', 'D':'TPrice', 'E':'THigh', 'F':'TLow', 'I':'TVol', 'J':'TAmt', 'e':'TDate', 'f':'TTime'}, inplace=True)
quote_df = quote_df.ix[:,[0,3,2,1,4,5,8,9,30,31]]

Zrób to

columns = ['STK', 'TPrice', 'TPCLOSE', 'TOpen', 'THigh', 'TLow', 'TVol', 'TAmt', 'TDate', 'TTime']
df = pd.read_csv(StringIO(str_of_all), sep=',', usecols=[0,3,2,1,4,5,8,9,30,31])
df.columns = columns

Spowoduje to odczytanie tylko interesujących Cię kolumn i poprawne ich nazwanie. Nie ma potrzeby używania złego obiektu .ix do robienia magicznych rzeczy.

 31
Author: firelynx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-14 08:48:49

Aby usunąć wszelkie wątpliwości, moim rozwiązaniem było zrobienie głębokiej kopii kawałka zamiast zwykłej kopii. Może to nie mieć zastosowania w zależności od kontekstu (ograniczenia pamięci / rozmiar plasterka, potencjalne pogorszenie wydajności - zwłaszcza jeśli kopia występuje w pętli, jak to miało miejsce dla mnie, itp...)

Aby było jasne, oto Ostrzeżenie, które otrzymałem:

/opt/anaconda3/lib/python3.6/site-packages/ipykernel/__main__.py:54:
SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame
See the caveats in the documentation:
http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy

Ilustracja

Miałem wątpliwości, czy ostrzeżenie zostało rzucone z powodu kolumny, którą upuszczałem na kopię plaster. Chociaż technicznie nie próbowano ustawić wartości w kopii plasterka, nadal była to modyfikacja kopii plasterka. Poniżej znajdują się (uproszczone) kroki, które podjąłem, aby potwierdzić podejrzenie, mam nadzieję, że pomoże to tym z nas, którzy próbują zrozumieć Ostrzeżenie.

Przykład 1: upuszczenie kolumny na oryginał wpływa na kopię

Wiedzieliśmy, że już, ale to jest zdrowe przypomnienie. To jest Nie o czym jest Ostrzeżenie.

>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

    A   B
0   111 121
1   112 122
2   113 123


>> df2 = df1
>> df2

A   B
0   111 121
1   112 122
2   113 123

# Dropping a column on df1 affects df2
>> df1.drop('A', axis=1, inplace=True)
>> df2
    B
0   121
1   122
2   123

Jest możliwe, aby uniknąć zmian wprowadzonych na df1, aby wpłynąć na df2

>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

A   B
0   111 121
1   112 122
2   113 123

>> import copy
>> df2 = copy.deepcopy(df1)
>> df2
A   B
0   111 121
1   112 122
2   113 123

# Dropping a column on df1 does not affect df2
>> df1.drop('A', axis=1, inplace=True)
>> df2
    A   B
0   111 121
1   112 122
2   113 123

Przykład 2: upuszczenie kolumny na kopię może mieć wpływ na oryginał

To faktycznie ilustruje Ostrzeżenie.
>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

    A   B
0   111 121
1   112 122
2   113 123

>> df2 = df1
>> df2

    A   B
0   111 121
1   112 122
2   113 123

# Dropping a column on df2 can affect df1
# No slice involved here, but I believe the principle remains the same?
# Let me know if not
>> df2.drop('A', axis=1, inplace=True)
>> df1

B
0   121
1   122
2   123

Możliwe jest uniknięcie zmian wprowadzonych na df2, aby wpłynąć na df1

>> data1 = {'A': [111, 112, 113], 'B':[121, 122, 123]}
>> df1 = pd.DataFrame(data1)
>> df1

    A   B
0   111 121
1   112 122
2   113 123

>> import copy
>> df2 = copy.deepcopy(df1)
>> df2

A   B
0   111 121
1   112 122
2   113 123

>> df2.drop('A', axis=1, inplace=True)
>> df1

A   B
0   111 121
1   112 122
2   113 123
Zdrówko!
 5
Author: Raphvanns,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-27 22:25:13