用权力查询填补时间空白
fill time gaps with power query
我有以下数据
start stop status
+-----------+-----------+-----------+
| 09:01:10 | 09:01:40 | active |
| 09:02:30 | 09:04:50 | active |
| 09:10:01 | 09:11:50 | active |
+-----------+-----------+-----------+
我想用"passive"
填补空白
start stop status
+-----------+-----------+-----------+
| 09:01:10 | 09:01:40 | active |
| 09:01:40 | 09:02:30 | passive |
| 09:02:30 | 09:04:50 | active |
| 09:04:50 | 09:10:01 | passive |
| 09:10:01 | 09:11:50 | active |
+-----------+-----------+-----------+
如何使用 M 查询语言执行此操作?
您可以尝试如下操作(我的前两个步骤 someTable
和 changedTypes
只是为了在我这边重新创建您的示例数据):
let
someTable = Table.FromColumns({{"09:01:10", "09:02:30", "09:10:01"}, {"09:01:40", "09:04:50", "09:11:50"}, {"active", "active", "active"}}, {"start","stop","status"}),
changedTypes = Table.TransformColumnTypes(someTable, {{"start", type duration}, {"stop", type duration}, {"status", type text}}),
listOfRecords = Table.ToRecords(changedTypes),
transformList = List.Accumulate(List.Skip(List.Positions(listOfRecords)), {listOfRecords{0}}, (listState, currentIndex) =>
let
previousRecord = listOfRecords{currentIndex-1},
currentRecord = listOfRecords{currentIndex},
thereIsAGap = currentRecord[start] <> previousRecord[stop],
recordsToAdd = if thereIsAGap then {[start=previousRecord[stop], stop=currentRecord[start], status="passive"], currentRecord} else {currentRecord},
append = listState & recordsToAdd
in
append
),
backToTable = Table.FromRecords(transformList, type table [start=duration, stop=duration, status=text])
in
backToTable
这是我开始的(在 changedTypes
步骤):
这就是我的结局:
要与您现有的 M
代码集成,您可能需要:
- 从我的代码中删除
someTable
和 changedTypes
(并替换为您现有的查询)
- 将
listOfRecords
步骤中的 changedTypes
更改为调用最后一步的任何内容(否则,如果代码中没有 changedTypes
表达式,则会出现错误).
编辑:
除了我的回答,我的建议是:
尝试更改上面代码中的这一行:
listOfRecords = Table.ToRecords(changedTypes),
至
listOfRecords = List.Buffer(Table.ToRecords(changedTypes)),
我发现将列表存储在内存中显着减少了我的刷新时间(如果量化的话可能约为 90%)。我认为存在限制和缺点(例如,如果列表不适合),但对于您的用例可能没问题。
您是否遇到过类似行为?此外,不幸的是,我的基本图表表明代码总体上是非线性的复杂性。
最后说明:我发现生成和处理 100k 行导致堆栈溢出,同时刷新查询(这可能是由于生成输入行而不是插入新行,不知道).很明显,这种方法有局限性。
我会按如下方式处理:
- 复制第一个 table。
- 将"active"替换为"passive"。
- 删除
start
列。
- 将
stop
重命名为 start
。
- 通过查找原始 table 中当前
stop
时间之后发生的最早 start
时间来创建新的 stop
列。
- 在此新列中过滤掉空值。
- 将此 table 附加到原始 table。
M 代码看起来像这样:
let
Source = <...your starting table...>
PassiveStatus = Table.ReplaceValue(Source,"active","passive",Replacer.ReplaceText,{"status"}),
RemoveStart = Table.RemoveColumns(PassiveStatus,{"start"}),
RenameStart = Table.RenameColumns(RemoveStart,{{"stop", "start"}}),
AddStop = Table.AddColumn(RenameStart, "stop", (C) => List.Min(List.Select(Source[start], each _ > C[start])), type time),
RemoveNulls = Table.SelectRows(AddStop, each ([stop] <> null)),
CombineTables = Table.Combine({Source, RemoveNulls}),
#"Sorted Rows" = Table.Sort(CombineTables,{{"start", Order.Ascending}})
in
#"Sorted Rows"
上面唯一棘手的一点是自定义列部分,我在其中定义新列,如下所示:
(C) => List.Min(List.Select(Source[start], each _ > C[start]))
这会获取 column/list Source[start]
中的每个项目并将其与当前行中的时间进行比较。它只选择当前行中时间之后出现的那些,然后对该列表进行最小化以找到最早的一个。
我想我可能有更好的解决方案。
从您的来源 table(假设它已排序),添加一个从 0
开始的索引列和一个从 1
开始的索引列,然后合并 table本身在索引列上进行左外连接并展开 start
列。
删除 stop
、status
和 start.1
以外的列并过滤掉空值。
将列重命名为 start
、status
和 stop
,并将 "active"
替换为 "passive"
。
最后,将此 table 附加到您原来的 table。
let
Source = Table.RenameColumns(#"Removed Columns",{{"Column1.2", "start"}, {"Column1.3", "stop"}, {"Column1.4", "status"}}),
Add1Index = Table.AddIndexColumn(Source, "Index", 1, 1),
Add0Index = Table.AddIndexColumn(Add1Index, "Index.1", 0, 1),
SelfMerge = Table.NestedJoin(Add0Index,{"Index"},Add0Index,{"Index.1"},"Added Index1",JoinKind.LeftOuter),
ExpandStart1 = Table.ExpandTableColumn(SelfMerge, "Added Index1", {"start"}, {"start.1"}),
RemoveCols = Table.RemoveColumns(ExpandStart1,{"start", "Index", "Index.1"}),
FilterNulls = Table.SelectRows(RemoveCols, each ([start.1] <> null)),
RenameCols = Table.RenameColumns(FilterNulls,{{"stop", "start"}, {"start.1", "stop"}}),
ActiveToPassive = Table.ReplaceValue(RenameCols,"active","passive",Replacer.ReplaceText,{"status"}),
AppendQuery = Table.Combine({Source, ActiveToPassive}),
#"Sorted Rows" = Table.Sort(AppendQuery,{{"start", Order.Ascending}})
in
#"Sorted Rows"
这应该是 O(n) 复杂度,逻辑与@chillin 相似,但我认为应该比使用自定义更快功能,因为它将使用内置的合并,这可能是高度优化的。
我有以下数据
start stop status
+-----------+-----------+-----------+
| 09:01:10 | 09:01:40 | active |
| 09:02:30 | 09:04:50 | active |
| 09:10:01 | 09:11:50 | active |
+-----------+-----------+-----------+
我想用"passive"
填补空白 start stop status
+-----------+-----------+-----------+
| 09:01:10 | 09:01:40 | active |
| 09:01:40 | 09:02:30 | passive |
| 09:02:30 | 09:04:50 | active |
| 09:04:50 | 09:10:01 | passive |
| 09:10:01 | 09:11:50 | active |
+-----------+-----------+-----------+
如何使用 M 查询语言执行此操作?
您可以尝试如下操作(我的前两个步骤 someTable
和 changedTypes
只是为了在我这边重新创建您的示例数据):
let
someTable = Table.FromColumns({{"09:01:10", "09:02:30", "09:10:01"}, {"09:01:40", "09:04:50", "09:11:50"}, {"active", "active", "active"}}, {"start","stop","status"}),
changedTypes = Table.TransformColumnTypes(someTable, {{"start", type duration}, {"stop", type duration}, {"status", type text}}),
listOfRecords = Table.ToRecords(changedTypes),
transformList = List.Accumulate(List.Skip(List.Positions(listOfRecords)), {listOfRecords{0}}, (listState, currentIndex) =>
let
previousRecord = listOfRecords{currentIndex-1},
currentRecord = listOfRecords{currentIndex},
thereIsAGap = currentRecord[start] <> previousRecord[stop],
recordsToAdd = if thereIsAGap then {[start=previousRecord[stop], stop=currentRecord[start], status="passive"], currentRecord} else {currentRecord},
append = listState & recordsToAdd
in
append
),
backToTable = Table.FromRecords(transformList, type table [start=duration, stop=duration, status=text])
in
backToTable
这是我开始的(在 changedTypes
步骤):
这就是我的结局:
要与您现有的 M
代码集成,您可能需要:
- 从我的代码中删除
someTable
和changedTypes
(并替换为您现有的查询) - 将
listOfRecords
步骤中的changedTypes
更改为调用最后一步的任何内容(否则,如果代码中没有changedTypes
表达式,则会出现错误).
编辑:
除了我的回答,我的建议是:
尝试更改上面代码中的这一行:
listOfRecords = Table.ToRecords(changedTypes),
至
listOfRecords = List.Buffer(Table.ToRecords(changedTypes)),
我发现将列表存储在内存中显着减少了我的刷新时间(如果量化的话可能约为 90%)。我认为存在限制和缺点(例如,如果列表不适合),但对于您的用例可能没问题。
您是否遇到过类似行为?此外,不幸的是,我的基本图表表明代码总体上是非线性的复杂性。
最后说明:我发现生成和处理 100k 行导致堆栈溢出,同时刷新查询(这可能是由于生成输入行而不是插入新行,不知道).很明显,这种方法有局限性。
我会按如下方式处理:
- 复制第一个 table。
- 将"active"替换为"passive"。
- 删除
start
列。 - 将
stop
重命名为start
。 - 通过查找原始 table 中当前
stop
时间之后发生的最早start
时间来创建新的stop
列。 - 在此新列中过滤掉空值。
- 将此 table 附加到原始 table。
M 代码看起来像这样:
let
Source = <...your starting table...>
PassiveStatus = Table.ReplaceValue(Source,"active","passive",Replacer.ReplaceText,{"status"}),
RemoveStart = Table.RemoveColumns(PassiveStatus,{"start"}),
RenameStart = Table.RenameColumns(RemoveStart,{{"stop", "start"}}),
AddStop = Table.AddColumn(RenameStart, "stop", (C) => List.Min(List.Select(Source[start], each _ > C[start])), type time),
RemoveNulls = Table.SelectRows(AddStop, each ([stop] <> null)),
CombineTables = Table.Combine({Source, RemoveNulls}),
#"Sorted Rows" = Table.Sort(CombineTables,{{"start", Order.Ascending}})
in
#"Sorted Rows"
上面唯一棘手的一点是自定义列部分,我在其中定义新列,如下所示:
(C) => List.Min(List.Select(Source[start], each _ > C[start]))
这会获取 column/list Source[start]
中的每个项目并将其与当前行中的时间进行比较。它只选择当前行中时间之后出现的那些,然后对该列表进行最小化以找到最早的一个。
我想我可能有更好的解决方案。
从您的来源 table(假设它已排序),添加一个从 0
开始的索引列和一个从 1
开始的索引列,然后合并 table本身在索引列上进行左外连接并展开 start
列。
删除 stop
、status
和 start.1
以外的列并过滤掉空值。
将列重命名为 start
、status
和 stop
,并将 "active"
替换为 "passive"
。
最后,将此 table 附加到您原来的 table。
let
Source = Table.RenameColumns(#"Removed Columns",{{"Column1.2", "start"}, {"Column1.3", "stop"}, {"Column1.4", "status"}}),
Add1Index = Table.AddIndexColumn(Source, "Index", 1, 1),
Add0Index = Table.AddIndexColumn(Add1Index, "Index.1", 0, 1),
SelfMerge = Table.NestedJoin(Add0Index,{"Index"},Add0Index,{"Index.1"},"Added Index1",JoinKind.LeftOuter),
ExpandStart1 = Table.ExpandTableColumn(SelfMerge, "Added Index1", {"start"}, {"start.1"}),
RemoveCols = Table.RemoveColumns(ExpandStart1,{"start", "Index", "Index.1"}),
FilterNulls = Table.SelectRows(RemoveCols, each ([start.1] <> null)),
RenameCols = Table.RenameColumns(FilterNulls,{{"stop", "start"}, {"start.1", "stop"}}),
ActiveToPassive = Table.ReplaceValue(RenameCols,"active","passive",Replacer.ReplaceText,{"status"}),
AppendQuery = Table.Combine({Source, ActiveToPassive}),
#"Sorted Rows" = Table.Sort(AppendQuery,{{"start", Order.Ascending}})
in
#"Sorted Rows"
这应该是 O(n) 复杂度,逻辑与@chillin 相似,但我认为应该比使用自定义更快功能,因为它将使用内置的合并,这可能是高度优化的。