2025, Nov 07 03:02

Как вычесть Series из столбцов pandas DataFrame, сохранив метку

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

Когда нужно вычесть одну опорную Series из множества столбцов в pandas DataFrame и при этом сохранить категориальную метку, наивный подход «столбец за столбцом» быстро становится неудобным. Ниже — лаконичный и надежный прием вычитания числовых столбцов df2 из построчного среднего df1 при сохранении столбца с меткой.

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

У нас есть два DataFrame с одинаковым числом строк. В первом — два числовых столбца и их среднее по строкам. Во втором — столбец с меткой и тридцать числовых признаков. Цель — создать третий DataFrame, где будет метка и разность между каждым числовым столбцом второго DataFrame и столбцом среднего из первого.

import pandas as pd
import numpy as np

# Воспроизводимая инициализация
np.random.seed(0)

# Создаем первый DataFrame со средним по строкам
vals_base = np.random.uniform(15, 40, size=(60, 2))
base_df = pd.DataFrame(vals_base, columns=[
    'col_A',
    'col_B',
])
base_df['metric_X'] = base_df.mean(axis=1)

# Создаем второй DataFrame с меткой и 30 числовыми столбцами
vals_src = np.random.uniform(10.5, 32.8, size=(60, 30))
source_df = pd.DataFrame(
    vals_src,
    columns=[f'field_{i}' for i in range(1, 31)],
)
source_df.insert(
    0,
    'Segment',
    np.repeat(['A', 'B', 'C'], 20, axis=0),
)

Распространенная, но хрупкая попытка

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

# Императивное, повторяющееся вычитание (не рекомендуется в больших масштабах)
result_df = pd.DataFrame()

result_df['diff_1'] = source_df['field_1'] - base_df['metric_X']
result_df['diff_2'] = source_df['field_2'] - base_df['metric_X']
result_df['diff_3'] = source_df['field_3'] - base_df['metric_X']
# ... и так далее до field_30

Что здесь действительно важно

Нужно вычесть одномерную Series (среднее по строкам) сразу из множества столбцов. Pandas поддерживает бродкастинг, значит можно выровнять среднее по всем числовым столбцам за один прием. Столбец с меткой должен остаться как есть, поэтому работаем только с числовым срезом.

Решение: используйте бродкастинг для вычитания и сохраните метку

Скопируйте второй DataFrame, исключите столбец с меткой по позиции и выполните вычитание со смещением через бродкастинг. Совпадение форм задается явно через values и новую ось, охватывая все числовые столбцы одним векторизованным действием.

final_df = source_df.copy()
final_df.iloc[:, 1:] -= base_df['metric_X'].values[:, np.newaxis]

Если вам ближе явное формирование имен, а не бродкастинг, соберите нужные столбцы небольшим циклом с f-строками.

loop_df = pd.DataFrame({'Segment': source_df['Segment']})
for idx in range(1, 31):
    loop_df[f'diff_{idx}'] = source_df[f'field_{idx}'] - base_df['metric_X']

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

Бродкастинг делает код коротким и прозрачным, избавляет от повторяющихся присваиваний и естественно масштабируется при изменении числа числовых столбцов. К тому же так легко оставить нетронутыми нечисловые или «меточные» столбцы, просто исключив их одним срезом.

Итоги

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

Материал основан на вопросе на StackOverflow от nasa313 и ответе Reinderien.