I have a simple array formula in excel that doesn't work in the way I wish. In columns A and B there is data (A1 is paired with B1 and so on) while in column F there is the calculation based on the parameter in column E.
In cell F1 the formula is:
{=SUM(MAX(A$1:A$9, E1)*B$1:B$9)}
What this formula does is:
=MAX(A$1:A$9, E1)*B$1 + MAX(A$1:A$9, E1)*B$2 + ...
Instead, I need a formula that does this:
=MAX(A$1, E1)*B$1 + MAX(A$2, E1)*B$2 + ...
In words, the formula I wrote (the first one) always finds the max between the values from A1 to A9 and E1, multiplies it by the i-th B value and sums the results. What I need is a formula that finds the max between the i-th A value and E1, and not between all the A values.
What I'm looking for is easily done by adding in column C the formula =MAX(A1;E$1)*B1 and then in F1 just =SUM(A1:A9), but I can't use this solution because in column F the same formula is repeated, with the E parameter changing every time.
I can use a IF instruction: in F1 I can write
{=SUM(IF(A$1:A$9>E1, A$1:A$9, E1)*B$1:B$9)}
While this formula does what I need in this case, I think it's a bad solution because I find it difficult to read and to expand. For example, if there is another parameter in column D and the factor is MIN(MAX(A$1:A$9;E1);D1), using IF will result in a very long and very unreadable and complicated formula.
Are there better solutions to my problem? Thank you all!
NOTE: syntax may vary a little because I am using the italian version of excel.
The problem is that MAX takes an array as an argument. Functions that normally take an array never return an array - they were designed to turn an array into one number. No matter how many arrays you throw at MAX, it's always just going to return one number.
I couldn't come up with a good solution, so here's a bad one
=SUMPRODUCT(((A1:A9*(A1:A9>E1))+(E1*(A1:A9<=E1)))*B1:B9)
I don't think that really increases the maintainability of the IF-based formula that you're trying to avoid. I think you're stuck with IF or a helper column.
Another possibility is a VBA function.
Public Function SumMaxMin(rRng1 As Range, rRng2 As Range, ParamArray vaMinMax() As Variant) As Double
Dim rCell As Range
Dim dReturn As Double
Dim aMult() As Double
Dim lCnt As Long
Dim i As Long
ReDim aMult(1 To rRng1.Cells.Count)
For Each rCell In rRng1.Cells
lCnt = lCnt + 1
aMult(lCnt) = rCell.Value
For i = LBound(vaMinMax) To UBound(vaMinMax) Step 2
If Not Evaluate(aMult(lCnt) & vaMinMax(i + 1) & vaMinMax(i)) Then
aMult(lCnt) = vaMinMax(i)
End If
Next i
Next rCell
For i = LBound(aMult) To UBound(aMult)
dReturn = dReturn + aMult(i) * rRng2.Cells(i).Value
Next i
SumMaxMin = dReturn
End Function
For your example
=SumMaxMin(A1:A9,B1:B9,E1,">")
Adding another condition
=SumMaxMin(A1:A9,B1:B9,E1,">",D1,"<")
It will error if your ranges aren't the same number of cells or you pass arguments that don't work with Evaluate.
Another possibility for avoiding repetitions of cell references is:
=SUM(B1:B9*ABS(A1:A9-E1*{1,-1}))/2
assuming values are non-negative. More generally to return an array of pairwise max values:
=MMULT((A1:A9-E1*{1,-1})^{2,1}^{0.5,1},{1;1}/2)
which returns:
MAX(A1,E1)
MAX(A2,E1)
...
MAX(A9,E1)
I don't remember ever cracking this problem, but for maintainability I'd probably do something like this:
{=SUM((A1:A9<E1)*E1*B$1:B$9) + SUM((A1:A9>=E1)*A1:A9*B$1:B$9)}
If I understand the problem correctly, using IF instead of MAX should do:
=SUM(IF($A$1:$A$9>E1;$A$1:$A$9;E1)*$B$1:$B$9)
Related
I am trying to write a simple formula to count how many times a particular name appears in a column. I am using COUNTIF as it is a pretty straight forward process but I cannot work out how to make it happen for a name in particular. This is the case:
The column named Age will display cells with one or more names, separated by commas in case there are more than one value. Putting "Young" as an example is easy to tell the COUNTIF formula to give me the number representing how many times this word appears, either being the only value in cell or as a part of a cell with a longer string value by giving the formula the "Young" statement.
The problem comes when I want the formula to count how many times "Mature" appears in my column. I cannot work out the way to make it count only when it says "Mature" without also taking all the "Early_Mature" or "Semi_Mature"
I know this is easy for whoever knows the basics of Excel so I don't think there is need to give more details.
Thanks
Most of the times I succeed solving such problems by adding the same delimiter (of our string) at the beginning and end of the main string.
So since your data is at COL:Y, you may create a new helper COL:Z and enter this formula:
="," & Y1 & ","
I did not use any spaces before or after comma since your data seems not having any space. Depending on your case, you may have to use spaces.
Now your string is wrapped with commas, which you may alter COUNTIF formula to such:
=COUNTIF(Z:Z,"*,"&B1&",*")
* characters are jokers which stand for "anything" in this context.
With an UDF. Code goes in a standard module added by opening the VBE with Alt + F11 then right-click in project explorer and add module.
Code
Option Explicit
Public Function GetCount(ByRef selectRange As Range, ByVal searchTerm As String) As Long
Application.Volatile
With selectRange
Dim arr(), joinedString As String, i As Long, outputCount As Long
arr = .Value
joinedString = Join(Application.WorksheetFunction.Transpose(Application.WorksheetFunction.Index(arr, 0, 1)), ",")
Dim arr2() As String
arr2 = Split(joinedString, ",")
For i = LBound(arr2) To UBound(arr2)
If Trim$(arr2(i)) = "Mature" Then
outputCount = outputCount + 1
End If
Next i
End With
GetCount = outputCount
End Function
Usage in sheet
To get the number of occurrences of Mature excluding those that have prefix you can use this array formula:
=SUM(((LEN(A2:A7)-LEN(SUBSTITUTE(A2:A7,"Mature",""))) / LEN("Mature"))-((LEN(A2:A7)-LEN(SUBSTITUTE(A2:A7,"_Mature",""))) / LEN("_Mature")))
Please take note that this formula is applied with Ctrl + Shift + Enter.
Given that your range is in Y:Y column, just change the range to one you need.
An alternative would be to change "Mature" to "Fully_Mature". Then you could just use Countif().
You'd have to do this in steps:
1) Change "Early_Mature" to "E_M"
2) Change "Semi_Mature" to "S_M"
3) Change "Mature" to "Fully_Mature"
4) reverse of step 1).
5) reverse of step 2).
I have an excel table were A1 = "YES", B1 = "YES" and C1 = "YES". I want to know how many "YES" are in my table, which is easily solved with =COUNTIF(A1:C1,"YES") and it will accurately give me the answer of 3.
A B C
1 YES YES YES
But if want to know how many "YES" are in A1 and C1 and ignore whatever B1 has it becomes tricky. The same function gives me 3 while I want it to give me 2 as an answer since I want to count only A1 and C1.
I want to know if there is a way to manage data so I can work only with that kind of non-consecutive cells? I found that a solution would be using something like =if(A1="YES",1)+if(C1="YES",1) and it works like a charm since it will always give me the right answer; however, that is not a satisfying solution because despite the simplicity in my example, my real situation requires to write around 100 cells from a 500 range for several different combinations which can become somewhat heavy.
I tried using name ranges but it seems the if functions doesn't let me use them the way I want. So any help will be apreciated, thanks.
If VBA is an option and "non-consecutive ranges in excel functions" is the question, then you could create a User Defined Function which takes a ParamArray and returns an array out of all given parameters.
Example
Public Function getMatrix(ParamArray aValues() As Variant) As Variant
Dim i As Long
Dim aReturnValues() As Variant
For Each vValue In aValues
For Each vSingleValue In vValue ' possible the vValue is also an array
ReDim Preserve aReturnValues(i)
aReturnValues(i) = vSingleValue
i = i + 1
Next
Next
getMatrix = aReturnValues
End Function
This could then be used like:
Formula in A4:
=SUMPRODUCT(--(getMatrix(A1,C1,E1:G1,D2:E3)="Yes"))
Note, the UDF is returning an array, not a Range. Thats why we cannot use COUNTIF. COUNTIF needs a Range as first parameter.
Why looking for a formula when you have other features in Excel? Add a row (#2), and have A2=1, B2=0, C2=1 till the last column [Drag it to populate, Make sure to Copy Cells]. Then select row and see the Sum in Status Bar.
Have a look at this thread. Basically what you need is something like this:
=SUM(COUNTIF(INDIRECT({"A1:A10","C1:c10"}),"a"))
Or just use multiple COUNTIFs, and add them. (Countif(...) + countif(...) etc.)
I tried to use the MATCH function in an array formula to return multiple matches (by default it only returns the first match). However, this doesn't seem to work. How can I solve this problem without a complex, unreadable formula?
How about this, without VBA? [entered on cell C9 as an array formula with CTRL + SHIFT + ENTER, where your searched column is A9:A24, and your search terms are in B1:B4], and dragged down to find multiple hits?
=SMALL(IFERROR(MATCH($B$1:$B$4,$A$9:$A$24,0),""),ROW()-ROW($C$8))
This first uses the array formula to show each 'hit' for any of the search terms matched in the searched column, and then using the Small function with reference to the current cell's row, it returns the earliest hit, then the 2nd hit, then the 3rd hit, etc.
Beyond this point, the reference points to the searched array can be used as needed (converted to the row location of an index function, etc.).
EDIT
On further review of the results from this formula, it only returns a single hit for each search term, even if that search term appears multiple times. To resolve this, I first used the formula:
=SMALL(IF($A$9:$A$24=$B$1,ROW($A$9:$A$24),""),ROW()-ROW($E$8))
This shows each hit for a match of the search term found in B1. Here is where I am stuck. I could only figure out how to resolve with the admittedly manual:
=SMALL(IF($A$9:$A$24={"a","b","c"},ROW($A$9:$A$24),""),ROW()-ROW($E$8))
Any suggestions on how to improve to allow multiple hits for multiple terms?
EDIT - Additional option
Okay, I've determined another method of picking up multiple hits. This one relies on considering the location of the previous matches already made. Depending on what you want your result vector to look like (which was never specified by the OP), the results from this are clean but the formula is fairly messy.
The first cell looks like this, in cell H9:
=ADDRESS(MIN(IFERROR(MATCH($B$1:$B$4,$A$9:$A$24,0),""))+ROW($A$8),1)
This shows the address of the first cell which matches any of the search terms, using the formula noted further above.
The cell below that (and every cell after that), has this (also an array formula):
=ADDRESS(MIN(IFERROR(MATCH($B$1:$B$4,INDIRECT(ADDRESS(ROW(INDIRECT(H9))+1,1)):$A$25,0),""))+ROW(INDIRECT(H9)),1)
This picks up the address of the cell found in the row above (adding 1 row to avoid re-hitting the same term), and from that new search column from that point to the end point (adding 1 row so that it properly stops at the last ending hit), it re-searches for any of the terms.
This one is again, not that clean [Yes I know there are some improvements I could make to determining what the search should be - either using the text manipulation functions or even doing a relative name reference that changes as you move down the column], but it is automated and, I would argue, cleaner than a VBA module. Especially as, depending on what you want your result vector to be, this could be much simpler.
Working\developing on the formulas posted by #Grade'Eh'Bacon ended up with this formula to retrieve all the results of a match function with several matches for several items.
Assuming input range is B2:B17 and the range with the items to match is F3:F5 enter this FormulaArray in H3
=IFERROR( SMALL( IF( $B$3:$B$17 = TRANSPOSE( $F$3:$F$5 ),
1 + ROW( $B$3:$B$17 ) - ROW( $B$3 ), "" ), ROWS($2:2 ) ), "" )
It's an FormulaArray returning all matches for several items
All merits go to #Grade'Eh'Bacon for his great work on the subject.
It is not possible with the built-in MATCH, however, using a VBA macro, you can achieve this:
Public Function MATCH_RANGE(values As Variant, ary As Variant, match_type As Integer)
Dim i As Integer
Dim elementCount As Integer
Dim result()
Dim value As Variant
Dim arySize As Integer
arySize = UBound(ary.Value2, 1)
Dim valueSize As Integer
valueSize = UBound(values.Value2, 1)
ReDim result(0 To arySize, 0 To 1)
elementCount = 0
For i = 1 To arySize
For j = 1 To valueSize
value = values(j, 1)
If (match_type = -1 And ary(i, 1) <= value) Or (match_type = 0 And ary(i, 1) = value) Or (match_type = 1 And ary(i, 1) >= value) Then
result(elementCount, 0) = i
elementCount = elementCount + 1
End If
Next j
Next i
For i = elementCount To arySize
result(i, 0) = -100000000
Next i
MATCH_RANGE = result
End Function
This function both returns multiple matches and allows you to pass a range of multiple values that you want matched. I've found this useful a number of times. Feedback welcome to help improve this.
NOTE: You must spread this formula across a few cells using an array-formula (CRTL-SHIFT-ENTER), in order to see the multiple matches.
I've been tearing my hair out over this and after endless searching online still can't find the answer.
Here's a quick summary of what I'm looking for an a longer explanation below!
I need a VBA function to replicate a formula similar to this: {=product(A:A+1)}. This goes through every cell in column A where there is a number and adds 1, then multiplies. This can be done using either (1) Evaluate unless there are blanks or text in the range, which gives an error or (2) worksheetfunction.product, but it doesn't like it when I look through the range and add 1.
If anyone can figure out a solution for using either of these options I would be over the moon. I may also be missing something very basic!
Longer version...
What I'm trying to do:
Create three VBA functions that calculate 1) compound return 2) annualised compound return and 3) annualised volatility. These would be for variable ranges and for the annualised functions you can also specify whether it is days, months or years. For example, the compound return function when used would look something "=compoundreturn(range,number of periods)"
The calculations are easy using formula but I want to create functions to make it a quick process.
I can get all three to work through VBA using EVALUATE.
They look like this,
Function PerRet(rng As range)
PerRet = Worksheet.Evaluate("=exp(sumproduct(ln(" & rng.Address & "+1)))-1")
End Function
Function AnnRet(rng As range, np As Double)
AnnRet = Evaluate("=exp((Average(ln(" & rng.Address & "+1)))*" & np & ")-1")
End Function
Function AnnVol(rng As range, np As Double)
AnnVol = Worksheet.Evaluate("=stdev.s(ln(" & rng.Address & "+1))*sqrt(" & np & ")")
End Function
The problem is that the range selected may sometimes by the entire column, which contains blanks and potentially text. Functions like AVERAGE or STDEV.S will ignore these when you are just using them in formulas. Unfortunately, they don't do this when using Evaluate.
I know that instead of using Evaluate for AVERAGE, I could use worksheetfunction.average, which would then ignore the blanks and text. But here I come across another problem in that part of the function has to look through a range of returns (postiive and negative) and add 1 to each before averaging. This doesn't seem to work for a range unless that range is simply one cell.
An example of what I have so far for this is
Function AnnRet(rng As range, np As Double)
AnnRet = exp(WorksheetFunction.Average(WorksheetFunction.Ln(rng + 1)) * np) - 1
End Function
Any ideas how I can make it add 1 to each of the cells in the range before getting the Ln and then averaging?
Many thanks for looking
Duncan
A little light testing suggests something along these lines might work:
Sub Tester()
Dim v
v = ActiveSheet.Evaluate("=SUM(IF(ISBLANK(A:A),false,A:A+1))")
Debug.Print v
End Sub
I just had a few numbers in ColA and it only added (number+1) where the cell was populated - ie. it did not also sum up a bunch of 1's where cells were empty.
EDIT:
After your comments about problem with non-numeric cells, a little more testing comes up with something like:
Function Tester(rng As Range)
Dim v, f
f = "=AVERAGE(IF(ISBLANK({a}),FALSE,IF(ISNUMBER({a}),LN({a}+1),FALSE)))"
'remove any unsed part of the input range (for performance)
Set rng = Application.Intersect(rng, rng.Parent.UsedRange)
v = rng.Parent.Evaluate(Replace(f, "{a}", rng.Address(False, False)))
Tester = v
End Function
This first filters out blanks - non-blanks can then safely be passed to ISNUMBER() to filter out non-numeric cells. This is not quite at your final formula but not far off. Do you have some inputs and expected output for testing?
EDIT 2:
Using Intersect() to restrict a full-column input range to only the "used" part of the sheet seems to improve performace significantly (by ~100x when I only had a thousand values in the range).
I've searched the web but I can't find anything specific for this.
I have column A as below, I need to search this column and find out how many occurrences there are of the data in column B, in the example below there a 4 (70011x3 + 70014x1).
A B
h323:70011 70011
70007 70012
70011 70013
h323:70014 70014
sip:70011#domain.com 70015
What formula would I need to use in Excel?
Thanks in advance
... I don't know if there's a simple way to do this with a single worksheet function, but I could think of 2 methods I would use to accomplish this (and would love to see if any person could figure out how to do this in a single, simple, worksheet function I couldn't think of).
Either way, my solutions would be:
1) Use Array Formulas:
This would look as follows:
A B C
h323:70011 70011 {=SUM(--ISNUMBER(FIND(B1,$A$1:$A$5,1)))}
70007 70012 {=SUM(--ISNUMBER(FIND(B2,$A$1:$A$5,1)))}
...etc
Note that for this solution, you have to type it in as an array formula (Hit ctrl+shift+enter) at the end, but when you drag down column C and sum it up, you will get the correct total.
The challenge with this one is that it doesn't really give you the answer in one cell.
2) Use a Custom VBA Function (My Preferred method):
You can write your own VBA function to do this VERY EASILY.
The one I created was:
Function MyFunc(CriteriaRng As Range, SearchRange As Range) As Double
Dim CriteriaCl As Range
Dim SearchCl As Range
Dim RunningTotal As Double: RunningTotal = 0
For Each CriteriaCl In CriteriaRng
For Each SearchCl In SearchRange
If InStr(1, SearchCl.Value, CriteriaCl.Value) > 0 Then RunningTotal = RunningTotal + 1
Next SearchCl
Next CriteriaCl
MyFunc = RunningTotal
End Function
All you can then do is in any cell type in =myfunc(B1:B5,A1:A5) and you will get the right answer.
Hope this helps!
the function COUNTIF takes wildcards.
with excel 2007, you can simply reference the whole column:
=COUNTIF(A:A,"*"&B1&"*")
with 2003 or lower, you have to put the range in as cell references
=COUNTIF(A1:A2000,"*"&B1&"*")
note that numbers will have to be formatted as text, or you can add in the countif to cover numbers
=COUNTIF(A:A,"*"&B1&"*")+COUNTIF(A:A,B1)
You can then use a SUM on the column to get the total.