Чому присвоєння [:] проти iloc [:] дає різні результати в пандах?


13

Мене так плутають різні методи індексації, які використовуються ilocв пандах.

Скажімо, я намагаюся перетворити 1-d Dataframe у 2-d Dataframe. По-перше, у мене є наступний 1-d 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-loop, щоб допомогти мені перетворити 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]. Перекриття немає, тому NaNприсвоюємо всі елементи. До цього моменту це має сенс для мене. Але, як і ви, мені незрозуміло, чому b_df.iloc[1][:] = ...поводиться по-різному - оглядаючи об'єкти b_df.iloc[1]та b_df.iloc[1][:]не виявляючи різниці між показниками. Моя найкраща здогадка - це те, що присвоєння безпосередньо копії ( [:]) Pandas трактується як особливий випадок, що змушує ігнорувати індекс одержувача і створювати цю невідповідність.
Себ

Я думаю, що це спричинено індексом і успіхом у першому ряду, оскільки він має той самий індекс
Phung Duy Phong,

1
Я важливо пам’ятати про панди - це те, що більшість всіх операцій в пандах використовують концепцію, що називається «внутрішнє вирівнювання даних». Це означає, що майже будь-яка операція, виконана з пандами, вирівняє індекси обох сторін виписки. Тут ви намагаєтеся встановити індекс 1 за допомогою індексу 0, панди призначать нан, оскільки в правій частині цього призначення немає індексу 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

Варто також згадати, що це форма прикутого завдання, що не є хорошою справою , а також робить ваш код важчим для читання та розуміння.


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). Тому ми маємо різну поведінку.


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]волі потім впливатиме [:]. Використовуючи це для встановлення значень, Pandas тут перешкоджає самому попередженню, тому не слід використовувати це:

Чи повертається копія чи посилання для операції налаштування, може залежати від контексту. Іноді це називається ланцюговим завданням і цього слід уникати


... і чому .iloc[i][:]працював у моєму прикладі вище, але ні.iloc[i,:]

Як зазначає @Scott в коментарях до ОП, вирівнювання даних є суттєвим , тому індекси в правій частині =не будуть включені, якщо вони відсутні в лівій частині. Ось чому є NaNзначення у 2-му ряду.

Отже, щоб залишити речі зрозумілими, ви можете зробити наступне:

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.