用电量查询填补时间空白
Posted
技术标签:
【中文标题】用电量查询填补时间空白【英文标题】:fill time gaps with power query 【发布时间】:2019-01-22 17:20:28 【问题描述】:我有以下数据
start stop status
+-----------+-----------+-----------+
| 09:01:10 | 09:01:40 | active |
| 09:02:30 | 09:04:50 | active |
| 09:10:01 | 09:11:50 | active |
+-----------+-----------+-----------+
我想用“被动”来填补空白
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 Query 语言中执行此操作?
【问题讨论】:
【参考方案1】:您可以尝试以下方法(我的前两个步骤 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)), listOfRecords0, (listState, currentIndex) =>
let
previousRecord = listOfRecordscurrentIndex-1,
currentRecord = listOfRecordscurrentIndex,
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 行会导致堆栈溢出(这可能是由于输入行的生成,可能不是新行的插入,不知道)。很明显,这种方法有局限性。
【讨论】:
请注意,如果您的数据未正确排序,则在移动索引值时需要小心。 是的,显示的数据(有问题)似乎已排序,所以我做了这个假设。如果不是,则需要在填补空白之前对其进行排序。 在对数万条记录执行此操作时,是否详细说明了此方法的性能? @intrixius 理论上,这种方法只查看(表的)每一行一次,因为它一次完成所有事情。它应该是 O(N) 并且尽可能高效(就复杂性而言)。但是,在实践中,我不知道是否还有其他更有效的方法,因为table
到 records
到 table
的转换可能无法很好地扩展(取决于 Power Query 的内部实现)。您可以使用自己的数据进行尝试——或使用List.Repeat
有效地将行数乘以 10k 以模拟该比例。我会这样做,但我是通过手机发帖的。
我已经用5000条记录测试了这种方法,但是在它还在运行的时候2分钟后取消了它......当我只保留500条记录时,大约需要7秒; 2000 行大约是 30 秒...【参考方案2】:
我想我可能有更好的解决方案。
从您的源表(假设它已排序)中,添加一个从 0
开始的索引列和一个从 1
开始的索引列,然后将表与自身合并,对索引列进行左外连接并展开start
专栏。
删除除stop
、status
和start.1
之外的列并过滤掉空值。
将列重命名为start
、status
和stop
,并将"active"
替换为"passive"
。
最后,将此表附加到您的原始表中。
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 类似的逻辑,但我认为应该比使用自定义函数更快,因为它将使用内置-in 可能高度优化的合并。
【讨论】:
不错的方法,我昨天想到了这个,但我认为它只会帮助确定在哪里插入行的初始部分。但我认为您关于合并高度优化的观点似乎是可能的,看看 OP 是否可以证实这一点会很有趣。好主意。 我刚刚用一组超过一百万个不同的开始时间对此进行了测试,并且查询的加载时间不到 30 秒。它几乎可以完全通过 GUI 完成! 非常酷,您的解决方案似乎更具可扩展性和性能。 OP 应该改变接受的解决方案。 让我也试试这个,我会带着我的结果回来并选择最佳答案:-) 这太完美了!它运行得非常顺利同时,我多次使用这个“添加索引(0)和添加索引(1)”原理来解决类似的问题,我必须找到以前的出现......【参考方案3】:我会这样处理:
-
复制第一个表。
将“主动”替换为“被动”。
删除
start
列。
将stop
重命名为start
。
通过在当前stop
时间之后的原始表中查找最早的start
时间来创建一个新的stop
列。
过滤掉这个新列中的空值。
将此表附加到原始表。
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]))
这将获取列/列表Source[start]
中的每个项目并将其与当前行中的时间进行比较。它只选择当前行中时间之后出现的那些,然后在该列表中取最小值以找到最早的那个。
【讨论】:
这也可以按预期工作,谢谢!由于Chilling的速度有点快,所以我接受了他的回答……您能否详细说明两者哪个性能更好?我需要对数万条记录执行此操作... 我猜它们都可以处理数万条记录。他的计算复杂度似乎较低,但很难预测性能。尝试他们两个,看看他们如何处理。如果它们都合理,那么如果您必须在一年内回来并对其进行修改,请选择您更容易理解和维护的那个。 我用更多数据测试了这种方法。我有一个 20.000 记录表;我忘了添加一个“保留顶部行”过滤器,所以它运行所有行......不幸的是,它使 Excel Power Query 无响应大约一分钟;当它再次响应时,它继续加载......之后我只保留了 500 行,它运行了 5 秒; 2000 行运行了 30 秒,但同样,excel 在一段时间内变得无响应......以上是关于用电量查询填补时间空白的主要内容,如果未能解决你的问题,请参考以下文章