I am new to VBA (as in I basically don't know anything about how to code in VBA, but I know python and general coding logic). I have an excel spreadsheet with a particular stock's data from 2004-2014 for every day the NYSE has been open. This data includes about 20 columns, but there are only 3 that I care about: the date (col a), a column with a specific percentage (col h), and a column with the price (col k). I want to write a simple program such that:
1) locate first positive percentage from column h, and the values in the same row for column a and k (i.e., if it's the third row, then find values from cells A3, H3, and K3)
2) locate the first negative percentage following the first positive percentage from step 1 in the same column h, and its corresponding values from column a and k (i.e., if it's the 7th row, then find values from cells A7, H7, and K7)
3) find the % difference of the 2 values found in the k column from the above two steps, and the difference in the number of rows from the positive and the negative percentage (i.e. 7-3=4)
4) Do this for the entire set of data which has almost 3000 rows of data - for example, after completing steps 1, 2, and 3, the program should find the first positive percentage following the negative percentage in step 2, then find the first negative percentage after that, and then find the % difference of the values in the k column and the difference in the number of rows
5) Place the information from the first 4 steps into a new spreadsheet (the values in the A, H, and K columns, the % differences, and the difference in the number of rows from the positive percentage and the negative percentage)
Can someone please write the VBA code for me for this, as I am new to VBA but need to get this done by tomorrow! Also, any tips on how to get started learning VBA would be very much appreciated! Thanks in advance, and Happy New Year!
Please let me know if you need anything clarified.
Just an idea:
Sub CompareData()
Dim srcwsh as Worksheet, dstwsh as Worksheet
Dim i as Integer, j as Integer
'source worksheet
Set srcWsh = Thisworkbook.Worksheets(1) ' or Thisworkbook.Worksheets("SheetName")
'destination worksheet
Set dstWsh = Thisworkbook.Worksheets.Add(After:=Thisworkbook.Worksheets(Thisworkbook.Worksheets.Count))
'define headers
dstWsh.Range("A1") = "Date"
dstWsh.Range("B1") = "Percentage"
dstWsh.Range("C1") = "Price"
dstWsh.Range("C1") = "Difference"
'start searching from 2. row
i = 2
Do While srcwsh.Range("A" & i)<> ""
'if value of column H is positive
If srcwsh.Range("H" & i)>0 Then
'find first empty row in a destination worksheet
j = GetFirstEmptyRow(dstWsh)
Union(srcWsh.Range("A" & i), srcWsh.Range("H" & i), srcWsh.Range("K" & i)).Copy dstWsh.Range("A" & j)
dstWsh.Range("D" & j).Formula = "=IF(ISERROR(D" & j-1 & "-D" & j & "),'',D" & j-1 & "-D" & j & ")"
End If
i = i+1
Loop
End Sub
Function GetFirstEmptyRow(ByVal wsh as Worksheet, ByVal Optional sCol As String = "A") As Integer
GetFirstEmptyRow = wsh.Range(sCol & wsh.Rows.Count).End(xlShiftUp).Row +1
End Function
Note: above code hasn't been tested!
All you need to do is to change the code to your needs. Good luck!
Related
Well I have done a lot of research and found a lot of relevant questions and answers but couldn't quite figure out how to cater that information to my specific need.
I am working on a project to create a macro that will correct mistakes and fill in information commonly found in product catalogs that I work with.
One thing I am trying to accomplish is to give the value "unassigned" to each blank cell in a row that is marked "Y" in column B.
I've found out how to change every cell in those particular rows and have it adjust dynamically to the number of rows. What I can't figure out is how to do the same for the number of columns. In my code below everything between columns B and S is included. Column B will always be in the same spot but column S will not always be the last column.
Dim tracked As String
Dim endCell As Range
Dim endRow As Long
Dim endColumn As Long
Dim start As Long
endRow = ActiveSheet.Range("D2").End(xlDown).Row
endColumn = ActiveSheet.Range("A1").End(xlToRight).Column
Let tracked = "B2:" & "B" & endRow
Set trackItem = ActiveSheet.Range(tracked)
For Each y In trackItem
If Left(y.Value, 1) = "Y" Then
'start = y.Row
'Set endCell = ActiveSheet.Cells(endColumn, start)
ActiveSheet.Range("B" & y.Row & ":" & "S" & endColumn).Value = "Unassigned"
End If
Next y
I included some code that I've left commented out so you can see what I've tried.
So, I can successfully change the value of all cells within that range but I need to know how to do it with a range where the number of columns will not always be the same. In addition, I want to select the blank cells only within this range and assign them a value. I imagine this will need to be done row by row as the correct criteria will not always be together.
I'm surprised more people don't use 'UsedRange' when there is a need to loop through all the cells that have data on a sheet. (Just yesterday someone was complaining that it takes too long to loop through all 17,179,869,184 cells on a worksheet...)
This example lists & counts the "used" range, and will easily adapt to your needs.
Sub List_Used_Cells()
Dim c As Range, x As Long
For Each c In ActiveSheet.UsedRange.Cells
Debug.Print c.Address & " ";
x = x + 1
Next c
Debug.Print
Debug.Print " -> " & x & " Cells in Range '" & ActiveSheet.UsedRange.Address & "' are considered 'used'."
End Sub
I have what I thought was a very basic VBA challenge, and have spent hours searching for an answer. Thanks if someone can point me to the right place if already addressed.
I have a formula that is B1 + C1 = D1, and have two 1x5 matrix of data inputs, one for cell B1 and one for cell C1, say [1,2,3,4,5] and [A,B,C,D,E], respectively, in cells (B2:B7) and (C2:C7). I would like to loop through the inputs, such that I get five unique answers [1+A, 2+B, 3+C, 4+D, 5+E], and output those answers in an adjacent 1x5 matrix, say in cells (D2:D7).
Recording a macro does not work here, as it records a copy/paste action that is inflexible for future use (for expanded matrices, other sheet locations, more complex formulas, etc).
Any help much appreciated.
Henry
UPDATE: I believe I need to be using "Do While" or some similar loop code, and additional "For" and "Next" coding.
UPDATE: Here is a step-by-step picture of what I am trying to do with the code:
step-by-step process results image
Here's the solution code:
Sub IterationMacro()
'Declare Variables
Dim h, i, j, k As Integer
Dim mySheet As Worksheet
Dim myAnswer As String
'Set Worksheet
Set mySheet = ActiveSheet
'Set # of Iterations
h = Range("B2").Value
'Clear Previous Contents
Range("C4:D4").ClearContents
Range("e5:e11").ClearContents
'Run Through Loops
For i = 5 To h + 4
For j = 3 To 4
mySheet.Cells(4, j).Value = mySheet.Cells(i, j).Value
Next
'Calculate Workbook
Calculate
mySheet.Cells(i, 5).Value = mySheet.Cells(4, 5).Value
Next
End Sub
If you could draw a table or something to use as an example, it might help.
Assuming I'm undersatnding you, you want to use a formula in D1, and fill down to D7, resulting in showing B+C=D in each row:
Range("D1").Formula="=B1+C1"
Range("D1:D7").Filldown
Edit:
Having been given the example image, it looks like you want math to happen in Row 2 (headers in Row 1). In Row 2 you want to pull up values from Row "i" and add them in Row 2, then paste the answer of that sum in Row "i".
Dim i as Integer 'i is the variable for the loop
For i = 3 to 9 'based on the picture, 3 to 9 are the 1 through 7 values
Cells(2,1).Value=Cells(i,1).Value 'pulls up Column A value from the loop to Row 2
Cells(2,2).Value=Cells(i,2).Value 'pulls up Column B value from the loop to Row 2
Cells(2,3).Formula="=A2+B2" 'Sums A2 and B2 into C2
Cells(2,3).Copy Cells(i,3) 'Copies the summed value to Row "i" in Column C
Next i 'Moves to next "i" in the loop
Let me know if that is more to your point.
Edit:
With dynamic ranges, you still know your starting point. You would look at something similar to:
Dim i as Integer
Dim LR as Long
Dim LC as Long
LR=Cells(Rows.Count,"A").End(xlUp).Row
LC=Cells(1,Columns.Count).End(xlToLeft).Column
For i = 3 to LR 'Still starting at 3, because of the example
Cells(2,1).Value=Cells(i,1).Value
Cells(2,2).Value=Cells(i,2).Value
Cells(2,LC+1).Formula="=A2+B2" 'Note the LC+1 goes one row BEYOND the last column
Cells(2,3).Copy Cells(i,LC+1)
Next i
In the last example, you can see syntax for dynamic ranges. Note that LR and LC are defined outside of the loop and do not change for the duration of the subroutine.
I'm trying to sum a variable range of data starting at row 3. sortedRow is the row # of the end of the data. lColumn is the last column used as a number.
I'm trying to sum the data in row 1, column H - lColumn.
I need to have the formula calculate the sum of column G, then iterating on referenced column by the current iteration (which will be columns h through x)
I've taken Scott Craner's advice and edited my code as
Dim lColumn As Long
lColumn = ActiveSheet.Cells(3, Columns.Count).End(xlToLeft).Column
sortedRow = Cells(Rows.Count, "D").End(xlUp).Row
Dim i As Integer 'iterated column sum cell
For i = 8 To lColumn
Cells(1, i).Value =Application.WorksheetFunction.SumIfs(Range(Cells(3,7),
Cells(sortedRow, 7)), Range(Cells(3, i), Cells(sortedRow, i)), 1)
Next i
The cells are not being updated with their values though.
It feels like I'm missing something really obvious with actually applying the number to the cell. I thought the above would do it.
added lColumn declaration because of issue with the formula being applied
added sortedRow declaration.
basically this. But with variable column lengths and number of columns.
It didn't matter for Column H that there was "#VALUE" in the volume column. So I assume it doesn't matter for the other columns.
Any help would be appreciated!
You have problems here.
("$G3:$G" & sortedRow, i & "3:" & i & sortedRow, 1)
("$G3:$G" & sortedRow & "," & i & "3:" & i & sortedRow, 1)
But you still have issues in there. If i=4 and sortedRow=12 let's say, this is what you would have.
(G3:G12,43:412, 1)
So you need to fix your Criteria_range1. I'm not clear from your question what you want the criteria to be.
Here is info on the arguments. https://msdn.microsoft.com/en-us/library/office/ff193011.aspx
The issue was resolved after fixing #VALUE cells in the sumifs columns I was trying to add. I made a conditional statement that set all errors to equal zero. This allowed the loop to function.
I'm searching for a VBA macro for Excel, which can detect the word "mean", in column A. After this it would copy the yellow row with the formula in C to J.
The formula counts the average from one row after the last "mean" to the next =AVERAGE (C1323:C1437)
after every sixth mean there also needs to be Area and 150 copyied two rows after mean and I and J Need to be changed. Consequently I and J would refer to the cell A1441 in this case (=G1439/C1439*$A$1441) till the end of the file.
I'm not quite sure if it's easy or not but I'm totally overchallenged. I would be very thankful for help.
Sub Makro1()
'
' Makro1 Makro
'
' Tastenkombination: Strg+q
strSearchWord = "Mean"
i = Application.WorksheetFunction.CountIf(Range("A:A"), strSearchWord)
Y = 2
For x = i To 0
i = Application.WorksheetFunction.Match(strSuchWort, Range("A:A"), 0)
Range("C" & i).Select
Application.CutCopyMode = False
ActiveCell.FormulaR1C1 = "=AVERAGE(R[-147]C:R[-1]C)" ' that's still wrong, should be something like i-y?
Selection.AutoFill Destination:=Range("C" & i:"J" & i), Type:=xlFillDefault
Range("CY:JY").Select
i = Y
'for each fifth i
'Range("A" & i + 3).Select
' ActiveCell.FormulaR1C1 = "=RC[-2]/RC[-6]*R2159C1"
Next x
End Sub
it's still wrong, but my first draft.
#stucharo the Area correction is difficult to describe I've added a better Picture with formulas. I hpe that now it's understandable
If your line ActiveCell.FormulaR1C1 = "=AVERAGE(R[-147]C:R[-1]C)" needs to change the number of rows betwen means each time then you'll need to add a variable as you comment suggests. Also, just writing the string to the cells value (ActiveCell.Value) means that you will see it written as a formaula when you click the cell in the workbook (and it'll highlight the range etc.). You could try replacing it with:
ActiveCell.Value = "=AVERAGE(R[" & i - Y & "]C:R[-1]C)"
although since I can't see the first row of your sheet I'm not certain that'll give you the correct range of rows each time.
If your row number is likely to change and you are copying over the same number of columns each time then it might also be just as easy to write the formula directly to cells within a loop, rather than explicitly copying it.
Adding text after every 6th "mean" would require you to keep count of how many means had passed so far. This can be done by incrememnting a counter variable and using the Mod operator will tell you the remainder after a division. Therefor numberOfMeans Mod 6 will give you the remainder when divided by 6 and when this equals zero you know you have a multiple of 6. I've tried to capture all this into the code below.....
Sub Test()
Application.ScreenUpdating = False
Dim startRow As Integer
startRow = 2
Dim endrow As Integer
endrow = Range("A2").End(xlDown).row
Dim lastMeanRow As Integer
lastMeanRow = startRow - 1
Dim areaRow as Integer
areaRow = lastMeanRow + 3
Dim meanCounter As Integer
meanCounter = 0
Dim avgColHeight As Integer
Dim col As Integer
Dim row As Integer
'Check each row in the sheet
For row = startRow To endrow
'Cols i and j in every row need to be modified
For col = 9 To 10
Cells(row, col).Value = "=RC[-2]/RC[-6]*R" & areaRow & "C1"
Next col
'If column 1 of that row contains "mean" then
If Cells(row, 1).Value = "mean" Then
'Calculate the column height to average over....
avgColHeight = row - lastMeanRow - 1
'...and loop through each of the columns....
'(including i and j to add average)
For col = 3 To 10
'....inserting the averaging formula.
Cells(row, col).Value = "=AVERAGE(R[-" & avgColHeight & "]C:R[-1]C)"
Next col
'Then increment the counter to keep track of the number of means
meanCounter = meanCounter + 1
'If the number of means is a multiple of 6 then
If (meanCounter Mod 6 = 0) Then
'insert the "Area" and "150" strings
Cells(row + 2, 1).Value = "Area"
Cells(row + 3, 1).Value = "150"
areaRow = row + 3
End If
'Finally change the lastMeanRow to the mean row we have just processed.
lastMeanRow = row
End If
'Do it again until we reach the end of the data
Next row
Application.ScreenUpdating = True
End Sub
I also noticed your point on the value of area changing periodically. Writing this programatically, as above, will aloow you to add some logic over the value of "Area" and when it changes.
You clearly have a long list of data and want to automate the creation of the rows and formulas you describe.
It is possible write VBA to scan through the data and modify the formulas etc but first I would question if this is the best approach to give you what you need.
Excel has a feature called "pivot tables" which essentially allows you to summerise data in a list.
for instance if the list had one row for each city in the world and gave the population in the city, and a column gave which country it was in. A pivot table could be used to create the average population for a country of the countries cities. I suspect you are doing this sort of thing.
If you don't know about pivot tables you should find out about them. See here
In your case your mean row is summeriseing data in the rows above it. To use pivot tables you would have to have a column that defined which group each row is in. You pivot table would sue this column as a row summary and you would then create the average for all the other column.
#Nathalie. It's hard to help without knowing more. eg Is the data delivered with the mean text already inserted. It looks like column A has a number the represent the row number within the group (and this could be used by a formula to create the "Group Name" column you need for pivot tables.
You can get the pivot tables to do the area adjustment by:
Creating a new set of columns which contains formulas that cause the values in columns C to J to be copied except for when it is the 6th set of data in which case you adjust the values in C to J accordingly).
You probably need to introduce columns that:
A. give the "group name"
B. give a count of which group it is in so every 6th you can do the adjustment you need.
4 by using pivot tables and basic techniques you will find it easie rot update the refresh the data, should you need to.
I'm using INDEX and MATCH functions to pull data which is concatenated string of G2 and H2 from column D (sorry I don't have enough points to attach pic). Column D has INDEX(column A and column B) and columns A and B have values till 12th row. MATCH is working fine giving me the position as 6 on the worksheet. But when I use this in VBA code as shown below,INDEX is working in the VBA code (can be seen through MsgBox) but MATCH function which would allot value to the variable 'check' isn't working. I have been breaking my head for really long. Need help from experts here. Somebody please tell me where am I going wrong?
Sub testindex()
Dim check As Long
Set sh = Sheets("Sheet1")
For j = 1 To 11
'Index value is correctly shown
MsgBox "Index Value=" & Application.WorksheetFunction.Index(sh.Range("A2:B12"), j, 1) & Application.WorksheetFunction.Index(sh.Range("A2:B12"), j, 2)
'Cells(7, 4)=ISA737775 same as G2&H2
MsgBox "Cells(7,4)=" & Cells(7, 4)
check = Application.WorksheetFunction.Match(Cells(7, 4), Application.WorksheetFunction.Index(sh.Range("A2:B12"), j, 1) & Application.WorksheetFunction.Index(sh.Range("A2:B12"), j, 2), 0)
Next j
End Sub
Thanks
Match expects the second paramater to be in the form of a range. When you call match through VBA that range actually needs to be a range object, not just some string like "A1:A12" or whatever it is that your concatenated Index formulas output.
At any rate, you are iterating already, so why not just call those values directly instead of pulling their values through Index?
check = Application.WorksheetFunction.Match(Cells(7, 4), sh.Range("A" & 2 + j).value & sh.Range("B" & 2 + j), 0)
Which is writing the same exact thing but without having to use a taxing INDEX function in VBA to do it. Note that this still won't work because the second parameter of match is still just a string which is a concatenated value from Column A and Column B. You could convert to a range by sticking them in the range object with:
check = Application.WorksheetFunction.Match(Cells(7, 4), sh.Range(sh.Range("A" & 2 + j).value & sh.Range("B" & 2 + j)), 0)
I'm assuming that the values in A and B are actual cell names that when concatenated will make a range. Like when j=1 then the it would be like check=Match(Cells(7,4), sh.Range("G2:H50"), 0) or something...