2025, Oct 20 19:00

Propagate parent row values in pandas using ffill when a marker column equals 'String'

Learn how to use pandas ffill to propagate values from parent rows down a column based on a marker, replacing non-parent entries with NaN. Loop-free cleaning.

Propagating a value down a column until the next marker row shows up is a common cleaning step in tabular data. In pandas this looks a lot like a forward fill, except you only want to carry over values when a specific “parent” condition is met. Here is a concise way to do exactly that without writing slow loops or custom indexing logic.

Problem setup

Suppose you have a table where certain rows are “parents” indicated by the last column equal to "String". The value in the third column on those parent rows is the reference you want to copy downward into the same column, until the next parent row appears. Non-parent rows might contain placeholder tokens like X or Y or just be irrelevant numbers you want to overwrite.

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

The intended result is to forward-fill the third column from each "String" row, so X and Y become 3 and 7 respectively until the next "String" row resets the reference.

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

Minimal, reproducible example

Below is a compact example that mirrors this behavior. Names are generic and focus on the two relevant columns: one column with the values to propagate and one column that marks the parent rows.

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)

This shows parent rows where label_col equals "String" and non-parent rows where the label is empty. The goal is to fill value_col downward from the last seen parent row.

Why the naïve approach feels tricky

Trying to compute start and end indices for each parent segment and then writing values into slices works, but it is slow and cumbersome. What you actually want is a vectorized operation that treats non-parent rows as missing and then fills forward from the most recent valid observation. That is precisely what a forward fill does, as long as non-parent rows are marked as missing.

Solution with forward fill

The key is to convert the non-parent rows in the target column into missing values first, and then apply a forward fill. Once those placeholders become NaN, pandas will propagate the last valid number down the column until it hits the next valid number.

# Turn all non-parent rows in the target column into NaN
frame.loc[frame['label_col'] != 'String', 'value_col'] = np.nan
# Forward-fill from the most recent parent row
frame['value_col'] = frame['value_col'].ffill()
print(frame[['value_col', 'label_col']])

The result applies the reference value from each parent row to the subsequent rows until the next parent row appears. The approach relies on label_col being exactly "String" on parent rows.

A note on dtypes

Assigning NaN to an integer column promotes it to float. In many cases this is acceptable, since float64 can hold exact integer values up to 9 quadrillions. If you truly need integer dtype, you can convert floats into int after the fill.

Why this matters

This pattern shows up in log files, semi-structured exports, and hierarchical flat files where a marker row defines context for the following rows. Replacing non-parent rows with NaN and then using ffill is both expressive and fast, avoiding manual iteration and brittle index arithmetic. It also reads clearly: mark irrelevant values as missing, then carry forward the last known good value.

Takeaways

When you have parent-child style rows, treat non-parent values as missing and let pandas do the heavy lifting with a forward fill. As long as your parent condition is clean, the two-step transform is straightforward, fast, and easy to maintain. If your pipeline is sensitive to dtype changes, plan to convert floats into int after filling.

The article is based on a question from StackOverflow by Lucas P and an answer by Aadvik.