Excel MAXIF function or emulation? - excel

I have a moderately sized dataset in excel from which I wish to extract the maximum value of the values in Column B, but those that correspond only to cells in Column A that satisfy certain criteria.
The desired functionality is similar to that of SUMIF or COUNTIF, but neither of those return data that is necessary. There isn't a MAXIF function; how do I emulate one?

You can use an array formula.In the cell in which you want the max calculated enter: =Max(If([test],[if true],[if false]) where you replace the values in square brackets with the test, what to return if true and what to return if false. For example:
=MAX(IF(MOD(A2:A25,2)=0,A2:A25,0)
In this formula I return the value in column A if the value divided by 2 has no remainder. Notice that I use a range of cells in my comparison and in the value if false rather than a single cell.
Now, while still editing the cell, hit Ctrl+Shift+Enter (hold down the Ctrl key and the Shift together and then hit enter).
This creates an array formula that acts on each value in the range.
EDIT BTW, did you want to do this programmatically or manually? If programmatically, then what environment are you using? VBA? C#?
EDIT If via VBA, you need to use the FormulaArray property and R1C1 references like so:
Range("A1").Select
Selection.FormulaArray = "=MAX(IF(MOD(R[1]C:R[24]C,2)=0,R[1]C:R[24]C,0))"

Array formulas don't work very well when you want to use dynamic or named ranges (e.g., "the maximum amount due for rows above the current row that have the same counterparty as the current row). If you don't want to use an array formula, you can always resort to VBA to do something like this:
Function maxIfs(maxRange As Range, criteriaRange As Range, criterion As Variant) As Variant
maxIfs = Empty
For i = 1 To maxRange.Cells.Count
If criteriaRange.Cells(i).Value = criterion Then
If maxIfs = Empty Then
maxIfs = maxRange.Cells(i).Value
Else
maxIfs = Application.WorksheetFunction.Max(maxIfs, maxRange.Cells(i).Value)
End If
End If
Next
End Function

A limitation with the code provided thus far is that you are restricted to 2 conditions. I decided to take this code further to not restrict the number of conditions for the MaxIfs function. Please see the code here:
Function MaxIfs(MaxRange As Range, ParamArray Criteria() As Variant) As Variant
Dim n As Long
Dim i As Long
Dim c As Long
Dim f As Boolean
Dim w() As Long
Dim k As Long
Dim z As Variant
'Error if less than 1 criteria
On Error GoTo ErrHandler
n = UBound(Criteria)
If n < 1 Then
'too few criteria
GoTo ErrHandler
End If
'Define k
k = 0
'Loop through cells of max range
For i = 1 To MaxRange.Count
'Start by assuming there is a match
f = True
'Loop through conditions
For c = 0 To n - 1 Step 2
'Does cell in criteria range match condition?
If Criteria(c).Cells(i).Value <> Criteria(c + 1) Then
f = False
End If
Next c
'Define z
z = MaxRange
'Were all criteria satisfied?
If f Then
k = k + 1
ReDim Preserve w(k)
w(k) = z(i, 1)
End If
Next i
MaxIfs = Application.Max(w)
Exit Function
ErrHandler:
MaxIfs = CVErr(xlErrValue)
End Function
This code allows 1 to multiple conditions.
This code was developed with reference to multiple code posted by Hans V over at Eileen's Lounge.
Happy coding
Diedrich

Related

Count merged cells that have substring

I have some code by a former coworker that counts merged cells with a particular string. For example, if there was a merged cell with size 3 with the name "Youtube" on it, it would return 3.
This is the function in question:
Function MergedCellsCount(rRange As Range, crit As Variant) As Double
Application.Volatile
MergedCellsCount = 0 'in case there are no matches
For Each c In rRange
If LCase(c.Value) = LCase(crit) Then
MergedCellsCount = MergedCellsCount + c.MergeArea.Cells.Count
prev_rng = c.MergeArea.Address
End If
Next
' MergedCellsCount = MergedCellsCount / 5
End Function
But now, i want to count the cells that have a substring within that string.
If for example there was a merged cell with size 3 with "Youtube | Spotify", I want to know how to change that function to search for the substring "Youtube".
Any suggestions on how to achieve this?
Thanks in advance!
Count Cells Containing a String (Merged Cells) (UDF)
Note that if you merge or unmerge cells within the Criteria Range, the cells count will not be updated until the next recalculation of the worksheet.
To trigger recalculation, a nice 'trick' is to autofit a column by double clicking the right border of its column header (e.g. for column A, the short line between A and B).
The Code
Option Explicit
Function MergedCellsCount(CriteriaRange As Range, _
Criteria As String) _
As Long
Application.Volatile
Dim c As Range
For Each c In CriteriaRange.Cells
If Not IsError(c) Then
If InStr(1, c.Value, Criteria, vbTextCompare) > 0 Then
MergedCellsCount = MergedCellsCount + c.MergeArea.Cells.Count
End If
End If
Next
End Function

Is there a function for finding "ends with"

Is there a function to find from entries in a column that ends with ".5" using vba?
I have a live feed that I take from a html page, in values in column B are float numbers and I want to know if I can use a VBA function to find out how many values are ending with 0.5
Well without VBA:
=SUMPRODUCT(--(RIGHT(B1:B23,2)=".5"))
and with vba, then:
Sub dural()
MsgBox Evaluate("SUMPRODUCT(--(RIGHT(B1:B23,2)="".5""))")
End Sub
EDIT#1:
The worksheet formula treats column B like Strings. and counts how many in column B end with .5. This is expressed as an array of 0/1 by the expression within the --().
SUMPRODUCT() just adds up this array.
And with VBA, as a user-defined function (UDF):
Public Function CountThePointFive(ByRef theArea As Range) As Long
Dim count As Long
Dim cell As Variant
For Each cell In theArea
Dim value As String
value = CStr(cell.value)
Dim integerPart As Long
integerPart = CLng(Left$(CStr(value), Len(value) - InStr(1, value, ".")))
If (cell.value - integerPart) = 0.5 Then
count = count + 1
End If
Next cell
CountThePointFive = count
End Function

EXCEL array formula to indicate if a value in one range is present in another range

I have a list of values in column A, and another list of values in column B.
I am trying to have a single cell in column C indicate TRUE if any value in column A is in Column B, or FALSE is no value in column A is in Column B.
I tried MATCH(lookup value, in range), but that only looks at one value in A.
Is it possible to do this without VBA? Open to VBA solutions also. Thanks
Here is a quick and dirty UDF that will do it.
Public Function Exist(a As Range, b As Range) As Boolean
Dim temp As Boolean
temp = False
For Each cel In a
If WorksheetFunction.CountIf(b, cel.value) > 0 Then
temp = True
Exit For
End If
Next cel
exist = temp
End Function
I've used something like this to compare values in one list to another in VBA. Could take a while if you have a lot of values to check. Just set the B range to however large the range is on your sheet.
Option Explicit
Dim Index As Long
Dim valueFound As Range
Sub compareAandB()
Index = 2
Do While ThisWorkbook.ActiveSheet.Cells(Index, 1) <> ""
Set valueFound = ThisWorkbook.ActiveSheet.Range("B2:B1000").Find(ThisWorkbook.ActiveSheet.Cells(Index, 1))
If valueFound Is Nothing Then
ThisWorkbook.ActiveSheet.Cells(Index, 3) = "Not Found"
Else
ThisWorkbook.ActiveSheet.Cells(Index, 3) = "Found"
End If
Index = Index + 1
Loop
End Sub

How can I check for a condition across rows in a matrix in VBA

Input: I have a range in an excel sheet. Let's say B1:F100 = 100X5 matrix on "Sheet 1".
Now I want to count the number of rows that have "Data" in any of the columns B to F (from rows 1 to 100). i.e. I'm looking for a function, let's say, ExistIfRow(B1:F100) that will return a 100X1 array of 0s or 1s. So I can simply do a sum(ExistIfRow(B1:F100)) to get the number of rows.
I would like to be able to select 100 cells and enter this function as an array formula to get that 100X1 result in the sheet.
I hope that makes sense.
In addition, I attempted to create this function but it doesn't show up in my excel sheet when I try to put it in a cell. Can someone please help me see what I am doing wrong?
This function exists under "Modules" in the worksheet.
Function RowWiseOR(Rin As Range, Condition As Variant) As Range
Dim rw As Range
Dim Out As Variant
Dim i As Integer
i = 0
For Each rw In Rin.Rows
If WorksheetFunction.CountIf(rw, Condition) > 0 Then Out(i).Value = 1 Else Out(i).Value = 0
i = i + 1
End
RowWiseOR = Out
End Function
I'm not sure why "it doesn't show up", but there were a few issues with the function itself. Specifically,
The function should return a Variant array, not a Range
The variable Out needed to be initialized as an array to hold the results
Elements of arrays don't have a .Value property. For example, use Out(i) = 1 instead of Out(i).Value = 1
Transpose the array before writing it out to the sheet if you want it to be a column vector.
See below for the revised function:
Function RowWiseOR(Rin As Range, Condition As Variant) As Variant
Dim rw As Range
Dim Out As Variant
Dim i As Integer
i = 0
ReDim Out(Rin.Rows.Count - 1)
For Each rw In Rin.Rows
If WorksheetFunction.CountIf(rw, Condition) > 0 Then Out(i) = 1 Else Out(i) = 0
i = i + 1
Next
RowWiseOR = Application.Transpose(Out)
End Function
Remember to enter it as an array formula. See here for more information.
The reason why Countif is giving an error because countif does not work with array.
Try the same in excel and you will again get an error.
Also i do not understand why you need to create a function to count 0's or 1's. this can be easly achieved by countif formula in excel - COUNTIF(A1:C3,"data")

Randomly choose a value in one column subset by another column

What is the simplest formula I can use to randomly choose a value in column A that is associated with a given B value. So in the table below, I'm looking to randomly choose an A where B = 3. So I'm randomly choosing between row 1 (5.4) and row 3 (4.2). Note that this table can be arbitrarily large.
A B
1 5.4 3
2 2.3 1
3 4.2 3
4 9.2 2
... ...
Conceptually you could do it a number of ways, but here's one (VBA) where you'd use an array of possible choices then get a random element from that list:
Create a udf that takes a range and the search value
Loop through the row and if it equals your search value, get the value in the cell offset -1 and store it in an array
Once you are done, you'll have an array of all possible answers. Use the randbetween function and give it the lbound and ubound of your array.
Return the i element where i is the random number it picked.
UPDATE:
Here is a code example that loops through the range for the number you specify, and if it find it, it adds the A column value to an array of possible results. Then a random number is generated and used to return a random value from that list.
Function GetRand(ByVal cell_range As Range, ByVal criteria As Double) As Double
Dim cell As Range
Dim rNum As Long
Dim i As Long
Dim possibleChoices() As Double
ReDim possibleChoices(1 To cell_range.Count)
i = 1
For Each cell In cell_range
If cell.Value = criteria Then
possibleChoices(i) = cell.Offset(0, -1).Value
i = i + 1
End If
Next
rNum = Application.WorksheetFunction.RandBetween(1, i - 1)
GetRand = possibleChoices(rNum)
End Function
Optimization:
Here is a more flexible version of the same function. It takes 3 paramteres - the range you want to look in, what you want to find, and the offset value of the cell you want a random result from. It also uses Variants, so you can search for text or numbers. So in your case, you'd write:
=GetRand(B1:B5, 3, -1)
Here is the code:
Function GetRand(ByVal cell_range As Range, _
ByVal criteria As Variant, _
ByVal col_offset As Long) As Variant
Application.ScreenUpdating = False
Dim cell As Range
Dim rNum As Long
Dim i As Long
Dim possibleChoices() As Variant
ReDim possibleChoices(1 To cell_range.Count)
i = 1
For Each cell In cell_range
If cell.Value = criteria Then
possibleChoices(i) = cell.offset(0, col_offset).Value
i = i + 1
End If
Next
rNum = Application.WorksheetFunction.RandBetween(1, i - 1)
GetRand = possibleChoices(rNum)
Application.ScreenUpdating = True
End Function
Old question I know......but if you're still interested here's a formula solution assuming data in A2:B10
=INDEX(A2:A10,SMALL(IF(B2:B10=3,ROW(A2:A10)-ROW(A2)+1),RANDBETWEEN(1,COUNTIF(B2:B10,3))))
returns #NUM! error if there are no 3s in B2:B10.....or enclose in IFERROR to return text of your choosing in that case....

Resources