Непредсказуемое поведение назначения slice pandas без настройкиWithCopyWarning

Он хорошо известенпонятный), что поведение pandas по сути, непредсказуемо при назначении срезу. Но я привык к предупреждению об этом с помощью предупреждения SettingWithCopy.

Почему предупреждение не генерируется ни в одном из следующих двух фрагментов кода, и какие методы могут уменьшить вероятность написания такого кода непреднамеренно?

# pandas 0.18.1, python 3.5.1
import pandas as pd
data = pd.DataFrame({'a': [1, 2, 3], 'b': ['a', 'b', 'c']})
new_data = data[['a', 'b']]
data = data['a']
new_data.loc[0, 'a'] = 100 # no warning, doesn't propagate to data

data[0] == 1
True


data = pd.DataFrame({'a': [1, 2, 3], 'b': ['a', 'b', 'c']})
new_data = data['a']
data = data['a']
new_data.loc[0] = 100 # no warning, propagates to data

data[0] == 100
True

Я думал, что объяснение состоит в том, что pandas выводит предупреждение только тогда, когда родительский DataFrame по-прежнему доступен из текущего контекста. (Это было бы слабостью алгоритма обнаружения, как показывают мои предыдущие примеры.)

В следующем фрагменте AFAIK исходный двухстолбцовый DataFrame больше недоступен, но механизм предупреждения pandas управляет (к счастью):

data = pd.DataFrame({'a': [1, 2, 3], 'b': ['a', 'b', 'c']})
new_data = data['a']
data = data[['a']]
new_data.loc[0] = 100 # warning, so we're safe

Edit:

При этом я нашел еще один случай отсутствия предупреждения:

data = pd.DataFrame({'a': [1, 2, 3], 'b': ['a', 'b', 'c']})
data = data.groupby('a')
new_data = data.filter(lambda g: len(g)==1)
new_data.loc[0, 'a'] = 100 # no warning, does not propagate to data
assert data.filter(lambda g: True).loc[0, 'a'] == 1

Даже если почти идентичный пример вызывает предупреждение:

data = pd.DataFrame({'a': [1, 2, 2], 'b': ['a', 'b', 'c']})
data = data.groupby('a')
new_data = data.filter(lambda g: len(g)==1)
new_data.loc[0, 'a'] = 100 # warning, does not propagate to data
assert data.filter(lambda g: True).loc[0, 'a'] == 1

Обновление: я отвечаю на ответ @firelynx здесь, потому что его трудно помещать в комментарий.

В ответе @firelynx говорит, что первый фрагмент кода не дает никаких предупреждений, потому что я беру весь файл данных. Но даже если я принимал участие в этом, я все равно не получаю предупреждение:

# pandas 0.18.1, python 3.5.1
import pandas as pd
data = pd.DataFrame({'a': [1, 2, 3], 'b': ['a', 'b', 'c'], c: range(3)})
new_data = data[['a', 'b']]
data = data['a']
new_data.loc[0, 'a'] = 100 # no warning, doesn't propagate to data

data[0] == 1
True

Ответы

Ответ 1

Объясняя, что вы делаете, шаг за шагом

Созданный Dataframe - это не представление

data = pd.DataFrame({'a': [1, 2, 3], 'b': ['a', 'b', 'c']})
data._is_view
False

new_data также не является представлением, потому что вы берете все столбцы

new_data = data[['a', 'b']]
new_data._is_view
False

теперь вы назначаете данные как Series 'a'

data = data['a']
type(data)
pandas.core.series.Series

Какой вид

data._is_view
True

Теперь вы обновляете значение в не-копии new_data

new_data.loc[0, 'a'] = 100 # no warning, doesn't propagate to data

Это не должно вызывать предупреждения. Это весь файл данных.

Series вы сами создали флаги как представление, но это не DataFrame и не ведет себя как представление DataFrame.

Избегайте написания кода, подобного этому

Проблема Series/Dataframe является очень распространенной в pandas [цитата не нужна, если вы некоторое время работали с pandas]

Проблема в том, что вы всегда должны писать

data[['a']] не data['a']

Влево создается представление данных, справа создается серия.

Некоторые люди могут утверждать, что никогда не пишут data['a'], но вместо этого data.a. Таким образом, вы можете добавлять предупреждения в свою среду для кода data['a'].

Это не работает. Прежде всего использование синтаксиса data.a вызывает когнитивный диссонанс.

Dataframe представляет собой набор столбцов. В python мы получаем доступ к членам коллекций с оператором []. Мы получаем атрибуты с помощью оператора .. Переключение между ними вызывает когнитивный диссонанс для всех, кто является программистом на питоне. Особенно, когда вы начинаете делать что-то вроде del data.a и замечаете, что он не работает. См. этот ответ для более подробного объяснения

Чистый код для спасения

Трудно видеть разницу между data[['a']] и data['a']

Это запах. Мы не должны этого делать.

Правильный способ использования принципов чистого кода и zen python "Явный лучше, чем неявный"

:

columns = ['a']
data[columns]

Возможно, это не так страшно, но посмотрите на следующий пример:

data[['ad', 'cpc', 'roi']]

Что это значит? Что это за колонны? Какие данные вы получаете здесь?

Это первые вопросы, которые должны появиться в любой голове при чтении этой строки кода.

Как его решить? Не комментируйте.

ad_performance_columns = ['ad', 'cpc', 'roi']
data[ad_performance_columns]

Более явный всегда лучше.

Для получения дополнительной информации, пожалуйста, подумайте о покупке книги по чистому коду. Возможно этот