Урок курса
Выборка столбцов и строк: [], loc и iloc без цепочной индексации
NumPy и pandas: практический тренажёрЧем отличаются loc и iloc
df.loc выбирает строки и столбцы по меткам индекса, а df.iloc — по их целочисленным позициям, начиная с нуля. В срезе loc конечная метка включается; в срезе iloc конечная позиция не включается. Для среза по меткам ниже индекс упорядочен.
import pandas as pd
df = pd.DataFrame({"name": ["Аня", "Боря", "Вера"], "score": [8, 5, 9]},
index=[10, 20, 30])
print(df.loc[20, "name"])
print(df.iloc[1, 0])
print(df.loc[10:20, "score"].tolist())
print(df.iloc[0:2, 1].tolist())Результат:
Боря
Боря
[8, 5]
[8, 5]df.loc[1] ищет метку 1: в этой таблице её нет, поэтому возникнет KeyError. df.iloc[1] возвращает вторую строку. Ниже разберём также выбор через [] и фильтрацию маской.
После того как в прошлом уроке DataFrame уже загружен с нужными типами, следующий шаг — уметь из него вытаскивать именно то, что нужно: конкретный столбец, набор столбцов, отдельные строки или ячейки. Начнём с самого очевидного инструмента — оператора [].
Самостоятельная практика. Закрепите разницу между метками и позициями: сравните границы срезов loc и iloc в задаче с проверкой кода. Решить задачу на срезы loc и iloc. Для последовательной практики выборки, очистки и объединения таблиц откройте бесплатный тренажёр NumPy и pandas. Пройти бесплатный тренажёр NumPy и pandas.
Выборка столбцов через [] — Series и DataFrame
Квадратные скобки после имени DataFrame работают по-разному в зависимости от того, что в них передать.
Если передать строку — одно имя столбца — получаешь Series:
import pandas as pd
df = pd.DataFrame({
'name': ['Анна', 'Борис', 'Вера', 'Григорий'],
'score': [85, 92, 78, 95],
'city': ['Москва', 'Казань', 'Москва', 'Уфа']
}, index=['a', 'b', 'c', 'd'])
scores = df['score']
print(type(scores)) # <class 'pandas.core.series.Series'>
print(scores)
# a 85
# b 92
# c 78
# d 95
Series сохраняет тот же индекс, что и DataFrame. Это одномерная структура — удобно, когда нужно работать с одним признаком: считать статистику, строить маску, передавать в функцию.
Если передать список строк — получаешь DataFrame:
subset = df[['name', 'city']]
print(type(subset)) # <class 'pandas.core.frame.DataFrame'>
print(subset)
# name city
# a Анна Москва
# b Борис Казань
# c Вера Москва
# d Григорий Уфа
Обрати внимание на двойные скобки: внешние — синтаксис индексирования, внутренние — обычный список Python. Порядок столбцов в результате определяется порядком в списке, а не порядком в исходном DataFrame. То есть df[['city', 'name']] вернёт те же данные, но сначала city, потом name.
Когда что использовать:
df['col']→ Series, когда нужен один признак для вычислений или фильтрации.df[['col1', 'col2']]→ DataFrame, когда нужно сохранить табличную структуру или передать несколько столбцов дальше по пайплайну.
Главная ловушка здесь не в синтаксисе, а в ожиданиях: если написать df['score', 'city'] без вложенного списка — pandas попытается найти кортеж ('score', 'city') как метку столбца и бросит KeyError. Ошибка немного запутывает, поэтому лучше сразу запомнить: один столбец — строка, несколько — список внутри скобок.
loc — выборка и фильтрация по меткам и булевым условиям
Оператор [] позволяет выбирать столбцы, но добраться до конкретных строк или ячеек через него удобно только в простейших случаях. Для полноценного доступа по строкам и ячейкам в pandas есть два инструмента: loc и iloc. Разберём loc — он работает с метками индекса.
Одна строка и одна ячейка
import pandas as pd
df = pd.DataFrame({
'name': ['Анна', 'Борис', 'Вера', 'Григорий'],
'score': [85, 92, 78, 95],
'city': ['Москва', 'Казань', 'Москва', 'Уфа']
}, index=['a', 'b', 'c', 'd'])
print(df.loc['b'])
# name Борис
# score 92
# city Казань
# Name: b, dtype: object
print(df.loc['b', 'score']) # 92
df.loc['b'] возвращает Series — всё, что лежит в строке с меткой 'b'. df.loc['b', 'score'] — скалярное значение конкретной ячейки. Первый аргумент — метка строки, второй — метка столбца.
Срез по меткам
print(df.loc['a':'c'])
# name score city
# a Анна 85 Москва
# b Борис 92 Казань
# c Вера 78 Москва
Важное отличие от обычного среза Python: обе границы включаются. df.loc['a':'c'] вернёт строки 'a', 'b' и 'c' — включая 'c'. Это поведение специфично для loc и часто удивляет тех, кто привык к list[0:2], где правая граница не входит в результат.
Фильтрация булевой маской
Вместо метки строки в loc можно передать булевый Series той же длины, что и индекс DataFrame:
mask = df['score'] >= 90
print(mask)
# a False
# b True
# c False
# d True
# Name: score, dtype: bool
print(df.loc[mask])
# name score city
# b Борис 92 Казань
# d Григорий 95 Уфа
df['score'] >= 90 сравнивает каждое значение и возвращает Series из True/False с теми же метками. df.loc[mask] оставляет только строки, где значение маски — True.
Комбинирование условий
Если условий несколько, каждое оборачивается в скобки, и между ними ставятся & (и) или | (или):
combined = df.loc[(df['score'] >= 90) & (df['city'] == 'Казань')]
print(combined)
# name score city
# b Борис 92 Казань
Скобки вокруг каждого условия обязательны: без них оператор & применяется раньше, чем >= или ==, и Python выбрасывает ошибку.
Маска + столбец
Вторым аргументом loc можно сразу указать столбец — тогда результат будет не DataFrame, а Series нужного столбца по отфильтрованным строкам:
print(df.loc[mask, 'name'])
# b Борис
# d Григорий
# Name: name, dtype: object
Это удобно, когда нужна не вся таблица, а одна колонка после фильтрации — например, чтобы получить список имён с высокими оценками.
В итоге loc покрывает всё, что касается доступа через метки: конкретная строка, ячейка, диапазон строк, произвольный фильтр. Следующий шаг — iloc, который делает то же самое, но через целочисленные позиции, не зависящие от меток индекса.
iloc — выборка по целочисленным позициям
iloc работает не с метками, а с порядковыми номерами строк и столбцов — теми самыми позициями, которые всегда начинаются с нуля независимо от того, что написано в индексе.
Это принципиально важно. В нашем DataFrame индекс — строки 'a', 'b', 'c', 'd'. Через loc к первой строке обращаешься как df.loc['a']. Через iloc — как df.iloc[0]. Если бы индекс был числовым, но начинался с 5, через iloc[0] всё равно получишь первую строку, а не ту, у которой метка 5.
Строка и ячейка по позиции
import pandas as pd
df = pd.DataFrame({
'name': ['Анна', 'Борис', 'Вера', 'Григорий'],
'score': [85, 92, 78, 95],
'city': ['Москва', 'Казань', 'Москва', 'Уфа']
}, index=['a', 'b', 'c', 'd'])
print(df.iloc[0])
# name Анна
# score 85
# city Москва
# Name: a, dtype: object
print(df.iloc[1, 0]) # 'Борис'
df.iloc[0] — первая строка, возвращает Series. df.iloc[1, 0] — строка с позицией 1, столбец с позицией 0 — скалярное значение. Столбцы тоже нумеруются с нуля: name → 0, score → 1, city → 2.
Срез по позициям
Здесь важное отличие от loc: правая граница среза не включается — точно как в обычном Python-срезе.
print(df.iloc[0:2])
# name score city
# a Анна 85 Москва
# b Борис 92 Казань
df.iloc[0:2] возвращает строки с позициями 0 и 1 — строка с позицией 2 (метка 'c') не попадает в результат. Для сравнения, df.loc['a':'c'] включало бы 'c'. Разная семантика границ — самая частая точка путаницы при переключении между loc и iloc.
Список позиций
print(df.iloc[[0, 3]])
# name score city
# a Анна 85 Москва
# d Григорий 95 Уфа
Список позиций позволяет выбирать строки в произвольном порядке — не обязательно подряд. Здесь берём первую и последнюю строку.
Когда нужен iloc, а не loc
iloc полезен тогда, когда метки индекса либо неизвестны, либо вообще не важны — например, при итерации с числовым шагом, при выборке первых N строк для быстрой проверки (df.iloc[:5]), или когда данные пришли из источника с нестандартным индексом. Если метки известны и осмысленны — удобнее loc.
Оба инструмента делают одно и то же с точки зрения результата, но опираются на разные системы координат: метки vs порядковые номера.
Цепочное присваивание: почему df[col][mask] = value ненадёжно и как его заменить
Умение читать данные через loc и iloc — это одно. Записывать через них — немного другое, и здесь есть конкретная ловушка, которую легко не заметить.
Предположим, нужно изменить значение city для строк с высоким баллом. Интуитивный, но ненадёжный способ:
mask = df['score'] >= 90
df['city'][mask] = 'Новосибирск' # так делать не надо
Выражение df['city'][mask] = 'Новосибирск' — это два последовательных индексирования. Сначала df['city'] возвращает объект — Series или его представление (view). Потом по этому объекту пытаемся присвоить значение через [mask]. Проблема в том, что pandas не гарантирует, вернул ли первый вызов представление оригинала или копию. Это зависит от внутренней структуры данных, версии pandas и режима работы.
Что происходит в зависимости от режима
В legacy-режиме (pandas 2.x без CoW) поведение непредсказуемо: иногда запись попадает в исходный DataFrame, иногда — только в промежуточный объект, который тут же отбрасывается. pandas предупреждает об этом через SettingWithCopyWarning, но запись формально завершается без исключения — просто данные могут не измениться.
Copy-on-Write (CoW) — режим, который в pandas 2.x включается явно (pd.options.mode.copy_on_write = True) и становится поведением по умолчанию начиная с pandas 3.0. При CoW цепочное присваивание не обновляет исходный DataFrame, а pandas диагностирует это предупреждением или ошибкой класса ChainedAssignmentError — в зависимости от версии и настроек предупреждений. В любом случае рассчитывать на то, что такая запись сработает, нельзя.
Версионно устойчивый способ — один вызов loc
Есть единственный паттерн, который работает одинаково в обоих режимах: присваивать через один вызов loc с двумя аргументами — маска строк и метка столбца.
import pandas as pd
df = pd.DataFrame({
'name': ['Анна', 'Борис', 'Вера', 'Григорий'],
'score': [85, 92, 78, 95],
'city': ['Москва', 'Казань', 'Москва', 'Уфа']
}, index=['a', 'b', 'c', 'd'])
mask = df['score'] >= 90
# Правильно: один вызов loc, строки и столбец в одном выражении
df.loc[mask, 'city'] = 'Новосибирск'
print(df['city'])
# a Москва
# b Новосибирск
# c Москва
# d Новосибирск
# Name: city, dtype: object
df.loc[mask, 'city'] = 'Новосибирск' — это одно индексирование, которое напрямую адресует нужные ячейки в исходном DataFrame. Никакого промежуточного объекта, никакой неопределённости. Результат одинаков в legacy-режиме и при CoW.
Аналогично работает iloc, когда нужно изменить конкретную ячейку по позиции:
df.iloc[0, 2] = 'Санкт-Петербург' # строка 0, столбец 2 (city)
print(df.loc['a', 'city']) # 'Санкт-Петербург'
Что именно запрещать, а что нет
Опасна именно цепочка df[col][mask] = value — условное изменение подмножества строк через два последовательных индексирования. Прямое присваивание целого столбца — df['new_col'] = ... — это другая операция: одно индексирование, которое корректно создаёт или заменяет столбец целиком. Такой способ не является цепочным и безопасен; подробнее он разбирается в следующем уроке.
Правило для условной записи простое: изменение выбранных строк или ячеек — через один вызов loc или iloc, а не через цепочку [][].
Задача 1
DataFrame df имеет столбцы 'a', 'b', 'c' и RangeIndex (0, 1, 2, 3). Что вернёт вызов df['a']?
Задача 2
DataFrame df имеет index=['a', 'b', 'c', 'd'] и столбец 'value'. Чем отличается df.iloc[1] от df.loc['b'] при обращении к одной и той же строке?
Задача 3
Дан код:
import pandas as pd
df = pd.DataFrame({'x': [1, 2, 3], 'y': [10, 20, 30]})
mask = df['x'] > 1
df['y'][mask] = 99
Почему это цепочное присваивание ненадёжно?
Задача 4
loc, iloc и безопасное присваивание: выборка и изменение DataFrame
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinцелое числоn— количество строк таблицы. - Читает следующие
nстрок; каждая содержит три токена через пробел:название(строка без пробелов),цена(целое число),количество(целое число). Пример строки:Стол 5000 3. - Строит
pd.DataFrameс именемdfиз словаря списков с ключами'name','price','qty'. Индекс — автоматическийRangeIndex. - Шаг A. Выбирает один столбец
'price'черезdf['price']. Выводит строку:col_type=<тип>, где<тип>—type(df['price']).__name__. - Шаг B. Выбирает два столбца
['name', 'qty']черезdf[['name', 'qty']]. Выводит строку:subset_type=<тип> subset_shape=<shape>— тип и shape результата. - Шаг C. Выбирает строку с меткой
1черезdf.loc[1]. Выводит строку:loc1_name=<значение поля name>, то естьdf.loc[1, 'name']. - Шаг D. Выбирает строку с позицией
0черезdf.iloc[0]. Выводит строку:iloc0_price=<значение поля price>, то естьdf.iloc[0, 1]. - Шаг E. Строит булеву маску
mask = df['price'] > threshold, гдеthresholdчитается изstdinкак последняя строка ввода (целое число). Выполняет одним вызовомdf.loc[mask, 'qty'] = 0. Выводит список значений столбца'qty'через пробел.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n name1 price1 qty1 ... nameN priceN qtyN threshold
Дополнительные ограничения: n >= 2.
Формат вывода
col_type=Series
subset_type=DataFrame subset_shape=(n, 2)
loc1_name=<name>
iloc0_price=<price>
<qty0> <qty1> ... <qtyN-1>
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 5
iloc с нестандартным индексом и выборка столбцов: разница между меткой и позицией
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinцелое числоn— количество строк таблицы. - Читает следующие
nстрок; каждая содержит три токена через пробел:город(строка без пробелов),температура(вещественное число),влажность(целое число). Пример строки:Москва 18.5 55. - Строит
pd.DataFrameс именемdfиз словаря списков с ключами'city','temp','humidity'. В качестве индекса устанавливаетpd.RangeIndex(start=10, stop=10 + n, step=1)— метки строк будут10, 11, ..., 10+n-1, а не0, 1, .... - Шаг A. Выбирает столбец
'city'черезdf['city']. Выводит строку:col_shape=<shape> col_type=<тип>, где<тип>—type(df['city']).__name__. - Шаг B. Выбирает два столбца
['temp', 'humidity']черезdf[['temp', 'humidity']]. Выводит строку:subset_shape=<shape> subset_type=<тип>, где<тип>—type(...).__name__. - Шаг C. Получает первую строку по позиции через
df.iloc[0]и выводит строку:iloc0_city=<значение поля city>. - Шаг D. Получает последнюю строку по позиции через
df.iloc[-1]и выводит строку:ilocN_humidity=<значение поля humidity>(целое число). - Шаг E. Получает ячейку по позиции строки
1и позиции столбца1черезdf.iloc[1, 1]. Выводит строку:cell=<значение>(вещественное число). - Шаг F. Пытается обратиться к строке с меткой
10черезdf.loc[10]и выводит строку:loc10_city=<значение поля city>. Затем пытается обратиться к той же первой строке черезdf.iloc[0]и выводит строку:iloc0_city_again=<значение поля city>. Убеждается, что оба значения совпадают.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n city1 temp1 humidity1 ... cityN tempN humidityN
Дополнительные ограничения: n >= 2.
Формат вывода
col_shape=(<n>,) col_type=Series
subset_shape=(<n>, 2) subset_type=DataFrame
iloc0_city=<city>
ilocN_humidity=<humidity>
cell=<temp>
loc10_city=<city>
iloc0_city_again=<city>
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 6
Найди и исправь: три ошибки в булевой фильтрации и присваивании через loc
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет.
В редакторе уже есть сломанный код. В нём ровно три ошибки в трёх независимых строках:
- Условие для
mask_bigиспользуетdf['amount'] >= 200, хотя по условию нужно отобрать строки, гдеamountстрого больше200. - Условие для
mask_paid_northкомбинирует два условия через|(или), хотя по условию нужны строки, где одновременноstatus == 'paid'иregion == 'Север'— то есть нужен&. - Присваивание
df['status'][mask_small] = 'archived'— цепочная индексация, ненадёжная в любом режиме pandas. Нужно заменить на один вызовdf.loc[mask_small, 'status'] = 'archived'.
Что нужно сделать
Найдите и исправьте все три ошибки так, чтобы программа:
- Читала из
stdinцелое числоn— количество строк таблицы. - Читала следующие
nстрок; каждая содержит четыре токена через пробел:order_id(целое),amount(вещественное),status(строка),region(строка). Пример:101 250.0 paid Север. - Строила
pd.DataFrameиз словаря списков с ключами'order_id','amount','status','region'. - Строила маску
mask_big = df['amount'] > 200(строго больше) и выводила строку:big_count=<число строк>. - Строила маску
mask_paid_north = (df['status'] == 'paid') & (df['region'] == 'Север')и выводила через пробел значенияorder_idотфильтрованных строк:paid_north_ids=<id1> <id2> .... - Строила маску
mask_small = df['amount'] <= 100и выполняла одним вызовомdf.loc[mask_small, 'status'] = 'archived'. Выводила значения столбца'status'через пробел:statuses=<s1> <s2> ....
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n order_id1 amount1 status1 region1 ... order_idN amountN statusN regionN
Формат вывода
big_count=<число>
paid_north_ids=<id1> <id2> ...
statuses=<s1> <s2> ...
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задача 7
loc-срез по меткам vs iloc-срез по позициям: одинаковые границы, разные результаты
Что уже дано
Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.
Что нужно сделать
Напишите программу, которая:
- Читает из
stdinцелое числоn— количество строк таблицы. - Читает следующие
nстрок; каждая содержит два токена через пробел:метка(строка без пробелов) изначение(целое число). Пример строки:alpha 100. - Строит
pd.DataFrameс одним столбцом'val'из прочитанных значений. В качестве индекса устанавливает список меток из входных данных (в порядке чтения). - Читает из
stdinстроку с двумя токенами через пробел:label_startиlabel_stop— метки дляloc-среза. - Читает из
stdinстроку с двумя целыми числами:pos_startиpos_stop— позиции дляiloc-среза. - Вычисляет
loc_result = df.loc[label_start:label_stop, ['val']]— срез по меткам (обе границы включаются). Выводит строку:loc_shape=<shape> loc_sum=<сумма столбца val>. - Вычисляет
iloc_result = df.iloc[pos_start:pos_stop, [0]]— срез по позициям (правая граница не включается). Выводит строку:iloc_shape=<shape> iloc_sum=<сумма столбца val>. - Выводит строку:
equal=<True/False>— совпадают ли суммы двух срезов.
Ввод и вывод
Программа читает stdin и пишет в stdout.
Формат ввода
n label1 val1 ... labelN valN label_start label_stop pos_start pos_stop
Дополнительные ограничения: n >= 1; все метки попарно различны; label_start и label_stop обязательно присутствуют среди меток входных строк.
Формат вывода
loc_shape=<shape> loc_sum=<int>
iloc_shape=<shape> iloc_sum=<int>
equal=<True/False>
Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.
Отправьте решение, чтобы увидеть результаты тестов.
Задачи к уроку
- Выборка столбцов и строк: [], loc и iloc без цепочной индексации — задача по NumPy и pandas
Практическая задача по теме «Выборка столбцов и строк: [], loc и iloc без цепочной индексации» с проверкой ответа онлайн.
- pandas iloc с нестандартным индексом: метка или позиция
Выберите строки DataFrame через iloc при нестандартных метках индекса и сравните позиции с метками. Практика pandas.
- pandas loc и iloc: разница меток, позиций и границ среза
Чем loc отличается от iloc в pandas? Пример с нестандартным индексом и включённой границей loc, затем задача с проверкой кода.
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
