2026, Jan 12 03:01
Векторизованные способы заменить цикл при копировании столбцов в pandas
Как перенести значения между столбцами в pandas по словарю соответствий без вложенных циклов: два векторизованных подхода — DataFrame.assign и reindex+rename. Примеры и советы.
Когда нужно «нормализовать» DataFrame в pandas, перенося значения из «исходных» столбцов в несколько «целевых» по словарю соответствий, быстрый вложенный цикл решает задачу. Но если вам ближе идиоматичный, векторизованный стиль, хорошо сочетающийся с цепочками методов, у pandas есть подходящие инструменты.
Пример данных и базовая реализация
Рассмотрим DataFrame, где некоторые столбцы должны дублировать значения других в соответствии с простым отображением. Нужно переписать A2 и A3 значениями из A1, а A5 — значениями из A4.
import pandas as pd
frame = pd.DataFrame({
"A1": [1, 11, 111],
"A2": [2, 22, 222],
"A3": [3, 33, 333],
"A4": [4, 44, 444],
"A5": [5, 55, 555]
})
rel_map = {
"A1": ["A2", "A3"],
"A4": ["A5"]
}
for src_col, targets in rel_map.items():
for dst_col in targets:
frame[dst_col] = frame[src_col]
print(frame)
Получаем ожидаемый результат:
A1 A2 A3 A4 A5
0 1 1 1 4 4
1 11 11 11 44 44
2 111 111 111 444 444
В чем суть проблемы?
Вложенный цикл прозрачен и понятен, но выполняет работу на уровне итераций Python. В pandas операции, описывающие присваивание целиком, обычно короче, лучше встраиваются в рабочие цепочки и точнее соответствуют семантике DataFrame. Задача — заменить цикл векторизованным, «по‑пандасовому» выражением без изменения результата.
Векторизованные решения
Первый вариант использует DataFrame.assign, чтобы собрать все обновления целевых столбцов в одном вызове. Второй опирается на проекцию столбцов: reindex в паре с rename и set_axis выравнивает столбцы с помощью инвертированного отображения.
Используем assign с переписанным отображением:
updated_one = frame.assign(**{dest: frame.get(src)
for src, dest_list in rel_map.items()
for dest in dest_list})
print(updated_one)
Учтите, что assign не работает in place. Используйте его в цепочке или присвойте результат той же переменной, если хотите сохранить изменения.
Используем reindex и rename/set_axis, инвертировав отображение:
alias_map = {alias: origin
for origin, aliases in rel_map.items()
for alias in aliases}
updated_two = (
frame.reindex(columns=frame.rename(columns=alias_map).columns)
.set_axis(frame.columns, axis=1)
)
print(updated_two)
Оба подхода дают тот же результат, что и цикл:
A1 A2 A3 A4 A5
0 1 1 1 4 4
1 11 11 11 44 44
2 111 111 111 444 444
Почему это важно
Когда трансформацию описываешь одним выражением, замысел становится очевидным, а решение легче вписывается в рабочие процессы pandas. Заодно сокращается объем итераций на стороне Python. Что до скорости, универсального ответа нет: относительная производительность зависит от общего числа столбцов, доли изменяемых столбцов, dtypes исходных данных и даже фрагментированности DataFrame. Надежнее всего — проверять на своих данных.
Выводы
Нужна наглядная, легко сочетаемая в цепочках конструкция — берите DataFrame.assign. Если удобнее управлять проекцией столбцов и важен точный контроль выравнивания, связка reindex плюс rename/set_axis тоже отлично работает. В обоих случаях целевые столбцы переписываются значениями из соответствующих источников — точно как в цикле. Когда важна скорость, замерьте оба варианта на реальной задаче и выберите тот, что лучше ведет себя в вашей среде.