我有一个包含 3 列的数据框,如下所示。
我想计算每种材料的潜在风险。对于每种材料,潜在风险从与剪切列中第一个非零值对应的日期开始,到剪切列中出现三个零值的日期结束。 Cut_Total 是通过计算该时间范围内的切割值总和来计算的。
材质 | 日期 | 切 |
---|---|---|
肥皂 | 2024年1月1日 | 1 |
肥皂 | 2024年1月2日 | 5 |
肥皂 | 2024年1月3日 | 0 |
肥皂 | 2024年1月4日 | 0 |
肥皂 | 2024年1月5日 | 0 |
肥皂 | 2024年1月6日 | 0 |
肥皂 | 2024年1月7日 | 2 |
肥皂 | 2024年1月8日 | 0 |
肥皂 | 2024年1月9日 | 2 |
肥皂 | 2024年10月1日 | 0 |
刷子 | 2024年1月1日 | 0 |
刷子 | 2024年1月2日 | 0 |
刷子 | 2024年1月3日 | 2 |
刷子 | 2024年1月4日 | 3 |
刷子 | 2024年1月5日 | 0 |
刷子 | 2024年1月6日 | 0 |
刷子 | 2024年1月7日 | 5 |
刷子 | 2024年1月8日 | 4 |
刷子 | 2024年1月9日 | 0 |
刷子 | 2024年10月1日 | 1 |
本次练习的预期结果如下:
材质 | 剪切_总计 | 风险_开始_日期 | 风险结束日期 |
---|---|---|---|
肥皂 | 6 | 2024年1月1日 | 2024年1月2日 |
肥皂 | 4 | 2024年1月7日 | 2024年1月9日 |
刷子 | 15 | 2024年1月3日 | 2024年10月1日 |
我试图使用窗口函数来做到这一点,但无法达到预期的结果。请帮助我解决这个问题的方法/代码。谢谢!
我试过这个:
window = Window.partitionBy("Material").orderBy("Date")
# Define a window to look forward up to three rows
forward_window = window.rowsBetween(0, 3)
# Add a new column that checks for non-zero 'Cut' values
df = df.withColumn("non_zero_cut", F.when(F.col("Cut") > 0, True).otherwise(False))
# Add a column to count consecutive zeros using forward-looking window
df = df.withColumn("consecutive_zeros", F.sum(F.when(F.col("Cut") == 0, 1).otherwise(0)).over(forward_window))
# Find rows where risk period starts (first non-zero 'Cut')
df = df.withColumn("start_of_risk", F.when((F.col("non_zero_cut") == True) & (F.lag("Cut", 1).over(window) == 0) | (F.lag("Cut", 1).over(window).isNull()), F.col("Date")).otherwise(None))
# Fill down 'start_of_risk' to propagate the start date
df = df.withColumn("Risk_Start_Date", F.max("start_of_risk").over(window.rowsBetween(Window.unboundedPreceding, 0)))
# Identify the end of the risk period (three consecutive 'Cut' values of zero)
df = df.withColumn("end_of_risk", F.when(F.col("consecutive_zeros") >= 3, F.lag("Date", -3).over(window)).otherwise(None))
# Fill down 'end_of_risk' to propagate the end date
df = df.withColumn("Risk_End_Date", F.max("end_of_risk").over(window.rowsBetween(Window.unboundedPreceding, 0)))
# Filter to keep only rows where risk period ends
df = df.filter(F.col("Risk_End_Date").isNotNull())
# Calculate 'Cut_Total' by summing 'Cut' values between 'Risk_Start_Date' and 'Risk_End_Date'
cut_risk_summary = df.groupBy("Material", "Risk_Start_Date", "Risk_End_Date").agg(F.sum("Cut").alias("Cut_Total"))
cut_risk_summary.display()
得到的结果如下,但不是预期的结果:
材质 | 风险_开始_日期 | 风险结束日期 | 剪切_总计 |
---|---|---|---|
肥皂 | 2024年1月1日 | 2024年1月5日 | 5 |
肥皂 | 2024年1月1日 | 2024年1月6日 | 0 |
肥皂 | 2024年1月1日 | 2024年1月7日 | 0 |
肥皂 | 2024年1月1日 | 2024年1月8日 | 0 |
肥皂 | 2024年1月7日 | 2024年1月8日 | 2 |
肥皂 | 2024年1月9日 | 2024年1月8日 | 2 |
让我们仔细考虑一下。我将在这个答案中使用本机 SQL,然后您可以按原样使用它或分解为函数调用。首先,让我们了解我们的风险开始日期是多少:
select main.Material, main.Date
from yourtable main
left join yourtable side
on main.Material = side.Material and
date_sub(main.Date, interval 1 day) = side.Date
where side.Material is null
这里我们找到不存在一对具有相同材料且
Date
为前一天的记录。
同样,风险结束日期:
select main.Material, main.Date
from yourtable main
left join yourtable side
on main.Material = side.Material and
date_add(main.Date, interval 1 day) = side.Date
where side.Material is null
好的。因此,我们的记录将通过查询表、连接开始日期和结束日期而得到,没有重叠:
select yourtable.Material, start_date.Date as Risk_Start_Date, end_date.Date as Risk_EndDate, sum(yourtable.Cut) as Cut_Total
from yourtable
join (
select main.Material, main.Date
from yourtable main
left join yourtable side
on main.Material = side.Material and
date_sub(main.Date, interval 1 day) = side.Date
where side.Material is null
) start_date
on yourtable.Date >= start_date.Date
join (
select main.Material, main.Date
from yourtable main
left join yourtable side
on main.Material = side.Material and
date_add(main.Date, interval 1 day) = side.Date
where side.Material is null
) end_date
on yourtable.Date <= end_date.Date
left join (
select main.Material, main.Date
from yourtable main
left join yourtable side
on main.Material = side.Material and
date_sub(main.Date, interval 1 day) = side.Date
where side.Material is null
) nonexistent_start_date
on nonexistent_start_date between date_add(start_date.Date interval 1 day) and date_sub(end_date.Date interval 1 day)
left join (
select main.Material, main.Date
from yourtable main
left join yourtable side
on main.Material = side.Material and
date_add(main.Date, interval 1 day) = side.Date
where side.Material is null
) nonexistent_end_date
on nonexistent_end_date between date_add(start_date.Date interval 1 day) and date_sub(end_date.Date interval 1 day)
where (nonexistent_start_date.Material is null) and
(nonexistent_end_date.Material is null)
group by yourtable.Material, start_date.Date, end_date.Date
我们在这里应用相同的逻辑,我们将开始日期和结束日期连接到我们的记录中,然后,为了排除过度延伸的间隔,我们将连接与假设的开始日期和结束日期之间以及那些没有进一步间隔的间隔连接起来。中间的开始日期和结束日期是我们正在寻求的实际结果。现在这已经很清楚了,我们对结果进行分组,以便我们可以计算切割的总和。
当然,如果我们为开始日期和结束日期创建临时表并插入选择适当的记录并使用它们而不是此处的子选择,则可以大大简化此解决方案。