Looping through dynamic columns and rows and transposing data - vba

I have a dataset that is dynamic, meaning N number of rows and N number of columns (groups). The first screenshot is how the data looks with 3 groups, but as I said it could be N number of groups. There can also be N number of items.
Initial Data:
The second screenshot shows how the data should look. I need to write the item name for every score (numeric value in that row). So I have to transpose the data somehow. I need to loop through the columns, but don't know how divide the groups in the loop since they have the same column headers. Only the definition and group number are always unique.
This has to be done in VBA.
Final data after looping through rows and columns and "transposing":
Thanks
EDIT: Here's the code I've tried so far, which leaves spaces between the sets and only works for the first group.
Sub transposeData()
Dim ws As Worksheet
Dim ws2 As Worksheet
Dim lastRow As Long
Dim i As Long
Dim lastCol As Long
Dim j As Long
Dim n As Integer
Dim y As Long
Dim tempVal As Integer
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set ws2 = Workbooks("Workbook2").Worksheets("Sheet1")
lastRow = ws.Cells(ws.Rows.Count, 2).End(xlUp).Row
lastCol = ws.Cells(ws2.Rows.Count, 1).End(xlUp).Row
For i = 3 To lastRow Step 1
For y = 3 To lastRow Step 1
For j = 3 To lastCol Step 1
If ws.Cells(i, j) <> vbNullString Then
tempVal = ws.Cells(i, j).Value
ws2.Cells(y, 2) = ws.Cells(i, 2).Value
ws2.Cells(y, 3) = tempVal
ws2.Cells(y, "K") = ws.Cells(2, j).Value
End If
If tempVal <> 0 And tempVal - 1 Then
y = y + 1
End If
If j = 41 Then
i = i + 1
End If
tempVal = 0
y = y
Next j
Next y
Next i
End Sub

I took advantage of Excel's Transpose ability to get this code to work based on your sample data exactly as it is shown:
Sub Transpose()
Dim ws As Worksheet
Set ws1 = ThisWorkbook.Worksheets("Sheet1") 'Sheets("Sheet1")
Set ws2 = Workbooks("Workbook2").Worksheets("Sheet1") 'Sheets("Sheet2")
ws2.Range("A1:D1").Value = Array("Name", "Value", "Test", "Defintion")
With ws1
'how many groups are there so we know how many times to transpose
'we find this out by counting the number of times "Defintion" appears
Dim lDef As Long
lDef = Application.WorksheetFunction.CountIf(.Rows(2), "Definition")
'get last row where grouped data appears
Dim lRow As Long
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
Dim l As Long
For l = 3 To lRow 'loop through items
Dim rDef As Range, sFirst As String
Set rDef = .Rows(2).Find("Definition") 'find first instance of "Definition"
sFirst = rDef.Address 'get address of first occurence so we can test if we reached it again
'list Name (aka Item) (for as many rows as needed defined by how many groups * 4 (1 for each test))
With ws2
.Range("A" & .Rows.Count).End(xlUp).Offset(1).Resize(4 * lDef).Value = ws1.Range("A" & l)
End With
Do
'transpose values
rDef.Offset(l - 2, 1).Resize(1, 4).Copy 'uses l-2 to offset for each row throughout the loop
With ws2
'paste values (test results)
.Range("B" & .Rows.Count).End(xlUp).Offset(1).Resize(4, 1).PasteSpecial xlPasteValues, Transpose:=True
'load test cases
.Range("C" & .Rows.Count).End(xlUp).Offset(1).Resize(4, 1).Value = Application.WorksheetFunction.Transpose(Array("A", "B", "C", "D"))
'load definitions
.Range("D" & .Rows.Count).End(xlUp).Offset(1).Resize(4, 1).Value = Application.WorksheetFunction.Transpose(rDef.Offset(1).Value)
End With
Set rDef = .Rows(2).FindNext(After:=rDef) 'find next definition
Loop Until rDef Is Nothing Or rDef.Address = sFirst
Next
End With
End Sub

Take a look at this macro and see what you think about it. I copied your sample set and was able to duplicate your desired results using nested for loops. Let me know if anything needs clarification.
Option Explicit
Sub customTransposing()
Dim i As Integer
Dim j As Integer
Dim k As Integer
Dim startingRow As Integer
Dim startingColumn As Integer
Dim numberOfPoints As Integer
Dim numberOfEntries As Integer
Dim numberOfGroups As Integer
Dim outputRowOffset As Integer
' -------------------------------------------------------------------------------------------
' User Variables
' -------------------------------------------------------------------------------------------
startingRow = 3
startingColumn = 1
numberOfPoints = 4 ' The number of test points i.e. A B C D
numberOfEntries = 0
numberOfGroups = 3
outputRowOffset = 10
' -------------------------------------------------------------------------------------------
' Counts the number of entries in the first column
' this section could most likely be improved
Cells(startingRow, startingColumn).Select
Do Until IsEmpty(ActiveCell)
If Not IsEmpty(ActiveCell) Then
numberOfEntries = numberOfEntries + 1
End If
ActiveCell.Offset(1, 0).Select
Loop
For j = 0 To numberOfEntries - 1
For k = 0 To numberOfGroups - 1
For i = 0 To numberOfPoints - 1
' first column
Cells(startingRow + numberOfEntries + (j * numberOfPoints * numberOfGroups) + outputRowOffset + i + k * numberOfPoints, startingColumn).Value = Cells(startingRow + j, startingColumn)
' second column
Cells(startingRow + numberOfEntries + (j * numberOfPoints * numberOfGroups) + outputRowOffset + i + k * numberOfPoints, startingColumn + 1).Value = Cells(startingRow + j, startingColumn + 2 + i + k * (numberOfGroups + 2))
' third column
Cells(startingRow + numberOfEntries + (j * numberOfPoints * numberOfGroups) + outputRowOffset + i + k * numberOfPoints, startingColumn + 2).Value = Cells(startingRow - 1, startingColumn + 2 + i)
' fourth column
Cells(startingRow + numberOfEntries + (j * numberOfPoints * numberOfGroups) + outputRowOffset + i + k * numberOfPoints, startingColumn + 3).Value = Cells(startingRow + j, startingColumn + 1 + k * (numberOfGroups + 2))
Next i
Next k
Next j
End Sub

Related

Count rows and columns in ranges of varying size

The following short program should cycle through all sheets within a workbook and compare the values in the cells from (C11:end) to the cell in column B for that row, and then repeat this for each row below it where there is data.
The code won't run due to an 'overflow' error, implying that the variable NumRows is too large for the integer type (I think?). However, whilst the NumRows and NumCols vary from sheet to sheet, there are always <100 in each case. Changing the type to 'Long' causes Excel to hang. I'm not sure why this is happening, C11 is always bounded on both its right and bottom side by data so the .End function shouldn't be generating massive numbers.
I'm very new to all this so if anyone could please explain or suggest edits I'd be very grateful.
Sub cond_format()
Application.ScreenUpdating = False
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
ws.Activate
Dim i As Integer
Dim j As Integer
Dim NumRows As Integer
Dim NumCols As Integer
NumRows = ws.Range("C11", ws.Range("C11").End(xlDown)).Rows.Count
NumCols = ws.Range("C11", ws.Range("C11").End(xlToRight)).Columns.Count
For i = 1 To NumRows
For j = 1 To NumCols
If Cells(10 + i, 2 + j).Value >= (Cells(i + 10, 2).Value) * 1.2 Then
Cells(10 + i, 2 + j).Interior.Color = 10092492
ElseIf Cells(10 + i, 2 + j).Value <= (Cells(i + 10, 2).Value) * 0.8 Then
Cells(10 + i, 2 + j).Interior.Color = 5263615
End If
Next
Next
Next ws
End Sub

Macro to Concatenate two columns at a time in a range

I have to create a Macro which lets me Concatenate two columns at a time in a given range. For example: In range C1:Z200, I want to concatenate Column C&D, E&F, G&H and so on. How do I do it. This is my current code which only concatenate first two columns..rest remains the same.
Set Range = ActiveSheet.Range("C1:Z100")
For Each c In Range
c.Select
ActiveCell.FormulaR1C1 = ActiveCell & " " & ActiveCell.Offset(0, 1)
ActiveCell.Offset(0, 1).Activate
Selection.Clear
ActiveCell.Offset(0, 2).Activate
Next c
Try this:
Sub Concat()
Dim i As Long, j As Long
For i = 1 To 100 'number of rows
j = 1 'reset column to 1
Do While j < 25 'max number of columns (until Column Y-Z)
j = j + 2 'start from third column (Column C)
Cells(i, j) = Cells(i, j) & " " & Cells(i, j + 1) 'concat
Cells(i, j + 1).ClearContents 'clear
Loop
Next i 'next row
End Sub
Try this:
Sub ConcatAltCellsInAltCols()
Dim oW As Worksheet: Set oW = ThisWorkbook.Worksheets("Sheet11")
Dim iLC As Long: iLC = oW.Cells(1, oW.Columns.Count).End(xlToLeft).Column
Dim iLR As Long: iLR = oW.Cells(oW.Rows.Count, 3).End(xlUp).Row
Dim iC As Long
Dim iR As Long
For iR = 1 To iLR
For iC = 3 To iLC Step 2
oW.Cells(iR, iC).Value = oW.Cells(iR, iC) & oW.Cells(iR, iC + 1)
Next
Next
End Sub
Try this using a one based array for better Performance:
Code
Option Explicit
Sub Conc()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Concat") ' <== change "Concat" to your sheet name to avoid subscript error
Dim v ' variant
Dim lng As Long
Dim j As Integer ' corr.
' use one based array to get field data
v = ws.Range("C1:Z100") ' your OP range
For lng = 1 To UBound(v)
' concatenate columns C&D, E&F, G&H, ...
For j = 0 To 11
v(lng, j * 2 + 1) = v(lng, j * 2 + 1) & v(lng, j * 2 + 2)
Next j
Next lng
' write array values back (overwriting D, F, H,... with the same values)
ws.Range("C1:Z100") = v ' your OP range
End Sub

Loop for deciding first and last row of different entry in column A

I'm looking for a loop code which provides me with the first and last row of an 'Name' in column A.
A
1 Phill
2 Phill
3 Phill
4 Phill
5 Phill
6 Phill
7 Matthew
8 Matthew
9 Matthew
10 Matthew
11 Hendry
12 Hendry
13 Hendry
etc. etc.
The results should be something like this on other sheet:
A B C
1 Name Start_Row End_Row
2 Phill 1 6
3 Matthew 7 10
4 Hendry 11 13
5 etc. etc. etc.
I experimented with different loops but can't seem to get the good loop code which gets me started.
This is what I have:
If wsData.Cells(i + DOF, 1) <> curName Then
wbMain.Activate
For i = 1 To LastRow
curName = wsData.Cells(i + DOF, 1).Value
NameCount = NameCount + 1
wbWellsTable.Sheets("Sheet1").Cells(NameCount + 1, 1) = wbMain.Sheets("Data").Rows(i + DOF)
Start_Row = wsData.Cells(i + DOF, 1).Value
Counter = Counter + 1
wbWellsTable.Sheets("Sheet1").Cells(Counter + 1, 2) = wbMain.Sheets("Data").Rows(i + DOF)
End_Row = wsData.Cells(i + DOF, 1).Value
Bounter = Bounter + 1
wbWellsTable.Sheets("Sheet1").Cells(Bounter + 1, 3) = wbMain.Sheets("Data").Rows(i + DOF)
Next i
End If
Hope you guys can help me!
I'm not going to write the whole code for the output etc, but here's a good general function to return the first & last rows for you:
Function FindRow(sht As Worksheet, Col As String, str As String, Direction As Long) As Long
FindRow = sht.Columns(Col).Cells.Find(str, SearchOrder:=xlByRows, LookIn:=xlFormulas, SearchDirection:=Direction).Row
End Function
You can call it in your regular sub/function like this:
Dim FirstRow As Long, LastRow As Long
FirstRow = FindRow(sht:=YourWorkSheetObject, Col:="A", str:="Text To Find", Direction:=xlNext)
LastRow = FindRow(sht:=YourWorkSheetObject, Col:="A", str:="Text To Find", Direction:=xlPrevious)
Depending on the direction, it simply returns the row number of the first or last row in the specified column which matches the text you want. With these values you should be able to factor them into the rest of your code.
Without VBA, place the names in column B. In C1 enter:
=MATCH(B1,A:A,0)
and copy down and in D1 enter:
=LOOKUP(2,1/(A:A=B1),ROW(A:A))
and copy down:
With VBA:
Option Explicit
Sub rowfinder()
Dim ws As Worksheet
Dim rng As Range
Dim cell As Range
Dim currentName As String
Dim currentMin As Integer
Dim startRow As Integer
Dim startColumn As Integer
Dim outputColumn As Integer
Dim outputRow As Integer
Set ws = ThisWorkbook.Worksheets(1)
startRow = 2
startColumn = 1
outputColumn = 2
outputRow = 2
ws.Cells(startRow + 1, startColumn).End(xlDown).Select
Set rng = ws.Range(ws.Cells(startRow + 1, startColumn), ws.Cells(startRow + 1, startColumn).End(xlDown))
currentName = ws.Cells(startRow, startColumn).Value
currentMin = Cells(startRow, startColumn).Row
ws.Cells(outputRow, outputColumn).Value = currentName
ws.Cells(outputRow, outputColumn + 1).Value = currentMin
For Each cell In rng
If cell.Value <> currentName Then
ws.Cells(outputRow, outputColumn + 2).Value = cell.Row - 1
currentName = cell.Value
currentMin = cell.Row
outputRow = outputRow + 1
ws.Cells(outputRow, outputColumn).Value = currentName
ws.Cells(outputRow, outputColumn + 1).Value = currentMin
End If
Next cell
Set cell = rng.End(xlDown)
ws.Cells(outputRow, outputColumn + 2).Value = cell.Row
End Sub
Using your worksheet names
Dim wsData as Worksheet
Dim wsMain as Worksheet
Set wsData = wbMain.Sheets("Data")
Set wsMain = wwbWellsTable.Sheets("Sheet1")
' Get first value
i = 1
lastName = wsData.Cells(i, 1).Value
i = i + 1
curName = wsData.Cells(i, 1).Value
startRow = i
NameCount = 1
Do until curName = ""
if curName <> lastName then
With wksMain
NameCount = NameCount + 1 ' increment row to skip first header line
.Cells(NameCount, 1) = lastName
.Cells(NameCount, 2) = startRow
.Cells(NameCount, 3) = i - 1 ' last Row
End With
lastName = curName
startRow = i
endif
i = i + 1
curName = wsData.Cells(i, 1).Value
Loop
' Write out lst record
With wksMain
NameCount = NameCount + 1
.Cells(NameCount, 1) = lastName
.Cells(NameCount, 2) = startRow
.Cells(NameCount, 3) = i - 1 ' last Row
End With

Creating UDF using VBA in excel to find similar values in a row where order does not matter [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I am dealing with unlimited new rows of data every day and I need a UDF that would find similar row values regardless of its order. As you can see in the example bellow A9:F9 and A4:F4 has a similar row values marked as SIMILAR ROW 1. You need to look at the overall data within the row to see that it has same values but not in the same order. I’m not familiar with VBA if someone could please help me it would very well be appreciated. I have been searching for this all over the web now.
Formula Example:
=Similarity(Range Of Data from A:F, Row Of Data)
My sheet looks like below image:
pls. try with below code
Sub test()
Dim data() As String
Dim i As Long
Dim dd As Long
Dim lastrow As Variant
Dim lastcolumn As Variant
Dim status As Boolean
lastrow = Range("A" & Rows.Count).End(xlUp).Row
lastcolumn = Cells(2, Columns.Count).End(xlToLeft).Column
ReDim data(lastrow - 1, lastcolumn)
For i = 2 To lastrow
For j = 1 To lastcolumn
data(i - 1, j) = Cells(i, j)
Next j
Next i
For i = 1 To lastrow - 1
Call similarity(data(), i)
Next i
End Sub
Public Function similarity(rdata() As String, currrow As Long)
lastrow = UBound(rdata)
matchcount = 0
lastcolumn = UBound(rdata, 2)
For Z = currrow To lastrow - 1
ReDim test(lastcolumn) As String
ReDim test1(lastcolumn) As String
For i = 1 To lastcolumn
test(i) = rdata(currrow, i)
test1(i) = rdata(Z + 1, i)
Next i
Call sort(test())
Call sort(test1())
For i = 1 To lastcolumn
If test(i) = test1(i) Then
matchcount = matchcount + 1
End If
Next i
If matchcount = lastcolumn Then
If Cells(currrow + 1, lastcolumn + 1).Value <> "" Then
Cells(currrow + 1, lastcolumn + 1).Value = Cells(currrow + 1, lastcolumn + 1).Value & "|" & "Match with " & Z + 2
Else
Cells(currrow + 1, lastcolumn + 1).Value = "Match with " & Z + 2
End If
If Cells(Z + 2, lastcolumn + 1).Value <> "" Then
Cells(Z + 2, lastcolumn + 1).Value = Cells(Z + 2, lastcolumn + 1).Value & "|" & "Match with " & currrow + 1
Else
Cells(Z + 2, lastcolumn + 1).Value = "Match with " & currrow + 1
End If
End If
matchcount = 0
Next Z
End Function
Sub sort(list() As String)
Dim First As Integer, Last As Long
Dim i As Long, j As Long
Dim temp As String
First = LBound(list)
Last = UBound(list)
For i = First To Last - 1
For j = i + 1 To Last
If list(i) > list(j) Then
temp = list(j)
list(j) = list(i)
list(i) = temp
End If
Next j
Next i
End Sub
Here is a start. It will help you to find which rows are permutations of other rows. Say we start with:
This UDF() will take the contents of a set of cells; sort the data; concatenate the data; and return the result as a single string:
Public Function SortRow(rng As Range) As String
ReDim ary(1 To rng.Count) As Variant
Dim CH As String, i As Long
CH = Chr(2)
For i = 1 To 6
ary(i) = rng(i)
Next i
Call aSort(ary)
SortRow = Join(ary, CH)
End Function
Public Sub aSort(ByRef InOut)
Dim i As Long, J As Long, Low As Long
Dim Hi As Long, Temp As Variant
Low = LBound(InOut)
Hi = UBound(InOut)
J = (Hi - Low + 1) \ 2
Do While J > 0
For i = Low To Hi - J
If InOut(i) > InOut(i + J) Then
Temp = InOut(i)
InOut(i) = InOut(i + J)
InOut(i + J) = Temp
End If
Next i
For i = Hi - J To Low Step -1
If InOut(i) > InOut(i + J) Then
Temp = InOut(i)
InOut(i) = InOut(i + J)
InOut(i + J) = Temp
End If
Next i
J = J \ 2
Loop
End Sub
So in G1 we enter:
=SortRow(A1:F1)
and copy down and in H1 enter:
=IF(COUNTIF($G$1:$G$7,G1)=1,"unique combination","duplicates")
and copy down:
This shows that rows 2 and 6 have data that are duplicated, but in different order.
Starting from this may help you achieve your goal.

VBA transform rows to column

I have a worksheet in excel which looks like this
name variant1 value1 variant2 value2
----- -------- ------ -------- ------
name1 variantA1 valueA1 variantB1 valueB1
name2 variantC2 valueC2 variantD2 valueD2
And I want to transform it to such a structure:
name variant value
---- -------- ------
name1
variantA1 valueA1
variantB1 valueB2
name2
variantC2 valueC2
variantD2 valueD2
I will use this macro quite often, it may happen that there will be 10 different variants for each name and I don't know how many rows will be in the worksheet.
I've written VBA macro and I'm not sure why it doesn't work:
Sub import_format()
Dim ws As Worksheet
Dim LastRow As Long, i As Long
Dim NumberOfVariants As Long
Dim j As Long
Dim ColumnIndex As Long
Set ws = Sheets("Sheet1") '~~> Name of the sheet which has the list
LastRow = ws.Range("A" & Rows.Count).End(xlUp).Row
NumberOfVariants = 2
ColumnIndex = 2 '<~~ index of the first variant
For i = 2 To LastRow '<~~ Set 2 if row 1 has headers
i = i + 1
ws.Rows(i & ":" & i + NumberOfVariants).Insert shift:=xlDown
i = i - 1
For j = 0 To (NumberOfVariants * 2) - 1
ws.Range(Cells(ColumnIndex + (j * 2), i), Cells(ColumnIndex + (j * 2) + 1, i)).Select
Selection.Cut
ws.Range(Cells(ColumnIndex + (j * 2), i + j + 1), Cells(ColumnIndex + (j * 2) + 1, i + j + 1)).Paste
Next j
Next i
End Sub
The way I tried to to this was to insert rows = number of variants and cut and paste proper values.
I will be grateful for all the hints! ;-)
Try this one :)
Option Explicit
Sub import_format()
Dim ws As Worksheet
Dim i, j, LastRow, ColumnIndex, NumberOfVariants As Integer
Set ws = Sheets("Sheet1") '~~> Name of the sheet which has the list
LastRow = ws.Range("A" & Rows.Count).End(xlUp).Row
NumberOfVariants = 4
ColumnIndex = 2 '<~~ index of the first variant
For i = 2 To LastRow + (LastRow - 2) * (NumberOfVariants - 1) Step NumberOfVariants '<~~ Set 2 if row 1 has headers; Actual last row = current last row + lines which will be inserted
ws.Rows(i + 1 & ":" & i + NumberOfVariants - 1).Insert shift:=xlDown 'Insert rows based on number of variants
For j = 2 To (NumberOfVariants - 1) * 2 Step 2 'Walking through the variants, starting in column 2, variant + value are 2 columns, therefore using step 2
ws.Range(Cells(i, ColumnIndex + j), Cells(i, ColumnIndex + j + 1)).Cut _
Destination:=ws.Range(Cells(i + j / 2, ColumnIndex), Cells(i + j / 2, ColumnIndex + 1))
Next j
Next i
End Sub