Нашли ошибку?
Скачать чистый
урок 2 из 3≈ 20 минупражнения: 0 из 4

Цепочки методов

Записать анализ как последовательность шагов сверху вниз: цепочка методов в скобках, assign с lambda, query, свои шаги через pipe. И почему цикл по строкам iterrows почти всегда хуже операций со столбцами.

Python запустится при первом выполнении

Анализ данных — это последовательность шагов: загрузить, добавить столбец, отфильтровать, сгруппировать, отсортировать. Код можно написать так, что эта последовательность видна с первого взгляда, — а можно так, что через неделю не разберётесь сами.

Промежуточные переменные

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

[ ]Шаги с промежуточными переменными
import pandas as pd

orders = pd.read_csv("data/shop_orders.csv", parse_dates=["date"])
df = orders.copy()
df["revenue"] = df["price"] * df["quantity"]
df2 = df[df["category"] == "Кофе"]
df3 = df2.groupby("product")["revenue"].sum()
df4 = df3.sort_values(ascending=False)
df4.head(3)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
product
Эфиопия 250 г          426600
Бразилия 1 кг          375390
Эспрессо-смесь 1 кг    372650
Name: revenue, dtype: int64

Работает. Но имена df2, df3, df4 ничего не значат, и за ними нужно следить: стоит перепутать df2 и df3 — и ошибка будет молчаливой. Переменные остаются в памяти, хотя больше не нужны. А если шаг нужно убрать, придётся переименовывать всё, что ниже.

Цепочка

Почти каждый метод pandas возвращает новую таблицу или Series — значит, следующий метод можно вызвать сразу у результата:

[ ]То же цепочкой
top_coffee = (
    orders
    .assign(revenue=orders["price"] * orders["quantity"])
    .query("category == 'Кофе'")
    .groupby("product")["revenue"]
    .sum()
    .sort_values(ascending=False)
    .head(3)
)
top_coffee
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
product
Эфиопия 250 г          426600
Бразилия 1 кг          375390
Эспрессо-смесь 1 кг    372650
Name: revenue, dtype: int64

Всё выражение взято в круглые скобки — внутри скобок Python разрешает переносить строки где угодно. Каждый шаг — на своей строке, начинается с точки. Читается сверху вниз как рецепт: взять заказы, добавить выручку, оставить кофе, сгруппировать по товару, сложить, отсортировать, взять три.

Промежуточных переменных нет; лишний шаг удаляется или отключается одним символом # в начале строки.

[ ]Упражнение 1Три города решено

Цепочкой методов получите top_cities — три города с наибольшей выручкой: добавьте столбец revenue методом assign, сгруппируйте по городу, сложите, отсортируйте по убыванию и возьмите первые три.

top_cities = (
    orders
    # допишите шаги цепочки: каждый с новой строки, начиная с точки
)

lambda: таблица «на этом шаге»

В примере выше assign обращается к orders — исходной таблице. Это работает, пока assign стоит первым. Но если перед ним строки уже отфильтрованы или добавлен новый столбец, в orders этих изменений нет:

[ ]Столбца revenue в orders нетошибка: KeyError
(
    orders
    .query("category == 'Чай'")
    .assign(revenue=orders["price"] * orders["quantity"])
    .assign(share=orders["revenue"] / orders["revenue"].sum())
    .head(3)
)
Ошибкасохранённый · нажмите «Выполнить», чтобы получить свой
KeyError: 'revenue'

orders["revenue"] — ошибка: столбец revenue появился внутри цепочки, а orders остался прежним. У таблицы «на этом шаге» нет имени — к ней нельзя обратиться.

Для этого assign принимает не только готовые значения, но и функцию: pandas вызовет её и передаст текущую таблицу. Короткую функцию без имени пишут словом lambda:

[ ]d — таблица на этом шаге
(
    orders
    .query("category == 'Чай'")
    .assign(revenue=lambda d: d["price"] * d["quantity"])
    .assign(share=lambda d: d["revenue"] / d["revenue"].sum())
    [["product", "revenue", "share"]]
    .head(3)
)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
product revenue share
1 Травяной сбор 50 г 260 0.000428
3 Чёрный чай 100 г 350 0.000576
5 Улун 100 г 1080 0.001777

Предпоследняя строка цепочки — [["product", "revenue", "share"]] — знакомый выбор столбцов. Он тоже шаг: квадратные скобки можно ставить сразу после предыдущего результата, без точки.

lambda d: d["price"] * d["quantity"] читается так: «функция, которая получает таблицу d и возвращает произведение её столбцов». Это то же самое, что

def make_revenue(d):
    return d["price"] * d["quantity"]

только короче и без имени. Букву после lambda выбираете вы; d или df — привычные варианты.

Правило: внутри цепочки обращайтесь к таблице через lambda — тогда шаги можно переставлять.

Вопрос
t = pd.DataFrame({"x": [1, 2, 3, 4]})
print(t.query("x > 2").assign(y=lambda d: d["x"] * 10)["y"].sum())

Что выведет этот код?

[ ]Упражнение 2Крупные покупки в приложении решено

Цепочкой методов получите app_bulk — выручку по категориям для покупок через приложение, где в строке не меньше трёх штук; результат — по убыванию выручки. Фильтр сделайте методом query, столбец revenue добавьте через assign с lambda.

app_bulk = (
    orders
    # допишите шаги цепочки: каждый с новой строки, начиная с точки
)

pipe: свой шаг в цепочке

Часть шагов повторяется из анализа в анализ: добавить выручку, добавить месяц, взять первые N. Их удобно оформить функциями. Метод pipe («труба») вставляет функцию в цепочку: таблица передаётся ей первым аргументом, остальные аргументы пишут после названия функции.

[ ]Две свои функции как шаги цепочки
def add_revenue(table):
    return table.assign(revenue=table["price"] * table["quantity"])


def top_rows(table, column, n):
    return table.nlargest(n, column)


(
    orders
    .pipe(add_revenue)
    .pipe(top_rows, "revenue", 3)
    [["date", "product", "revenue"]]
)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
date product revenue
1911 2025-10-31 Бразилия 1 кг 9030
1995 2025-11-13 Бразилия 1 кг 9030
2349 2025-12-22 Бразилия 1 кг 9030

.pipe(top_rows, "revenue", 3) — это то же, что top_rows(таблица, "revenue", 3). Но запись с pipe сохраняет порядок чтения сверху вниз, а вложенные вызовы top_rows(add_revenue(orders), "revenue", 3) читаются изнутри наружу.

У функций-шагов одно правило: получить таблицу — вернуть новую таблицу, не меняя полученную.

[ ]Упражнение 3Шаг «добавить месяц» решено

Допишите функцию add_month(table): она возвращает таблицу с новым столбцом month — номером месяца из столбца date. Затем цепочкой с двумя pipe (функция add_revenue уже есть) получите by_month — выручку по месяцам.

def add_month(table):
    return ...


by_month = (
    orders
    # допишите шаги цепочки: каждый с новой строки, начиная с точки
)

Когда цепочка не нужна

Цепочка хороша, пока читается. Если в ней пятнадцать шагов, разбейте её на две-три с осмысленными именами: priced, by_month. Если шаг упал с ошибкой — закомментируйте нижние строки и посмотрите на промежуточный результат. Цель — не «одна строка», а код, который легко прочитать.

Цикл по строкам

Тем, кто пришёл из обычного Python, хочется пройти по таблице циклом. Метод iterrows это позволяет: на каждом шаге даёт метку строки и саму строку как Series.

[ ]Цикл по первым трём строкам
total = 0
for label, row in orders.head(3).iterrows():
    revenue = row["price"] * row["quantity"]
    print(label, row["product"], revenue)
    total = total + revenue
print(total)
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
0 Кофемолка ручная 6400
1 Травяной сбор 50 г 260
2 Шоколад горький 180
6840

Посчитаем циклом сумму к оплате со скидкой 10 % на позиции от трёх штук:

[ ]Цикл по всем строкам
total = 0
for label, row in orders.iterrows():
    revenue = row["price"] * row["quantity"]
    if row["quantity"] >= 3:
        revenue = revenue * 0.9
    total = total + revenue
print(round(total))
Выводсохранённый · нажмите «Выполнить», чтобы получить свой
3150009

Результат верный. Но так писать не стоит, и причин три:

  • медленно: на каждой строке pandas собирает отдельный Series, а арифметику выполняет Python — для таблицы в миллион строк это десятки секунд вместо сотых долей секунды, разница в тысячи раз;
  • длинно: шесть строк и переменная-накопитель вместо двух выражений;
  • хрупко: результат цикла — одно число, а промежуточного столбца, который можно проверить глазами, нет.

Почти всё, что хочется сделать циклом, выражается операциями со столбцами: арифметика, np.where, map, groupby. Цикл по строкам оправдан редко — например, когда для каждой строки нужно отправить запрос в интернет.

[ ]Упражнение 4Без цикла решено

Получите тот же результат без цикла. Цепочкой методов соберите discounted: таблица orders со столбцом revenue (цена × количество) и столбцом to_pay — выручка со скидкой 10 %, если количество не меньше 3, и без скидки в остальных строках. В total_fast сохраните сумму to_pay.

import numpy as np

discounted = (
    orders
    # допишите шаги цепочки: каждый с новой строки, начиная с точки
)
total_fast = ...

Итоги

  • Методы возвращают новую таблицу — их можно вызывать цепочкой; всё выражение — в круглых скобках, шаг на строку.
  • В assign внутри цепочки пишите lambda d: ... — d будет таблицей на этом шаге.
  • pipe(функция, аргументы) вставляет свою функцию как шаг.
  • Цикл iterrows работает, но медленно; сначала ищите операцию со столбцами.

Подробнее в справочнике

Нашли ошибку?