#python #pandas
Вопрос:
Рассмотрим следующий фрагмент кода:
df = pd.DataFrame(data=[(i,i 1) for i in range(0,20,2)])
perm = list(np.random.permutation(10))
perm_df = df.iloc[perm, :]
perm_df.iloc[perm, :] = perm_df.values
Цель проста-один раз переставить фрейм данных, а затем вернуть его в исходную форму.
Индексация цепочки не используется, и предупреждение не выдается.
В результате получается следующее
0 1
6 10 11
8 2 3
2 4 5
5 16 17
1 12 13
0 10 11
7 12 13
4 12 13
9 16 17
3 16 17
Я подозревал, что это может быть как-то связано с целочисленным индексом, но преобразование его в str
тип не помогло.
Затем я закончил тем, что следовал золотому правилу копирования перед назначением: perm_df.iloc[perm, :] = perm_df.copy().values
и в итоге получил (частично) ожидаемый результат:
0 1
6 0 1
8 2 3
2 4 5
5 6 7
1 8 9
0 10 11
7 12 13
4 14 15
9 16 17
3 18 19
Я понимаю, что индекс не изменится после iloc
выполнения задания, поэтому reset_index
сделаю свое дело.
Итак, вопрос в том, почему это perm_df.iloc[perm, :] = perm_df.values
приводит к такому поведению? Это определенно связано с тем фактом , что я использовал то же DataFrame
самое, но я ожидал values
просто вернуть копию содержимого.
Мое предположение заключается в том, что под капотом values
происходят изменения во время выполнения задания. Известно ли это поведение?
Комментарии:
1. Кстати, это не связано с пандами, это глупо. Попробуйте
x = np.arange(20).reshape(10, 2); y = x[perm]
то , что вы делаете (и что приводит к нежелательным результатам)y[perm] = y
, потому что массив индексов используется для редактирования самого себя, поэтому он меняется по мере выполнения назначения. Что действительно работает, так этоy[perm] = y.copy()
.
Ответ №1:
Это поведение происходит из строки 981 pandas/core/internals/blocks.py
setitem
функции:
values[indexer] = value
Та же настройка, что и раньше:
import numpy as np
import pandas as pd
np.random.seed(5)
df = pd.DataFrame(data=[(i, i 1) for i in range(0, 20, 2)])
perm = list(np.random.permutation(10))
perm_df = df.iloc[perm, :]
Обратите внимание на разницу между:
value = perm_df.values
values = perm_df.values
values[(perm, slice(None, None, None))] = value
values
:
[[ 0 1]
[10 11]
[ 4 5]
[18 19]
[ 8 9]
[10 11]
[ 8 9]
[ 8 9]
[ 8 9]
[18 19]]
И
value = perm_df.copy().values
values = perm_df.values
values[(perm, slice(None, None, None))] = value
values
:
[[ 0 1]
[ 2 3]
[ 4 5]
[ 6 7]
[ 8 9]
[10 11]
[12 13]
[14 15]
[16 17]
[18 19]]
Since arrays are mutable, by not copying the values
, only one array exists, and that single array is being updated iteratively.
Процесс назначения выглядит примерно так:
perm
:
[9, 5, 2, 4, 7, 1, 0, 8, 6, 3]
values
:
[[18 19] # 0
[10 11] # 1
[ 4 5] # 2
[ 8 9] # 3
[14 15] # 4
[ 2 3] # 5
[ 0 1] # 6
[16 17] # 7
[12 13] # 8
[ 6 7]] # 9
0 -> 9
[[18 19] # 0
[10 11] # 1
[ 4 5] # 2
[ 8 9] # 3
[14 15] # 4
[ 2 3] # 5
[ 0 1] # 6
[16 17] # 7
[12 13] # 8
[18 19]] # 9 ([ 6 7])
1 -> 5
:
[[18 19] # 0
[10 11] # 1
[ 4 5] # 2
[ 8 9] # 3
[14 15] # 4
[10 11] # 5 ([ 2 3])
[ 0 1] # 6
[16 17] # 7
[12 13] # 8
[18 19]] # 9
2 -> 2
:
[[18 19] # 0
[10 11] # 1
[ 4 5] # 2 ([ 4 5])
[ 8 9] # 3
[14 15] # 4
[10 11] # 5
[ 0 1] # 6
[16 17] # 7
[12 13] # 8
[18 19]] # 9
3 -> 4
:
[[18 19] # 0
[10 11] # 1
[ 4 5] # 2
[ 8 9] # 3
[ 8 9] # 4 ([14 15])
[10 11] # 5
[ 0 1] # 6
[16 17] # 7
[12 13] # 8
[18 19]] # 9
4 -> 7
:
[[18 19] # 0
[10 11] # 1
[ 4 5] # 2
[ 8 9] # 3
[ 8 9] # 4
[10 11] # 5
[ 0 1] # 6
[ 8 9] # 7 ([16 17])
[12 13] # 8
[18 19]] # 9
5 -> 1
:
[[18 19] # 0
[10 11] # 1
[ 4 5] # 2
[ 8 9] # 3
[ 8 9] # 4
[10 11] # 5 ([10 11])
[ 0 1] # 6
[ 8 9] # 7
[12 13] # 8
[18 19]] # 9
6 -> 0
:
[[ 0 1] # 0 ([18 19])
[10 11] # 1
[ 4 5] # 2
[ 8 9] # 3
[ 8 9] # 4
[10 11] # 5
[ 0 1] # 6
[ 8 9] # 7
[12 13] # 8
[18 19]] # 9
7 -> 8
:
[[ 0 1] # 0
[10 11] # 1
[ 4 5] # 2
[ 8 9] # 3
[ 8 9] # 4
[10 11] # 5
[ 0 1] # 6
[ 8 9] # 7
[ 8 9] # 8 ([12 13])
[18 19]] # 9
8 -> 6
:
[[ 0 1] # 0
[10 11] # 1
[ 4 5] # 2
[ 8 9] # 3
[ 8 9] # 4
[10 11] # 5
[ 8 9] # 6 ([ 0 1])
[ 8 9] # 7
[ 8 9] # 8
[18 19]] # 9
9 -> 3
:
[[ 0 1] # 0
[10 11] # 1
[ 4 5] # 2
[18 19] # 3 ([ 8 9])
[ 8 9] # 4
[10 11] # 5
[ 8 9] # 6
[ 8 9] # 7
[ 8 9] # 8
[18 19]] # 9
Когда есть два разных массива, эта проблема не возникает, так как массив не перезаписывается при его реструктуризации (именно поэтому назначение функции копирования выполняется должным образом).
Примечание. вызов copy
фрейма данных сразу после iloc
этого не решает эту проблему, поскольку проблема заключается в дублировании ссылки на один и тот же (базовый) массив, а не в ссылке на прежний фрейм данных ( df
):
perm_df = df.iloc[perm, :].copy()
perm_df.iloc[perm, :] = perm_df.values
perm_df
:
[[ 0 1]
[10 11]
[ 4 5]
[18 19]
[ 8 9]
[10 11]
[ 8 9]
[ 8 9]
[ 8 9]
[18 19]]
Комментарии:
1. Спасибо, это именно то, что я подозревал. Чтобы завершить это и то, и
iloc
другое иvalues
вернуть ссылку на базовый массив, правильно ли это? Кроме того, насколько я понимаю , эта проблема несколько специфична для использования перестановок — в случае, если мы используем какое-либо преобразование с сохранением индексаvalues
, мы в целости и сохранности.2. Да
values
возвращает ссылку на базовый массив иiloc
изменяет этот же массив. Эта проблема на самом деле относится к любомуnumpy
массиву, который изменяет себя. (именно для этого и решается этот вопрос)
Ответ №2:
В Pandas индексирование фрейма данных возвращает ссылку на исходный фрейм данных. Таким образом, изменение подмножества приведет к изменению исходного кадра данных. Таким образом, вы захотите использовать копию, если хотите убедиться, что исходный кадр данных не должен изменяться. Рассмотрим следующий код:
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
панды используют форму, известную как глубокая копия
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.copy.html
Комментарии:
1. Проблема здесь заключалась не в том, какой объект изменяется, а скорее в том, как он был изменен. Тот факт, что изменение происходит итеративно что оно относится к ранее измененным записям во время этой итерации, является причиной расхождения.