匹配预测值与实际值

我收到一个预测给定类别的值的日常文件。 当FileDate = FcstDate时,值FcstVal实际上是实际的实际值。 现在,我正在使用Excel Power Query(XL'16:Get&Transform)轻松地将几十个文件一起拖放到类似下面的表格(400k +行,现实中的18个级别)中。

我需要说的是,对1-1,1-2分类的AA | AC | null预测分别为1-3,43,64,44,但实际值为43。 大多数(但不是全部)独特的行组合在文件之间是常见的。 最终,我将不得不担心处理重命名的水平…

Table.Partition,Table.FillUp,Table.FromPartitions Power Query函数完美地expression了逻辑,但Power Query太慢了,因为它好像每次读取每个非常大的.xlsx文件( 每行加1x?! ),更糟糕,因为我需要一个索引表,所有不同的类别级别和预测date进行分区。

现在我简化为在Excel表中使用这个公式: =SUMIFS([ActualVal], [Lvl1],[@[Lvl1]], [Lvl2],[@[Lvl2]], [Lvl3],[@[Lvl3]], [FileDt]],[@[FcstDt]], [@[Eq]]="Y")然而,这需要将所有的空白值设置为“空”,改变以“=”或“”开头的值, >“等,并花费数小时来计算。

我一直在努力学习PowerPivot / DAX,因为我知道它能够有效地过滤和计算大型数据集。 我希望有一个解决scheme,将通过老式的Excel公式将DAX计算的“上下文”设置为同一行,并将值移到我想要的列中 – 但是我还没有弄清楚。

如果可能的话,我非常喜欢PowerPivot解决scheme,但是如果没有的话,我有时可以理解python / pandas。 但是,我们坚持使用来自第三方提供商的Excelinput。

     Lvl1 |  Lvl2 |  Lvl3 |  FileDt |  FcstDt | 公式|  FcstVal |  ActualVal | 通缉!
 1-1:________________________________________________________________________
      AA AB AD 1-1 1-1 Y 100 100 100
      AA AC AE 1-1 1-1 Y 50 50 50
      AA AB(null)1-1 1-2 110 105
      AA AC(null)1-1 1-2(null)45
      AA AB(null)1-1 1-3 120 105
      AA AC(null)1-1 1-3 70 43
 1-2档案:___________________________________________________________________
      AA AB(null)1-2 1-2 Y 105 105 105
      AA AC(null)1-2 1-2 Y 45 45 45
      AA AB(null)1-2 1-3 113(null)
      AA AC(空)1-2 1-3 44 43
 1-3档案:___________________________________________________________________
  (缺less行AA | AB!)1-3 1-3 Y(null)(null)(null)
      AA AC(空)1-3 1-3 Y 43 43 43
      AA AB(null)1-3 1-4 108(null)
      AA AC(空)1-3 1-4 42(null)

编辑:

我会分享我的代码,因为有些部分可能对别人有用,而我的问题可能在其他部分。

我的策略是根据打开的Excel中的表格加载一组工作簿。 我应用了一个简单的函数来从工作簿内容中提取我想要的表,然后还应用一个函数,尽可能在表上尽可能多的处理,同时仍然是分离的,认为multithreading可能更好地利用,因为它们仍然是独立的是对的吗?)。

这结束了第一个查询:。 我宁愿在这里停下来,如果可以做PowerPivot(如果需要的话,最后使用Table.Combine)。

在Power Query中,我必须将表格合并 – 两次。 第一个是所有字段,第二个是所有表中不同的分组字段(不包含值或As-of Date字段)。 不能使用单个(即第一个)表,因为分组组合可能存在于不在第一个表中的后续表中,反之亦然。 这个独特的表格得到一个索引。

我通过Table.NestedJoinjoin第二个到第一个,并从连接列中只提取索引。 这使我可以将数据划分为仅具有相同预测date和组的分区。 在这里我可以填充,因为这些表在Prep_Data_Table函数中按照date的先后顺序进行了预先sorting,所以实际值(如果有的话)向下stream到同一组的其他部分,而不再进一步。

之后,只需重新组合表格。

码:

FieldMetadata保存字段的数据types和订购信息。 来源包含path名和是否加载指定的文件。

ImportParameters:

 [ThisWB = Excel.CurrentWorkbook() Sources = ThisWB{[Name="Sources"]}[Content], FieldMetadata = ThisWB{[Name="FieldMetadata"]}, FieldTypes = Table.ToRows(GetCfg({"Type"})), CategoryFields = List.Buffer(List.Transform(List.Select(List.Transform(FieldTypes, each {List.First(_), TypeFromString(List.Last(_))}), each List.Last(_) = type text), each List.First(_))), CategoryFieldTypes = List.Buffer(List.Transform(FieldTypes, (_) => {List.First(_), TypeFromString(List.Last(_))})) 

GetCfg:

 let Cfg = (Columns as list) as table => let FilterList = List.Transform(Columns, each "[" & _ & "]" <> null"), ExpressionText = Text.Combine(FilterList, " and "), Source = Excel.CurrentWorkbook(){Name="FieldMetadata"]}[Content], #"Changed Type" = Table.TransformColumnTypes(Source, {{"Field", type text}, {"Type", type text"}, {"Grouping", Int32.Type}, {"Presentation"}, Int32.Type}}), Custom1 = Table.SelectColumns(#"Changed Type", List.Combine({{"Field"}, Columns})), #"Filtered Rows" = Table.SelectRows(Custom1, each Expression.Evaluate(ExpressionText, [_=_])) /* The above line is a bit of a mind bender. It lets me apply filteres without hard-coding column names. Very useful. Credit to http://www.thebiccountant.com/2016/03/08/select-rows-that-have-no-empty-fields-using-expression-evaluate-in-power-bi-and-power-query/ */ in #"Filtered Rows" in Cfg 

FieldSortOrder

 let SortOn = (SortOn as text) as list => let Source = ImportParameters[FieldMetadata], #"Changed Type" = Table.TransformColumnTypes(Source, {{"Field", type text}, {"Grouping", type number}}), SelectedSort = Table.SelectXolumns(Source, {"Field", SortOn}), RenamedSortColumn = Table.RenameColumns(SelectedSort, {{SortOn, "Sort"}}), NoNulls = Table.SelectRows(RenamedSortColumn, each ([Sort] <> null)), SortedFields = Table.Sort(NoNulls, {{"Sort", Order.Ascending}})[Field] in SortedFields in SortOn 

TypeFromString

 let Type = (TypeName as text) as type => let TypeNameFix = if TypeName = "Table" then "_Table" else TypeName, // because Table is a reserved word TypR = [Any=Any.Type, Binary=Binary.Type, // The whole list of types I could find. ... _Table=Table.Type, ... WebMethod=WebMethod.Type], TheType = try Record.Field(TypR, TypeNameFix) otherwise error [Reason="TypeName not found", Message="Parameter was not found among the list of types defined within the TypeFromString function.", in TheType in Type 

Extract_Data_Table:

 let Source = (Table as table) as table => let #"Filtered Rows" = Table.SelectRows(Table, each ([Kind] = "Table" and ([Item] = "Report Data" or [Item] = "Report_Data"))), #"Select Columns" = Table.SelectColumns(#"Filtered Rows", "Data"), DataTable = #"Select Columns"[Data]{0} in DataTable in Source 

Prep_Data_Table:

 let PrepParams = (HorizonEnd as date, CategoryFieldTypes as list) as function => let HorizonEnd = HorizonEnd, CategoryFieldTypes = List.Buffer(CategoryFieldTypes), Source = (InputTable as table, FileDate as date) as table => let EndFields = {"As-of Date", "PERIOD", "Actual", "Forecast"} as list, PeriodsAsDates = Table.TransformColumnTypes(InputTable, {{"PERIOD", type date}}), #"Remove Errors" = Table.RemoveRowsWithErrors(PeriodsAsDates, {"PERIOD"}), WithinHorizon = Table.SelectRows(#"Remove Errors", each ([PERIOD] <= HorizonEnd)), RenamedVAL = Table.RenameColumns(WithinHorizon, {"VAL", "Forecast"}), // Forecast was originally named VAL MovedActual = Table.AddColumn(RenamedVAL, "Actual", each if [PERIOD]=FileDate then (if [Forecast] is null then 0 else [Forecast]) else null), IncludesOfDate = Table.AddColumn(MovedActual, "As-of Date", each FileDate, Date.Type), AppliedCategoryFieldTypes = Table.TransformColumnTypes(IncludeAsOfDate, CategoryFieldTypes), TransformedColumns = Table.TransformColumns(AppliedCategoryFieldTypes, {{"{Values}", Text.Trim, type text}, {"Actual", Number.Abs, Currency.Type}, {"Forecast", Number.Abs, Currency.Type}}), Sorted = Table.Sort(TransformedColumns, {{"Actual", Order.Descending}}), // Descending order is important because Table.FillDown is more efficient than Table.FillUp OutputTable = Table.SelectColumns(Sorted, List.Distinct(List.Combine({List.Transform(CategoryFieldTypes, each List.First(_)), EndFields}))), Output = OutputTable in Output in Source in PrepParams 

工作簿:

 let // Import Data Source = ImportParameters[Sources], #"Changed Type" = Table.TransformColumnTypes(Source, {{"As-of Date", type date}, {"Folder Path", type text}, {"Tab", type text}, {"Load", type logical}}), #"Filtered Rows"= Table.SelectRows(#"Changed Type", each ([Load] = true)), WorkbookPaths = Table.AddColumn(#"Filtered Rows", "File Path", each [Folder Path] & [File], type text), LoadWorkbooks = Table.AddColumn(WorkbookPaths, "Data", each Excel.Workbook(File.Contents([File Path])) meta [#"As-of Date" = [#"As-of Date"]]), LoadDataTables = Table.TransformColumns(LoadWorkbooks, {"Data", each Extract_Data_Table(_) meta [#"As-of Date" = Value.Metadata(_)[#"As-of Date"]]}), PrepFunc = Prep_Data_Table(List.Max(LoadDataTables[#"As-of Date"]), ImportParameters[CategoryFieldTypes]), // This TransformColumns step references the column's list, not the table, so the As-of Date field of the column is out of scope. Use metadata to bring the As-of Date value into the same scope PrepDataTables = Table.TransformColumns(LoadDataTables, {"Data", each Table.Buffer(PrepFunc(_, Value.Metadata(_)[#"As-of Date"]))}), Output = Table.SelectColumns(PrepDataTables, {"Data", "As-of Date"}) in Output 

MakeComparison:

 let CategoryFields = ImportParameters[CategoryFields] DataTableList = Workbooks[Data], CategoryIndex = Table.AddIndexColumn(Table.Distinct(Table.Combine(List.Transform(DataTableList, each Table.SelectColumns(_, CategoryFields)))), "Index"), ListOfDataTablesWithNestedIndexTable = List.Transform(DataTableList, each Table.NestedJoin(_, CategoryFields, CategoryIndex, CategoryFields, "Index", JoinKind.Inner)), ListOfIndexedDataTables = List.Transform(ListOfDataTablesWithNestedIndexTable, each Table.TransformColumns(_, {"Index", each List.Single(Table.Column(_, "Index")) as number, type number})), Appended = Table.Combine(ListOfIndexedDataTables), Merged = Table.Join(CategoryIndex, "Index", Table.SelectColumns(Appended, {"As-of Date", "Actual", "Forecast", "Index"}), "Index"), Partitioned = Table.Partition(Merged, "Index", Table.RowCount(CategoryIndex), each _), CopiedActuals = List.Transform(Partitioned, each Table.FillDown(_, {"Actual"})), ToUnpartition = List.Transform(CopiedActuals, each {List.First(_[Index]), Table.RemoveColumns(_, {"Index"})}), UnPartitioned = Table.FromPartitions("Index", ToUnpartition, type number), Output = Unpartitioned in Output 

问题:是否合格作为closures?

问题:使用Table.FromPartitions还是简单地使用Table.Combine重新组合表是否重要? 有什么不同?

问题:Fast Data Load究竟做了什么? 什么时候它/它没有区别?

问题:是否有任何性能优势来指定所有types(x表格,y列表,z数字等)?

问:我读了一些文件,让..in只是logging的语法糖。 我已经开始喜欢logging,因为所有中间值都可用。 任何性能影响?

问题:数字types之间有什么区别? Int32.Type与Int64.Type?

你的XLSX文件有多大? 我同意你的想法,很可能我们每行打开一次文件。 鉴于XLSX是一种档案格式,每张纸是一个大文件,在文件内寻找将是非常缓慢的。

特别是如果总数不到RAM的一半,并且运行64位办公室,则可以通过在来自XLSX的表上调用Table.Buffer来显着提高Power Query性能。

或者,如果您可以以某种方式将您的XLSX数据转换为CSV源文件,那么您每次都不用花钱来解开XLSX文件。 或者,如果您可以将数据加载到具有列索引的Sql Server之类的源代码,那么这确实会加快您的查询速度。 (我们通常将查询操作转换为Sql Server,在查询引擎中的查询引擎比我们在Power Query中创build的查询引擎强大得多。)可能你可以使用Power Pivot引擎来代替它,但是我可以不是很熟悉。

一个单独的性能优化:我们这样实现了Table.FillUp:

 table => Reverse(FillDown(Reverse(table))) 

从性能angular度来看,这是非常糟糕的。 如果您可以执行一次FillUp操作,保存数据,然后查询新的数据,这将有助于查询性能。