Question: I have some data in 2 dataframes that look like: import polars as pl data = {"channel": [0, 1, 2, 1, 2, 0, 1], "time": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]} time_df = pl.DataFrame(data) data = { "time": [10.0, 10.5], "event_table": [["start_1", "stop_1", "start_2", "stop_2"], ["start_3"]], } events_df = pl.DataFrame(data)  where channel 0 in the time_df means that a new "event table" start here. I want to explode each row of the event_table starting at the channels 0 in the event_df and have a result like: data = { "channel": [1, 2, 1, 2, 1], "time": [0.2, 0.3, 0.4, 0.5, 0.7], "event": ["start_1", "stop_1", "start_2", "stop_2", "start_3"], } result_df = pl.DataFrame(data)  What I am currently doing is to remove all channel 0 from the first dataframe, explode the second data frame, and use hstack to combine both dataframes. This works OK if my data is perfect. In reality, the event table can have more (or less) events. In these cases I want to "truncate" the explosion (or fill with nulls) e.g. import polars as pl data = {"channel": [0, 1, 2, 1, 2, 0, 1], "time": [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]} time_df = pl.DataFrame(data) data = { "time": [10.0, 10.5], "event_table": [["start_1", "stop_1", "start_2"], ["start_3", "stop_3"]], } events_df = pl.DataFrame(data) data = { "channel": [1, 2, 1, 2, 1], "time": [0.2, 0.3, 0.4, 0.5, 0.7], "event": ["start_1", "stop_1", "start_2", None, "start_3"], } result_df = pl.DataFrame(data)  I appreciate any help.

Answer: Not sure if I'm overcomplicating things here but: The initial approach that comes to mind is to number the events so they align with the row index from the channels. shape: (2, 2) ┌──────────────────────────────────┬───────┐ │ event_table ┆ index │ │ --- ┆ --- │ │ list[str] ┆ u32 │ ╞══════════════════════════════════╪═══════╡ │ ["start_1", "stop_1", "start_2"] ┆ 0 │ # [1, 2, 3] │ ["start_3", "stop_3"] ┆ 5 │ # [6, 7] └──────────────────────────────────┴───────┘  1. Run-length encoding We find the start index and run-length of each channel 0: rle_df = ( time_df .with_row_index("start") .filter(channel = 0) .select( "start", len = pl.col("start").diff().shift(-1) - 1 ) .with_row_index() )  shape: (2, 3) ┌───────┬───────┬──────┐ │ index ┆ start ┆ len │ │ --- ┆ --- ┆ --- │ │ u32 ┆ u32 ┆ i64 │ ╞═══════╪═══════╪══════╡ │ 0 ┆ 0 ┆ 4 │ │ 1 ┆ 5 ┆ null │ └───────┴───────┴──────┘  If len < event_len we want to use that as the new length. 2. Truncate We join the run-lengths to events_df and use  .min_horizontal() to give use the "least" length truncate with .list.head()  events_with_index = ( events_df .with_row_index() .join(rle_df, on="index", how="left") .select( pl.col("event_table").list.head( pl.min_horizontal(pl.col("len"), pl.col("event_table").list.len()) ), index = pl.col("start") ) .explode("event_table") .with_columns( pl.col("index") + pl.col("index").cum_count().over("index") ) )  After the .explode() we have: ┌─────────────┬───────┐ │ event_table ┆ index │ │ --- ┆ --- │ │ str ┆ u32 │ ╞═════════════╪═══════╡ │ start_1 ┆ 0 │ │ stop_1 ┆ 0 │ │ start_2 ┆ 0 │ │ start_3 ┆ 5 │ │ stop_3 ┆ 5 │ └─────────────┴───────┘  The .cum_count() addition then gives us: shape: (5, 2) ┌─────────────┬───────┐ │ event_table ┆ index │ │ --- ┆ --- │ │ str ┆ u32 │ ╞═════════════╪═══════╡ │ start_1 ┆ 1 │ # index of 1st channel 0 (plus 1) │ stop_1 ┆ 2 │ │ start_2 ┆ 3 │ │ start_3 ┆ 6 │ # index of 2nd channel 0 (plus 1) │ stop_3 ┆ 7 │ └─────────────┴───────┘  3. Join With the indexes aligned, we can join and filter out the channel 0 rows.  A left-join gives us the "fill with nulls" behaviour.  (time_df .with_row_index() .join( events_with_index, on = "index", how = "left" ) .filter(pl.col("channel") != 0) )  shape: (5, 4) ┌───────┬─────────┬──────┬─────────────┐ │ index ┆ channel ┆ time ┆ event_table │ │ --- ┆ --- ┆ --- ┆ --- │ │ u32 ┆ i64 ┆ f64 ┆ str │ ╞═══════╪═════════╪══════╪═════════════╡ │ 1 ┆ 1 ┆ 0.2 ┆ start_1 │ │ 2 ┆ 2 ┆ 0.3 ┆ stop_1 │ │ 3 ┆ 1 ┆ 0.4 ┆ start_2 │ │ 4 ┆ 2 ┆ 0.5 ┆ null │ │ 6 ┆ 1 ┆ 0.7 ┆ start_3 │ └───────┴─────────┴──────┴─────────────┘