Почему назначение с [:] по сравнению с iloc [:] дает разные результаты в пандах?

13

Я так запутался с различными методами индексации, используемыми ilocв пандах.

Допустим, я пытаюсь преобразовать 1-й Dataframe в 2-й Dataframe. Сначала у меня есть следующий 1-й Dataframe

a_array = [1,2,3,4,5,6,7,8]
a_df = pd.DataFrame(a_array).T

И я собираюсь преобразовать это в 2-й Dataframe с размером 2x4. Я начну с установки 2-го кадра данных следующим образом:

b_df = pd.DataFrame(columns=range(4),index=range(2))

Затем я использую цикл for, чтобы помочь мне преобразовать a_df(1-d) в b_df(2-d) с помощью следующего кода

for i in range(2):
    b_df.iloc[i,:] = a_df.iloc[0,i*4:(i+1)*4]

Это только дает мне следующие результаты

     0    1    2    3
0    1    2    3    4
1  NaN  NaN  NaN  NaN

Но когда я изменился b_df.iloc[i,:]на b_df.iloc[i][:]. Результат правильный, как следующий, что я хочу

   0  1  2  3
0  1  2  3  4
1  5  6  7  8

Может ли кто-нибудь объяснить мне, в чем разница между .iloc[i,:]и .iloc[i][:], и почему .iloc[i][:]работал в моем примере выше, но не.iloc[i,:]

Томми Йип
источник
Это любопытно. b_df.iloc[1] = a_df.iloc[0, 4:8]назначает серию с индексом [4, 5, 6, 7]серии с индексом [0, 1, 2, 3]. Нет перекрытия, поэтому NaNs присваивается всем элементам. До этого момента это имеет смысл для меня. Но, как и вам, мне непонятно, почему b_df.iloc[1][:] = ...ведет себя по-разному - проверяет объекты b_df.iloc[1]и не b_df.iloc[1][:]обнаруживает разницы между показателями. Моим лучшим предположением будет то, что назначение непосредственно copy ( [:]) рассматривается Pandas как особый случай, который заставляет его игнорировать индекс правопреемника и создавать это несоответствие.
Себ
Я думаю, что это связано с индексом и успехом первой строки, потому что он имеет тот же индекс
Phung Duy Phong
1
Ключевым моментом, который следует помнить о пандах, является то, что в большинстве случаев в пандах используется концепция, называемая «внутренним выравниванием данных». Это означает, что почти любая операция, которую вы выполняете с пандами, выровняет индексы обеих сторон оператора. Здесь вы пытаетесь установить индекс 1, используя индекс 0, pandas назначит nans, потому что в правой части этого назначения нет индекса 0. Также помните, что заголовки столбцов тоже являются индексом. Таким образом, панды выровняют заголовок столбца с заголовком столбца.
Скотт Бостон
3
Во-вторых, использование .iloc [i] [:] называется цепочкой индексов, и это, как правило, довольно большое «нет-нет» в пандах. Есть некоторые изы с пандами, создающими представления объекта или создающие совершенно новый объект в памяти, который может привести к неожиданным результатам.
Скотт Бостон
Пожалуйста, не забудьте высказать все рабочие ответы и принять тот, который вам нравится больше всего. Вероятно, вы это знаете, но это должно сообщить сообществу, какие ответы были полезны, и вознаградить людей за их время и усилия;) См. Этот meta.stackexchange.com/questions/5234/ и meta.stackexchange.com/ вопросы / 173399 /
alan.elkin

Ответы:

3

Существует очень, очень большая разница между series.iloc[:]и series[:]при назначении назад. (i)locвсегда проверяет, соответствует ли то, что вы назначаете, индексу уполномоченного. Между тем [:]синтаксис присваивается базовому массиву NumPy, минуя выравнивание индекса.

s = pd.Series(index=[0, 1, 2, 3], dtype='float')  
s                                                                          

0   NaN
1   NaN
2   NaN
3   NaN
dtype: float64

# Let's get a reference to the underlying array with `copy=False`
arr = s.to_numpy(copy=False) 
arr 
# array([nan, nan, nan, nan])

# Reassign using slicing syntax
s[:] = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])                 
s                                                                          

0    1
1    2
2    3
3    4
dtype: int64

arr 
# array([1., 2., 3., 4.]) # underlying array has changed

# Now, reassign again with `iloc`
s.iloc[:] = pd.Series([5, 6, 7, 8], index=[3, 4, 5, 6]) 
s                                                                          

0    NaN
1    NaN
2    NaN
3    5.0
dtype: float64

arr 
# array([1., 2., 3., 4.])  # `iloc` created a new array for the series
                           # during reassignment leaving this unchanged

s.to_numpy(copy=False)     # the new underlying array, for reference                                                   
# array([nan, nan, nan,  5.]) 

Теперь, когда вы понимаете разницу, давайте посмотрим, что происходит в вашем коде. Просто распечатайте RHS ваших петель, чтобы увидеть, что вы назначаете:

for i in range(2): 
    print(a_df.iloc[0, i*4:(i+1)*4]) 

# output - first row                                                                   
0    1
1    2
2    3
3    4
Name: 0, dtype: int64
# second row. Notice the index is different
4    5
5    6
6    7
7    8
Name: 0, dtype: int64   

При назначении b_df.iloc[i, :]на второй итерации индексы отличаются, поэтому ничего не назначается, и вы видите только NaN. Однако, изменение b_df.iloc[i, :]на b_df.iloc[i][:]означает, что вы присваиваете базовый массив NumPy, поэтому выравнивание индексации обходится. Эта операция лучше выражается как

for i in range(2):
    b_df.iloc[i, :] = a_df.iloc[0, i*4:(i+1)*4].to_numpy()

b_df                                                                       

   0  1  2  3
0  1  2  3  4
1  5  6  7  8

Стоит также упомянуть, что это форма цепного присваивания, что нехорошо , а также затрудняет чтение и понимание вашего кода.

cs95
источник
1
Теперь я это понимаю, спасибо. Прежде чем присудить награду, не могли бы вы добавить ссылку на это: « [:]синтаксис присваивается базовому массиву NumPy»?
Себ
@Seb На самом деле вы не найдете ссылок на это в документации, потому что это что-то вроде реализации. Может быть проще найти код на GitHub, который отвечает за это, но я думаю, что самый простой способ - просто продемонстрировать, что происходит. Я отредактировал небольшой пример в верхней части моего ответа, чтобы показать, как обрабатывается базовый массив во время различных видов переназначения. Надеюсь, это прояснит ситуацию!
cs95
Спасибо огромное! Теперь все намного понятнее.
Томми Ип
0

Разница в том, что в первом случае интерпретатор Python выполнял код следующим образом:

b_df.iloc[i,:] = a_df.iloc[0,i*4:(i+1)*4]
#as
b_df.iloc.__setitem__((i, slice(None)), value)

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

b_df.iloc[i][:] = a_df.iloc[0,i*4:(i+1)*4]
#as
b_df.iloc.__getitem__(i).__setitem__(slice(None), value)

где снова значение будет в правой части уравнения.

В каждом из этих двух случаев в setitem будет вызываться другой метод из-за разницы в ключах (i, slice (None)) и slice (None). Следовательно, мы ведем себя по-разному.

MAPY
источник
b_df.iloc[i]и b_df.iloc[i][:]имеют те же показатели, хотя. Почему вы можете назначить серию с несовпадающим индексом одному, а другому - нет?
Себ
в первом случае вызов _set_item будет во втором вызове one_setitem_slice. Итак, подозреваю, что из-за разницы в этих методах у нас вышеупомянутое поведение
MaPy
0

Может кто - нибудь объяснить мне , в чем разница между .iloc[i,:]и .iloc[i][:]является

Разница между .iloc[i,:]и.iloc[i][:]

В случае, если .iloc[i,:]вы обращаетесь непосредственно к конкретной возможности DataFrame, выбрав все ( :) столбцы в iстроке. Насколько я знаю, это эквивалентно оставить 2-е измерение неуказанным ( .iloc[i]).

В случае .iloc[i][:]вы выполняете 2 цепных операций. Таким образом, результат .iloc[i]будет зависеть от [:]. Использование этого для установки значений не приветствуется самой Пандой здесь с предупреждением, поэтому вы не должны использовать его:

Будет ли копия или ссылка возвращена для операции установки, может зависеть от контекста. Это иногда называется цепным присваиванием и его следует избегать


... и почему .iloc[i][:]работал в моем примере выше, но не.iloc[i,:]

Как упомянул @Scott в комментариях OP, выравнивание данных является внутренним , поэтому индексы в правой части =не будут включены, если их нет в левой части. Вот почему NaNво втором ряду есть значения.

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

for i in range(2):
    # Get the slice
    a_slice = a_df.iloc[0, i*4:(i+1)*4]
    # Reset the indices
    a_slice.reset_index(drop=True, inplace=True)
    # Set the slice into b_df
    b_df.iloc[i,:] = a_slice

Или вы можете конвертировать listвместо использования reset_index:

for i in range(2):
    # Get the slice
    a_slice = a_df.iloc[0, i*4:(i+1)*4]
    # Convert the slice into a list and set it into b_df
    b_df.iloc[i,:] = list(a_slice)
alan.elkin
источник