我在 excel 中有 3 个时间段 - 我需要知道最长连续时间段的持续时间

Posted

技术标签:

【中文标题】我在 excel 中有 3 个时间段 - 我需要知道最长连续时间段的持续时间【英文标题】:I have 3 time periods in excel - I need to know the duration of the longest continuous period 【发布时间】:2021-10-02 02:35:42 【问题描述】:

请帮忙!

理想情况下,我真的很想只使用公式来解决这个问题 - 而不是 VBA 或任何我认为“花哨”的东西。

我为一个奖励持续参与奖金的计划工作。我们有三个(有时更多)参与时间段,这些时间段可能重叠和/或可能有不参与的空间。神奇的数字是连续订婚84天。我们一直在手动检查每条线路(数百条线路),以查看这些时间段加起来是否达到 84 天的连续参与,没有不活动的时间段。

在链接中有一张我们使用的摘要的图片。例如,第 3 行在 3 个时间段中的任何一个中都没有 84 天,但前 2 个时间段的组合包括 120 连续天。日期不会按日期顺序显示 - 例如早期参与可能会在第 3 期列出。

非常期待您的建议。

安妮

【问题讨论】:

你已经尝试过哪些公式? 我真的不知道从哪里开始。我在想一个嵌套的 IF 语句,在日期上使用某种最大减最小值,但是由于三个时期的日期不是按日期顺序排列的,所以这不起作用:-( 有两种主要方法可以做到这一点-(1)差距和岛一***.com/questions/53572815/…(2)从最早开始日期和最晚结束日期之间的所有天数和交叉关闭那些不在使用时间段内的。你能说出最早的开始日期和最晚的结束日期可能是什么(例如,它们都在 2021 年吗?) 你有 Excel 365 吗? 嗨,Tom - 是的,我有 Excel 365。非常感谢您的建议。今天早上我会试一试,看看情况如何。非常感谢你! 【参考方案1】:

@TomSharpe 向您展示了一种使用公式解决此问题的方法。如果您有超过三个时间段,则必须对其进行修改。

不确定您是否会认为 Power Query 解决方案“太花哨”,但它确实允许无限数量的时间段,如您在示例中所示。

通过 PQ,我们

为每对开始/结束构建所有连续日期的列表 合并每一行的列表,删除重复项 对每行生成的日期列表应用间隙和孤岛技术 统计每个“岛”的条目数并返回最大值

请注意:我计算了开始日期和结束日期。在您的天数列中,您没有(除了一个实例)。如果您想同时计算两者,请保持代码不变;如果您不这样做,我们可以进行小修改

使用 Power Query

创建一个排除第一行合并单元格的表格 按照我在屏幕截图中显示的格式重命名表格列,因为表格中的每个列标题都必须有不同的名称。 选择该数据表中的某个单元格 Data => Get&Transform => from Table/Range 当 PQ 编辑器打开时:Home => Advanced Editor 记下第 2 行中的表 Name 粘贴下面的 M 代码代替您看到的内容 将第 2 行中的表名称更改回最初生成的名称。 阅读 cmets 并探索 Applied Steps 以更好地理解算法

M 代码编辑代码以对日期列表进行排序以处理某些情况

let
    Source = Excel.CurrentWorkbook()[Name="Table2"][Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,"Start P1", type datetime, "Comment1", type text, "End P1", type datetime, "Days 1", Int64.Type, "Start P2", type datetime, "Comment2", type text, "End P2", type datetime, "Days 2", Int64.Type, "Start P3", type datetime, "Comment3", type text, "End P3", type datetime, "Days 3", Int64.Type),

//set data types for columns 1/5/9... and 3/7/11/... as date
dtTypes = List.Transform(List.Alternate(Table.ColumnNames(#"Changed Type"),1,1,1), each _,Date.Type),
typed = Table.TransformColumnTypes(#"Changed Type",dtTypes),

//add Index column to define row numbers
rowNums = Table.AddIndexColumn(typed,"rowNum",0,1),

//Unpivot except for rowNum column
    #"Unpivoted Other Columns" = Table.UnpivotOtherColumns(rowNums, "rowNum", "Attribute", "Value"),

//split the attribute column to filter on Start/End => just the dates
//then filter and remove the attributes columns
    #"Split Column by Delimiter" = Table.SplitColumn(#"Unpivoted Other Columns", "Attribute", Splitter.SplitTextByEachDelimiter(" ", QuoteStyle.Csv, false), "Attribute.1", "Attribute.2"),
    #"Changed Type1" = Table.TransformColumnTypes(#"Split Column by Delimiter","Attribute.1", type text, "Attribute.2", type text),
    #"Removed Columns" = Table.RemoveColumns(#"Changed Type1","Attribute.2"),
    #"Filtered Rows" = Table.SelectRows(#"Removed Columns", each ([Attribute.1] = "End" or [Attribute.1] = "Start")),
    #"Removed Columns1" = Table.RemoveColumns(#"Filtered Rows","Attribute.1"),
    #"Changed Type2" = Table.TransformColumnTypes(#"Removed Columns1","Value", type date, "rowNum", Int64.Type),

//group by row number
//generate date list from each pair of dates
//combine into a single list of dates with no overlapped date ranges for each row
    #"Grouped Rows" = Table.Group(#"Changed Type2", "rowNum", 
        "dateList", (t)=> List.Sort(
            List.Distinct(
                List.Combine(
                    List.Generate(
                        ()=>[dtList=List.Dates(
                                t[Value]0,
                                Duration.TotalDays(t[Value]1-t[Value]0)+1 ,
                                #duration(1,0,0,0)),idx=0],
                        each [idx] < Table.RowCount(t),
                        each [dtList=List.Dates(
                                    t[Value][idx]+2,
                                    Duration.TotalDays(t[Value][idx]+3-t[Value][idx]+2)+1,
                                    #duration(1,0,0,0)),
                                idx=[idx]+2],
                        each [dtList]))))
            ),

//determine Islands and Gaps
    #"Expanded dateList" = Table.ExpandListColumn(#"Grouped Rows", "dateList"),

//Duplicate the date column and turn it into integers
    #"Duplicated Column" = Table.DuplicateColumn(#"Expanded dateList", "dateList", "dateList - Copy"),
    #"Changed Type3" = Table.TransformColumnTypes(#"Duplicated Column","dateList - Copy", Int64.Type),

//add an Index column
//Then subtract the index from the integer date
// if the dates are consecutive the resultant ID column will => the same value, else it will jump
    #"Added Index" = Table.AddIndexColumn(#"Changed Type3", "Index", 0, 1, Int64.Type),
    #"Added Custom" = Table.AddColumn(#"Added Index", "ID", each [#"dateList - Copy"]-[Index]),
    #"Removed Columns2" = Table.RemoveColumns(#"Added Custom","dateList - Copy", "Index"),

//Group by the date ID column and a Count will => the consecutive days
    #"Grouped Rows1" = Table.Group(#"Removed Columns2", "rowNum", "ID", "Count", each Table.RowCount(_), Int64.Type),
    #"Removed Columns3" = Table.RemoveColumns(#"Grouped Rows1","ID"),

//Group by the Row number and return the Maximum Consecutive days
    #"Grouped Rows2" = Table.Group(#"Removed Columns3", "rowNum", "Max Consecutive Days", each List.Max([Count]), type number),

//combine the Consecutive Days column with original table
    result = Table.Join(rowNums,"rowNum",#"Grouped Rows2","rowNum"),
    #"Removed Columns4" = Table.RemoveColumns(result,"rowNum")
in
    #"Removed Columns4"

【讨论】:

哇,@Ron,你肯定是 Power Query 的首选。直到现在我还没有想到这一点,但我想在 Power Query 中实现 gap-and-island(显然,我最喜欢解决这类问题的方法)不会太难。该方法最初来自SQL。我认为您需要等效的 Lag 函数来执行此操作。 @TomSharpe 这是我第一次看到这种问题,所以我想有更有效的方法。但我只是将三个日期范围变成了一个唯一日期列表;然后合并它们并确定合并列表中有多少子组(基于没有间隙的连续日期);然后计算每个子组的数量。这会类似于差距和孤岛分析吗?嗯,我可能也应该对列表进行排序。 我正在努力寻找原始参考资料——这是一个相当早的参考资料,但我认为在某处有更简单的解释。 blogs.solidq.com/en/sqlserver/packing-intervals 比我记忆中的还要难! @TomSharpe 很复杂,是的。 (=_=) 这是一个link to an algorithm 与我所做的类似。 嗨罗恩-谢谢!我以前从未使用过 Power Query,因此非常感谢您的分步说明 :-) 我收到“增量”参数超出范围的错误。任何想法为什么会发生这种情况?【参考方案2】:

不幸的是,Gap 和 Island 似乎无法启动,因为我认为您不能在没有 VBA 或大量帮助列的情况下使用它,而且开始日期需要按顺序排列。很遗憾,因为最长连续时间的任务(AKA 最大的岛)很容易退出 VBA 版本,可以说它比see this 下面的数组公式版本更容易理解。

继续选项 2,如果您有 Excel 365,则可以使用序列生成特定范围内的日期列表,然后检查每个日期是否属于这样的参与期之一:

=LET(array,SEQUENCE(Z$2-Z$1+1,1,Z$1),
period1,(array>=A3)*(array<=C3),
period2,(array>=E3)*(array<=G3),
period3,(array>=I3)*(array<=K3),
SUM(--(period1+period2+period3>0)))

假设 Z1 和 Z2 包含您感兴趣的日期范围的开始和结束(我使用了 1/1/21 和 31/7/21)。

如果您没有 Excel 365,则可以使用 Row 函数来生成日期列表。我建议使用名称管理器来创建一个命名范围日期:

=INDEX(Sheet1!$A:$A,Sheet1!$Z$1):INDEX(Sheet1!$A:$A,Sheet1!$Z$2)

那么公式就是:

= SUM(--(((ROW(Dates)>=A3) * (ROW(Dates)<=C3)  +( ROW(Dates)>=E3) * (ROW(Dates)<=G3) + (ROW(Dates)>=I3) * (ROW(Dates)<=K3))>0))

您可能必须使用 CtrlShiftEnter 或使用 Sumproduct 而不是 Sum。

编辑

正如@Qualia 敏锐地指出的那样,您希望持续参与的时间最长。这可以通过将频率应用于第一个公式来找到:

=LET(array,SEQUENCE(Z$2-Z$1+1,1,Z$1),
period1,(array>=A3)*(array<=C3),
period2,(array>=E3)*(array<=G3),
period3,(array>=I3)*(array<=K3),
onDays,period1+period2+period3>0,
MAX(FREQUENCY(IF(onDays,array),IF(NOT(onDays),array)))
)

non_365 版本变成

=MAX(FREQUENCY(IF((ROW(Dates)>=A3)*(ROW(Dates)<=C3)+(ROW(Dates)>=E3)*(ROW(Dates)<=G3)+(ROW(Dates)>=I3)*(ROW(Dates)<=K3),ROW(Dates)),
IF( NOT(  (ROW(Dates)>=A3)*(ROW(Dates)<=C3)+(ROW(Dates)>=E3)*(ROW(Dates)<=G3)+(ROW(Dates)>=I3)*(ROW(Dates)<=K3) ),ROW(Dates))))

【讨论】:

如果我正确理解了这个问题,那么用户正在寻找连续参与。您的公式似乎总结了所有日子,即使期间之间有休息。例如。在第 1 行中,连续部分将从 2021-02-01 变为 2021-06-01。 是的,你是对的,很好。还是可以用同样的方法,大概用Frequency包起来得到最长的连续部分。 嗨,汤姆 - 如果这是一个愚蠢的问题,我很抱歉,但我刚刚尝试了你的公式,但我无法让它发挥作用。您的屏幕截图正是我正在寻找的 - 完全参与和持续参与。将我的数据格式化为表格是否重要? 你能告诉我你尝试过哪个版本(Excel 365 或替代版本),以及当你尝试它时会发生什么(是否有错误消息?) 嗨,Tom,我正在使用的版本(抱歉,如果我提供了错误的信息)是 Microsoft Office Professional Plus 2019。我在单元格中收到 #NAME 消息。我有一个截图,但不知道我可以在这里发布它。

以上是关于我在 excel 中有 3 个时间段 - 我需要知道最长连续时间段的持续时间的主要内容,如果未能解决你的问题,请参考以下文章

3 Excel 中的可变加权平均值

需要为来自 R Shiny 中的 Excel 工作表的 3 个 InsertUI 字段建立依赖关系

Excel日期公式

导入 excel .csv 文件并将其添加到 phpMyAdmin 的列中

VBA Excel SendKeys 宏

如何将 EXCEL 转换为 SQL(我在 excel 中有 143864 行和 100 列)总计 48,316 KB