Background:
I need to retrieve the values that an array formula (in the excel interface) calculates in order to loop through the results there.
Problem:
I have not found a way on how to store all the values that it calculates. I can store only the first one.
Code:
Sub test()
Dim ArrTopValues() As Long
'Fails
ArrTopValues = Application.Evaluate("=LARGE(A1:A11,{1,2,3})")
End Sub
I need to work with the 3 values that the formula can hold on the excel interface by pressing "F9"
Further thoughts
I know that I can write a UDF that recreates the Large function (or even that just evaluates the "k" on the Large function and build the Array variable that way). Please understand that I am looking how to store this array evaluations for more scenarios and a workaround to solve this has been done already to "make it work".
Use INDEX to return the array and you need to make the array a variant:
Sub test()
Dim ArrTopValues()
ArrTopValues = Application.Evaluate("=INDEX(LARGE(A1:A11,{1,2,3}),0)")
End Sub
Related
I hope I'm asking this in the correct forum:
I'm writing a UDF in VBA for MS-Excel; it basically builds a status message for the transaction on that row. It steps through a series of IF statements, evaluating cell values in different columns FOR THAT ROW.
However, this UDF will reside in multiple rows. So it might be in C12, C13, C14, etc. How would the UDF know which row to use? I'm trying something like this, to no effect
Tmp_Row = Application.Evaluate("Row()")
which appears to return a null
What am I missing here ?
Thanking everyone in advance
Application.Caller is seldom used, but when a UDF needs to know who called it, it needs to know about Application.Caller.
Except, you cannot just assume that a function was invoked from a Range. So you should validate its type using the TypeOf...Is operator:
Dim CallingCell As Excel.Range
If TypeOf Application.Caller Is Excel.Range Then
'Caller is a range, so this assignment is safe:
Set CallingCell = Application.Caller
End If
If CallingCell Is Nothing Then
'function wasn't called from a cell, now what?
Else
'working row is CallingCell.Row
End If
Suggestion: make the function take its dependent cells as Range parameters (if you need the Range metadata; if you only need the values then take in Double, Date, String parameters instead) instead of making it fetch values from the sheet. This decouples the worksheet layout from the function's logic, which in turn makes it much more flexible and easier to work with - and won't need any tweaks if/when the worksheet layout changes.
Application.ThisCell
MS Docs:
Returns the cell in which the user-defined function is being called from as a Range object.
You can put it to the test using the following code:
Function testTC()
testTC = Application.ThisCell.Row
End Function
In Excel use the formula
=testTC()
and (Cut)Copy/Paste to various cells.
I have been working with conditional formatting and I thought how it would look like if replaced with a "manual" comparison in VBA.
Let's say I want to compare cells between Row 1 and Row 2, meaning I compare A1 to A2, B1 to B2 etc. Whenever the value in row 2 is bigger, I want to highlight this in row 2.
If I don't want to do this with conditional formatting, how do I go about this? Do I have to loop through the cells to compare or is there a way to do it without a loop? With a loop it should look like this:
Option Explicit
Sub Testing()
Dim ws As Worksheet
Dim i As Long
Dim rng As Range
Set ws = ThisWorkbook.ActiveSheet
Set rng = ws.Range("A2:E2")
For i = 1 To rng.Count
If ws.Cells(2, i).Value > ws.Cells(1, i).Value Then
ws.Cells(2, i).Interior.ColorIndex = 44
End If
Next
End Sub
Is there a way to use less code to achieve the same result? I'm just wondering if I'm unaware of some smarter, alternative way to go about this.
"Is there a way to use less code to achieve the same result?"
Wouldn't recommend this for multiple reasons, but it can be done in a one-liner:
With Sheet1
.Range(Join(Filter(.[IF(A2:E2>A1:E1,CHAR(COLUMN(A2:E2)+64)&2,"%")], "%", False), ",")).Interior.ColorIndex = 44
End With
Why does this work:
.[IF(A2:E2>A1:E1,CHAR(COLUMN(A2:E2)+64)&2,"%")] is short for the Application.Evaluate method. Anything between .[..] is basically a Worksheet.Function where VBA is smart enough to know I want to return an array of results. The . in front would make this refer to Sheet1. Resulting array > {"A2","B2","%","%","E2"}
Filter function (not really known) would take this array, and output an resulting array, filtering out the "%" values. Hence the FALSE parameter. Resulting array > {"A2","B2","E2"}
Then the Join function would concatenate this array into a string using a "," as delimiter. Rather basic, resulting in "A2,B2,E2"
This, on its turn, is a valid Range.Address we can use withing the Range(...) reference. Once we have our Range object we can set it's Interior property with the intended ColorIndex value.
Why would I not recommend this:
While readability may already be an issue, .[..] does not take variables, meaning a dynamic array will need to be written with the .Evaluate(..) method instead, including variables, extra quotes etc. adding to the issues with readability.
While VBA is smart enough to recognize we need an array returned, this may become sluggish on large Range object. A small one like in the example is no problem at all though.
Range address can have a maximum of 255 characters. Larger arrays mean larger strings, meaning a larger change this is going to return an error at some point.
There are ways to overcome the above, but really would be too much effort preventing the obvious > Use the build-in conditional formatting instead (or if you must, your current code is nice and clean too).
Very new to VBA so apologize if it's a really simple question. This is finance/ portfolio management related.
I would like to create a VBA function that can give me the Global Minimum Variance Portfolio (GMVP) weights of a set of assets with just the assets' variance-covariance matrix.
I'm able to key in this formula directly into the cells on Excel to get the weights: =MMULT(MINVERSE(S),Ones)/SUM(MMULT(MINVERSE(S),Ones)) where S is the variance-covariance matrix and ones is a column matrix containing "1"s
There's no issues with mapping the dimensions of the matrices (if S is 5x5 then ones will be 5x1)
I've been trying to make a VBA function so that I don't have to type the lengthy formula every time to find the GMVP. Here's the code I have. Before this I also tried just writing everything in a really long single line, using "/" to divide (didn't work)
I wonder if it is because we can't use "/" to divide matrices in VBA? Is there a way to make the formula into a function?
Thank you!
Function GMVPcol(S As Range, Ones As Range) As Range
Dim num As Range
Dim dem As Range
num = Application.WorksheetFunction.MMult(Application.WorksheetFunction.MInverse(S), Ones)
dem = Application.WorksheetFunction.Sum(Application.WorksheetFunction.MMult(Application.WorksheetFunction.MInverse(S), Ones))
GMVPcol = Application.WorksheetFunction.MMult(num, Application.WorksheetFunction.MInverse(dem))
End Function
If you simplify your formula extremely, to something like this:
then, this is the correct way to represent it as a VBA function and call it in Excel:
Public Function SimpleMultiplication(matrix1 As Range, matrix2 As Range) As Variant
SimpleMultiplication = WorksheetFunction.MMult(matrix1, matrix2)
End Function
The function returns a variant, not a range. And in your code, make sure to always use the keyword Set, when an object of type range is to be assigned. In general, if you want to pass through a middle range and assign it, then it is better to work with an array and take the numbers from there - Creating an Array from a Range in VBA
I have a series of functions for each of listobject columns. The file is heavy and crashing, so I want just to keep the results of each formula as static values. I can allocate formula to the range and ask excel to convert the range to value. But I am wondering if there is a way to ask VBA to write only the static values in the range instead of the formula itself.
Here is what I have so far:
Sub calculate2()
Dim i As Long, t As Long
t = Timer
With Sheet3.ListObjects(1)
For i = 3 To 9
.ListColumns(i).DataBodyRange.ClearContents
.Range.Cells(2, i).Formula = sheets3.range("formula").cells(i,1).formula
.ListColumns(i).DataBodyRange = .ListColumns(i).DataBodyRange.Value
Next i
End With
Debug.Print Timer - t
End Sub
As an answer to my own question,
What I was looking for is: " application.Evaluate " I am posting that so if anyone came here by search can find the idea and the sollution I eventually found. Here is an example:
Sheet3.ListObjects(1).ListColumns(3).DataBodyRange = [IFERROR(IF(COUNTIFS(ZZ84!$B:$B,[WO],ZZ84!$E:$E,"=*V99",ZZ84!$L:$L,"<>")=1,1,0),"")]
in this case there is no need to loop and for each range has to write needful line of code (Embed the function in VBA, what I excatly was looking for). The only different in above function with directly putting that in a excel cell is using [WO] instead of [#WO]. So evaluat caculate an array of data and directly write that in specified range. (here body range of list columns 3 ).
For me it helped to avoid crashes beacause of voilate calculation by my functions.
Another simple example would be:
range("b1:b10")=[if(row(1:10),if(a1:a10>3,"big","Small"))]
or
range("b1:b10") = evaluate("if(row(1:10),if(" & range("a1:a10").address&">3,""big"",""small""))")
Kind regards,
M
I have an Excel cell with a complicated formula relating to many other cells.
Does a function exist that would allow me to look at what the value of that cell would be if I changed the value of another cell to a certain value?
Basically, I'm looking for the functionality of a simple data table in a function I can put directly in a cell. I can do this in VBA or by creating a data table, but I'm looking for a built-in method if one exists.
No.
As you have mentioned, you would be best served using a data table - there isn't an equivalent inbuilt function. The plus with data tables is that you can explicitly control their calculation
#brettdj's answer is correct. Unfortunately, I was wrong that I could do what I want even in VBA; the code below was what I was planning to write, but it does not work because functions called from a worksheet cannot change the worksheet:
Public Function Hypothetical(setthiscell As Range, tothisvalue As Variant, andobservethiscell As Range) As Variant
Dim oldvalue As Variant
oldvalue = setthiscell.Value
setthiscell.Value = tothisvalue
Dim result As Variant
result = andobservethiscell.Value
setthiscell.Value = oldvalue
Hypothetical = result
End Function
Neither Scenarios or the Solver address my problem because I want to the solution to be automated. Apparently the only way to accomplish this task is to use data tables.