2025, Oct 20 19:16

Условный forward fill в pandas: протягиваем значение вниз без циклов

Показываем, как в pandas сделать условный forward fill: протягивать значение вниз по столбцу только между строками‑маркерами. Без циклов, наглядный пример и код

Прокатывать значение вниз по столбцу до следующей строки‑маркера — типичный шаг очистки табличных данных. В pandas это похоже на прямую (forward) заливку, только переносить значения нужно лишь тогда, когда выполнено конкретное «родительское» условие. Ниже — лаконичный способ сделать именно это без медленных циклов и кастомной индексной логики.

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

Предположим, у вас есть таблица, где некоторые строки — «родители», что определяется тем, что в последнем столбце стоит значение "String". Значение в третьем столбце у таких родительских строк — это опорное значение, которое нужно копировать вниз по тому же столбцу до появления следующей родительской строки. Неродительские строки могут содержать заглушки вроде X или Y или просто неважные числа, которые вы хотите перезаписать.

1   2   3   'String'
''  4   X   ''
''  5   X   ''
''  6   7   'String'
''  1   Y   ''

Ожидаемый результат — протянуть по третьему столбцу значения с каждой строки "String", так чтобы X и Y превратились в 3 и 7 соответственно, пока следующая строка "String" не задаст новое опорное значение.

1   2   3   'String'
''  4   3   ''
''  5   3   ''
''  6   7   'String'
''  1   7   ''

Минимальный воспроизводимый пример

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

import pandas as pd
import numpy as np
frame = pd.DataFrame({
    'k1': [1, '', '', '', ''],
    'k2': [2, 4, 5, 6, 1],
    'value_col': [3, 'X', 'X', 7, 'Y'],
    'label_col': ['String', '', '', 'String', '']
})
print(frame)

Здесь видно, что родительские строки — те, где label_col равен "String", а неродительские — где метка пустая. Цель — протянуть value_col вниз, начиная с последней встреченной родительской строки.

Почему наивный подход кажется сложным

Вычислять вручную начальные и конечные индексы каждого сегмента и затем записывать значения в срезы можно, но это и медленно, и громоздко. Нужна векторизованная операция: воспринимать неродительские строки как пропуски и заполнять вперёд из последнего корректного наблюдения. Именно это и делает forward fill, если неродительские строки помечены как пропущенные.

Решение с forward fill

Ключевой шаг — сначала превратить неродительские строки в целевом столбце в пропущенные значения, а затем применить заполнение вперёд. Как только заглушки станут NaN, pandas будет протягивать последнее валидное число вниз по столбцу до следующего валидного числа.

# Превратить все неродительские строки в целевом столбце в NaN
frame.loc[frame['label_col'] != 'String', 'value_col'] = np.nan
# Заполнить вперёд из последней родительской строки
frame['value_col'] = frame['value_col'].ffill()
print(frame[['value_col', 'label_col']])

В результате опорное значение с каждой родительской строки применяется к последующим строкам до появления следующей родительской. Подход опирается на то, что в label_col на родительских строках стоит ровно "String".

Замечание о dtypes

Присваивание NaN целочисленному столбцу переводит его в float. Во многих случаях это приемлемо, поскольку float64 точно хранит целые значения до примерно 9 квадриллионов. Если вам принципиален целочисленный dtype, после заполнения можно привести тип обратно из float к int.

Зачем это нужно

Такой шаблон часто встречается в логах, полуструктурированных выгрузках и плоских иерархических файлах, где строка‑маркер задаёт контекст для следующих строк. Замена неродительских строк на NaN с последующим ffill — выразительное и быстрое решение, избавляющее от ручной итерации и хрупкой арифметики индексов. И читается ясно: пометьте несущественные значения как пропущенные и перенесите дальше последнее корректное значение.

Выводы

Когда у вас данные в стиле «родитель—потомки», считайте неродительские значения пропущенными и позвольте pandas сделать основную работу с помощью forward fill. Если условие для определения родительских строк формализовано, двухшаговое преобразование получается простым, быстрым и удобным в сопровождении. Если конвейер чувствителен к смене dtype, планируйте привести float обратно к int после заполнения.

Статья основана на вопросе на StackOverflow от Lucas P и ответе от Aadvik.