2016-10-26 16 views
2

特定のカテゴリの値を予測する毎日のファイルを受け取ります。 FileDate = FcstDateの場合、値FcstValは実際の実際の値です。 今、私はExcel Power Query(XL'16:Get & Transform)を使用して、数十のファイルを下のような表に簡単に取り込みます(実際には400k行、18レベル)。予測値と実際の値との一致

私は、On 1-1、1-2カテゴリAA | AC | NULLは1-3でそれぞれ60、44と予測されましたが、実際の値は43でした。行。ほとんどではなく、すべてではなく、一意の行の組み合わせはファイル間で共通です。最終的に私は名前の変更されたレベルを扱うことについて心配する必要があります...

テーブル・パーティション、テーブル・フィルアップ・テーブル・フロア・パーティション・パワー・クエリー・ファンクションはロジックを完全に表現しますが、非常に大きい.xlsxファイルを何度も何度も読み込みます(+ 1x行あたり?!)、すべての異なるカテゴリレベルを持つインデックステーブルが必要なため、悪化しました&パーティションを作成する予測日。

今、私はエクセルの表に、この式を使用して減少しています: =SUMIFS([ActualVal], [Lvl1],[@[Lvl1]], [Lvl2],[@[Lvl2]], [Lvl3],[@[Lvl3]], [FileDt]],[@[FcstDt]], [@[Eq]]="Y") しかし、これは、「NULL」に、すべての空白を設定する「=」または「>」で始まる値などを変更し、必要で計算するには時間が必要です。

PowerPivot/DAXの学習には、&大きなデータセットを効率的にフィルタリングできることが分かっています。私はDAX計算の 'コンテキスト'を旧式のExcel式で参照するのと同じ行に設定するソリューションを期待しています&値を私の 'wanted'カラムに移動します - しかし、私はそれを理解していません。

私は可能な限り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) 

EDIT:

いくつかの部分が他の人に役に立つかもしれないので、私は自分のコードを共有しましょう、と私の問題は、他の部分であるかもしれません。

私の戦略は、開いているExcelのテーブルごとにワークブックのセットをロードすることです。私は、ワークブックの内容から必要なテーブルを抽出するための単純な関数を適用し、テーブル上で可能な限り多くの処理を行う関数を適用して、まだ独立しているのでマルチスレッド化をより有効に活用できると考えていますそれは正しい?)。

これで、最初のクエリ:が終了します。残りの作業ができる場合は、ここで中止してPowerPivotを使用することをお勧めします(必要に応じて最終的なTable.Combineを使用)。

パワークエリーでは、テーブルを2回結合する必要があります。最初のフィールドにはすべてのフィールドがあり、2番目のフィールドには、すべてのテーブル(値フィールドまたはAs-Dateフィールドなし)のグループ化フィールドがあります。グループ化の組み合わせは、最初のテーブルにない後のテーブルに存在する可能性があるので、単一の(すなわち、第1の)テーブルを使用することはできない。その逆もある。&このDistinct Tableはインデックスを取得します。

私は最初のvia Table.NestedJoinに2番目のテーブルを結合します。&結合された列からインデックスのみを抽出します。これにより、データを同じ予測日&グループのみのパーティションに分割することができます。ここでは、テーブルがPrep_Data_Table関数の日付の降順で事前ソートされているため、実際の値が同じグループの他のグループに流れて、それ以上はないため、FillDownできます。

この後、テーブルを単純に再結合します。

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は数値など)を指定すると、パフォーマンス上の利点はありますか?

質問:いくつかのドキュメントでは、let..inはレコードのまわりの文法的な砂糖です。すべての中間値が利用可能であるため、レコードを優先し始めました。すべてのパフォーマンスの影響?

質問:数値型の違いはありますか? Int32.TypeとInt64.Type?

+0

こんにちは。 質問がまだ有効な場合は、おそらく最大のファイルとコードを共有してください。あなたの投稿を編集し、あなたのコードで実装したロジックを記述しますか?いくつかの行動は私にとっては過度に思われます。 次のように、実行するデータ変換を明確に(段階的に)明記してください。1.空の行と無効なデータを除外します。 2.別の(何?)フィルタを適用する。 3.何か他のことをする。 あなたの質問は非常に挑戦的です! :) – Eugene

答えて

0

XLSXファイルのサイズは非常に大きいですか?私はあなたが1行に1回ファイルを開く可能性が高いというあなたの考えに同意します。 XLSXがアーカイブ形式であり、各シートが大きなファイルである場合、ファイル内の検索は非常に遅くなるでしょう。

特に合計がRAMの半分より少なく、64ビットオフィスを実行している場合は、XLSXからのテーブルでTable.Bufferを呼び出すことで、Power Queryのパフォーマンスを大幅に向上させることができます。

また、XLSXデータを何らかの形でCSVソースに変換することができれば、毎回XLSXファイルをクラックさせないように料金を支払う必要はありません。または、Sql Serverのようなソースに列インデックスを使用してデータをロードすることができれば、クエリのスピードが上がるはずです。 (私たちは一般的にSql Serverへの「クエリー・フォールド」操作を提供しています.Sql Serverは、Power Queryで作成したものよりもはるかに強力なパフォーマンスヒューリスティックをクエリエンジンに持っています)。代わりにPower Pivotエンジンを使用できますあまりそれに精通していない。

+0

私はたくさんのRAMを持っていて、Table.Bufferを試しましたが、全く効果がないようです。 PQは〜20 MBの各ファイルをロードしましたが、各ファイルから一晩に1ギガバイトをダウンロードしたと言われるまで、ファイルを引き継ぎました。私は全体のネットワークモニタ上に来るデータを見ることができました。私はブックにロードされた各ブックのブックをロードするQueryでTable.Bufferを試してみました。 私はBufferがメモリ内のオブジェクト内のすべてのデータを熱心に評価してフリーズすると想定していましたが、確かに起こっていません – alazyworkaholic

0

つの別々のPERFの最適化:我々はこのようTable.FillUp実装しました:

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

パフォーマンスの観点からかなり悪いです。一度FillUpアクションを実行することができれば、データを保存してから新しいデータをクエリし、クエリのパフォーマンスを向上させるのに役立ちます。

関連する問題