почему я должен сделать копию фрейма данных в пандах

190

При выборе подкадра данных из родительского, я заметил, что некоторые программисты делают копию фрейма данных, используя .copy()метод. Например,

X = my_dataframe[features_list].copy()

... а не просто

X = my_dataframe[features_list]

Почему они делают копию фрейма данных? Что будет, если я не сделаю копию?

Элизабет Сьюзан Джозеф
источник
6
Я предполагаю, что они принимают дополнительные меры предосторожности, чтобы не изменять исходный фрейм данных. Возможно, в этом нет необходимости, но когда вы собираете что-то вместе в интерактивном режиме, это лучше, чем потом сожалеть.
Пол Х
8
Я предполагаю, что это не глупый вопрос для отрицательного ответа.
Элизабет Сьюзан Джозеф

Ответы:

208

Это расширяет ответ Павла. В Pandas индексирование DataFrame возвращает ссылку на начальный DataFrame. Таким образом, изменение подмножества изменит начальный DataFrame. Таким образом, вы захотите использовать копию, если хотите убедиться, что исходный DataFrame не должен изменяться. Рассмотрим следующий код:

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

Ты получишь:

x
0 -1
1  2

Напротив, следующее оставляет df без изменений:

df_sub_copy = df[0:1].copy()
df_sub_copy.x = -1
cgold
источник
6
это глубокая копия?
Бикашг
6
Да. Режим по умолчанию - «глубокая» копия! pandas.pydata.org/pandas-docs/stable/reference/api/…
Ambareesh
45

Потому что, если вы не сделаете копию, то индексами все равно можно будет манипулировать в другом месте, даже если вы присваиваете dataFrame другое имя.

Например:

df2 = df
func1(df2)
func2(df)

func1 может изменить df, изменив df2, чтобы избежать этого:

df2 = df.copy()
func1(df2)
func2(df)
воробей
источник
Подожди, подожди, ты можешь объяснить, ПОЧЕМУ это происходит? Не имеет смысла
NoName
2
это потому, что в первом примере `df2 = df , both variables reference the same DataFrame instance. So any changes made to df` или df2будет создан для того же экземпляра объекта. Принимая во внимание, что df2 = df.copy()во втором экземпляре объекта создается копия первого, но теперь dfи df2ссылки на разные экземпляры объекта, и любые изменения будут внесены в их соответствующий экземпляр DataFrame.
Педро
17

Необходимо отметить, что возвращаемая копия или представление зависит от вида индексации.

Документация панд гласит:

Возврат вида против копии

Правила о том, когда возвращается представление данных, полностью зависят от NumPy. Всякий раз, когда в операцию индексации включается массив меток или логический вектор, результатом будет копия. При индексировании и разрезании по одной метке / скаляру, например, df.ix [3: 6] или df.ix [:, 'A'], будет возвращено представление.

Гусев Слава
источник
12

Основная цель состоит в том, чтобы избежать цепной индексации и устранить SettingWithCopyWarning.

Здесь цепная индексация что-то вроде dfc['A'][0] = 111

В документе говорится, что при возвращении представления вместо копии следует избегать цепной индексации . Вот немного измененный пример из этого документа:

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

Здесь aColumnэто представление, а не копия исходного DataFrame, поэтому изменение aColumnтакже приведет к изменению оригинала dfc. Далее, если мы сначала проиндексируем строку:

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

На этот раз zero_rowэто копия, поэтому оригиналdfc не изменяется.

Из этих двух примеров выше мы видим неоднозначно, хотите ли вы изменить исходный DataFrame. Это особенно опасно, если вы напишите что-то вроде следующего:

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

На этот раз это не сработало. Здесь мы хотели изменить dfc, но мы фактически изменили промежуточное значение, dfc.loc[0]которое является копией и немедленно отбрасывается. Это очень трудно предсказать , будет ли промежуточное значение , как dfc.loc[0]и dfc['A']вид или копию, так что это не гарантировано , будет ли или не будет обновляться оригинал DataFrame. Вот почему следует избегать цепной индексации, и pandas генерирует SettingWithCopyWarningдля этого вида обновления цепочечной индексации.

Теперь это использование .copy(). Чтобы устранить предупреждение, сделайте копию, чтобы явно выразить свое намерение:

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

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

Поскольку вы dfcизменяете копию, вы знаете, что оригинал никогда не изменится, и вы не ожидаете, что она изменится. Ваше ожидание соответствует поведению, а затем SettingWithCopyWarningисчезает.

Примечание. Если вы хотите изменить исходный DataFrame, в документе предлагается использовать 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
источник
2

В целом, работать с копиями безопаснее, чем с исходными фреймами данных, за исключением случаев, когда вы знаете, что вам больше не понадобится оригинал, и хотите перейти к манипулированной версии. Обычно вам все равно придется использовать исходный фрейм данных для сравнения с манипулированной версией и т. Д. Поэтому большинство людей работают с копиями и объединяются в конце.

bojax
источник
0

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

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

Когда вы хотели бы создать другой, df2который идентичен df1, безcopy

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

И хотел бы изменить значение df2 только как показано ниже

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

В то же время 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

Поскольку два df одинаковы object, мы можем проверить это, используяid

id(df1)
140367679979600
id(df2)
140367679979600

Таким образом, они как один и тот же объект, и один изменяет другой, передают одинаковое значение.


Если мы добавим copy, а теперь df1и будем df2считать разными object, если мы сделаем одно и то же изменение, то одно из них не изменится.

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

Стоит упомянуть, что, когда вы задаете исходный фрейм данных, можно также добавить копию, чтобы избежать SettingWithCopyWarning

YOBEN_S
источник