Powerquery pivot & unpivot columns with different contents (headers) - pivot

I need to clean up a report with 45.000 lines of poorly formated sales data, that looks like this:
Col1
Col2
Col3
Col4
00/01
Week
ProductA
ProductB
00/01
1
5
5
00/01
2
5
5
00/02
Week
ProductA
ProductB
00/02
1
5
5
00/02
2
5
5
00/03
Week
ProductC
ProductB
00/03
2
5
5
The Problems:
We have multiple header lines repeating in inconsistent spacing (as sometimes a sales-week is missing for some stores), so Table.Split in fixed intervals doesnt work
When a store doesn't list a product it simple is not outputted in the report, so in store 00/03 is the only store with ProductC
I need to unpivot Col3 (and actually all product columns somehow to arrive at proper table I can work with like:
StoreNo
Week
ProductA
ProductB
ProductC
00/01
1
5
5
00/01
2
5
5
00/02
1
5
5
00/02
2
5
5
00/03
2
5
5
I looked far and wide, but either don't know enough PowerQuery M and/or don't know how to call this problem to find a proper solutions.
Note: The real table has 88 columns like this with various mixed products (about 30 different products all in all)

NOTE: Use David answer as the better one
let Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
Base=({"Col1","Col2"}), //names of columns to keep
repeating_groups=1, // then stack repeating groups of xx columns
Combo = List.Transform(List.Split(List.Difference(Table.ColumnNames(Source),Base),repeating_groups), each Base & _),
#"Added Custom" =List.Accumulate(
Combo,
#table({"Column1"}, {}),
(state,current)=> state & Table.Skip(Table.DemoteHeaders(Table.SelectColumns(Source, current)),1)
),
#"Added Custom1" = Table.AddColumn(#"Added Custom", "Custom", each if [Column2]="Week" then [Column3] else null),
#"Filled Down" = Table.FillDown(#"Added Custom1",{"Custom"}),
#"Filtered Rows" = Table.SelectRows(#"Filled Down", each ([Column2] <> "Week")),
#"Pivoted Column1" = Table.Pivot(#"Filtered Rows", List.Distinct(#"Filtered Rows"[Custom]), "Custom", "Column3", List.Sum),
#"Renamed Columns1" = Table.RenameColumns(#"Pivoted Column1",{{"Column2", "Week"}, {"Column1", "StoreNo"}})
in #"Renamed Columns1"

let
Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WMjDQNzBUUNJRCk9NzQbRAUX5KaXJJY5IbCelWB0klWDCFEygShhhkTAi2mwjXGYb4TLbGIvZzljNNsYwIhYA", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [#"Col1 " = _t, #"Col2 " = _t, #"Col3 " = _t, Col4 = _t]),
#"Changed Type" = Table.TransformColumnTypes(Source,{{"Col1 ", type text}, {"Col2 ", type text}, {"Col3 ", type text}, {"Col4", type text}}),
#"Grouped Rows" = Table.Group(#"Changed Type", {"Col1 "}, {{"All", each _, type table [#"Col1 "=nullable text, #"Col2 "=nullable text, #"Col3 "=nullable text, Col4=nullable text]}}),
Custom1 = Table.TransformColumns(#"Grouped Rows", {{"All", each Table.PromoteHeaders(_) }}),
#"Expanded All" = Table.ExpandTableColumn(Custom1, "All", {"Week ", "ProductA ", "ProductB", "ProductC "}, {"Week ", "ProductA ", "ProductB", "ProductC "})
in
#"Expanded All"

Related

Unpivot Several Columns to Result in Two

I receive data that looks like this:
Name
01/01/2023
01/02/2023
Revenue
Revenue
Chris
1
3
£100
£300
Colin
5
8
£500
£800
Pete
2
5
£200
£500
Where name is self-explanatory, the next two columns are dates (in UK format) with the number of days worked in the period shown below, and the final two columns are revenue.
I want to modify this data in Power Query so it looks like this:
Name
Date
Work Days
Revenue
Chris
01/01/2023
1
£100
Chris
01/02/2023
3
£300
Colin
01/01/2023
5
£500
Colin
01/02/2023
8
£800
Pete
01/01/2023
2
£200
Pete
01/02/2023
5
£500
I thought this would be some kind of a pivot operation but I can't figure it out.
Any assistance will be gratefully received.
Thanks,
Chris
One simple way
let Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
Set0=List.FirstN(Table.ColumnNames(Source),1),
Set1= List.Combine({Set0,List.Alternate(Table.ColumnNames(Source),1,1)}),
Set2=List.Combine({Set0,List.Alternate(List.RemoveFirstN(Table.ColumnNames(Source),1),1,1)}),
Part1 = Table.SelectColumns(Source,Set1),
Part2 = Table.SelectColumns(Source,Set2),
Date1 = Table.AddColumn(Part1,"Date" , each Table.ColumnNames(Part1){1}),
Date2 = Table.AddColumn(Part2,"Date" , each Table.ColumnNames(Part2){1}),
Rename1 = Table.RenameColumns(Date1,{{Table.ColumnNames(Part1){2}, "Revenue"}, {Table.ColumnNames(Part1){1}, "Work Days"}}),
Rename2 = Table.RenameColumns(Date2,{{Table.ColumnNames(Part2){2}, "Revenue"}, {Table.ColumnNames(Part2){1}, "Work Days"}}),
combined = Rename1 & Rename2
in combined
or
let Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Added Index" = Table.AddIndexColumn(Source, "Index", 0, 1, Int64.Type),
#"Unpivoted Other Columns" = Table.UnpivotOtherColumns(#"Added Index", {"Index", "Name"}, "Attribute", "Value"),
#"Added Custom" = Table.AddColumn(#"Unpivoted Other Columns", "Date", each if Text.Start([Attribute],3)="Rev" then null else [Attribute]),
#"Added Custom2" = Table.AddColumn(#"Added Custom", "count", each if Text.Start([Attribute],3)="Rev" then null else [Value]),
#"Added Index1" = Table.AddIndexColumn(#"Added Custom2", "Index.1", 0, 1, Int64.Type),
#"Inserted Modulo" = Table.AddColumn(#"Added Index1", "Modulo", each Number.Mod([Index.1], 2), type number),
#"Sorted Rows" = Table.Sort(#"Inserted Modulo",{{"Index", Order.Ascending}, {"Modulo", Order.Ascending}, {"Attribute", Order.Ascending}}),
#"Filled Down" = Table.FillDown(#"Sorted Rows",{"Date", "count"}),
x=Table.AlternateRows(#"Filled Down",0,1,1),
#"Removed Other Columns" = Table.SelectColumns(x,{"Name", "Value", "Date", "count"})
in #"Removed Other Columns"
Here's another way:
Using List.Generate, create a List of Tables using each Date/Revenue Pair.
For each of the tables, ensure the Revenue Column is named Revenue (and not Revenue2, Revenu3, etc) and then Unpivot the table.
Then expand the column that has the list of tables
The rest is "housekeeping"
Code Edited to provide for varying numbers of "First Columns" to be retained before the sets of Date and Revenue columns
*Change #"Retained Column Count" to reflect that number of Columns
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
colNames = List.Buffer(Table.ColumnNames(Source)),
//How many columns at the beginning are non data pairs
#"Retained Column Count" = 4,
#"First Columns" = List.FirstN(colNames,#"Retained Column Count"),
#"Date Columns" = List.Range(colNames,#"Retained Column Count",(List.Count(colNames)-#"Retained Column Count")/2),
#"Revenue Columns" = List.LastN(colNames,List.Count(#"Date Columns")),
//set data types
types = List.Transform(#"First Columns", each {_, type text}) &
List.Transform(#"Date Columns", each {_, Int64.Type}) &
List.Transform(#"Revenue Columns", each {_, Currency.Type}),
#"Changed Type" = Table.TransformColumnTypes(Source, types, "en-GB"),
//create a list of tables consisting of each date/revenue pair
// then unpivot each table
// ensure Revenue column has the same name throughout
#"Data Pairs" = List.Generate(
()=>[t=Table.SelectColumns(#"Changed Type",#"First Columns" & {#"Date Columns"{0}} & {#"Revenue Columns"{0}}), idx=0],
each [idx] < List.Count(#"Date Columns"),
each [t=Table.SelectColumns(#"Changed Type",#"First Columns" & {#"Date Columns"{[idx]+1}} & {#"Revenue Columns"{[idx]+1}}), idx=[idx]+1],
each Table.Unpivot(
Table.RenameColumns([t], {Table.ColumnNames([t]){#"Retained Column Count"+1},"Revenue"}),
{#"Date Columns"{[idx]}},"Date","Work Days")),
//to a table
// then combine the tables with column names in desired order
#"Converted to Table" = Table.FromList(#"Data Pairs", Splitter.SplitByNothing(), null, null, ExtraValues.Error),
#"Expanded Column1" = Table.ExpandTableColumn(#"Converted to Table", "Column1", #"First Columns" & {"Date","Work Days","Revenue"}),
#"Changed Type with Locale" = Table.TransformColumnTypes(#"Expanded Column1",
List.Transform(#"First Columns", each {_, type text}) &
{{"Date", type date},
{"Work Days", Int64.Type},
{"Revenue", Currency.Type}}, "en-GB"),
#"Sorted Rows" = Table.Sort(#"Changed Type with Locale",{{"Name", Order.Ascending}, {"Date", Order.Ascending}})
in
#"Sorted Rows"

How Can One Sum Dynamic Column Values in Powerquery Efficiently

I have been learning and thesame time carrying out a project using powerquery.
I am trapped on adding column values.Some of the column values contain text.I intend to sum in each record of my table, all values with integer type.However, there is a challenge .
When i add up the column values with the interger type,i get a wrong answer.Secondly, this column headers are dynamic.
How do i sum effectively dynamic column headers in powerquery
Example: My Challenge: When i sum the column with interger type like this [MEC101]+[THER305] i get a null values on some records and i dont know why?
When wrapped the sum using list.sum function, it partially works ,buh whenever,one of the column headers is missing, it gives a wrong answer.I want a suituation, when a column header is missing, it will ignore the missing column headers and sum the values from the available column headers.
Thank you.
ID
MEC101
MEC-GRADE
THER305
THER305-GRADE
TOTAL
1002
70
A
40
D
1003
50
C
60
B
1004
60
B
30
F
EXPECTED RESULTS 1:
ID
MEC101
MEC-GRADE
THER305
THER305-GRADE
TOTAL
1002
70
A
40
D
110
1003
50
C
60
B
110
1004
60
B
30
F
90
EXPECTED RESULTS 2:
ID
MEC101
MEC-GRADE
TOTAL
1002
70
A
70
1003
50
C
50
1004
60
B
60
try this which will sum the numeric columns, excluding 1st column
let Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
totals = Table.AddColumn(Source, "Sum", each List.Sum(List.Transform(List.RemoveNulls(List.RemoveFirstN(Record.FieldValues(_),1)), each if Value.Is(_,type number) then _ else 0)))
in totals
EDIT try this which will sum the numeric columns, excluding 1st and 2nd columns
let Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
totals = Table.AddColumn(Source, "Sum", each List.Sum(List.Transform(List.RemoveNulls(List.RemoveFirstN(Record.FieldValues(_),2)), each if Value.Is(_,type number) then _ else 0)))
in totals
or, using unpivot, grouping and merging:
let Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Unpivoted Other Columns" = Table.UnpivotOtherColumns(Source, {"ID"}, "Attribute", "Value"),
#"Added Custom" = Table.AddColumn(#"Unpivoted Other Columns", "Custom", each if Value.Is([Value],type number) then [Value] else null),
#"Grouped Rows" = Table.Group(#"Added Custom", {"ID"}, {{"Total", each List.Sum([Custom]), type nullable number}}),
#"Merged Queries" = Table.NestedJoin(Source, {"ID"}, #"Grouped Rows", {"ID"}, "Table1", JoinKind.LeftOuter),
#"Expanded Table1" = Table.ExpandTableColumn(#"Merged Queries", "Table1", {"Total"}, {"Total"})
in #"Expanded Table1"
Try this.
let
Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WMjQwMFJQ0lEyNwCRjiDCBMx0UVCK1QHLG4O4pmBBZxBhBmY6weVNUAR1lIzBTDegfCwA", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [ID = _t, MEC101 = _t, #"MEC-GRADE" = _t, THER305 = _t, #"THER305-GRADE" = _t]),
#"Changed Type" = Table.TransformColumnTypes(Source,{{"ID", Int64.Type}, {"MEC101", Int64.Type}, {"MEC-GRADE", type text}, {"THER305", Int64.Type}, {"THER305-GRADE", type text}}),
#"Added Custom" = Table.AddColumn(#"Changed Type", "Custom", each let
listA = Record.ToList(Record.RemoveFields(_,"ID")),
result = List.Accumulate(listA, 0,(state, current) => if Value.Is(current, Number.Type) then state + current else state)
in result)
in
#"Added Custom"

How to group multiple rows for a specific key in one single row, in power query?

I'm stuck with trying to group multiple rows into one row, in Excel Power Query.
The table I'm working with is listed as below:
Product ID
Purchase ID
Amount
1
10
50
1
11
25
2
12
10
2
13
20
3
14
10
How I want it to be displayed
Product ID
Purchase 1
Amount
Purchase 2
Amount
1
10
50
11
25
2
12
10
13
20
3
14
10
Is there an easy process on how to display multiple rows in one single row?
I have tried to group on product id and then merge data. But I would like to try to get the result like above.
Thanks!
Another method, relying on unpivoting. Has the benefit of directly working on any number of columns adjacent to ProductID
let Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Unpivoted Other Columns" = Table.UnpivotOtherColumns(Source , {"Product ID"}, "Attribute", "Value"),
#"Group"= Table.Group(#"Unpivoted Other Columns", {"Product ID"}, {{"GRP", each Table.TransformColumns(Table.AddIndexColumn(_, "Index", 0, (1/(List.Count(Table.ColumnNames(Source))-1))),{{"Index", each Number.RoundTowardZero(_), type number}}), type table}}),
#"Expanded GRP" = Table.ExpandTableColumn(#"Group", "GRP", {"Attribute", "Value", "Index"}, {"Attribute", "Value", "Index"}),
#"Added Custom" = Table.AddColumn(#"Expanded GRP", "Custom", each [Attribute]&Text.From([Index])),
#"Removed Columns" = Table.RemoveColumns(#"Added Custom",{"Attribute", "Index"}),
#"Pivoted Column" = Table.Pivot(#"Removed Columns", List.Distinct(#"Removed Columns"[Custom]), "Custom", "Value", List.Sum)
in #"Pivoted Column"
Assume the table name is Table1:
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
Group = Table.Group(
Source,
"Product ID",
{
"a",
each let
a =Table.ToRows(Table.RemoveColumns(_,"Product ID")),
b = List.Accumulate(
{0..List.Count(a)-1},
{},
(s,d)=>s &{"Purchase ID " & Text.From(d+1),"Amount " & Text.From(d+1)}
),
c =#table({"Product ID"} & b,{{_[Product ID]{0}} & List.Combine(a)})
in c
}
),
Res = Table.Combine(Group[a])
in
Res

Automatically reorganizing large Excel table to separate each column into multiple columns based on grouping variables

I have an Excel table with data organized such that each row is a sample and each column has a different property of that sample. However, I need to reorganize it so that it works with GraphPad Prism.
Currently the data is organized like this:
Sample ID
Exposure Level
Drug
Score 1
…
Score 22
101
1
A
0.675815
0.17351
102
1
B
0.276413
0.677079
103
2
A
0.914725
0.387529
104
3
A
0.504221
0.135295
105
3
B
0.963684
0.710081
106
2
B
0.964099
0.146872
And I want to make a box and whisker plot showing the score of each exposure level, like this:
I need to do this including all the samples and then again for just drug A and just drug B.
However, in order to do that in Prism, to my knowledge, each combination of variables you want needs to have in own column, like this:
Score 1 Exposure 1
Score 1 Exposure 2
Score 1 Exposure 3
Score 1 Exposure 1 (Just Drug A)
Score 1 Exposure 2 (Just Drug A)
Score 1 Exposure 3 (Just Drug A)
etc.
0.675815
0.914725
0.504221
0.675815
0.914725
0.504221
0.276413
0.964099
0.963684
This would be easy enough to do manually if there were just one score column, but there are twenty-two, so I'd rather not. Is there some automated way I can reorganize the data table like this?
To create a Box & Whiskers graph similar to what you show,
merely use the Exposure Level for the x-axis and the Score 1 column for the y-axis
To create a table similar to the results you show, you can use Power Query.
I created it as a single table, with each row representing a drug. You can then filter it by drug for your drug specific results.
The MCode is commented so by reading the comments, and also looking at the Applied Steps window, I hope I was clear in what was going on.
Most of the MCode is generated from the UI, but, especially, the colNames and ExpandTableColumns steps near the end are manually entered. Otherwise the number of columns in the expansion would not be flexible.
MCode
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
//Won't need ID column so get rid of it
#"Removed Columns2" = Table.RemoveColumns(Source,{"Sample ID"}),
//Unpivot the Score columns to put them in a single column
#"Unpivoted Columns" = Table.UnpivotOtherColumns(#"Removed Columns2", {"Exposure Level", "Drug"}, "Attribute", "Value"),
//sort by Score, Attribute, Drug so the results will be properly ordered
#"Sorted Rows" = Table.Sort(#"Unpivoted Columns",{{"Attribute", Order.Ascending}, {"Exposure Level", Order.Ascending}, {"Drug", Order.Ascending}}),
//Create what will become a two line header column
// and remove the originals
#"Added Custom" = Table.AddColumn(#"Sorted Rows", "Headers", each "Exposure " & Text.From([Exposure Level]) & "#(lf)" & [Attribute]),
#"Removed Columns" = Table.RemoveColumns(#"Added Custom",{"Exposure Level", "Attribute"}),
//Move headers to first column
#"Reordered Columns" = Table.ReorderColumns(#"Removed Columns",{"Headers", "Drug", "Value"}),
//Group by Drug
#"Grouped Rows" = Table.Group(#"Reordered Columns", {"Drug"}, {{"Grouped", each _, type table [Headers=text, Drug=text, Value=number]}}),
//Add an Index column
#"Added Index" = Table.AddIndexColumn(#"Grouped Rows", "Index", 0, 1, Int64.Type),
/*From each grouped table, remove Drug Column
and remove Header column EXCEPT fromk the first table
then Transpose each grouped table*/
#"Added Custom1" = Table.AddColumn(#"Added Index", "Custom", each
Table.Transpose(
if [Index] = 0 then
Table.RemoveColumns([Grouped],"Drug")
else
Table.RemoveColumns([Grouped],{"Headers","Drug"}))),
//Remove no longer needed Grouped and Index columns
#"Removed Columns1" = Table.RemoveColumns(#"Added Custom1",{"Grouped", "Index"}),
//Expand the table columns, promote headers, and rename the drug column to get final results
colNames = Table.ColumnNames(#"Removed Columns1"[Custom]{0}),
#"Expanded Custom" = Table.ExpandTableColumn(#"Removed Columns1", "Custom", colNames),
#"Promoted Headers" = Table.PromoteHeaders(#"Expanded Custom", [PromoteAllScalars=true]),
#"Changed Type" = Table.TransformColumnTypes(#"Promoted Headers",{{"A", type text}, {"Exposure 1#(lf)Score 1", type number}, {"Exposure 2#(lf)Score 1", type number}, {"Exposure 3#(lf)Score 1", type number}, {"Exposure 1#(lf)Score 22", type number}, {"Exposure 2#(lf)Score 22", type number}, {"Exposure 3#(lf)Score 22", type number}}),
#"Renamed Columns" = Table.RenameColumns(#"Changed Type",{{"A", "Drug"}})
in
#"Renamed Columns"
EDIT
#user3316549 commented below that he might have multiple entries for the same drug for the same Score/Exposure and wanted the results for each shown separately.
A Pivot table would be useful here, except a classic pivot table will only have a single entry for each intersection of Drug with Score/Exposure.
This problem is solved with a custom function for the pivot that adds an extra row when needed. The credits for that function are included and you can examine the link for a detailed explanation of the algorithm used for that part of the code.
The custom function is added as a blank query. You can name it what you choose and call it that way in your main code.
M Code
Main Query
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
//Unpivot the Score columns to put them in a single column
#"Unpivoted Columns" = Table.UnpivotOtherColumns(Source, {"Sample ID","Exposure Level", "Drug"}, "Attribute", "Value"),
//sort by multiple columns so the results will be properly ordered to our liking
#"Sorted Rows" = Table.Sort(#"Unpivoted Columns",{{"Attribute", Order.Ascending}, {"Exposure Level", Order.Ascending}, {"Drug", Order.Ascending},{"Sample ID", Order.Ascending}}),
//Create what will become a two line header column
// and remove the originals
#"Added Custom" = Table.AddColumn(#"Sorted Rows", "Headers", each [Attribute] & "#(lf)" & "Exposure " & Text.From([Exposure Level])),
#"Removed Columns" = Table.RemoveColumns(#"Added Custom",{"Sample ID","Exposure Level", "Attribute"}),
//custom pivot function for non-aggregation
pivotAll = fnPivotAll(#"Removed Columns","Headers","Value")
in
pivotAll
M Code
Custom Function named fnPivotAll
//credit: Cam Wallace https://www.dingbatdata.com/2018/03/08/non-aggregate-pivot-with-multiple-rows-in-powerquery/
(Source as table,
ColToPivot as text,
ColForValues as text)=>
let
PivotColNames = List.Buffer(List.Distinct(Table.Column(Source,ColToPivot))),
#"Pivoted Column" = Table.Pivot(Source, PivotColNames, ColToPivot, ColForValues, each _),
TableFromRecordOfLists = (rec as record, fieldnames as list) =>
let
PartialRecord = Record.SelectFields(rec,fieldnames),
RecordToList = Record.ToList(PartialRecord),
Table = Table.FromColumns(RecordToList,fieldnames)
in
Table,
#"Added Custom" = Table.AddColumn(#"Pivoted Column", "Values", each TableFromRecordOfLists(_,PivotColNames)),
#"Removed Other Columns" = Table.RemoveColumns(#"Added Custom",PivotColNames),
#"Expanded Values" = Table.ExpandTableColumn(#"Removed Other Columns", "Values", PivotColNames)
in
#"Expanded Values"

Excel power Query: Grouping and transposing

I have this table in SQL which I will constantly have new storedId and newproductID when I will refresh my data table.
StoreID ProductID Quantity
1 A 4
1 B 5
1 B 9
2 B 3
2 C 4
.
.
7 F 2
I want to group my StoreID together and transpose the Product ID with the sums of Quantity like the table bellow.
Each of my store can have between 0 and 30 products. So If i have 30 products, I would like to have 30 columns "productID" and 30 columns "SumQuantity"
StoredId ProductId1 ProductID2 ... SumQuantity1 SumQuantity2 ...
1 A B 4 14
2 B C 3 4
.
.
7 F 2
.
.
How i can do this in Power Query Excel?
Its hard to do this without custom coding.
Basically ..
Group on StoreID and ProductID then combine the Quantity
Group again on StoreID, and then combine rows for ProductID and Quantity with semicolon delimiter
Split out the columns on the delimiter. This requires 4 lines of custom code, since we don't know how many columns we need in advance.
Convert the numerical ones to numbers from text, which was needed format for above steps
Sample:
let Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WMlTSUXIEYhOlWB0IzwmITVF4lnCeMxAbGsG5LiCuMZhrBFWL4DnDTTUHstyAGKgxFgA=", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [StoreID = _t, ProductID = _t, Quantity = _t]),
#"Changed Type" = Table.TransformColumnTypes(Source,{{"Quantity", type number}}),
// group and sum quantities
#"Grouped Rows" = Table.Group(#"Changed Type", {"StoreID", "ProductID"}, {{"Quantity", each List.Sum([Quantity]), type number}}),
// group second time and combine ProductID and Quantity from different rows onto a single row with ; as delimiter
#"Grouped Rows1" = Table.Group(#"Grouped Rows", {"StoreID"}, {{"ProductID", each Text.Combine(List.Transform([ProductID], each Text.From(_)), ";"), type text},{"Quantity", each Text.Combine(List.Transform([Quantity], each Text.From(_)), ";"), type text}}),
//Dynamically split the two delimited columns into any number of columns
DynamicColumnList = List.Transform({1 ..List.Max(Table.AddColumn(#"Grouped Rows1","Custom", each List.Count(Text.PositionOfAny([ProductID],{";"},Occurrence.All)))[Custom])+1}, each "ProductID." & Text.From(_)),
DynamicColumnList2 = List.Transform({1 ..List.Max(Table.AddColumn(#"Grouped Rows1","Custom", each List.Count(Text.PositionOfAny([Quantity],{";"},Occurrence.All)))[Custom])+1}, each "Quantity." & Text.From(_)),
#"Split Column by Delimiter" = Table.SplitColumn( #"Grouped Rows1", "ProductID", Splitter.SplitTextByDelimiter(";", QuoteStyle.Csv), DynamicColumnList),
#"Split Column by Delimiter2" = Table.SplitColumn( #"Split Column by Delimiter" , "Quantity", Splitter.SplitTextByDelimiter(";", QuoteStyle.Csv), DynamicColumnList2),
// convert numerical to numbers
#"ConvertToNumbers" = Table.TransformColumnTypes (#"Split Column by Delimiter2", List.Transform ( List.Difference(Table.ColumnNames(#"Split Column by Delimiter2"),Table.ColumnNames(#"Split Column by Delimiter")),each {_,type number}))
in #"ConvertToNumbers"
I'm not sure why you'd want data in such a terrible format but here's how I'd approach it.
let
Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WMlTSUXIEYhOlWB0IzwmITVF4lnCeMxAbGsG5LiCuMZhrBFWL4DnDTTUHstyAGKgxFgA=", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [StoreID = _t, ProductID = _t, Quantity = _t]),
#"Changed Type" = Table.TransformColumnTypes(Source,{{"StoreID", Int64.Type}, {"ProductID", type text}, {"Quantity", Int64.Type}}),
#"Grouped Rows" = Table.Group(#"Changed Type", {"StoreID", "ProductID"}, {{"SumQuantity", each List.Sum([Quantity]), type nullable number}}),
#"Custom Grouping" = Table.Group(#"Grouped Rows", {"StoreID"},
{
{"Products", each Table.FromRows({[ProductID]},List.Transform({1..List.Count([ProductID])}, each "ProductID" & Number.ToText(_))), type table},
{"Quantities", each Table.FromRows({[SumQuantity]},List.Transform({1..List.Count([SumQuantity])}, each "SumQuantity" & Number.ToText(_))), type table},
{"Count", Table.RowCount, Int64.Type}
}
),
ColumnsToExpand = List.Max(#"Custom Grouping"[Count]),
ProductColumns = List.Transform({1..ColumnsToExpand}, each "ProductID" & Number.ToText(_)),
QuantityColumns = List.Transform({1..ColumnsToExpand}, each "SumQuantity" & Number.ToText(_)),
#"Expanded Products" = Table.ExpandTableColumn(#"Custom Grouping", "Products", ProductColumns, ProductColumns),
#"Expanded Quantities" = Table.ExpandTableColumn(#"Expanded Products", "Quantities", QuantityColumns, QuantityColumns)
in
#"Expanded Quantities"
The first grouping on StoreID and ProductID is the same as #horseyride suggests but I go a different way from there.
The next step is to group by only StoreID and construct the desired table for each store. This step result looks something like this in the editor (the preview on the bottom is what the selected cell contains):
Let's look at how this table is constructed for "Products".
Table.FromRows(
{[ProductID]},
List.Transform(
{1..List.Count([ProductID])},
each "ProductID" & Number.ToText(_)
)
)
This list [ProductID] is the list of IDs associated with the current store. For the cell selected, it's simply {"A","B"}. Since there are two values, the List.Count is 2. So the above simplifies to this:
Table.FromRows(
{{"A", "B"}},
List.Transform(
{1,2},
each "ProductID" & Number.ToText(_)
)
)
After the list transform this is simply
Table.FromRows({{"A", "B"}}, {"ProductID1", "ProductID2"})
which looks like the preview in the screenshot above.
The construction for the quantities data is analogous. After that column is defined, all that is left is to expand both these columns:
Edit:
As #horseyrides points out, the expansion needs to be made dynamic, so I've added a column to the custom grouping to determine the number of columns to expand into. This number is the maximum count of products for one store, which is then used to generate a list of column names.

Resources