Generating a glidepath in power BI

154 views Asked by At

I have generated a complex transformation in Power BI to generate a glide path from the following snippet of extracted data as shown below:

enter image description here

Note: This snippet of data isn't enough to produce the entire glide path shown.

The aim is to measure the status of profiles over time to December 15th 2023.

The status of each profile can be, Assigned, Started, PeerReviewStarted, Submitted, or Approved. The date for when each stage per profile commences is captured. Profiles which have no date as considered as 'Pending' though for now this is not considered.

Basically, I would like to provide the M code and Input data and see if someone better at transforming data can make it much more efficient as it takes a long time to load and really is just confusing.

The request:

Intuitively I feel this can be really improved as the M code is a bit of a hot mess.

Feel free to tweak or reinvent entirely.

The main request is to generate a glide path for each attribute of the profile on the same chart. In additon I have the Target, and attribute rate projections as linear projections (there may be a better way to predict this sort of data but linear was okay). To be honest the main request is just the 'glidepaths' but anything else is a bonus.

SAMPLE DATA (Please see onedrive file for all data):

ProfileID Assigned Started PeerReviewStarted Submitted Approved
INGPR0467 10/08/2023 null null null null
INGPR0007 22/08/2023 null null null null
INGPR0604 null null null null null
INGPR0448 13/09/2023 13/09/2023 02/11/2023 12/10/2023 null
INGPR0613 27/09/2023 27/09/2023 02/11/2023 null null
INGPR0538 07/11/2023 07/11/2023 10/11/2023 null null
INGPR0634 null null null null null
INGPR0625 10/08/2023 null null null null
INGPR0425 null null null null null
INGPR0442 null null null null null
INGPR0328 null null null null null
INGPR0513 null null null null null
INGPR0441 null null null null null
INGPR0499 26/07/2023 27/07/2023 null null null
INGPR0044 11/07/2023 null null null null
INGPR0478 null null null null null
INGPR0615 null null null null null
INGPR0096 11/07/2023 27/07/2023 null null null
INGPR0119 23/08/2023 04/10/2023 null null null
INGPR0097 11/07/2023 11/07/2023 02/08/2023 12/10/2023 17/11/2023
INGPR0361 null null null null null
INGPR0145 null null null null null
INGPR0385 null null null null null
INGPR0338 null null null null null
INGPR0452 22/08/2023 null null null null
INGPR0440 22/08/2023 null null null null
INGPR0564 null null null null null
INGPR0496 31/07/2023 31/07/2023 31/07/2023 08/08/2023 17/08/2023
INGPR0172 null null null null null
INGPR0395 null null null null null

M CODE VERSION 2 COMBINED A FEW STEPS BUT NOT NECESSARILY MORE EFFICIENT:

            let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("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", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [ProfileID = _t, Assigned = _t, Started = _t, PeerReviewStarted = _t, Submitted = _t, Approved = _t]),
    #"Replaced Value1" = Table.ReplaceValue(Source,null,"",Replacer.ReplaceValue,{"Assigned", "Submitted", "PeerReviewStarted", "Approved", "Started"}),
    RowCount = Table.RowCount(Source),
    #"Unpivoted Other Columns" = Table.UnpivotOtherColumns(#"Replaced Value1", {"ProfileID"}, "Attribute", "Value"),
    #"Changed Type" = Table.TransformColumnTypes(#"Unpivoted Other Columns",{{"Value", type date}}),
    #"Merged Queries" = Table.NestedJoin(#"Changed Type", {"Value"}, #"All Dates", {"Date"}, "All Dates", JoinKind.RightOuter),
    #"Expanded All Dates" = Table.ExpandTableColumn(#"Merged Queries", "All Dates", {"Date"}, {"Date"}),
    //StartDate = List.Min(#"Expanded All Dates"[Value]),
    //FinalDate = List.Max(#"Expanded All Dates"[Value]),
    #"Filtered Rows" = Table.SelectRows(#"Expanded All Dates", each [Date] >= List.Min(#"Expanded All Dates"[Value])),
    Custom1 = Table.AddColumn(#"Filtered Rows", "Gradient", each Number.Round(RowCount * (Duration.Days(List.Max(#"Expanded All Dates"[Value]) - [Date]) / Duration.Days(List.Max(#"Expanded All Dates"[Value]) - List.Min(#"Expanded All Dates"[Value]))), 0)),
    #"Sorted Rows5" = Table.Sort(Custom1,{{"Gradient", Order.Descending}}),
    #"Sorted Rows" = Table.Sort(#"Sorted Rows5",{{"Date", Order.Ascending}}),

    #"Added Index" = Table.AddIndexColumn(#"Sorted Rows", "Index", 1, 1),
    #"Grouped Rows1" = Table.Group(
    #"Added Index",
    {"Attribute"},
    {
        {
            "Grouped",
            each Table.AddIndexColumn(_, "Count", List.Max(#"Added Index"[Gradient]) - 1, -1, Int64.Type)
        }
    }
),
    #"Replaced Value" = Table.ReplaceValue(#"Grouped Rows1",null,"Pending",Replacer.ReplaceValue,{"Attribute"}),
    #"Expanded Grouped" = Table.ExpandTableColumn(#"Replaced Value", "Grouped", {"Date", "ProfileID", "Index", "Count", "Gradient"}, {"Date","ProfileID", "Index", "Count", "Gradient"}),
    #"Appended Query" = Table.Combine({#"Expanded Grouped", Attribute}),
    Custom2 = Table.ReplaceValue(#"Appended Query",null,Date.AddDays(List.Min(#"Expanded All Dates"[Value]), -1),Replacer.ReplaceValue,{"Date"}),
    Custom3 = Table.ReplaceValue(#"Custom2",null,RowCount,Replacer.ReplaceValue,{"Index", "Count", "Gradient"}),
    #"Pivoted Column" = Table.Pivot(Custom3, List.Distinct(Custom3[Attribute]), "Attribute", "Count"),
    #"Sorted Rows4" = Table.Sort(#"Pivoted Column",{{"Date", Order.Ascending}}),
    #"Removed Columns" = Table.RemoveColumns(#"Sorted Rows4",{"Pending", "Index"}),
    #"Sorted Rows3" = Table.Sort(#"Removed Columns",{{"Date", Order.Ascending}, {"Assigned", Order.Descending}, {"Started", Order.Descending}, {"PeerReviewStarted", Order.Descending}, {"Submitted", Order.Descending}, {"Approved", Order.Descending}}),
    Custom4 = Table.AddColumn( #"Sorted Rows3", "Assigned Rate",
    each 
        (
            -(
                (List.Max(#"Sorted Rows3"[Assigned]) - List.Min(#"Sorted Rows3"[Assigned]))
                / Duration.Days(Date.From(DateTime.LocalNow()) - List.Min(#"Expanded All Dates"[Value]))
            )
            * Duration.Days([Date] - Date.AddDays(List.Min(#"Expanded All Dates"[Value]), -1))
        )
        + List.Max(#"Sorted Rows3"[Gradient])
    ),
Custom5 = Table.AddColumn(Custom4, "Started Rate",
    each 
        (
            -(
                (List.Max(Custom4[Started]) - List.Min(Custom4[Started]))
                / Duration.Days(Date.From(DateTime.LocalNow()) - List.Min(#"Expanded All Dates"[Value]))
            )
            * Duration.Days([Date] - Date.AddDays(List.Min(#"Expanded All Dates"[Value]), -1))
        )
        + List.Max(Custom4[Gradient])
    ),
Custom7 = Table.AddColumn(Custom5, "PeerReviewStarted Rate",
    each 
        (
            -(
                (List.Max(Custom5[PeerReviewStarted]) - List.Min(Custom5[PeerReviewStarted]))
                / Duration.Days(Date.From(DateTime.LocalNow()) - List.Min(#"Expanded All Dates"[Value]))
            )
            * Duration.Days([Date] - Date.AddDays(List.Min(#"Expanded All Dates"[Value]), -1))
        )
        + List.Max(Custom5[Gradient])
    ),
Custom6 = Table.AddColumn(Custom7, "Submitted Rate",
    each 
        (
            -(
                (List.Max(Custom7[Submitted]) - List.Min(Custom7[Submitted]))
                / Duration.Days(Date.From(DateTime.LocalNow()) - List.Min(#"Expanded All Dates"[Value]))
            )
            * Duration.Days([Date] - Date.AddDays(List.Min(#"Expanded All Dates"[Value]), -1))
        )
        + List.Max(Custom7[Gradient])
    ),
Custom8 = Table.AddColumn(Custom6, "Approved Rate",
    each 
        (
            -(
                (List.Max(Custom6[Approved]) - List.Min(Custom6[Approved]))
                / Duration.Days(Date.From(DateTime.LocalNow()) - List.Min(#"Expanded All Dates"[Value]))
            )
            * Duration.Days([Date] - Date.AddDays(List.Min(#"Expanded All Dates"[Value]), -1))
        )
        + List.Max(Custom6[Gradient])
    ),
#"Changed Type3" = Table.TransformColumnTypes(Custom8, {
    {"Assigned", Int64.Type}, 
    {"Started", Int64.Type}, 
    {"PeerReviewStarted", Int64.Type}, 
    {"Submitted", Int64.Type}, 
    {"Approved", Int64.Type}, 
    {"Assigned Rate", Int64.Type}, 
    {"Started Rate", Int64.Type}, 
    {"PeerReviewStarted Rate", Int64.Type}, 
    {"Submitted Rate", Int64.Type}, 
    {"Approved Rate", Int64.Type}
})
in
    #"Changed Type3"

ALL DATES M CODE:

let
    Source = #date(2023, 1, 1),
    EndDate = #date(2023, 12, 15),
    Custom1 = List.Dates(Source, Duration.Days(EndDate - Source) + 1, #duration(1, 0, 0, 0)),
    #"Converted to Table" = Table.FromList(Custom1, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Renamed Columns" = Table.RenameColumns(#"Converted to Table", {{"Column1", "Date"}}),
    #"Inserted Year" = Table.AddColumn(#"Renamed Columns", "Year", each Date.Year([Date]), Int64.Type),
    #"Inserted Quarter" = Table.AddColumn(#"Inserted Year", "QuarterNo", each Date.QuarterOfYear([Date]), Int64.Type),
    #"Added Custom" = Table.AddColumn(#"Inserted Quarter", "Quarter", each "Q" & Text.From([QuarterNo])),
    #"Inserted Month" = Table.AddColumn(#"Added Custom", "MonthNo", each Date.Month([Date]), Int64.Type),
    #"Inserted Month Name" = Table.AddColumn(#"Inserted Month", "Month Name", each Text.Start(Date.MonthName([Date]), 3), type text),

    // Append a new row with a blank date value
    BlankRow = #table(type table[Date=date, Year=number, QuarterNo=number, Quarter=text, MonthNo=number, #"Month Name"=text], {{null, null, null, null, null, null}}),
    ResultTable = Table.Combine({#"Inserted Month Name", BlankRow})
in
    ResultTable

ATTRIBUTE M CODE:

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WciwuzkzPS01RitWJVgouSSwqgbFLk3IzS2A8x4KCovwyKCcgNbUoKLUsM7UcriEWAA==", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Column1 = _t]),
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Column1", type text}}),
    #"Renamed Columns" = Table.RenameColumns(#"Changed Type",{{"Column1", "Attribute"}}),
    #"Added Custom" = Table.AddColumn(#"Renamed Columns", "ProfileID", each "")
in
    #"Added Custom"

Or if you just want to download the Power BI Workbook you can find a made up example here (Max 400 profiles rather than actual 581 as in image).

ONE DRIVE FILE

https://1drv.ms/f/s!AsrLaUgt0KCLhXtP-jYDd4Z0ujKQ?e=UbVift

0

There are 0 answers