Проблемы с присвоением значений ячейке фрейма данных в pandas

#python #pandas #indexing

#python #pandas #индексирование

Вопрос:

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

Случай 1:

 import pandas as pd


ind_1 = ['a','a','b','c','c']
df_1 = pd.DataFrame(index=ind_1,columns=['col1','col2'])

df_1.col1.loc['a'].iloc[0] = 1
df_1.col1.loc['b'] = 2
df_1.col1.loc['c'].iloc[0] = 3

print('Original df_1')
print(df_1)

# Original df_1
#   col1 col2
# a    1  NaN
# a  NaN  NaN
# b    2  NaN
# c    3  NaN
# c  NaN  NaN
  

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

 ind_1_sorted = sorted(ind_1,reverse=True)
df_1_sorted = pd.DataFrame(index=ind_1_sorted,columns=['col1','col2'])

df_1_sorted.col1.loc['a'].iloc[0] = 1
df_1_sorted.col1.loc['b'] = 2
df_1_sorted.col1.loc['c'].iloc[0] = 3

print('Sorted df_1')
print(df_1_sorted)

# Sorted df_1
#  col1 col2
# c  NaN  NaN
# c  NaN  NaN
# b    2  NaN
# a  NaN  NaN
# a  NaN  NaN
  

Теперь вы можете видеть, что присвоение работает только для неповторяющегося индекса. Я думал, что проблема должна быть связана с сортировкой, но давайте посмотрим на следующий случай.

Случай 2:

 ind_2 = ['c','c','b','a','a']
df_2 = pd.DataFrame(index=ind_2,columns=['col1','col2'])

df_2.col1.loc['a'].iloc[0] = 1
df_2.col1.loc['b'] = 2
df_2.col1.loc['c'].iloc[0] = 3

print('Original df_2')
print(df_2)

# Original df_2
#  col1 col2
# c  NaN  NaN
# c  NaN  NaN
# b    2  NaN
# a  NaN  NaN
# a  NaN  NaN
  

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

 ind_2_sorted = sorted(ind_2,reverse=False)
df_2_sorted = pd.DataFrame(index=ind_2_sorted,columns=['col1','col2'])

df_2_sorted.col1.loc['a'].iloc[0] = 1
df_2_sorted.col1.loc['b'] = 2
df_2_sorted.col1.loc['c'].iloc[0] = 3

print('Sorted df_2')
print(df_2_sorted)

# Sorted df_2
#   col1 col2
# a    1  NaN
# a  NaN  NaN
# b    2  NaN
# c    3  NaN
# c  NaN  NaN
  

И теперь присвоение работает после сортировки!! Единственное различие, которое я вижу, заключается в том, что назначение работает, когда индекс отсортирован «стандартным способом» (в данном случае в алфавитном порядке). Имеет ли это какой-либо смысл?

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

Спасибо!

Комментарии:

1. Я думаю, это связано с проблематикой прикованного присваивания, и вы просто не получаете SettingWithCopyWarning . Проверьте pandas-documentation . В двух словах: вы не должны устанавливать элементы, как вы делаете 😉

2. Я согласен с @Quickbeam2k1. Документация Pandas объясняет, что присвоение может завершиться ошибкой при цепной индексации без ошибок или предупреждений. Это происходит потому, что вы можете присвоить значение копии исходных данных, что абсолютно законно, но не то, что вы хотите (точно так же, как изменяемые и неизменяемые типы данных). Я думаю, это не ошибка, а функция Python. Поэтому будьте осторожны с цепной индексацией в pandas. Также может быть хорошей привычкой всегда использовать .loc/.at синтаксис для индексирования.

3. @Quickbeam2k1 Очень точное определение проблемы здесь, спасибо.

Ответ №1:

Как упоминал пользователь Quickbeam2k1, проблема связана с назначением цепочки.

У объектов индекса есть метод с именем get_loc , который можно использовать для преобразования меток в позиции, однако его возвращаемый тип полиморфный, поэтому я предпочитаю его не использовать.

Используя np.nonzero и фильтрацию по индексу и столбцу фрейма данных, мы можем преобразовать метки в позиционные ссылки и изменить фрейм данных, используя iloc вместо loc

т.Е. ваш первый пример кода может быть переписан как:

 # original
df_1.col1.loc['a'].iloc[0] = 1
df_1.col1.loc['b'] = 2
df_1.col1.loc['c'].iloc[0] = 3

# works for all indices
col1_mask = df_1.columns == 'col1'
a_mask, = np.nonzero(df_1.index == 'a')
b_mask, = np.nonzero(df_1.index == 'b')
c_mask, = np.nonzero(df_1.index == 'c')
df_1.iloc[a_mask[0], col1_mask] = 1
df_1.iloc[b_mask, col1_mask] = 1
df_1.iloc[c_mask[0], col1_mask] = 3
  

Аналогично для других примеров