我在 excel 中有 3 个时间段 - 我需要知道最长连续时间段的持续时间
I have 3 time periods in excel - I need to know the duration of the longest continuous period
请帮忙!
理想情况下,我真的很想只使用公式来解决这个问题 - 而不是 VBA 或任何我认为 'fancy'.
我为一个奖励持续参与的项目工作。我们有三个(有时更多)参与时间段可以重叠 and/or 可以有没有参与的空间。神奇的数字是 84 天的持续参与。我们一直在手动审查每一行(数百行),以查看时间段加起来是否连续参与了 84 天,没有不活动的时间。
在 link 中有一张我们工作的摘要图片。例如第 3 行,3 个时间段中的任何一个都没有 84 天,但前 2 个时间段加起来包括连续 120 天。日期不会按日期顺序出现 - 例如早期参与可能会在第 3 期列出。
非常期待您的建议。
安妮
不幸的是,Gap 和 Island 似乎无法启动,因为我认为如果没有 VBA 或大量帮助列,您将无法使用它,而且开始日期需要按顺序排列.很遗憾,因为最长的 continuous 任务时间(也就是最大的岛)很容易从 VBA 版本中掉线,并且可以说它比下面的数组公式版本更容易理解.
继续选项 2,如果您有 Excel 365,您可以使用 Sequence 生成特定范围内的日期列表,然后检查每个日期是否都属于订婚期之一像这样:
=LET(array,SEQUENCE(Z-Z+1,1,Z),
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):INDEX(Sheet1!$A:$A,Sheet1!$Z)
则公式为:
= SUM(--(((ROW(Dates)>=A3) * (ROW(Dates)<=C3) +( ROW(Dates)>=E3) * (ROW(Dates)<=G3) + (ROW(Dates)>=I3) * (ROW(Dates)<=K3))>0))
您可能需要使用 CtrlShiftEnter 或使用 Sumproduct 输入总和
编辑
正如@Qualia 敏锐地指出的那样,您想要最长的持续 参与时间。这可以通过将频率应用于第一个公式来找到:
=LET(array,SEQUENCE(Z-Z+1,1,Z),
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))))
@TomSharpe 向您展示了一种使用公式解决此问题的方法。如果你有超过三个时间段,你将不得不修改它。
不确定您是否会认为 Power Query 解决方案“太花哨”,但它确实允许无限数量的时间段,如示例中所示。
有了 PQ,我们
- 为每对 start/end
构造所有连续日期的列表
- 合并每一行的列表,删除重复项
- 对每一行的结果日期列表应用间隙和岛技术
- 计算每个“岛屿”的条目数和return最大值
请注意:我计算了开始日期和结束日期。在你的日子栏中,你没有(除了一个例子)。如果你想同时计算两者,请保持代码不变;如果你不这样做,我们可以做一个小修改
使用 Power Query
- 创建一个table,其中排除第一行合并单元格
- 按照我在屏幕截图中显示的格式重命名 table 列,因为 table 中的每一列 header 必须具有不同的名称。
- Select 该数据中的某个单元格 Table
Data => Get&Transform => from Table/Range
- 当 PQ 编辑器打开时:
Home => Advanced Editor
- 记下第 2 行中的 Table 名称
- 粘贴下面的 M 代码代替您看到的内容
- 将第 2 行中的 Table 名称更改回最初生成的名称。
- 阅读评论并探索
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"
请帮忙!
理想情况下,我真的很想只使用公式来解决这个问题 - 而不是 VBA 或任何我认为 'fancy'.
我为一个奖励持续参与的项目工作。我们有三个(有时更多)参与时间段可以重叠 and/or 可以有没有参与的空间。神奇的数字是 84 天的持续参与。我们一直在手动审查每一行(数百行),以查看时间段加起来是否连续参与了 84 天,没有不活动的时间。
在 link 中有一张我们工作的摘要图片。例如第 3 行,3 个时间段中的任何一个都没有 84 天,但前 2 个时间段加起来包括连续 120 天。日期不会按日期顺序出现 - 例如早期参与可能会在第 3 期列出。
非常期待您的建议。
安妮
不幸的是,Gap 和 Island 似乎无法启动,因为我认为如果没有 VBA 或大量帮助列,您将无法使用它,而且开始日期需要按顺序排列.很遗憾,因为最长的 continuous 任务时间(也就是最大的岛)很容易从 VBA 版本中掉线,并且可以说它比下面的数组公式版本更容易理解
继续选项 2,如果您有 Excel 365,您可以使用 Sequence 生成特定范围内的日期列表,然后检查每个日期是否都属于订婚期之一像这样:
=LET(array,SEQUENCE(Z-Z+1,1,Z),
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):INDEX(Sheet1!$A:$A,Sheet1!$Z)
则公式为:
= SUM(--(((ROW(Dates)>=A3) * (ROW(Dates)<=C3) +( ROW(Dates)>=E3) * (ROW(Dates)<=G3) + (ROW(Dates)>=I3) * (ROW(Dates)<=K3))>0))
您可能需要使用 CtrlShiftEnter 或使用 Sumproduct 输入总和
编辑
正如@Qualia 敏锐地指出的那样,您想要最长的持续 参与时间。这可以通过将频率应用于第一个公式来找到:
=LET(array,SEQUENCE(Z-Z+1,1,Z),
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))))
@TomSharpe 向您展示了一种使用公式解决此问题的方法。如果你有超过三个时间段,你将不得不修改它。
不确定您是否会认为 Power Query 解决方案“太花哨”,但它确实允许无限数量的时间段,如示例中所示。
有了 PQ,我们
- 为每对 start/end 构造所有连续日期的列表
- 合并每一行的列表,删除重复项
- 对每一行的结果日期列表应用间隙和岛技术
- 计算每个“岛屿”的条目数和return最大值
请注意:我计算了开始日期和结束日期。在你的日子栏中,你没有(除了一个例子)。如果你想同时计算两者,请保持代码不变;如果你不这样做,我们可以做一个小修改
使用 Power Query
- 创建一个table,其中排除第一行合并单元格
- 按照我在屏幕截图中显示的格式重命名 table 列,因为 table 中的每一列 header 必须具有不同的名称。
- Select 该数据中的某个单元格 Table
Data => Get&Transform => from Table/Range
- 当 PQ 编辑器打开时:
Home => Advanced Editor
- 记下第 2 行中的 Table 名称
- 粘贴下面的 M 代码代替您看到的内容
- 将第 2 行中的 Table 名称更改回最初生成的名称。
- 阅读评论并探索
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"