Visual Basic in Excel - WorksheetFunction.max - vba

What is the behavior of this function to be when the values being compared are equal?
If
Cell1 = 10
Cell2 = 11
Debug.Print WorksheetFunction.max(Cell1,Cell2)
Will return Cell2. But if
Cell1 = 10
Cell2 = 10
It returns Cell1.
What am I missing here?
UPDATE:
I have a label at Offset(0,-3)
If one value is greater then I get the appropriate label.
When the values are equal I get on label in particular.
Since I have not defined a consequence when the vales are equal, I wonder what I am missing
about the behavior of this function such that it will choose one value over the other when they are equal.
How much more coding should I assume is necessary?
Function testWinner(rng As Range) As String
testWinner = WorksheetFunction.Index(rng, WorksheetFunction.Match(WorksheetFunction.max(rng), rng, 0)).Offset(0, -3)
End Function

It's nothing to do with Max (now that you've edited your question). Max simply returns the value (since they are all the same) and your Match function is looking for an exact match so it scans through the range until it finds the first match then that position is passed to Index.

WorksheetFunction.Max does not return a cell/range. It returns the largest value in a set of value.
By the way: in case of when both values are equal (10,10), the result of Min, Max and Average function will be the same: 10.
If by saying the behaviour of function you mean how it works, it sorts data and gets the highest value. So, in case of set of value: {10, 10, 8}
Max(10,10,8)** = 10
Max.K({10,10,8},2) = 10 'where K = 2, which means the second highest value
Max.K({10,10,8},3) = 8 'where K = 3, which means the third highest value
On the other hand...
row/column A B
1 10 =RANK(A$1:A$3,A1) 'returns 1
2 10 =RANK(A$1:A$3,A2) 'returns 1
3 8 =RANK(A$1:A$3,A3) 'returns 3
Is it more understandable, now?

In your updated example the following happens in your testWinner function:
The Max value is determined: in example one this is 15 (in example 2 it's 14). Note that only the value but not a reference to any cell is returned
The MATCH function searches through through D2:D3 for the value 15 (14). It does so top down and return the first exact match. I.e. in the first example, this results in 1 (in the second example the result is 2)
INDEX will return the 2nd element of D2:D3 (the 1st in the second example), i.e. cell D2 (D3 in ex. 2)
OFFSET returns the cell three to the left
Thus, the answer to your question is not the return of the MAX function - but the MATCH function - which goes top to bottom until it finds the first element matching your criteria, in your case the max.
Independent of this: why do you use a User-Defined-Function for this? If you'd build this with the normal Excel functions, performance would be higher - and non-VBA users could still understand it. Using =INDEX($A$2:$A$3,MATCH(MAX($D$2:$D$3),$D$2:$D$3,0)) you would save the OFFSET - and make it less error prone, as you can now insert columns between A and D without breaking your model!

Related

Extracting "hidden" data from expanding/collapsing pivot table - Excel

I'm not sure if this is possible but as you can see I have a pivot table with multiple dependent and expandable fields. I am trying to concatenate the data from columns A:D into one cell which works fine in row 2 but doesn't work with blank parent cells, as you can see in column F.
Any ideas for how to achieve this?
Pivot table
This answer assumes that you don't want to just Repeat All Item Labels in the PivotTable from the "Report Layout" drop-down on the Pivt Table Tools "Design" tab.
A formula to get the first non-blank value on or above the same row as the current cell from Column B can be constructed with a combination of AGGREGATE, SUMPRODUCT and OFFSET, like so:
=OFFSET($B2,SUMPRODUCT(AGGREGATE(14,6,ROW($B$1:$B$100)*--(ROW($B$1:$B$100)<=ROW())*--(LEN($B$1:$B$100)>0),1))-ROW(),0)
How does it work?
Starting with the outermost part, OFFSET($B2, VALUE, 0) - this will start in cell B2, then look up or down by VALUE rows to get the value.
Next we need to know how many rows we will need to look up-or-down. Now, if we can work out the bottom-most row with data, we can subtract the current ROW() from that, giving us OFFSET($B2, NON_BLANK-ROW(),0)
So, to finish up we need to work out which rows are not blank, AND which rows are on-or-above our current row, then take the largest of those. This is going to take an ArrayFormula, but we can use SUMPRODUCT to make that calculate properly. To find the largest number we could use MAX or LARGE - but we get less errors if we pick AGGREGATE(14,6,..,1). (The 14 means "we want the kth largest number", the 6 means "ignore error values", and the 1 is k - so "we want the largest number, ignoring errors")
But, what list of numbers are we going to look at, I don't hear you ask. Well, we want the ROW for output from our range (I'm using $B$1:$B$100, because using the whole column B would take far to long to calculate repeatedly), a comparison against the current ROW(), and check that the LENgth is > 0. Those last two are comparisons, so let's write them out first:
ROW($B$1:$B100)<=ROW()
and
LEN($B$1:$B$100)>0
We want to use -- to convert TRUE and FALSE to 1 and 0 - this means that any "bad" values become 0, and any "good" values are larger than 0:
ROW($B$1:$B$100)*--(ROW($B$1:$B$100)<=ROW())*--(LEN($B$1:$B$100)>0)
This gives us the Row number when the Row is on-or-before the current row AND Column B is not blank - if either of those are False, then we get 0 instead. Stick that in the AGGREGATE to find the largest number:
AGGREGATE(14, 6, ROW($B$1:$B$100)*--(ROW($B$1:$B$100)<=ROW())*--(LEN($B$1:$B$100)>0), 1)
Then put it in a SUMPRODUCT to force Excel to treat it as an ArrayFormula, and that's your NON_BLANK. This then gives you that first formula right at the top of the post

How does one subtract the .Value from the Offset.Value of any given 'i' in a For Each loop?

I have the following code written in VBA:
For Each W In Range("B5:B15000").Cells
If W.Offset(-1, 0).Value - W.Value > 1.5 Then
W.Offset(-1, 0).EntireRow.Delete
End If
Next W
The intent is to iterate down 'Column B', between Rows 5 and 15000, subtracting each of the current 'W' values from the Offset 'W' values. If the resultant difference from the equation is greater than 1.5 then delete the entire following 'W'(Offset) row. If the difference is less than or equal to 1.5, move on to the next 'W'.
For a better understanding of the data set, column B is essentially a time stamp series. As you go further down the column, the time stamp increases in varying amounts of seconds. I need to pick out the gaps (so to speak) that are greater than 1.5 seconds and delete them.
Here is a sample of the column data:
The task seems simple enough, but I am receiving a 'Type Mismatch' error. I am relatively new to the For Each loop, so I do not quite understand what portions are mismatched.
The formats are definitely numbers, so you shouldn't get a type mismatch. My guess is row 5 is the first row which contains data so offsetting by -1 to row 4 causes the error because you cannot subtract a string (the header text) and number.
This aside, your logic of going top down could present a problem (in addition the implementation problem of deletion of the iterator, W, modifying the collection). Consider this data:
12
13
15
16
17
Going top down would delete row 3 and then compare row 4 to row 2 (since 3 was deleted) and delete it is well since the difference is larger than 1.5 (and so on).
Instead you will want to go bottom up. In the above example, row 5 and row 4 would remain because they would be compared with respect to their intended value with row 3 still being deleted.
The code for that could look something like this:
Dim i As Long
For i = 15000 to 6 Step -1 '6 because 6-1 would mean 5 is the first row which contains data.
If Cells(i - 1, "B").Value - Cells(i, "B").Value > 1.5 Then
Rows(i).Delete
End If
Next
Edit: Going bottom up is still going to leave large gaps in your data. For example in your provided data the following (among others) would be deleted:
50
31
23
A Type Mismatch error is due to a value that isn't the type you think it is. Most likely, one of the cells is blank, or contains text, or something that's not a number.
Explicit conversion to a Double with CDbl will likely not fix the Type Mismatch error, because if either value is not a number, things go boom anyway - implicit conversion or not.
The right thing to do is to handle all the edge cases - you need to decide how to handle values that are not a number - here's a function that treats them as 0:
Private Function GetNumericValue(rng As Range) As Double
Dim result As Double
On Error Resume Next
result = CDbl(rng.Value)
If Err.Number <> 0 Then result = 0
On Error GoTo 0
GetNumericValue = result
End Function
Now you can do this:
If GetNumericValue(W.Offset(-1, 0)) - GetNumericValue(W.Value) > 1.5 Then
If that still doesn't fix it, and your data is completely made of what appears to be numbers, then my bet is on the decimal separator character being off. Verify that in Control Panel / Regional Settings. Your spreadsheet seems to be expecting a dot, but your settings might have it as a comma.

VBA Match ignores my range parameter and returns wrong value

fnd = "MultistateIndicator1.1.St_Caption"
'find the row with the text to change
nRowSavedRecord = Application.Match(fnd, Sheets("Sheet1").Range("D1027:D6000"), 0)
Will return a value like 8or 102 when those rows are clearly outside the range of D1027:D6000 plus here is the match it thinks it found:
Looked for: MultistateIndicator1.1.St_Caption
and Match found: EnablePushButton.1.St_Caption (in Column D row 8)
I have 0 in the parameter so it finds exact match.
The row number returned by Match is relative to the first row of the source range, starting with 1.
The value of 8 represents the row 1027 + 8 - 1, which is Range("D1027:D6000").Cells(8).
WorksheetFunctions.Match() returns a relative position. You might want to look at cell D1034 for the found value.

Returning entry from an array that fits a range condition

Is there a way to have a cell in Excel 2007 view through an array of six numbers and return the highest of any of the numbers that fit into a specified range?
My particular numbers in the array are 22 18 14 10 7 4 and my range is 6 to 12. I was thinking of creating a separate column with the AND function to simplify some steps but I can't make it work.
I am not sure what you are referring to when you say array, but if the array refers to cells:
{=MAX(IF(B18:G18<12,IF(B18:G18>6,B18:G18,0),0))}
Will give you the value between (6,12) which is highest in the range A1:A6.
Be sure to make the function an array wise function by using control + shift + enter.

A program that will return the cell address of the minimum value in a row?

So I have a chart that looks something like this. Assume that the top left value, 1, is in cell A1:
x= 1 2 3 4 5 6 7 8
4 3 2 1 2 3 4 5
9 8 7 6 7 8 9 10
8 7 6 5 4 3 2 1
Sum= 21 18 15 12 13 14 15 16
There are x values from 1 to 8, and a three columns of values resulting from using it an equation or something below it. The sum is the sum of the three values below their corresponding x-value.
I'm stuck trying to figure something out that will go through the row of sums, find the smallest value, and then assign it's corresponding x-value to a variable. I also need to assign the values to the left and right of that x-value to other variables.
For this particular chart, 12 is the smallest of the sums, so I would assign variable1 = 4, since that is that column's corresponding x-value. Then my second variable, which is called lowerbound, would equal 3, since it is to the left of x = 4, and my third variable, which is called upperbound, would equal 5, since it is to the right of x = 4.
If I could get the cell address returned of the x-value that corresponds to the smallest sum, then I could assign it to a variable, and then simply offset from that cell to assign the other variables. Even if I could make a program that will return me the cell of the minimum sum value, I could offset to the x-row, and go from there.
How would I do something like that?
TL:DR: To ask more clearly, since that's a lot of words: What would a program look like that detects the smallest value in the sum row, and returns the cell address of that value?
The length of the rows are an unknown, and vary a lot, but the length of the columns are given. They do change depending on the problem, but they will always be known. So I will always know how many rows are in a column, but I will not know how many columns are in a row.
This is the most confusingly-worded thing I've ever written in my entire life, but I hope I've explained it well enough to make some sense.
You guys really are amazing, by the way. I've gotten so far on this program, and it's all because of how helpful you are. I honestly think I would still be stuck at the beginning with you guys! You're willing to tolerate a newbie's incessant questions.
I am assuming that the sum is in A4:H4. Please change as applicable
You can use a formula like
=CELL("address",INDEX(A4:H4,MATCH(MIN(A4:H4),A4:H4,0)))
If you want to use VBA then you can use this
Sub Sample()
MsgBox Application.Evaluate("=CELL(""address"",INDEX(A4:H4,MATCH(MIN(A4:H4),A4:H4,0)))")
End Sub
Using your example, the following formula returns the cell address in row 1 whose value in row 5 is the lowest:
=ADDRESS(1,MATCH(MIN(A5:H5),A5:H5,0))
And if you want that cell's value, use INDIRECT. It takes the address as a string.
=INDIRECT(ADDRESS(1,MATCH(MIN(A5:H5),A5:H5,0)))
If you sum the columns by taking the sum of the array. Here is the VBA version:
For j = 1 To 8
For i = 1 To 3
sum(j) = sum(j) + Cells(i + 1, j + 1)
Next i
Cells(5, j + 1) = sum(j)
Next j