Out of interest, I was trying to explode only the months:
(
df.with_columns(
(pl.col.DATE - pl.col.DATE_PREV).dt.total_days().alias("TOTAL_DAYS") + 1,
pl.date_ranges(pl.col.DATE_PREV.dt.month_start(), pl.col.DATE.dt.month_end(), interval="1mo").alias("MONTH_START")
)
.explode("MONTH_START")
)
shape: (5, 6)
┌─────┬────────────┬────────────┬──────────┬────────────┬─────────────┐
│ ID ┆ DATE_PREV ┆ DATE ┆ REV_DIFF ┆ TOTAL_DAYS ┆ MONTH_START │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ date ┆ date ┆ i64 ┆ i64 ┆ date │
╞═════╪════════════╪════════════╪══════════╪════════════╪═════════════╡
│ 1 ┆ 2025-07-31 ┆ 2025-08-10 ┆ 5000 ┆ 11 ┆ 2025-07-01 │
│ 1 ┆ 2025-07-31 ┆ 2025-08-10 ┆ 5000 ┆ 11 ┆ 2025-08-01 │
│ 2 ┆ 2025-06-01 ┆ 2025-06-01 ┆ 2500 ┆ 1 ┆ 2025-06-01 │
│ 3 ┆ 2025-01-15 ┆ 2025-02-28 ┆ 60000 ┆ 45 ┆ 2025-01-01 │
│ 3 ┆ 2025-01-15 ┆ 2025-02-28 ┆ 60000 ┆ 45 ┆ 2025-02-01 │
└─────┴────────────┴────────────┴──────────┴────────────┴─────────────┘
It seems from here, there are 3 possible cases:
case
when DATE_PREV > MONTH_START then DAY_PREV_DAYS
when SAME_YEAR_MONTH(DATE, MONTH_START) then DATE_DAYS
else MONTH_DAYS
end
.when() can be used to construct similar logic.
(
df
.lazy()
.with_columns(
(pl.col.DATE - pl.col.DATE_PREV).dt.total_days().alias("TOTAL_DAYS") + 1,
pl.date_ranges(pl.col.DATE_PREV.dt.month_start(), pl.col.DATE.dt.month_end(), interval="1mo").alias("MONTH_START")
)
.explode("MONTH_START")
.with_columns(
pl.when(pl.col.DATE_PREV > pl.col.MONTH_START)
.then(pl.col.DATE_PREV.dt.days_in_month() + 1 - pl.col.DATE_PREV.dt.day())
.when(
pl.col.DATE.dt.year() == pl.col.MONTH_START.dt.year(), # .dt.to_string("%y%m") was a bit slower
pl.col.DATE.dt.month() == pl.col.MONTH_START.dt.month(),
)
.then(pl.col.DATE.dt.day())
.otherwise(pl.col.MONTH_START.dt.days_in_month())
.alias("MONTH_DAYS")
)
.with_columns(
(pl.col.REV_DIFF * (pl.col.MONTH_DAYS / pl.col.TOTAL_DAYS)).alias("REVENUE")
)
.collect(engine="streaming")
)
shape: (5, 8)
┌─────┬────────────┬────────────┬──────────┬────────────┬─────────────┬────────────┬──────────────┐
│ ID ┆ DATE_PREV ┆ DATE ┆ REV_DIFF ┆ TOTAL_DAYS ┆ MONTH_START ┆ MONTH_DAYS ┆ REVENUE │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ i64 ┆ date ┆ date ┆ i64 ┆ i64 ┆ date ┆ i8 ┆ f64 │
╞═════╪════════════╪════════════╪══════════╪════════════╪═════════════╪════════════╪══════════════╡
│ 1 ┆ 2025-07-31 ┆ 2025-08-10 ┆ 5000 ┆ 11 ┆ 2025-07-01 ┆ 1 ┆ 454.545455 │
│ 1 ┆ 2025-07-31 ┆ 2025-08-10 ┆ 5000 ┆ 11 ┆ 2025-08-01 ┆ 10 ┆ 4545.454545 │
│ 2 ┆ 2025-06-01 ┆ 2025-06-01 ┆ 2500 ┆ 1 ┆ 2025-06-01 ┆ 1 ┆ 2500.0 │
│ 3 ┆ 2025-01-15 ┆ 2025-02-28 ┆ 60000 ┆ 45 ┆ 2025-01-01 ┆ 17 ┆ 22666.666667 │
│ 3 ┆ 2025-01-15 ┆ 2025-02-28 ┆ 60000 ┆ 45 ┆ 2025-02-01 ┆ 28 ┆ 37333.333333 │
└─────┴────────────┴────────────┴──────────┴────────────┴─────────────┴────────────┴──────────────┘
Using the Streaming Engine yielded the fastest results in my testing, so I've added lazy and collect calls.