2025, Dec 13 00:01

Предсказуемые цепочки преобразований в pandas при Copy-On-Write

Разберём, как строить предсказуемые цепочки преобразований в pandas при Copy-On-Write: без inplace-мутаций, с rename, assign, apply. Конвейер для DataFrame.

Когда вы начинаете нормализовать данные в pandas, один из первых практических вопросов звучит просто: изменит ли метод текущий DataFrame или вернёт новый? Если включён режим Copy-On-Write, самый надёжный способ сохранить предсказуемость — использовать только операции, которые возвращают новый DataFrame, и собирать их в единый конвейер. Такой подход убирает угадайку вокруг изменений «на месте», делает код читабельным и упрощает проверку отдельных шагов.

Постановка задачи

Рассмотрим цепочку преобразований для данных, загруженных из листа Excel. Логика ниже работает, но на каждом шаге одна и та же переменная переназначается: в одном месте смешиваются присваивания, мутации и переиндексация.

import pandas as pd
pd.options.mode.copy_on_write = True
data = pd.read_excel("my_excel_file.xls", sheet_name="my_sheet", usecols="A:N")
data = data.dropna(how='all')
data = data.iloc[:-1, :]
data.columns.array[0] = "Resource"
data = data.astype({"Resource": int})
data.columns = data.columns.str.replace('Avg of ', '').str.replace('Others', 'others')
data = data.set_index("Resource")
data = data.sort_index(axis=0)
data = data / 100
data = data.round(4)
data = data.reindex(columns=sorted(data))

Что на самом деле вызывает путаницу

Смешение прямых изменений атрибутов с вызовами методов размывает границу: где возвращается новый объект, а где меняется состояние. В одних строках идёт присваивание результата вызова, в других — прямое обращение к атрибутам вроде массива columns. Стиль получается неоднородным и подталкивает к постоянным переназначениям, особенно если вы стараетесь действовать «безопасно» при включённом Copy-On-Write.

Предсказуемый подход на основе цепочек

Простое правило: придерживайтесь методов, которые возвращают DataFrame, и компонуйте их. Любую строку можно закомментировать, чтобы проверить отдельный шаг. Вариант ниже передаёт ту же логику, не опираясь на неявные мутации и не смешивая стили.

import pandas as pd
pd.options.mode.copy_on_write = True
frame = pd.read_excel("my_excel_file.xls", sheet_name="my_sheet", usecols="A:N")
frame = (
    frame
      .dropna(how='all')
      .iloc[:-1, :]
      .rename(columns={frame.columns[0]: "Resource"})
      .astype({"Resource": int})
      .rename(columns=lambda s: s.replace('Avg of ', '').replace('Others', 'others'))
      .set_index("Resource")
      .sort_index(axis=0)
      .div(100)
      .round(4)
      .sort_index(axis=1)
)

Так сохраняется замысел каждого шага и гарантируется, что каждая операция создаёт новый DataFrame. Конвейер линейный, наглядный и легко меняется.

Приёмы, которые удобно переиспользовать

Нужно привести к числовому типу отдельные столбцы, а остальные не трогать? Применяйте функцию по столбцам с условием. Так преобразования остаются явными и локальными.

cleaned = (
    frame
      .apply(lambda col: pd.to_numeric(col) if col.name in ['Quantity'] else col)
)

Если нужно привести к числу один столбец и вернуть его в составе цепочки, используйте assign с лямбда-функцией, читающей из текущего объекта.

updated = (
    frame
      .assign(Resource=lambda x: x['Resource'].apply(pd.to_numeric, errors='coerce'))
)

Хотите переименовать все столбцы разом, без явного словаря соответствий? Задайте новый заголовок явно.

renamed = (
    frame
      .set_axis(['Product', 'Quantity'], axis=1)
)

Все эти приёмы сводятся к одной идее: отдавайте предпочтение цепочечным операциям, возвращающим DataFrame, чтобы свободно компоновать их без побочных эффектов «на месте».

Почему это важно

Цепочки из методов, возвращающих DataFrame, формируют устойчивую модель в голове. Каждый шаг самодостаточен: можно отключить его, не затрагивая остальное, и не смешивать неявные мутации с переназначениями. При Copy-On-Write такой стиль делает ход преобразований предсказуемым и помогает чётко понимать, где именно меняются данные.

Итоги

Предпочитайте конвейер из методов, возвращающих DataFrame, и избегайте прямых присваиваний атрибутам и неявных мутаций. Для работы с заголовками используйте rename и set_axis, для точечных обновлений столбцов — assign, а для выборочных преобразований — apply. Так логика подготовки данных остаётся явной, проверяемой и удобной в сопровождении.