2016-10-26 2 views
2

주어진 카테고리의 값을 예측하는 일별 파일이 수신됩니다. FileDate = FcstDate 인 경우 값 FcstVal은 실제로 실제의 실제 값입니다. 지금 Excel Power Query (XL'16 : Get & Transform)를 사용하여 수십 개의 파일을 아래 (400k + 행, 실제로는 18 레벨)와 유사한 테이블로 쉽게 가져옵니다. 1-3 (44)는 각각 널 (60)이 될 것으로 예상했지만, 실제 값은 다른 모든 위해 43 동감이었다 | AC가 |예측 된 값을 실제 값으로 일치

나는 1-1에서 1-2 종류 AA, 말할 수 있어야합니다 열. 대부분은 아니지만 모든 고유 한 행 조합은 파일간에 공통입니다. 결국 변경된 레벨을 처리하는 것에 대해 걱정해야 할 것입니다 ...

Table.Partition, Table.FillUp, Table.FromPartitions Power Query 함수는 로직을 완벽하게 나타내지 만 Power Query는 너무 느립니다. 내가 & 예보가 분할 날짜 모든 별개의 범주 수준으로 인덱스 테이블을 필요할 것 때문에 (행 당 + 1 배!이) 악화 각 매우 큰 .XLSX 파일을 여러 번 읽어 보시기 바랍니다. =SUMIFS([ActualVal], [Lvl1],[@[Lvl1]], [Lvl2],[@[Lvl2]], [Lvl3],[@[Lvl3]], [FileDt]],[@[FcstDt]], [@[Eq]]="Y") 그러나이 "="또는 ">", 등을 시작 '널 (NULL)', 변경 값으로 모두 공백 설정 필요 지금 나 엑셀 표에서 공식을 이용하여 감소하고있어

계산에 시간 걸립니다.

내가 효율적 & 큰 데이터 세트를 계산 필터링 할 수있어 이해하기 때문에의 PowerPivot/DAX를 배우려고 노력했습니다. DAX 계산의 '컨텍스트'를 구식 엑셀 수식을 통해 참조하는 동일한 행으로 설정하는 솔루션을 기대합니다. & 값을 '원한'열로 이동합니다. 그러나 그것을 이해하지 못했습니다. .

나는 가능하면 많이가 된 PowerPivot 솔루션을 선호 싶지만,하지 않을 경우, 가끔 파이썬/팬더의 감각을 만들 수 있습니다. 그러나 타사 제공 업체의 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에서 테이블 당으로 통합 문서의 집합을로드하는 것입니다. 간단한 함수를 적용하여 통합 문서 내용에서 원하는 테이블을 추출한 다음 테이블에서 가능한 한 많은 처리를 수행하는 함수를 적용하여 여전히 분리 된 상태에서 멀티 스레딩이 여전히 독립적이므로 더 잘 활용 될 수 있다고 생각합니다. 그게 맞습니까?).

첫 번째 쿼리 :가 종료됩니다. 나는 여기서 멈추고 PowerPivot이 나머지를 할 수 있다면 (필요한 경우 최종 Table.Combine으로) 선호한다.

Power Query에서 필자는 테이블을 두 번 결합해야합니다. 첫 번째 필드에는 모든 필드가 있고 두 번째 필드에는 값 또는 As-Date 필드가없는 모든 테이블의 고유 한 그룹화 필드가 있습니다. 그룹화 조합이 첫 번째 테이블에없는 후속 테이블에 존재할 수 있기 때문에 단일 (즉, 첫 번째) 테이블을 사용할 수 없습니다. 그 반대의 경우도 마찬가지입니다. & 이 고유 표는 색인을 얻습니다.

난 Table.NestedJoin & 추출물 통해 제 내지 제 접합부 열의 인덱스 만 가입. 이를 통해 데이터를 동일한 예측일 & 그룹 만있는 파티션으로 나눌 수 있습니다.PrepareData_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 
,174,

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을 사용하여 테이블을 재결합하는 것이 중요합니까? 차이점이 뭐야?

질문 : 빠른 데이터로드는 실제로 무엇을합니까? 언제/그게 효과가 있지 않습니까?

질문 : 모든 유형 (x는 표, y는 목록, z는 숫자 등)을 지정하면 성능상의 이점이 있습니까?

질문 : 일부 문서에서는 let..in이 레코드 주위의 구문 설탕이라는 것을 읽었습니다. 중간 값을 모두 사용할 수 있으므로 레코드를 선호하기 시작했습니다. 성능에 미치는 영향은 무엇입니까?

질문 : 숫자 유형간에 차이가 있습니까? Int32.Type 대 Int64.Type?

+0

안녕하세요. 질문이 여전히 유효하면 일부 (아마도 가장 큰) 파일과 코드를 공유 할 수 있습니까? 또한 게시물을 편집하고 코드에서 구현 한 로직을 설명하십시오. 어떤 행동은 나에게 과도한 것처럼 보입니다. 다음과 같이 명확하게 (단계별로) 데이터 변환을 명시하십시오. 1. 빈 행과 유효하지 않은 데이터를 필터링하십시오. 2. 다른 (무엇?) 필터를 적용합니다. 3. 다른 일을하십시오. 등등. 당신의 질문은 아주 도전적 보인다! :) – Eugene

답변

0

XLSX 파일의 크기는 얼마나 큽니까? 나는 우리가 한 줄에 한 번씩 파일을 여는 것 같아서 당신의 생각에 동의합니다. XLSX가 아카이브 형식이고 각 시트가 큰 파일이기 때문에 파일 내에서 찾기가 매우 느릴 것입니다.

특히 총 RAM이 RAM의 절반보다 작고 64 비트 사무실을 실행하는 경우 XLSX에서 오는 테이블에서 Table.Buffer을 호출하여 Power Query 성능을 크게 향상시킬 수 있습니다.

또는 어떻게 든 XLSX 데이터를 CSV 소스로 변환 할 수 있으면 매번 XLSX 파일을 비싼 가격을 지불하지 않아도됩니다. 또는 열 인덱스가있는 SQL Server와 같은 원본에 데이터를로드 할 수 있으면 쿼리 속도가 빨라집니다. (우리는 일반적으로 Power Query에서 만든 것보다 쿼리 엔진에 훨씬 더 강력한 성능 추론을 가진 Sql Server에 대해 "쿼리 배"작업을 수행합니다.) 대신 Power Pivot 엔진을 사용할 수도 있지만 그리 친숙하지 않다.

+0

나는 RAM이 충분하고 Table.Buffer를 시도했지만 전혀 효과가없는 것으로 보였다. PQ는 각 파일의 ~ 20MB를로드했지만 파일이 밤마다 1GB 이상 다운로드되었다고 말할 때까지 파일을 반복해서 가져 왔습니다. 나는 전체 시간 동안 네트워크 모니터를 통해 오는 데이터를 볼 수 있었다. 통합 문서를로드하는 쿼리에서 Table.Buffer를 시도했는데로드 된 각 통합 문서에서 한 번에 내 코드의 거의 모든 테이블 개체에 절망적이었습니다. 저는 Buffer가 메모리에있는 객체의 모든 데이터를 열심히 평가하고 동결한다고 가정 했었지만 확실히 그렇지 않습니다. – alazyworkaholic

0

한 별도의 반환 한 최적화는 : 우리는 다음과 같이 Table.FillUp 구현했습니다 :

성능 측면에서 매우 나쁜
table => Reverse(FillDown(Reverse(table))) 

. FillUp 작업을 한 번 수행 할 수있는 경우 데이터를 저장 한 다음 새 데이터를 쿼리하면 성능을 쿼리하는 데 도움이됩니다.

관련 문제