Question: I'm trying to create a rolling_median column as follow: For each row:  Filter the DataFrame to only keep rows of the same Category, having Date_B < my_current_row.Date_A Sort this filtered DataFrame by Date_B Calculate the median of Value using the N last entries with my filters : for example, if N=2, I will use the last 2 rows that respect my filtering to estimate the mean for my current row  Doing it using iterrows is doable but is not scalable. Do you have an idea how to do it in a more "Vectorized" way with less complexity ? Please find a snippet on how to generate a similar dataframe: import pandas as pd import numpy as np # Sample DataFrame (replace this with your actual DataFrame) data = { 'Category': ['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'], 'Date_A': ['2023-07-08', '2023-07-09', '2023-07-11', '2023-07-12', '2023-07-13', '2023-07-08', '2023-07-09', '2023-07-11', '2023-07-12', '2023-07-13'], 'Date_B': ['2023-07-08', '2023-07-10', '2023-07-12', '2023-07-12', '2023-07-13', '2023-07-08', '2023-07-10', '2023-07-12', '2023-07-12', '2023-07-13'], 'Value': [10, 15, 20, 25, 30, 35, 40, 45, 50, 55] } df = pd.DataFrame(data) # Convert 'Date_A' and 'Date_B' columns to datetime type df['Date_A'] = pd.to_datetime(df['Date_A']) df['Date_B'] = pd.to_datetime(df['Date_B']) df['rolling_mean'] = np.nan # Last 2 values N=2 for category in df.Category.unique(): df_cat = df[df.Category==category] for idx, row in df_cat.iterrows(): rm = df_cat[df_cat.Date_B < row.Date_A][:N].Value.mean() df.at[idx, 'rolling_mean'] = rm df   Category Date_A Date_B Value rolling_mean 0 A 2023-07-08 2023-07-08 10 NaN 1 A 2023-07-09 2023-07-10 15 10.0 2 A 2023-07-11 2023-07-12 20 12.5 3 A 2023-07-12 2023-07-12 25 12.5 4 A 2023-07-13 2023-07-13 30 12.5 5 B 2023-07-08 2023-07-08 35 NaN 6 B 2023-07-09 2023-07-10 40 35.0 7 B 2023-07-11 2023-07-12 45 37.5 8 B 2023-07-12 2023-07-12 50 37.5 9 B 2023-07-13 2023-07-13 55 37.5 

Answer: Probably not the most efficient way, but seems a little faster than the double for-loop: import pandas as pd import numpy as np # Sample DataFrame (replace this with your actual DataFrame) data = { 'Category': ['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B'], 'Date_A': ['2023-07-08', '2023-07-09', '2023-07-11', '2023-07-12', '2023-07-13', '2023-07-08', '2023-07-09', '2023-07-11', '2023-07-12', '2023-07-13'], 'Date_B': ['2023-07-08', '2023-07-10', '2023-07-12', '2023-07-12', '2023-07-13', '2023-07-08', '2023-07-10', '2023-07-12', '2023-07-12', '2023-07-13'], 'Value': [10, 15, 20, 25, 30, 35, 40, 45, 50, 55] } df = pd.DataFrame(data) # Convert 'Date_A' and 'Date_B' columns to datetime type df['Date_A'] = pd.to_datetime(df['Date_A']) df['Date_B'] = pd.to_datetime(df['Date_B']) df['rolling_mean'] = np.nan # Last 2 values N=2 df["rolling_mean"] = df.groupby("Category").apply( lambda grp: grp["Date_A"].map( lambda a: grp.loc[grp.Date_B < a, "Value"].head(N).mean() ), ).droplevel(level="Category") print(df)   Category Date_A Date_B Value rolling_mean 0 A 2023-07-08 2023-07-08 10 NaN 1 A 2023-07-09 2023-07-10 15 10.0 2 A 2023-07-11 2023-07-12 20 12.5 3 A 2023-07-12 2023-07-12 25 12.5 4 A 2023-07-13 2023-07-13 30 12.5 5 B 2023-07-08 2023-07-08 35 NaN 6 B 2023-07-09 2023-07-10 40 35.0 7 B 2023-07-11 2023-07-12 45 37.5 8 B 2023-07-12 2023-07-12 50 37.5 9 B 2023-07-13 2023-07-13 55 37.5  Time comparison via %%timeit Original %%timeit for category in df.Category.unique(): df_cat = df[df.Category==category] for idx, row in df_cat.iterrows(): rm = df_cat[df_cat.Date_B < row.Date_A][:N].Value.mean() df.at[idx, 'rolling_mean'] = rm  7.42 ms ± 277 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)  Proposed %%timeit df["rolling_mean"] = df.groupby("Category").apply( lambda grp: grp["Date_A"].map( lambda a: grp.loc[grp.Date_B < a, "Value"].head(N).mean() ), ).droplevel(level="Category")  4.55 ms ± 214 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)  Some explanation: # this handles the initial for-loop over "Categories" # it returns an Iterator of tuples with the first # item being the group name and the second being the dataframe group df.groupby("Category")  # `grp` is the dataframe part returned by the `.groupby` above. # it has the same columns as the original, but filtered down # to the rows in the selected group. .apply( lambda grp: grp["Date_A"]... )  # `map` iterates over the values of the declared series (i.e. "Date_A") # `a` is one of the values in "Date_A" and it is compared to the # series "Date_B". # `grp` is filtered to the rows where "Date_B" is < `a`. # we keep the first `N` rows of the returned dataframe and take the mean .map( lambda a: grp.loc[grp.Date_B < a, "Value"].head(N).mean() )  # we drop the newly generated "Category" index so we can join back # with the original `df`. .droplevel(level="Category")  References  pandas.core.groupby.DataFrameGroupBy.apply pandas.Series.apply pandas.Series.map