loc против iloc: метки и позиции
Два индексатора pandas работают в разных системах координат, и путаница между ними — источник большинства ошибок отбора.
loc отбирает по меткам индекса, iloc — по целочисленным позициям. Это две независимые системы координат.
Зачем вообще два индексатора
У DataFrame есть метки (имена столбцов и метки строк) и есть позиции (0, 1, 2…). Иногда вы знаете метку («строка с id=5», «столбец Цена»), иногда — позицию («первые три строки», «последний столбец»). Эти задачи решают разные инструменты, и явное разделение убирает двусмысленность, которая преследует обычные квадратные скобки.
Базовая форма у обоих одинаковая: df.loc[строки, столбцы] и df.iloc[строки, столбцы]. Разница — что вы кладёте внутрь: метки или позиции.
import pandas as pd
df = pd.DataFrame(
{"max_speed": [1, 4, 7], "shield": [2, 5, 8]},
index=["cobra", "viper", "sidewinder"],
)
# max_speed shield
# cobra 1 2
# viper 4 5
# sidewinder 7 8
df.loc["viper"] # строка по метке → Series (4, 5)
df.iloc[1] # строка по позиции 1 → та же Series
df.loc["cobra", "shield"] # одно значение по меткам → 2
df.iloc[0, 1] # то же по позициям → 2
Главная ловушка: срезы по-разному включают конец
Это различие надо знать наизусть. Срез loc включает конец, срез iloc — нет. Причина логична: позиционный срез копирует поведение Python (list[0:2] — два элемента, конец не входит), а срез по меткам не знал бы, какой элемент «следующий за концом», поэтому конец включают.
df.loc["cobra":"viper"] # cobra И viper — обе строки (конец включён)
df.iloc[0:2] # позиции 0 и 1 — viper, конец НЕ включён
Оба примера выше возвращают одни и те же две строки, но loc до метки viper включительно, а iloc до позиции 2 не включая. Перепутать тут — значит молча захватить или потерять строку.
Почему так? Логика на самом деле стройная. Позиционный срез копирует поведение списков Python, где list[0:2] по соглашению означает «два элемента, начиная с нулевого» — это удобно для арифметики длин (len = stop - start). А вот для меток правило «не включая конец» было бы бессмысленным: что значит «до метки viper, не включая её», если pandas не знает, какая метка идёт «перед» viper в произвольном индексе из строк? Поэтому срез по меткам берёт обе границы — вы явно называете и начало, и конец диапазона. Запомнить помогает фраза: «метки — это имена, ты называешь обе границы; позиции — это длины, конец не входит».
Выбор столбцов
Для столбцов работают те же правила. Один столбец удобнее доставать обычными скобками, но loc/iloc дают полный контроль над строками и столбцами одновременно:
df["shield"] # один столбец → Series
df[["max_speed", "shield"]] # список столбцов → DataFrame
df.loc[:, "max_speed":"shield"] # срез столбцов по меткам (включительно)
df.iloc[:, 0] # первый столбец по позиции
df.loc[df["shield"] > 4, "max_speed"] # маска по строкам + выбор столбца
Двоеточие : означает «все по этой оси». df.loc[:, "a"] — все строки, столбец «a». Последняя строка — самый частый реальный паттерн: «возьми столбец max_speed только для строк, где shield > 4».
Булевы маски внутри loc
Булева маска — это Series из True/False той же длины, что и строки. loc оставляет строки, где True. Маски строятся сравнениями и комбинируются операторами & (и), | (или), ~ (не) — обязательно в скобках, потому что у этих операторов высокий приоритет.
Реализуем фильтрацию по маске на чистом Python, чтобы увидеть механику «оставить строки, где условие истинно»:
rows = [
{"город": "Москва", "население": 13_000_000},
{"город": "Казань", "население": 1_300_000},
{"город": "Сочи", "население": 470_000},
{"город": "Тверь", "население": 420_000},
]
# 1) строим булеву маску: True там, где население > 1 млн
mask = [r["население"] > 1_000_000 for r in rows]
print("маска:", mask)
# 2) оставляем строки, где маска True (это и делает loc)
selected = [r for r, keep in zip(rows, mask) if keep]
for r in selected:
print(r["город"], r["население"])
Вывод:
маска: [True, True, False, False] Москва 13000000 Казань 1300000
В pandas это пишется как df.loc[df["население"] > 1_000_000] — маска строится векторно, а loc применяет её ко всем столбцам сразу.
Маска — это не просто True/False, это выровненная по индексу Series. Поэтому когда вы пишете df.loc[df["a"] > 0], pandas сопоставляет маску с DataFrame по индексу, а не по позиции. Обычно это незаметно, но если маску построили из другого объекта с иным индексом, результат может удивить — pandas выровняет по меткам, и часть строк выпадет в NaN/выпадет вовсе. Отсюда практическое правило: стройте маску из того же DataFrame, к которому её применяете. Ещё один частый приём — callable внутри loc: df.loc[lambda d: d["a"] > 0]. Это особенно удобно в цепочках методов, где промежуточный DataFrame не имеет имени, но к нему нужно применить условие.
Когда использовать loc, а когда iloc
| Задача | Инструмент |
| Строка/столбец по имени или id | loc |
| Фильтр по условию (маска) | loc |
| Первые/последние N строк, «каждая вторая» | iloc |
| Доступ по позиции, когда метки не важны | iloc |
| Присваивание в подмножество строк/столбцов | loc (безопасно) |
Подводные камни
- Срезы ведут себя по-разному.
loc["a":"c"]включает «c»,iloc[0:3]— нет. Самая частая ошибка новичков. - Приоритет операторов в масках.
df["a"] > 1 & df["b"] < 2без скобок интерпретируется неправильно. Всегда:(df["a"] > 1) & (df["b"] < 2). - and/or не работают с масками. Питоновские
and/orждут одного булева значения, а маска — целый массив. Нужны поэлементные&/|. - iloc и булева Series.
ilocпринимает булев массив, но не выровненную булеву Series; для маски-Series используйтеloc.
Лучшие практики
- По умолчанию используйте
loc— он явный и совпадает с тем, как вы думаете о данных («строка с id», «столбец Цена»). - Берите
ilocтолько когда действительно важна позиция, а не смысл метки. - Для присваивания в подмножество всегда применяйте одну операцию
df.loc[маска, столбец] = значение— это и корректно, и безопасно (см. урок про Copy-on-Write).
Итог
loc— метки,iloc— позиции; это разные системы координат.- Срез
locвключает конец, срезiloc— нет. - Маски комбинируются через
& | ~в скобках, не черезand/or. - Форма
df.loc[строки, столбцы]даёт полный контроль над выборкой и присваиванием.