Нашли ошибку?
продвинутыйpandas 3.0.6

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

Записать обработку таблицы одной цепочкой — без промежуточных переменных и без изменения исходных данных.

Нужно знать:Столбцы: добавить, изменить, удалитьФильтрация строкФункции: map, apply, pipe

Содержание статьи

Коротко

  • Большинство методов возвращают новую таблицу, поэтому их можно вызывать подряд: df.dropna().assign(...).query(...).
  • Цепочку берут в круглые скобки и пишут по методу на строку — так её легко читать и комментировать.
  • Внутри цепочки нельзя сослаться на промежуточный результат по имени — используйте lambda d: … в assign и loc или pd.col("…").
  • pipe(функция, …) встраивает в цепочку свою функцию от всей таблицы, в том числе для отладки.
  • С Copy-on-Write цепочки не копируют данные лишний раз, а исходная таблица остаётся нетронутой.

Синтаксис

(
  df
  .rename(columns={...})
  .dropna(subset=[...])
  .assign(new=lambda d: d["a"] * 2, other=pd.col("b") + 1)
  .query("new > 10")
  .loc[lambda d: d["other"] > 0, ["a", "new"]]
  .pipe(my_func, arg=1)
)

Примеры

Данные для примеровиспользуются во всех примерах ниже
raw = pd.DataFrame({
    "Город": ["омск", "тула", "омск", "сочи", "тула", "омск"],
    "Чашки": [30, 12, None, 25, 20, 16],
    "Цена": [180, 165, 180, 210, 165, 190],
})
Пошагово, с переменной df
df = raw.rename(columns={"Город": "city", "Чашки": "cups", "Цена": "price"})
df = df.dropna(subset=["cups"])
df["city"] = df["city"].str.title()
df["revenue"] = df["cups"] * df["price"]
df = df[df["revenue"] > 3000]
df.groupby("city")["revenue"].sum()
Вывод
city
Омск    8440.0
Сочи    5250.0
Тула    3300.0
Name: revenue, dtype: float64
То же цепочкой
(
    raw
    .rename(columns={"Город": "city", "Чашки": "cups", "Цена": "price"})
    .dropna(subset=["cups"])
    .assign(
        city=lambda d: d["city"].str.title(),
        revenue=lambda d: d["cups"] * d["price"],
    )
    .query("revenue > 3000")
    .groupby("city")["revenue"]
    .sum()
)
Вывод
city
Омск    8440.0
Сочи    5250.0
Тула    3300.0
Name: revenue, dtype: float64
pd.col и loc с lambda
(
    raw
    .rename(columns={"Город": "city", "Чашки": "cups", "Цена": "price"})
    .assign(revenue=pd.col("cups") * pd.col("price"))   # pandas 3.0
    .loc[lambda d: d["revenue"] > 3000, ["city", "revenue"]]
)
Вывод
   city  revenue
0  омск   5400.0
3  сочи   5250.0
4  тула   3300.0
5  омск   3040.0
Своя функция в цепочке
def top(df, n, by):
    return df.nlargest(n, by)

(
    raw
    .rename(columns={"Город": "city", "Чашки": "cups"})
    .pipe(top, n=2, by="cups")
)
Вывод
   city  cups  Цена
0  омск  30.0   180
3  сочи  25.0   210

Подводные камни

Длинную цепочку трудно отлаживать

Если результат неожиданный, вставьте между шагами pipe с печатью размера или head() — цепочку не придётся разбирать.

Печать размеров между шагами
def show_shape(df, label):
    print(label, df.shape)
    return df

(
    raw
    .pipe(show_shape, "исходно:")
    .dropna()
    .pipe(show_shape, "без пропусков:")
    .query("Цена > 170")
    .pipe(show_shape, "дороже 170:")
    ["Чашки"].sum()
)
Вывод
исходно: (6, 3)
без пропусков: (5, 3)
дороже 170: (3, 3)
np.float64(71.0)

lambda видит промежуточный результат

В assign(revenue=lambda d: …) переменная d — таблица на этом шаге цепочки, а не исходная raw. Поэтому после rename в ней уже новые имена столбцов.