I'm teaching myself VBA at the moment, & one thing that's really bugged me is that Excel doesn't have MEDIANIFS() function. My research has found:
A MEDIANIF (one condition, not multiple) formula in VBA
The array formula for {MEDIAN(IF(...))}
Putting medians into pivot tables
PS. This is not self study, so posting a solution is fine, but tips are also welcome
Edited version:
My question is, given that I have a MEDIANIF() formula programmed in VBA, how can I modify it to take more than one condition? To turn it into a MEDIANIFS()
> Function MedianIf(rng As Range, Criteria As Variant) As Variant
> Dim cell As Range
> Dim ar() As Variant
> Dim i As Long
>
> With WorksheetFunction
> If .CountIf(rng, Criteria) = 0 Then
> MedianIf = CVErr(2036) '-- #NUM!
> Else
> ReDim ar(1 To rng.Cells.Count)
> For Each cell In rng.Cells
> If .CountIf(cell, Criteria) = 1 Then
> i = i + 1
> ar(i) = cell.Value
> End If
> Next
> MedianIf = .Median(ar)
> End If
> End With End Function
Source: http://www.vbforums.com/showthread.php?650584-RESOLVED-Writing-MedianIf-in-VBA
You could use a parameter array to be able to include multiple criteria. To give you an idea of the mechanics:
Sub MultiCriteria(target As Range, ParamArray Criteria() As Variant)
Dim s As String
Dim i As Long
Dim criterion As Variant
s = target.Address & " to be filtered by:"
For i = LBound(Criteria) To UBound(Criteria)
criterion = Criteria(i)
s = s & vbCrLf & "Criterion " & i & ": " & criterion
Next i
Debug.Print s
End Sub
Sub test()
MultiCriteria Range("A1:B10"), "<5", "<>6", "=C5"
End Sub
Output of test:
$A$1:$B$10 to be filtered by:
Criterion 0: <5
Criterion 1: <>6
Criterion 2: =C5
You could possibly use AND() to gather the criteria into 1, or you can apply the criteria in a loop. I don't have time to develop a complete implementation of MEDIANIF right now but this should get you started. Ask another question if you run into more difficulties.
I think your 2 options are an Array Formula (as suggested in no2) or a User Defined Funtion (UDF) in VBA, but you havent really given us much to go on. Perhaps some sample data and an expected output might help the community more?
Thanks,
Ojf
edit: This might help with the array-formula approach as well...
http://www.mrexcel.com/forum/excel-questions/535743-median-ifs.html
Related
[Edited]
I am so sorry I did not describe this very clear:
My question is about the VLookup function:
I used the code like:
B5'n$CX$"
with the intention to let VBA go to the B5 worksheet and also determine which
"n"(either n25 n35 and so on) it has to go to.
This was not working so I am here to seek if I can get any suggestion.
Below is the original post:
I am very newbie in VBA, just started one or two times touching it.
I have modified the code as below, and my objective is to let
VBA find the values for "DI"&i, depending on the values of "DE"&i
in the spreadsheet B5, but in B5, the number "n" which depends on the
values of "CX"&i, and when VBA goes to look up the values of "DE"&i,
it has to determine the value of n firstly with the help of the values
"CX"&i.
The screenshot of the worksheet B5 is as below:
https://www.dropbox.com/s/do6i7zeylaz0sch/B5.jpg?dl=0
My code is as below:
firstly, if "DE"&i >3.9, I would like VBA set "DI"&i = 0,
else with vlookup function.
Thank you very much for any help and advice.
Appreciated.
Sub FindPl()
For i = 2 To 1730
If .Cells("DE" & i).Value > 3.9 Then .Cells("DI" & i).Value = 0
Else: .Cells("DI" & i).Value =
Application.WorksheetFunction.VLookup($DE$" &
i&",'[C:\Users\chenj5\Documents\Meeting_Jan_2019\simulation of Z1.9 for
Ultra Multi-Focal\Meeting 0220\Dataset used for simulation]B5'n$CX$" &
i&", 2, True)
End If
Next cell
Next i
End Sub
Will take a stab at some example code... I guessed on your source data and fixed some syntax errors... spend some time updating and appropriately dimensioning. There is too much going on/wrong with your posted code to provide anything more clear.
Sub FindPl()
dim i as long, wb as workbook, OUTPUTRANGE as range, SEARCHRANGE as range
set wb = "C:\Users\chenj5\Documents\Meeting_Jan_2019\simulation of Z1.9 for Ultra Multi-Focal\Meeting 0220\Dataset used for simulation.xlsx" 'added xlsx extension
with wb
set OUTPUTRANGE = .range(.cells(5,"D"),.cells(100,"D")) 'guessed... the range with the desired output
set SEARCHRANGE = .range(.cells(5,"B"),.cells(100,"B")) 'guessed... the range where you will find .cells(i,"DE")
end with
with activeworkbook.sheets(1) 'FIX THIS TO FIT YOUR NEEDS
For i = 2 To 1730
If .Cells(i, "DE").Value > 3.9 Then 'FIXED SYNTAX ERROR
.Cells(i, "DI").Value = 0
Else:
.Cells(i, "DI").Value = Application.Index(OUTPUTRANGE, Application.Match(.cells(i, "DE").value,SEARCHRANGE, 0))
End If
Next
end with
End Sub
This is a segment of code that has been troubling me, as I feel certain some simple function exists that will make looping through the array values redundant.
Instead I have used an array, a loop and a boolean to tell me whether the cells are empty (or test their length) and an If statement to run the last part of the code.
I thought perhaps Max would work but I believe that is only for integers. (See the debug.print part
Dim arrArchLoc As Variant
Dim boolArchLoc As Boolean
Dim rowCounter As Long
boolArchLocEmpty = False
arrArchLoc = ActiveSheet.Range(Cells(2, colArchiveLocation), Cells(lastRow, colArchiveLocation))
For rowCounter = LBound(arrArchLoc) To UBound(arrArchLoc)
If Cells(rowCounter, colArchiveLocation) <> "" Then boolArchLocEmpty = True
Next rowCounter
'Debug.Print workshetfunction.Max(arrArchLoc)
If boolArchLocEmpty = True Then
ActiveSheet.Cells(1, colArchiveLocation).Value = "Arch Loc"
Columns(colArchiveLocation).ColumnWidth = 6
End If
Does such a function or simple method exist?
EDIT:
Whilst that specialcells(xlCellTypeBlanks) solution looks pretty good, I would still rather get the string length solution.
My apologies, the code initially had something like...
If len(Cells(rowCounter, colArchiveLocation)) > 6 then...
but I have since removed it after having to get something in place that would work.
Is there something I could do with LEN(MAX)? I experimented with it but didn't get very far.
Given the range is A2:A100, the result you want would be expressed on the sheet as an array formula:
={MAX(LEN(A2:A100))}
In order to execute that from VBA as an array formula and not a regular formula, you need to use Evaluate:
max_len = Evaluate("=MAX(LEN(A2:A100))")
Or, in terms of your code,
Dim arrArchLoc As Range
With ActiveSheet
Set arrArchLoc = .Range(.Cells(2, colArchiveLocation), .Cells(lastRow, colArchiveLocation))
End With
Dim max_len As Long
max_len = Application.Evaluate("=MAX(LEN(" & arrArchLoc.Address(external:=True) & "))")
However it is much better to calculate it explicitly with a loop, like you were already doing.
Why not something like so
activesheet.range(cells(1,1),cells(10,1)).specialcells(xlCellTypeBlanks)
Another way to check if the range is empty or not
Sub Sample()
Debug.Print DoesRangeHaveEmptyCell(Range("A1:A10")) '<~~ Change as applicable
End Sub
Function DoesRangeHaveEmptyCell(rng As Range) As Boolean
If rng.Cells.Count = Application.WorksheetFunction.CountA(rng) Then _
DoesRangeHaveEmptyCell = False Else DoesRangeHaveEmptyCell = True
End Function
In SQL Server, MIN and MAX can act on text that doesn't evaluate to numbers, returning the text item with the lowest or highest text sort order, or as it's known in SQL Server-speak, "collation order".
Is it possible to do that in Excel without going to a UDF that actually sorts?
For example, for MIN("bb","aa","cc") to return "aa", and MAX("bb","cc","aa") to return "cc".
Excel's MIN/MAX ignore text, and although MINA/MAXA can work on text, they break on text that doesn't resolve to a number. LARGE/SMALL don't do it either.
FWIW, a coworker asked me how to do this in a pivot. I don't see a way without going to a custom function. Am I wrong?
This array formula looks promising. since it is an array it needs to be entered with ctrl-shift-enter.
Max:
=INDEX(A2:A6,MATCH(0,COUNTIF(A2:A6,">"&A2:A6), 0))
Min:
=INDEX(A2:A6,MATCH(0,COUNTIF(A2:A6,"<"&A2:A6), 0))
Change the three ranges to what you want.
The max and min versions are the same except for the > versus <.
I believe you are correct, a custom function is best. The good thing to note is the normal comparator operators work similarly as you described.
Public Function MinStr(ByVal strVal As Range) As String
Dim i As Integer
Dim cell As Range
MinStr = ""
'Check to make sure the range is not empty
if strVal.Rows.Count > 0 then
'Initialize MinStr to a known value
MinStr = strVal.cells(1,1).Value
'Iterate through the entire range
For Each cell in strVal.Cells
if(MinStr > cell.Value) then
MinStr = cell.Value
end if
Next cell
end if
End Function
Public Function MaxStr(ByVal strVal As Range) As String
Dim i As Integer
Dim cell As Range
MaxStr = ""
'Check to make sure the range is not empty
if strVal.Rows.Count > 0 then
'Initialize MaxStr to a known value
MaxStr = strVal.cells(1,1).Value
'Iterate through the entire range
For Each cell in strVal.Cells
if(MaxStr < cell.Value) then
MaxStr = cell.Value
end if
Next cell
end if
End Function
I am using the following Index Match function to get the name of a company where the spend data matches that of which I type into cell BF17.
=INDEX($AM$16:$BB$16,MATCH(BF17,AM17:BB17,0))
What I want to be able to do is list multiple results within the same cell and separate these with a comma.
Does anyone know if this is possible and if so can someone please show me how?
Thanks
Code:
Insert this code in a module in your workbook:
Public Function hLookupList(KeyVal, Vals As Range, Ret As Range) As String
Dim i As Long
Dim vw As Worksheet
Dim rw As Worksheet
Dim RetStr As String
Application.Volatile True
Set vw = Vals.Worksheet
Set rw = Ret.Worksheet
If Vals.Rows.Count > 1 Then
hLookupList = "Too Many Value Rows Selected!"
Exit Function
End If
If Ret.Rows.Count > 1 Then
hLookupList = "Too Many Return Rows Selected!"
Exit Function
End If
If Vals.Columns.Count <> Ret.Columns.Count Then
hLookupList = "Value Range and Return Range must be the same size!"
Exit Function
End If
For i = Vals.Column To Vals.Column + Vals.Columns.Count - 1
If vw.Cells(Vals.Row, i) = KeyVal Then
RetStr = RetStr & rw.Cells(Ret.Row, Ret.Column + i - 1) & ", "
End If
Next i
hLookupList = Left(RetStr, Len(RetStr) - 2)
End Function
Then:
Insert this in the cell where you want your list: =hLookupList(BF17, $AM$16:$BB$16, $AM$17:$BB$17)
Unfortunately there is no built-in way to make a vlookup or index/match function return an array. You could do it with a custom formula or if you know there are a limited number of results, a few nested lookups. Lewiy at mrexcel.com wrote a great custom function that I use, which can be found here. This function can be slow if you are looking up a large number of rows.
Since you are looking up columns and want commas separating the results instead of spaces, you will need to modify the code as follows:
Function MYVLOOKUP(lookupval, lookuprange As Range, indexcol As Long)
Dim r As Range
Dim result As String
result = ""
For Each r In lookuprange
If r = lookupval Then
result = result & "," & r.offSet(indexcol, 0)
End If
Next r
result = Right(result, Len(result) - 1)
MYVLOOKUP = result
End Function
Your formula would then be =MYVLOOKUP(BF17,AM17:BB17,-1)
If you want a space after the comma (in the results), change:
result = result & "," & r.offSet(indexcol, 0)
to
result = result & ", " & r.offSet(indexcol, 0)
If you haven't used custom functions before, hit Alt + F11 when in Excel to bring up the VBE, and add a new module to the workbook you are working on (Insert --> Module). Just copy and paste this code in there. I would recommend Paste Special --> Values before sending the workbook to anyone. Let me know if you have any questions implementing it!
I use check boxes on individual worksheets to set ranges for performing VLookup functions. One of the check boxes needs to set two distinct ranges in which to search. I'm out of ideas on how to make this work. All the other possible variants are searching a continuous string of cells (i.e. [S9:T20] or [S55:T66] but not both. If I end up having to u multiple variables and perform the function twice the rest of my code will probably not work. Any ideas would be appreciated including if some sort of Find function might do similar work.
Below are snippets of the code that I use:
Dim rngO As Variant
ElseIf ActiveSheet.Shapes("Check Box 43").ControlFormat.Value = 1 Then
rngO = [S9:T20;S55:T66]
The rngO variant is used as shown below (one example):
Case 2
With ActiveSheet
.Range("U2").Value = "1Y"
.Range("V2").Value = WorksheetFunction.VLookup("1Y", rngO, 2, False)
.Range("U3").Value = "1P"
.Range("V3").Value = WorksheetFunction.VLookup("1P", rngO, 2, False)
.Range("U4").Value = "."
.Range("V4").Value = "."
short answer: Yes - it is!
longer answer:
You wrap the WorksheetFunction.VLookup() by some code looking at each area of your source range individually.
Function MyVLookup(Arg As Variant, Source As Range, ColNum As Integer, Optional CmpSwitch As Boolean = True) As Variant
Dim Idx As Integer
MyVLookup = CVErr(xlErrNA) ' default return value if nothing found
On Error Resume Next ' trap 1004 error if Arg is not found
For Idx = 1 To Source.Areas.Count
MyVLookup = WorksheetFunction.VLookup(Arg, Source.Areas(Idx), ColNum, CmpSwitch)
If Not IsError(MyVLookup) Then Exit For ' stop after 1st match
Next Idx
End Function
and in your original code replace all calls to WorksheetFunction.VLookup() by calls to MyVLookup() with the same parameters.
Alternatively you can use this function directly in a cell formula (that's what I usually do with it ...)