В чём разница между loc и iloc в Pandas

Проверь себя · 1/3разбор после ответа
Какое значение будет у msg после выполнения name = "Анна" и msg = f"Привет, {name}!"?

Зачем нужны loc и iloc

Когда вы открываете DataFrame и хотите вытащить конкретные строки и столбцы, Pandas предлагает два почти одинаковых на вид способа — loc и iloc. Именно из-за этой похожести путаница между ними становится одним из самых частых багов в коде аналитика и одним из самых популярных вопросов на собеседовании по Pandas.

Разница умещается в одно слово. loc адресует данные по меткам — по именам строк (значениям индекса) и по именам столбцов. iloc адресует по позициям — по целочисленным номерам начиная с нуля, ровно как индексация в обычном Python-списке. Буква i в iloc — от integer, целое число.

Звучит просто, но дьявол в деталях. Срезы у них ведут себя по-разному, булеву маску по-человечески принимает только loc, а при числовом индексе один и тот же df.loc[5] и df.iloc[5] вернут совершенно разные строки. Разберём всё по порядку — с кодом, который можно скопировать и проверить.

loc — индексация по меткам

df.loc[строки, столбцы] использует метки индекса и имена столбцов. Если индекс — числа, loc всё равно работает по значениям индекса, а не по порядковым позициям. Через запятую идут две оси: сначала строки, потом столбцы.

import pandas as pd

df = pd.DataFrame({
    'name': ['Анна', 'Борис', 'Вика', 'Глеб'],
    'city': ['Москва', 'Питер', 'Казань', 'Москва'],
    'salary': [80000, 120000, 95000, 110000]
}, index=['a', 'b', 'c', 'd'])

# Одна строка по метке
df.loc['b']
# name      Борис
# city      Питер
# salary    120000

# Несколько строк + конкретные столбцы
df.loc[['a', 'c'], ['name', 'salary']]
#    name   salary
# a  Анна    80000
# c  Вика    95000

# Срез по меткам — обе границы включены
df.loc['a':'c', 'name':'city']
#     name    city
# a   Анна    Москва
# b   Борис   Питер
# c   Вика    Казань

Обратите внимание на последний пример: срез 'a':'c' вернул строки a, b и c — правая метка включена. Это поведение отличается от стандартного Python, и о нём подробно ниже. Аналогично 'name':'city' по столбцам захватил оба крайних столбца.

iloc — индексация по позициям

df.iloc[строки, столбцы] работает как обычная индексация в Python: по целочисленным позициям с нуля, с исключением правой границы в срезах. Имена строк и столбцов здесь не имеют значения — только их порядковые номера.

# Первая строка
df.iloc[0]
# name      Анна
# city      Москва
# salary    80000

# Строки на позициях 0, 1, 2 и столбцы 0, 2
df.iloc[0:3, [0, 2]]
#    name   salary
# a  Анна    80000
# b  Борис   120000
# c  Вика    95000

# Последняя строка
df.iloc[-1]
# name      Глеб
# city      Москва
# salary    110000

# Топ-3 по зарплате: сортируем и берём первые три позиции
top3 = df.sort_values('salary', ascending=False).iloc[:3]

Срез 0:3 вернул строки на позициях 0, 1 и 2 — позиция 3 исключена, как в обычном Python. Отрицательные позиции тоже работают: iloc[-1] — последняя строка, iloc[-3:] — последние три.

Срезы: ключевое отличие

Это самый важный пункт и любимая ловушка интервьюеров. Срезы в loc включают конечную метку, а в iloc — исключают конечную позицию, ровно как обычный Python-срез.

# loc: правая метка ВКЛЮЧЕНА
df.loc['a':'c']      # строки a, b, c — три строки

# iloc: правая позиция ИСКЛЮЧЕНА
df.iloc[0:3]         # позиции 0, 1, 2 — три строки
df.iloc[0:2]         # позиции 0, 1 — две строки

Почему так. loc работает с метками, а метками могут быть строки, даты или произвольные значения — для них понятие «следующий элемент после конечной метки» в общем случае не определено, поэтому Pandas включает обе границы. iloc работает с целыми позициями, где правило «правая граница не входит» давно зафиксировано в самом языке: и range(0, 3), и lst[0:3] дают три элемента. Запомнить можно так: iloc ведёт себя как range(), а loc — нет.

Булева фильтрация

loc принимает булеву маску — это основной, читаемый способ фильтрации строк по условию. Маска — это Series из True/False той же длины, что и индекс.

# Все из Москвы
df.loc[df['city'] == 'Москва']
#    name   city    salary
# a  Анна   Москва  80000
# d  Глеб   Москва  110000

# Зарплата > 100k, только имя и зарплата
df.loc[df['salary'] > 100000, ['name', 'salary']]
#     name    salary
# b   Борис   120000
# d   Глеб    110000

Главное преимущество loc здесь — можно в одном выражении и отфильтровать строки маской, и выбрать нужные столбцы по именам. iloc булеву маску из Series напрямую не принимает (нужно передавать массив или .values), а столбцы пришлось бы указывать числами — менее читаемо и хрупко при изменении порядка колонок. Поэтому для фильтрации по условию практически всегда берут loc.

Закрепи Python для аналитика
200+ задач по pandas, numpy и работе с данными — с разборами
Тренировать Python в Telegram

Присваивание через loc и iloc

И loc, и iloc умеют не только читать, но и записывать данные. Распространённое заблуждение, что loc меняет DataFrame, а iloc только читает (или наоборот) — это миф. Оба аксессора работают в обе стороны: слева от = они присваивают, справа — возвращают значения.

# Присвоить по условию — самый частый случай
df.loc[df['city'] == 'Питер', 'salary'] = 130000

# Изменить одну ячейку по позиции
df.iloc[0, 2] = 85000          # salary первой строки

# Пересчитать целый столбец
df.loc[:, 'salary'] = df['salary'] * 1.1

Реальная причина, по которой для условного присваивания всегда берут loc, — не в том, что iloc «не умеет писать», а в chained indexing. Если выбрать строки и столбец двумя отдельными квадратными скобками подряд, Pandas может вернуть копию, и запись уйдёт в никуда — с предупреждением SettingWithCopyWarning:

# Плохо: chained indexing, запись может не примениться
df[df['city'] == 'Питер']['salary'] = 130000

# Хорошо: один вызов loc, запись гарантирована
df.loc[df['city'] == 'Питер', 'salary'] = 130000

Правило простое: любое присваивание по условию делайте одним обращением через loc (или через iloc, если адресуете по позициям). Тогда Pandas пишет в исходный DataFrame, а не в его временную копию.

at и iat — доступ к одной ячейке

Для скалярного доступа к одной ячейке есть at (по метке) и iat (по позиции). Это укороченные версии loc и iloc: они умеют работать только с одной строкой и одним столбцом, зато на этом быстрее.

# По метке
df.at['b', 'salary']   # 120000

# По позиции
df.iat[1, 2]           # 120000

# Запись тоже работает
df.at['b', 'salary'] = 125000

На большом DataFrame разница в скорости заметна — at/iat в несколько раз быстрее loc/iloc для скалярного доступа, потому что не строят промежуточный объект. Минус — они отдают и принимают ровно один скаляр: срез, список меток или булеву маску туда передать нельзя. В обычном анализе данных loc/iloc удобнее и универсальнее, а at/iat берут в горячих циклах и при точечном обновлении ячеек.

Частые ошибки

Путаница при числовом индексе. Когда индекс DataFrame — целые числа, loc и iloc ведут себя по-разному, и это самый частый источник багов. loc ищет строку по значению метки, iloc — по порядковому номеру.

df2 = pd.DataFrame(
    {'val': [10, 20, 30]},
    index=[5, 10, 15]
)

df2.loc[5]    # строка с меткой 5 → val: 10
df2.iloc[5]   # IndexError — в DataFrame всего 3 строки
df2.iloc[0]   # первая строка по позиции → val: 10

Если индекс числовой — всегда явно используйте loc или iloc и не полагайтесь на df[5], иначе легко получить не ту строку или исключение.

Off-by-one в срезах. Помните, что df.loc['a':'c'] отдаёт три строки (включая c), а df.iloc[0:3] — тоже три, но за счёт исключения позиции 3. Перепутать границы — значит молча потерять или добавить строку. Лучший способ перестать путать loc и iloc — прогнать десяток задач на индексацию в Python-тренажёре и поймать ошибку руками, а не на собеседовании.

Chained indexing при записи. Запись вида df[mask]['col'] = ... может не примениться и выдаёт SettingWithCopyWarning. Всегда присваивайте одним выражением: df.loc[mask, 'col'] = ....

Передача булевой маски в iloc. iloc не принимает Series-маску напрямую. Для фильтрации по условию используйте loc, а если нужен именно iloc — преобразуйте маску в массив через .values.

Связанные темы

FAQ

В чём главная разница между loc и iloc?

loc выбирает данные по меткам индекса и именам столбцов, iloc — по целочисленным позициям с нуля. В срезах loc включает конечную метку, а iloc исключает конечную позицию — как стандартный Python-срез.

Почему loc включает правую границу среза, а iloc — нет?

loc работает с метками, которые могут быть строками, датами или любыми значениями, — для них «следующий элемент после границы» не определён, поэтому включаются обе границы. iloc работает с целыми позициями по тем же правилам, что range() и срезы списков, где правая граница не входит.

Можно ли использовать отрицательные индексы в iloc?

Да. iloc[-1] — последняя строка, iloc[-3:] — последние три строки. В loc отрицательные числа сработают только если такие метки реально есть в индексе, иначе будет ошибка.

Когда брать at и iat вместо loc и iloc?

Когда точно нужно одно значение в одной ячейке — например, в цикле или при точечном обновлении. at (по метке) и iat (по позиции) быстрее, но умеют работать только со скаляром. Для срезов, списков и масок нужны loc/iloc.

Где взять больше задач на индексацию в Pandas?

Подборка реальных вопросов с собеседований аналитика — в каталоге примеров вопросов. Там есть задачи на индексацию, срезы и фильтрацию DataFrame с разбором.