我收到预测给定类别值的每日文件。 其中FileDate = FcstDate,值FcstVal实际上是实际的实际值。 现在我正在使用Excel Power Query(XL'16:获取&变换)轻松地将几十个文件一起拖入类似下面的表格(400k +行,现实中的18个级别)中。匹配预测值与实际值
我需要说的是,对于1-1,1-2类别AA | AC | null预测分别为1-3,但实际值为43,对于所有其他行。大多数但不是全部的独特行组合在文件之间是常见的。最终,我将不得不担心处理重命名的级别...
Table.Partition,Table.FillUp,Table.FromPartitions Power Query函数完美地表达了逻辑,但Power Query太慢了,因为它似乎多读取每个非常大的.xlsx文件(+每行1个?!),因为我需要一个包含所有不同类别级别的索引表&预测分区日期。
现在我降低到一个excel表使用如下公式: =SUMIFS([ActualVal], [Lvl1],[@[Lvl1]], [Lvl2],[@[Lvl2]], [Lvl3],[@[Lvl3]], [FileDt]],[@[FcstDt]], [@[Eq]]="Y")
然而,这要求所有空格设置为“空”,改变与“=”或“>”,等,并且开始值需要小时来计算。
我一直在试图学习PowerPivot/DAX,因为我知道它能够有效地过滤&计算大型数据集。我希望有一个解决方案,可以将DAX计算的“上下文”设置为通过老式的Excel公式将同一行的引用设置为&将值移到我想要的列中 - 但我还没有弄清楚。
我非常喜欢PowerPivot解决方案,如果可能的话,但如果没有,我有时可以理解python/pandas。但是,我们坚持使用来自第三方提供商的Excel输入。
Lvl1 | Lvl2 | Lvl3 | FileDt | FcstDt | Eq | FcstVal | ActualVal | Wanted! 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 file: ___________________________________________________________________ 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 (null) 1-2 1-3 44 43 1-3 file: ___________________________________________________________________ (missing row AA|AB!) 1-3 1-3 Y (null) (null) (null) AA AC (null) 1-3 1-3 Y 43 43 43 AA AB (null) 1-3 1-4 108 (null) AA AC (null) 1-3 1-4 42 (null)
编辑:
我会分享我的代码,因为某些部分可能是有用的人,和我的问题可能是在其他地方。
我的策略是根据打开的Excel中的表格加载一组工作簿。我应用了一个简单的函数来从工作簿内容中提取我想要的表,然后还应用一个函数在尽可能多的表上进行处理,同时仍然分离,认为多线程可能更好地利用,因为它们仍然是独立的是对的吗?)。
这结束了第一个查询:。我宁愿停在这里,如果可以完成剩下的工作(使用最终的Table.Combine,如果需要的话),可以使用PowerPivot。
在Power Query中,我必须组合这些表 - 两次。第一个包含所有字段,第二个包含所有表中不同的分组字段(不包含值或As-of Date字段)。不能使用单个(即第一个)表格,因为分组组合可能存在于不在第一个表格中的后续表格中,反之亦然。这个独特的表格获得一个索引。
我通过Table.NestedJoin加入第二个到第一个&只从联合列中提取索引。这使我可以将数据划分为仅具有相同预测日期组的分区。在这里,我可以填充按钮,因为在Prep_Data_Table函数中,按照日期降序对表进行预先排序,所以实际值(如果有的话)向下流到同一组的其他部分,并且不再进一步。
之后,只需重新组合表格。
CODE:
FieldMetadata保持数据类型&为字段的订购信息。 源文件保存路径名称&是否加载指定的文件。
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
问:是否有资格作为一个封闭?
问题:无论我使用Table.FromPartitions还是仅使用Table.Combine重新组合表,它都无关紧要吗?有什么不同?
问题:Fast Data Load究竟做了什么?它是什么时候/它没有影响?
问:是否有任何性能优势来指定所有类型(x表格,y表格,z表示数字等)?
问题:我读了一些文件,让..in只是记录的句法糖。我开始喜欢唱片,因为所有中间值都可用。任何性能影响?
问题:数字类型之间有什么区别? Int32.Type与Int64.Type?
你好。 如果问题仍然有效,请分享一些(也许是最大的)文件和代码吗?你也可以编辑你的文章并描述你在代码中实现的逻辑。有些行为对我来说似乎过分。 请同时清楚(甚至一步一步)说明您要进行的数据转换,如下所示:1.过滤掉空行和无效数据; 2.应用另一个(什么?)过滤器; 3.做别的事;等等 你的问题看起来相当具有挑战性! :) – Eugene