I would appreciate some help on the following VBA Macro problem,
screenshot here:
I have to compare the data in 2 columns - Index & Sec_Index. In case of a match it should check which Values is assigned to the Sec_Index and fill a "1" to the matching Value column corresponding to Index and "0" for the other Value columns (I hope the screenshot explains it better)
I wrote a short macro which works good. However I have huge amounts of data - both Index columns contain at least 400000-500000 lines. This makes my code useless since it will take extreme long durations to execute.
Is there a way to make this work? I read about Variant arrays, but I'm not that familiar with them.
You can put this formula (if Excel 2007 or above):
=COUNTIFS($H$2:$H$5,$B2,$I$2:$I$5,"A")
into C2 and copy it down and across; just change "A" to "B" and "C".
Added In view of the number of rows, I would import the data into MS Access, create a Crosstab Query, then copy this data back to Excel.
Try this, not overly robust but does work. Not sure how quickly this will compare to what you may have had?
It did about 60,000 rows with 25 keys in about 5 seconds.
Edit: Added timer to function.
Public Declare Function GetTickCount Lib "kernel32.dll" () As Long
public Sub main()
Dim t As Long
t = GetTickCount
Application.ScreenUpdating = False
Dim Arr1(), Arr() As Double
Dim x, y, i, j As Double
Dim v As String
x = Cells(Rows.Count, 2).End(xlUp).Row - 2
y = Cells(Rows.Count, 8).End(xlUp).Row - 2
Range("c2", "e" & x + 2) = 0
ReDim Arr1(x)
ReDim Arr2(y)
i = 0
Do Until Cells(i + 2, 2) = ""
Arr1(i) = Cells(i + 2, 2)
i = i + 1
Loop
i = 0
Do Until Cells(i + 2, 8) = ""
Arr2(i) = Cells(i + 2, 2)
i = i + 1
Loop
i = 0
Do Until i > UBound(Arr1)
j = 0
Do Until j > UBound(Arr2)
If Arr1(i) = Arr2(j) Then
v = Cells(Arr2(j) + 1, 9)
Select Case v
Case "a"
Cells(i + 2, 3) = 1
Case "b"
Cells(i + 2, 4) = 1
Case "c"
Cells(i + 2, 5) = 1
End Select
Exit Do
End If
j = j + 1
Loop
i = i + 1
Loop
MsgBox GetTickCount - t, , "Milliseconds"
End Sub
Related
An analyst observed that the upward movement of stocks on Bovespa is repeated according to a mathematical sequence. He wants to find out what the next bullish sequences will be. Generate and save in Excel cells using macro the sequence 1, 3, 4, 7, 11, 18, 29, ... up to its twentieth term?
following my code in vba:
Sub GerarSequencia()
Dim num As Long
Dim previous As Long
Dim i As Integer
num = 0
previous = 0
For i = 1 To 20
If i = 1 Then
num = 1
Else
num = num + previous
End If
Cells(i, 1).Value = num
previous = num
Next i
End Sub
I tried to generate the sequence of the exercise but did it generate another one?
The sequence is a sommation of the earlier two values. So 1 + 3 = 4 and so on. Before you can start the sqequence you have to have two numbers. I think you can work with:
Sub GerarSequencia()
Dim intFirstNum, intSecondNum As Integer
Dim intCounter As Integer
intFirstNum = 1
intSecondNum = 3
Cells(1, 1) = intFirstNum
Cells(2, 1) = intSecondNum
For intCounter = 3 To 20
Cells(intCounter, 1).Value = Cells(intCounter - 2, 1).Value + Cells(intCounter - 1, 1).Value
Next intCounter
End Sub
So you see that I have made two additional variables which are filled with 1 and 3 (if you change them you can start wherever you want). From that point on, I start the loop from position 3. This is because the first two are already known.
From that point on you can run the sequence. You don't need an if statement in that case.
Generating a Sequence
Sub GerarSequencia()
Const nCOUNT As Long = 20
Dim nPrev As Long: nPrev = 1
Dim nCurr As Long: nCurr = 3
Cells(1, 1).Value = nPrev
Cells(2, 1).Value = nCurr
Dim nNext As Long
Dim i As Long
For i = 3 To nCOUNT
nNext = nPrev + nCurr ' sum up
Cells(i, 1).Value = nNext ' write
nPrev = nCurr ' swap
nCurr = nNext ' swap
Next i
' ' Return the worksheet results in the Immediate window (Ctrl + G).
' For i = 1 To 20
' Debug.Print Cells(i, 1).Value
' Next i
End Sub
I am trying to make a macro that assigns the name of maneuver (Ramping Up, Flat Cruise, Ramping Down) for specific behaviour of set of data. I decided to divide the large data set into subsets that consist 5 cells and the code is checking its behaviour (either is the value getting smaller or bigger).
.csv file consists more or less 20k rows and the code iterates through it for 5 minutes. Can I make it somewhat faster?
The outer for loop is dividing the set of data into subsets that consists 5 cells. Slow iterators manipulate those values.
Then it just assigns values depending the values in cells are decreasing or increasing
Sub maneuverSet(lu As Worksheet, nr As Long)
lu.Activate
Dim fast_ite As Long, slow_ite As Integer, numRows As Long
Dim slow_1 As Long, slow_2 As Long, slow_3 As Long, slow_4 As Long, slow_5
numRows = nr 'definition in main: numRows = Range("A2", Range("A2").End(xlDown)).Rows.Count
Application.ScreenUpdating = False
For fast_ite = 4 To numRows Step 5
Dim ite_array As Variant
slow_1 = fast_ite - 2
slow_2 = fast_ite - 1
slow_3 = fast_ite
slow_4 = fast_ite + 1
slow_5 = fast_ite + 2
ite_array = Array(slow_1, slow_2, slow_3, slow_4, slow_5)
' Now it just checks if the cell consist the values that ramping up with each row or not.
If Cells(slow_1, "A") < Cells(slow_2, "A") And Cells(slow_1, "A") < Cells(slow_2, "A") And Cells(slow_2, "A") < Cells(slow_3, "A") _
And Cells(slow_3, "A") < Cells(slow_4, "A") And Cells(slow_4, "A") < Cells(slow_5, "A") Then
For Each iterator In ite_array
Cells(iterator, "AB") = "RampUp"
Next
ElseIf Cells(slow_1, "A") > Cells(slow_2, "A") And Cells(slow_1, "A") > Cells(slow_2, "A") And Cells(slow_2, "A") > Cells(slow_3, "A") _
And Cells(slow_3, "A") > Cells(slow_4, "A") And Cells(slow_4, "A") > Cells(slow_5, "A") Then
For Each iterator In ite_array
Cells(iterator, "AB") = "RampDown"
Next
Else
For Each iterator In ite_array
Cells(iterator, "AB") = "Cruise"
Next
End If
Next
Application.ScreenUpdating = True
End Sub
Read/write to/from cell is usually a very slow action so you will want to avoid that as much as possible.
Instead, store the values into an array then process the logic through the array.
Without changing too much of your logic, this is probably what an array approach looks like:
Sub maneuverSet(lu As Worksheet, nr As Long)
lu.Activate
Dim fast_ite As Long, numRows As Long
numRows = nr 'definition in main: numRows = Range("A2", Range("A2").End(xlDown)).Rows.Count
Application.ScreenUpdating = False
For fast_ite = 4 To numRows Step 5
'Assign the 5 values into an array for processing
Dim dataArr As Variant
dataArr = lu.Cells(fast_ite, "A").Offset(-2).Resize(5).Value
Dim outcome As String 'used to store the outcome of this loop
If dataArr(1, 1) < dataArr(2, 1) And _
dataArr(2, 1) < dataArr(3, 1) And _
dataArr(3, 1) < dataArr(4, 1) And _
dataArr(4, 1) < dataArr(5, 1) Then
outcome = "RampUp"
ElseIf dataArr(1, 1) > dataArr(2, 1) And _
dataArr(2, 1) > dataArr(3, 1) And _
dataArr(3, 1) > dataArr(4, 1) And _
dataArr(4, 1) > dataArr(5, 1) Then
outcome = "RampDown"
Else
outcome = "Cruise"
End If
'Write the outcome into the 5 cells at once.
lu.Cells(fast_ite, "AB").Offset(-2).Resize(5).Value = outcome
Next
Application.ScreenUpdating = True
End Sub
Below should be even faster as it only read and write cells twice:
Sub maneuverSet(lu As Worksheet, nr As Long)
lu.Activate
Application.ScreenUpdating = False
'Read the entire data into an array
Dim dataArr As Variant
dataArr = lu.Range("A2:A" & nr).Value
'Create another array of the same size to store the outcome (to be written into column AB)
Dim outputArr() As String
ReDim outputArr(1 To UBound(dataArr, 1), 1 To 1) As String
'Loop through the array as per your logic
Dim i As Long
For i = 1 To UBound(dataArr, 1) Step 5
Dim outcome As String
If dataArr(i, 1) < dataArr(i + 1, 1) And _
dataArr(i + 1, 1) < dataArr(i + 2, 1) And _
dataArr(i + 2, 1) < dataArr(i + 3, 1) And _
dataArr(i + 3, 1) < dataArr(i + 4, 1) Then
outcome = "RampUp"
ElseIf dataArr(i, 1) > dataArr(i + 1, 1) And _
dataArr(i + 1, 1) > dataArr(i + 2, 1) And _
dataArr(i + 2, 1) > dataArr(i + 3, 1) And _
dataArr(i + 3, 1) > dataArr(i + 4, 1) Then
outcome = "RampDown"
Else
outcome = "Cruise"
End If
Dim n As Long
For n = i To i + 4
outputArr(n, 1) = outcome
Next n
Next i
'Write the entire outcome array into the worksheet
lu.Range("AB2:AB" & nr).Value = outputArr
Application.ScreenUpdating = True
End Sub
I wrote a macro to insert a row between cells when their values are different. I have 9 columns in my data and the data starts at row 2. I want my macro to check all the values down column 3 (also known as column "C") and as it goes through, if the value changes (i.e. 2, 2, 2, 3, 3) it will insert a row between the changed value (i.e. 2, 2, 2, INSERT ROW, 3, 3). The problem is, my macro is reading column 5(E) not 3(C). What is wrong with it, I can't figure it out! The reason I know this too is because I placed a msgbox to spit the value of the cell and it matches everything in column 5 but not 3. Here is my code:
Sub Dividers()
Dim DividerRange As Range, lastrow As Long, k As Integer, counter As Integer
lastrow = Range("C2").End(xlDown).Row
Set DividerRange = Range(Cells(2, 3), Cells(lastrow, 3))
counter = 0
For k = 2 To DividerRange.Count
MsgBox DividerRange(k + counter, 3).Value
If DividerRange(k + counter, 3).Value = DividerRange(k + counter - 1, 3).Value Then
DividerRange(k + counter, 3).EntireRow.Insert
counter = counter + 1
Else
End If
Next k
End Sub
DividerRange(k + counter, 3).Value is a relative reference. DividerRange is a range starting at C2, so when you ask for the (i,j)th cell, i.e. (i,3) you get something from column E where jth columns would be: (C = 1, D = 2, E = 3)
You can simplify it quite a lot, there's no need for the Range or Range count, or counter:
Sub Dividers()
Dim lastrow As Long, k As Integer
lastrow = Range("C2").End(xlDown).Row
For k = 2 To lastrow
If Cells(k, 3).Value <> Cells(k - 1, 3).Value Then
Cells(k, 3).EntireRow.Insert
'Now skip a row so we don't compare against the new empty row
k = k + 1
End If
Next k
End Sub
What I am trying to do is take a column of data and transpose it into a single header row, but space out each record such that 'Sheet1'A1 -> 'Sheet2'B1, 'Sheet1'A2 -> 'Sheet2'G1 and so on. (i.e. spaced out every 5 columns)
I am brand new so I was playing around with a loop such that:
Dim i As Integer
Dim j As Integer
i = 1
j = 1
Do While i < 200
Cells(1, i + 1).Value = "='Project List'!A1"
i = i + 5
j = j + 1
Loop
I was trying to use the Int 'j' as a way to cursor through the cell reference in "='Project List'!A1" but can't seem to find a way to do it. I attempted recording a macro but it was using the FormulaR1C1 = "='Project List'!RC[-1]" format and I couldn't figure out how step through R1C1 references either. The 200 was an arbitrary number so that I would capture the whole list of projects, I'm still trying to find a "repeat to blank" style of reference.
Does anyone have any suggestions on how to step through my column of data? Loop? Is there a better way of doing this?
Thanks,
Dane
There are tons of ways to do this but yours is a viable method with some slight modifications. You should create Worksheet objects to reference your input and output worksheets. My code also implements the repeat til blank logic you requested. As you can see, I commented out the first of the outWS lines. Either of those two lines will do the job. Keep the formula one if you need the values to update dynamically. Otherwise use the other one. Obviously you can change Book7.xlsm and Sheet2 as needed. You can also swap Workbooks("Book7.xlsm") for ThisWorkbook if the code is in the same spreadsheet as the data.
Sub transposeAndSpace()
Dim i As Integer
Dim j As Integer
Dim inWS As Worksheet, outWS As Worksheet
i = 1
j = 1
Set inWS = Workbooks("Book7.xlsm").Worksheets("Project List")
Set outWS = Workbooks("Book7.xlsm").Worksheets("Sheet2")
Do While inWS.Cells(j, 1).Value <> ""
'outWS.Cells(1, i + 1).Formula = "='Project List'!A" & j
outWS.Cells(1, i + 1).Value = inWS.Cells(j, 1).Value
i = i + 5
j = j + 1
Loop
Set inWS = Nothing
Set inWS2 = Nothing
End Sub
Assuming the Do/While loop is working for you:
i = 1
j = 1
Do While i < 200
Worksheets("Sheet2").Cells(1, i + 1).Value = "='Project List'!" & Range("A1").Offset(,j).Address
i = i + 5
j = j + 1
Loop
When i & j = 1, this should yield:
Worksheets("Sheet2").Cells(1, i + 1).Value --> Sheet2.Range("B1").Value
And it will put the formula:
"='Project List'!$A$1`
When i = 6, j = 2, should yield:
Worksheets("Sheet2").Cells(1, i + 1).Value --> Sheet2.Range("G1").Value
And it will put the formula:
"='Project List'!$A$2`
etc.
I am receiving a large file 500k+ lines but all the content is in column A. I need to run a macro that will transpose the data into matrix form but will only create a new row when it finds "KEY*" in the ActiveCell. For example:
| KEY 4759839 | asljhk | 35049 | | sklahksdjf|
| KEY 359 | skj | 487 |y| 2985789 |
The above data in my file would originally look like this in column A:
KEY 4759839
asljhk
35049
sklahksdjf
KEY 359
skj
487
y
2985789
Considerations:
Blank cells need to be transposed as well, so the macro cant stop based on emptyCell
The number of cells between KEY's is not constant so it actually needs to read the cell to know if it should create a new row
It can either stop based on say 20 empty cells in a row or prompt for a max row number
(Optional) It would be nice if there was some sort of visual indicator for the last item in a row so that its possible to tell if the last item(s) were blank cells
I searched around and found a macro that had the same general theme but it went based on every 6 lines and I did not know enough to try to modify it for my case. But in case it helps here it is:
Sub kTest()
Dim a, w(), i As Long, j As Long, c As Integer
a = Range([a1], [a500000].End(xlUp))
ReDim w(1 To UBound(a, 1), 1 To 6)
j = 1
For i = 1 To UBound(a, 1)
c = 1 + (i - 1) Mod 6: w(j, c) = a(i, 1)
If c = 6 Then j = j + 1
Next i
[c1].Resize(j, 6) = w
End Sub
I would greatly appreciate any help you can give me!
This works with the sample data you provided in your question - it outputs the result in a table starting in B1. It runs in less than one second for 500k rows on my machine.
Sub kTest()
Dim originalData As Variant
Dim result As Variant
Dim i As Long
Dim j As Long
Dim k As Long
Dim countKeys As Long
Dim countColumns As Long
Dim maxColumns As Long
originalData = Range([a1], [a500000].End(xlUp))
countKeys = 0
maxColumns = 0
'Calculate the number of lines and columns that will be required
For i = LBound(originalData, 1) To UBound(originalData, 1)
If Left(originalData(i, 1), 3) = "KEY" Then
countKeys = countKeys + 1
maxColumns = IIf(countColumns > maxColumns, countColumns, maxColumns)
countColumns = 1
Else
countColumns = countColumns + 1
End If
Next i
'Create the resulting array
ReDim result(1 To countKeys, 1 To maxColumns) As Variant
j = 0
k = 1
For i = LBound(originalData, 1) To UBound(originalData, 1)
If Left(originalData(i, 1), 3) = "KEY" Then
j = j + 1
k = 1
Else
k = k + 1
End If
result(j, k) = originalData(i, 1)
Next i
With ActiveSheet
.Cells(1, 2).Resize(UBound(result, 1), UBound(result, 2)) = result
End With
End Sub
Tested and works:
Sub test()
Row = 0
col = 1
'Find the last not empty cell by selecting the bottom cell and moving up
Max = Range("A650000").End(xlUp).Row 'Or whatever the last allowed row number is
'loop through the data
For i = 1 To Max
'Check if the left 3 characters of the cell are "KEY" and start a new row if they are
If (Left(Range("A" & i).Value, 3) = "KEY") Then
Row = Row + 1
col = 1
End If
Cells(Row, col).Value = Range("A" & i).Value
If (i > Row) Then
Range("A" & i).Value = ""
End If
col = col + 1
Next i
End Sub