Reputation: 13
I'm trying to convert a table to reflect baskets per fruit item instead of fruit per basket number. Kind of like a transpose action, but the data changes.
Original data shows Basket No. with identifier per fruit basket. Next to each fruit basket number, there are 10 columns with "Fruit 1," "Fruit 2," etc with list validation.
Instead of showing "[Basket No.]/[fruit1]/[fruit2]/etc," I need to see the fruit names as table headers, with each basket those fruits are included in under their name.
I'm thinking there is a way to add more rows to create one big fruit column with duplicate basket numbers, then consolidate from that point. I do not know where to get started with this, despite searching.
Upvotes: 0
Views: 112
Reputation: 345
Assuming that you do not want to use Power Query, there is a "brute-force" way to do this using VBA. You could, for example, place the following code in a new VBA module:
Option Compare Text 'To keep string comparisons case-insensitive
Option Explicit
Sub InvertLists(ByVal dataRange As Range, ByRef outputCell As Range)
'[dataRange] should have horizontal lists of "fruits" in each "basket"
'This method will produce vertical lists of "baskets" for each "fruit"
'WARNING: If [dataRange] is of size R x C, this method will overwrite all
' the data in a (R + 1) x (C - 1) rectangle starting in [outputCell]
'ASSUMPTION: [dataRange] does not include any header row, just the "fruit" lists
'Read in the current table of data
Dim oldArr As Variant: oldArr = dataRange.value
'A single cell cannot be inverted
If Not IsArray(oldArr) Then 'There is only 1 cell in the range
outputCell.value = "#ERROR: No data to invert"
Exit Sub
End If
'Get the old table's dimensions
Dim rowLb As Long: rowLb = LBound(oldArr, 1)
Dim rowUb As Long: rowUb = UBound(oldArr, 1)
Dim colLb As Long: colLb = LBound(oldArr, 2)
Dim colUb As Long: colUb = UBound(oldArr, 2)
Dim oldRowCnt As Long: oldRowCnt = rowUb - rowLb + 1
Dim oldColCnt As Long: oldColCnt = colUb - colLb + 1
'There is no way to do the inversion if the input range only has 1 column
If colUb <= colLb Then
outputCell.value = "#ERROR: Range must have 2+ columns"
Exit Sub
End If
'The new table will have 1 extra row and 1 less column
Dim newArr() As Variant
ReDim newArr(1 To oldRowCnt + 1, 1 To oldColCnt - 1)
Dim newColCnt As Long: newColCnt = 0 'To count the number of new columns
Dim rowValue As Variant, gridValue As Variant
Dim found As Boolean
Dim r As Long, c As Long, nr As Long, nc As Long
'Go row by row in the old table
For r = rowLb To rowUb
rowValue = oldArr(r, colLb) '"Basket" at the start of the row
'Go through the rest of the row in the old table;
' the rest of the row is a HORIZONTAL list of "fruits"
For c = colLb + 1 To colUb
gridValue = oldArr(r, c) '"Fruit" to place somewhere in row 1 of the new table
'A blank value means the end of the list in the current row
If gridValue & "" = "" Then GoTo row_Continue
'Look for [gridValue] amongst the "fruits" in the new row 1
found = False
nc = 0
Do While Not found And nc < newColCnt
nc = nc + 1
found = (newArr(1, nc) & "" = gridValue & "")
Loop
If Not found Then
newColCnt = newColCnt + 1 'We have a new "fruit" in the new table
nc = newColCnt
newArr(1, nc) = gridValue 'Add the new "fruit" in row 1
End If
'If we are here, we know newArr(1, nc) equals gridValue
'Look for [rowValue] in the list of "baskets" in column [nc] of new data;
' the new table will have VERTICAL lists of "baskets"
found = False
nr = 1 'Each list of "baskets" starts in row 2 (row 1 has [gridValue], the "fruit" name)
Do Until found Or nr > oldRowCnt
nr = nr + 1
If newArr(nr, nc) & "" = "" Then Exit Do 'Blank value = end of vertical list
found = (newArr(nr, nc) & "" = rowValue & "")
Loop
'Add [rowValue] to the list of "baskets" in column [nc] if it was not there already
If Not found Then newArr(nr, nc) = rowValue
Next 'c
row_Continue:
Next 'r
'Write out the new table, starting at [outputCell]
outputCell.Resize(oldRowCnt + 1, oldColCnt - 1).value = newArr
End Sub
To use this method for a specific range of cells, you would need to add a macro such as the one below (in a separate module, or along with the previous code) ...
Sub DoSpecificInversion()
InvertLists Range("A2:K9"), Range("A13")
End Sub
Naturally, you would have to change the range "A2:K9" to where you have your original table (excluding the headers row), and you would have to change "A13" to the cell where the new table would be copied. You could then add a button and attach it to that macro. After clicking on the button, you would have something like the following:
If the original table, without the headers, is R rows by C columns in size, the new table will be (R + 1) rows by (C - 1) columns in size. Since the macro overwrites data in the workbook, you may want to save a copy of the workbook before running the macro for the first time.
Upvotes: 1
Reputation: 345
As Ron Rosenfeld shared in his comment to the question, this operation seems doable with Power Query. Since you basically start with a crosstab table, you can probably do the following in Power Query:
Forgive my ignorance if there is an easier way to do this in Power Query, but these would be the steps I would follow.
Upvotes: 0