Урок курса

Выборка столбцов и строк: [], 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. Никаких заранее созданных переменных нет — вы читаете всё сами.

Что нужно сделать

Напишите программу, которая:

  1. Читает из stdin целое число n — количество строк таблицы.
  2. Читает следующие n строк; каждая содержит три токена через пробел: название (строка без пробелов), цена (целое число), количество (целое число). Пример строки: Стол 5000 3.
  3. Строит pd.DataFrame с именем df из словаря списков с ключами 'name', 'price', 'qty'. Индекс — автоматический RangeIndex.
  4. Шаг A. Выбирает один столбец 'price' через df['price']. Выводит строку: col_type=<тип>, где <тип> — type(df['price']).__name__.
  5. Шаг B. Выбирает два столбца ['name', 'qty'] через df[['name', 'qty']]. Выводит строку: subset_type=<тип> subset_shape=<shape> — тип и shape результата.
  6. Шаг C. Выбирает строку с меткой 1 через df.loc[1]. Выводит строку: loc1_name=<значение поля name>, то есть df.loc[1, 'name'].
  7. Шаг D. Выбирает строку с позицией 0 через df.iloc[0]. Выводит строку: iloc0_price=<значение поля price>, то есть df.iloc[0, 1].
  8. Шаг 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>
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 5

iloc с нестандартным индексом и выборка столбцов: разница между меткой и позицией

Что уже дано

Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.

Что нужно сделать

Напишите программу, которая:

  1. Читает из stdin целое число n — количество строк таблицы.
  2. Читает следующие n строк; каждая содержит три токена через пробел: город (строка без пробелов), температура (вещественное число), влажность (целое число). Пример строки: Москва 18.5 55.
  3. Строит pd.DataFrame с именем df из словаря списков с ключами 'city', 'temp', 'humidity'. В качестве индекса устанавливает pd.RangeIndex(start=10, stop=10 + n, step=1) — метки строк будут 10, 11, ..., 10+n-1, а не 0, 1, ....
  4. Шаг A. Выбирает столбец 'city' через df['city']. Выводит строку: col_shape=<shape> col_type=<тип>, где <тип> — type(df['city']).__name__.
  5. Шаг B. Выбирает два столбца ['temp', 'humidity'] через df[['temp', 'humidity']]. Выводит строку: subset_shape=<shape> subset_type=<тип>, где <тип> — type(...).__name__.
  6. Шаг C. Получает первую строку по позиции через df.iloc[0] и выводит строку: iloc0_city=<значение поля city>.
  7. Шаг D. Получает последнюю строку по позиции через df.iloc[-1] и выводит строку: ilocN_humidity=<значение поля humidity> (целое число).
  8. Шаг E. Получает ячейку по позиции строки 1 и позиции столбца 1 через df.iloc[1, 1]. Выводит строку: cell=<значение> (вещественное число).
  9. Шаг 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>
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 6

Найди и исправь: три ошибки в булевой фильтрации и присваивании через loc

Что уже дано

Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет.

В редакторе уже есть сломанный код. В нём ровно три ошибки в трёх независимых строках:

  1. Условие для mask_big использует df['amount'] >= 200, хотя по условию нужно отобрать строки, где amount строго больше 200.
  2. Условие для mask_paid_north комбинирует два условия через | (или), хотя по условию нужны строки, где одновременно status == 'paid' и region == 'Север' — то есть нужен &.
  3. Присваивание df['status'][mask_small] = 'archived' — цепочная индексация, ненадёжная в любом режиме pandas. Нужно заменить на один вызов df.loc[mask_small, 'status'] = 'archived'.

Что нужно сделать

Найдите и исправьте все три ошибки так, чтобы программа:

  1. Читала из stdin целое число n — количество строк таблицы.
  2. Читала следующие n строк; каждая содержит четыре токена через пробел: order_id (целое), amount (вещественное), status (строка), region (строка). Пример: 101 250.0 paid Север.
  3. Строила pd.DataFrame из словаря списков с ключами 'order_id', 'amount', 'status', 'region'.
  4. Строила маску mask_big = df['amount'] > 200 (строго больше) и выводила строку: big_count=<число строк>.
  5. Строила маску mask_paid_north = (df['status'] == 'paid') & (df['region'] == 'Север') и выводила через пробел значения order_id отфильтрованных строк: paid_north_ids=<id1> <id2> ....
  6. Строила маску 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> ...
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задача 7

loc-срез по меткам vs iloc-срез по позициям: одинаковые границы, разные результаты

Что уже дано

Проверяющая система запускает вашу программу и подаёт данные через stdin. Никаких заранее созданных переменных нет — вы читаете всё сами.

Что нужно сделать

Напишите программу, которая:

  1. Читает из stdin целое число n — количество строк таблицы.
  2. Читает следующие n строк; каждая содержит два токена через пробел: метка (строка без пробелов) и значение (целое число). Пример строки: alpha 100.
  3. Строит pd.DataFrame с одним столбцом 'val' из прочитанных значений. В качестве индекса устанавливает список меток из входных данных (в порядке чтения).
  4. Читает из stdin строку с двумя токенами через пробел: label_start и label_stop — метки для loc-среза.
  5. Читает из stdin строку с двумя целыми числами: pos_start и pos_stop — позиции для iloc-среза.
  6. Вычисляет loc_result = df.loc[label_start:label_stop, ['val']] — срез по меткам (обе границы включаются). Выводит строку: loc_shape=<shape> loc_sum=<сумма столбца val>.
  7. Вычисляет iloc_result = df.iloc[pos_start:pos_stop, [0]] — срез по позициям (правая граница не включается). Выводит строку: iloc_shape=<shape> iloc_sum=<сумма столбца val>.
  8. Выводит строку: 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>
РешениеPython

Без регистрации. Гостевая проверка не сохраняет прогресс. Для сохранения перейдите к уроку.

Отправьте решение, чтобы увидеть результаты тестов.

Задачи к уроку

Все задачи курса →

Продолжить с сохранением прогресса

Перейдите к уроку в учебном кабинете.

Перейти к интерактивному уроку