Up front let me say I can do this in VBA but I am trying to do it without using VBA with the assistance of the new LAMBA function.
I have a list, let us say A,B,C,D,C,E,B,B
what I am trying to write is an enumeration function which gives me the individual positions of set of identical items. So in the above example it would return a list 1,1,1,1,2,1,2,3 (the last item is a three because it is the 3rd "b" element etc). It is easy enough to do in cells with the answer in the next row:
a1:a8 contain A,B,C,D,C,E,B,B
b1 contains =COUNTIF(A1:$A$1,A1) and that gets dragged across all the columns
But I need the output in a dynamic array and something like b1
=COUNTIF(A1:$A$1,A1:H1) obviously won't work.
I've also tried writing a recursive LAMBDA statement which runs through a diminishing range but that gives me VALUE errors and is pretty complex in any case.
Any suggestions?
Use Offset inside the COuNTIFS:
=COUNTIF(OFFSET(A1,0,0,1,SEQUENCE(,COUNTA(1:1))),INDEX(1:1,,SEQUENCE(,COUNTA(1:1))))
For a non Volatile version use:
=MMULT(SEQUENCE(,COLUMNS(A1:H1),1,0),(A1:H1=TRANSPOSE(A1:H1))*(SEQUENCE(,COLUMNS(A1:H1))>=SEQUENCE(COLUMNS(A1:H1))))
With LET:
=LET(x,A1:H1,y,COLUMNS(x),MMULT(SEQUENCE(,y,1,0),(x=TRANSPOSE(x))*(SEQUENCE(,y)>=SEQUENCE(y))))
Related
Excel doesn't seem to let you use function such as RANK/COUNTIF on a FILTER(...) formula without first having the output of the filter somewhere in the spreadsheet. Ie, functions take range as argument but not arrays.
Let's assume in Column A, I have number 1 to 10 on the ten first rows
and in column B a filter on even numbers, =FILTER(A1:A10,MOD(A1:A10,2)=0)
If I want to count the number of rows of array returned by the filter function, I can use =COUNT(B1#)
If I want to count only the values above 5 using COUNTIF, this will also work =COUNTIF(B1#, ">5")
Now, I would like to get rid of the intermediary B Column.
For the COUNT function, it works. =COUNT(FILTER(A1:A10,MOD(A1:A10,2)=0)) returns 5.
For the COUNTIF function, it doesn't work anymore. I assume this because it takes a range as a parameter and not an array.
Excel warns in a popup: "There's a problem with this formula".
Is there a workaround this without using a temporary column to store the array? It seems I can only use functions that takes an array as parameter on top of the filter function.
EDIT:
I have used COUNTIF to build a simple example but I am more interested in other functions such has RANK.
COUNTIF was not the best example because you can obviously move the IF conditions in the FILTER conditions. Thank you #JvdV
I am trying to figure out how to reference just one area of a named formula and return it as an array. This is so I can then count the number of rows in the areas referenced and eventually sum them. I going to eventually integrate the results of this into a pretty complicated mess of other formulas that automatically join and rank multiple matrices. I am trying to do this using formulas, not VBA, as a portability requirement. Some folks are a bit weary about running other folks code...
For now, though, I've come up with a simple example. Let's pretend that in the name manager we have a formula named Letters that is defined as:
=A1:A4,C1:C6
The range A1:A4 contains the letters "A" through ."D" and the range C1:C6 contains the letters "E" through "J".
If I write a simple INDEX formula I can return the first or second area of Letters like so:
=INDEX(Letters,,,1)
=INDEX(Letters,,,2)
I know this works by doing an F9 in on the formula and it returns the expected array of letters ({"A";"B";"C";"D"} or {"E";"F";"G";"H";"I";"J"}) for the appropriate area. But doing it this way makes the assumption that there will always be two areas in Letters. I'd like to keep my formula dynamic in case I was to add another area. I can create another formula named Letters_Areas and make it equal to the following:
=ROW(INDEX(Sheet1!$A:$A,1):INDEX(Sheet1!$A:$A,AREAS(Letters)))
This will return an array with the value of {1;2} for the example (or more if there was more areas) and I can pass that to an IF to loop like so:
=IF(Letters_Areas,INDEX(Letters,,,Letters_Areas)
But that doesn't work. It always returns just the first area in Letters because Letters_Areas in the second argument of the IF always returns 1 as the value, not the first and then second value of the array. I guess my question in a formula is:
=IF(Letters_Areas,INDEX(Letters,,,What_Do_I_Put_Here))
Where What_Do_I_Put_Here counts up for each iteration of the IF like a For loop would in VBA. Essentially, I need to be able to get i in my For i = 1 to 2 in this case inside the IF.
I know that the failure is the Letters_Areas in the second argument of the IF because I can test it. At first glance you would just do it as such:
=IF(Letters_Areas,Letters_Areas)
This returns the expected {1,2}. However, this is misleading because you can find the true behavior by doing this:
=IF(Letters_Areas,INDEX(Letters_Areas,Letters_Areas))
And this always returns {1,1} which tells me that is the part that is failing.
The final version of the formula, minus the part I cannot figure out should look something like:
=IF(Letters_Areas,ROWS(INDEX(Letters,,,What_Do_I_Put_Here)))
And in our test example this would return {4;6}. Again, stuck using no VBA. Any ideas?
Could you use this formula to count areas, assuming that your areas always begin in row 1 as in your example: COLUMNS(A1:Z1)-(SUM(IF(ISBLANK(A1:Z1),1,0)))? Any non-blank cells within that range will be counted as an area and will give the correct count. I am not sure if this is what you are seeking according to your question.
I often find myself writing Excel formulas that have something like this in the formula:
= IF(<long expression>=<some condition>,<long expression>,0)
Is there any way to accomplish this without needing to type out <long expression> twice (and also without using helper cells)?
Ideally, something that works similar to IFERROR, i.e.
= IFERROR(<some expression>,0)
This checks if <some expression> would return any type of error, and if it doesn't, it automatically returns <some expression> (without needing to again explicitly type it out a second time).
Is there an Excel function (or combination of Excel functions) similar to IFERROR but instead of checking an error condition, it checks a general (user-defined) condition based on the formula?
When it comes to formula efficiency and calculation speed, using helper cells can be of great value, even if they may initially muck up the spreadsheet design.
Put calculations into a helper cell and refer to the helper cell in the IF statement. That way the calculation will only happen once.
This method is preferred by spreadsheet auditors over the alternative of packing everything into one formula, because it is also much easier to follow and pick apart.
With careful spreadsheet planning you can house helper cells in a different (hidden) sheet or in columns that you hide to tidy up the design.
This answer applies to Excel 365 as of March 2020. A new function is now available in Insider builds of Excel. It is called LET() and is used to define, and assign a value to, a variable that can then be used multiple times inside the braces of the LET() function.
Example:
=LET(MyResult,XLOOKUP(C1,A1:A3,B1:B3),IF(MyResult=0,"",MyResult))
The first parameter is the name of a new variable, MyResult. The variable is assigned a value with the second parameter. This can be a constant, like a number or a text, or like in this case, a formula.
The third parameter is a calculation that can use the variable value.
In the following screenshot, the Xlookup returns a 0 because the found cell is empty.
In the next formula down, the Xlookup is wrapped in an IF statement, evaluated, and then repeated. This is the approach where the calculation is duplicated, as described in the question.
The third formula shows the LET() function and its result.
I have a question on using OFFSET in Excel.
For instance, I have a table with values varying by years.
Then, I have a table with some values varying by year/months.
!!Click here for the tables!!
I would like to write a formula e.g.
=SUMPRODUCT((E2:E37)*OFFSET(A1,C2:C37,1),E2:E37)
but it returns #VALUE!
In short, I would like to use an array of values in C2:C37 i.e. {1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3} to obtain the array {0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.03,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.04,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,0.05,}, and this array is then used in the SUMPRODUCT function.
Can someone help me solve the #VALUE! issue?
Thanks in advance!
Apparently (I didn't know this) Offset is unusual because it can return a set of range objects when used with an array as the second argument. Most functions can't handle this if you pass it to them. But if you put it through the N function, you get the right answer.
=SUMPRODUCT((E2:E37)*N(OFFSET(A1,C2:C37,1)))
or
=SUMPRODUCT((E2:E37),N(OFFSET(A1,C2:C37,1)))
Note that these appear to give an array of #Value! errors when you run them through Evaluate Formula but these resolve after passing through the N function.
Of course it would be more common to do this the easy way and use Index or Vlookup with a helper column. My first thought when trying to do it in a single Array formula without using Offset was this:-
=SUM(E2:E37*MMULT(N(C2:C37=TRANSPOSE((ROW(A2:A4)-1))),B2:B4))
I would think that the Offset way is easier and more efficient.
But in this particular case where there are only a small number of categories you could use this array formula which is perhaps the simplest approach:-
=SUM(CHOOSE(C2:C37+1,0,B2,B3,B4)*E2:E37)
The above formulae only work for the special case where the 'key' is the same as the row number in the lookup column. The offset method can easily be adapted to incorporate a lookup:-
=SUM((E2:E37)*N(OFFSET(A1,IFERROR(MATCH(C2:C37,A2:A4,0),0),1)))
See this reference
I have a row of string values, which I'd like to do a vlookup on each and then compute the average of the results. If this were C#, I'd do a Select( str => VLookup(str,dict)).Average(), is there a way to do this in a single excel function?
I'm using version 2010
In general, no. For your specific case, kind of.
Say you have a table like this
a 42
b 2
c 3
in E8:F10. If you call VLOOKUP with an array like this:
=VLOOKUP({"a","c"},E8:F10,2,FALSE)
you'll get back an array of values: {42,3}. (Enter the formula as an array function...Ctrl+Shift+Enter.) So the map part you can do in this case.
Unfortunately, AVERAGE doesn't seem to work with the array that VLOOKUP returns if you put it all in one formula and put the result in one cell. Worse, it appears to work, but just uses the first element, even if you enter the whole thing as an array formula. That is, if you put:
=AVERAGE(VLOOKUP({"a","c"},E8:F10,2,FALSE))
in cell H12, even if you enter it as an array formula, you'll get back 42 and not 22.5.
Oddly, putting the same formula in two cells, say H16:I16, and entering it as an array formula gives you back an array with two elements: {22.5, 22.5}. (It's probbaly just a one-element array getting expanded into two cells.) So you can get what you need without having to have a whole large intermediate array of results. (Using arrays in place of non-array arguments to worksheet functions can be wierd. See: Is there any documentation of the behavior of built-in Excel functions called with array arguments? )
Of course, the more Excel-like way to do this is to use an intermediate array and not try to compress it into a fancy array-formula. You'd have a list of the strings you want to look up, then drag a plain VLOOKUP (with an absolute reference to your lookup table) down/across a parallel row/column, and then average the results.