How to use named range in an Excel array? - excel

Basically the problem boils down to - how do I use a named reference/range within array in an Excel spreadsheet formula?
Example:
={"this","is","my","house"}
Produces 4 cells in one row with correct text
But this
={"this","is","my", House}
where House is a named range of a cell containing some text fails.

Your attempt failed, because of the array notation {}.
An array, typed like that is limited to number values and/or text strings, for instance {"a",1,"b"}.
A range can not be used inside an array notation neither can a named range.
To avoid the array notation and still get the array to include the named range you can make use of VSTACK or HSTACK who both create arrays or append arrays even.
In this case your array {"this","is","my"} can be used inside HSTACK and the named range House can be appended:
=HSTACK({"this","is","my"},House)
This will give the desired result, but as HSTACK creates arrays by appending numerous values/ranges/arrays, we no longer need the {}:
Proper notation: =HSTACK("this","is","my", House)
Would be the proper notation.
If you would not have access to HSTACK, but have access to LET, you could use this little more complex solution:
=LET(a,{"this","is","my"},
b,House,
count_a,COUNTA(a),
seq,SEQUENCE(1,count_a+1),
CHOOSE(IF(seq<=count_a,1,2),a,b))
First a (the text array) and b (the named range) are declared.
Then count_a is declared, which counts the number of strings in array a (3).
Then seq is declared to create a (horizontal) sequence from 1 to the count of strings in a (count_a) and adding 1 (resulting in {1,2,3,4}.
Then calculating if the sequence seq is smaller than or equal to the count of strings in a results in TRUE for the first 3 values of the sequence and false for the fourth: {TRUE,TRUE,TRUE,FALSE}.
Using that in combination with IF (if TRUE 1, else 2) results in an array of {1,1,1,2}.
Using that as the CHOOSE argument results in the 1st 3 times choosing values from a and the 4th time (the first) value of named range b.
Not using LET and SEQUENCE will result in a very unmanageable formula, which would require more work fixing the values within the formula then just typing them out, probably, but this would create the array in older Excel versions:
=CHOOSE(
IF(
COLUMN($A$1:
INDEX($1:$1048576,,COUNTA({"this","is","my"})+1))
<=COUNTA({"this","is","my"}),
1,
2),
{"this","is","my"},
House)
Requires entered with ctrl+shift+enter and would appear as being one value only, because older Excel doesn't spill arrays into a range, but the array could be referenced inside a formula or as a named range.
Here COLUMN($A$1:INDEX($1:$1048576,,COUNTA({1,2,3}))) simulates the sequence function.

If you have access to Excel O365:
With HSTACK, it would simply be =HSTACK("this","is","my",house).
House could be a single value or an array. If "House" is a named range {"A","B,"C"} then the HSTACK function above returns a 6 element array {"this","is","my","A","B,"C"}

Related

Is it possible to point to array of named ranges with INDEX reference form?

I'm keeping data in several equally sized arrays saved as named ranges Arr1, Arr2, Arr3 etc. I want to look up things from any these arrays with INDEX or CHOOSE wrapped inside another INDEX e.g. by going INDEX(INDEX((Arr1;Arr2;Arr3);;MATCH...);2;2)
As my list of arrays grows large and I need to apply my INDEX/CHOOSE-MATCH many places, I'd like to avoid repetition and store the names of my named ranges in another array or table, let's call it Arr_list.
Is it possible to substitute the reference part, i.e. '(Arr1;Arr2;Arr3)' of an INDEX formula with a call to the Arr_list array?
Wrap the INDEX(Arr_list) in INDIRECT - then the outermost INDEX understands the result of INDEX(ARr_list) refers to a named range.
An example:
=INDEX(INDIRECT(INDEX(ArrayList,2)),2)
A way to do this without the INDIRECT function(which is volatile, and may slow down workbook with large ranges) is to make your Arr_List into one array using the colon Range operator, or the comma union operator.
I show an example of it below with the Master named ranges themselves as named ranges.
You may form a Union like so:
=Name,Age,Sales,Gender OR
=name:Age:Sales:Gender
The comma/Union way is referenced with the 2nd form of INDEX which takes a reference, and then gets the area as parameter like:
=INDEX(Master2, 2, 1, 4) to get Gender.
The Colon/Range way is referenced by the 1st form of Index:
=INDEX(Master, 2, 4)

How do I use INDIRECT inside an Excel array formula?

The situation
In the sheet "Planning" I have an area that contains pairs of sessions (strings) and hours (numbers) in adjacent cells (e.g. D11 and E11, I12 and J12 etc.) One session can occur multiple times.
D11:E11 is | Foo | 8 |
I12:J12 is | Foo | 4 |
In another sheet, I want to find a session in the Planning sheet and return an array with all the hours booked on that session (to calculate a total)
I use an array formula with a conditional and intend to use the SMALL function to retrieve the results from the array
The problem
The following formula returns all the correct references to hours booked on "Foo", so far so good.
=IF(Planning!$D$11:$CV$18="Foo";ADDRESS(ROW(Planning!$D$11:$CV$18);COLUMN(Planning!$D$11:$CV$18)+1;;;"Planning"))
{"Planning!$E$11"\FALSE\FALSE\FALSE\FALSE\"Planning!$J$12"}
However, if I use the INDIRECT function to retrieve the values of those references, they always return the value of the first reference in the array ("Planning!$E$11")
=IF(Planning!$D$11:$CV$18="Foo";INDIRECT(ADDRESS(ROW(Planning!$D$11:$CV$18);COLUMN(Planning!$D$11:$CV$18)+1;;;"Planning")))
{8\FALSE\FALSE\FALSE\FALSE\8}
How do I retrieve the correct values? Or should I tackle the problem in a whole different way?
Screenshots
The planning sheet
The overview I want
Since I was mainly interested in the total of planned hours, I eventually used the following formula:
=SUM(SUM(INDIRECT(IF(Planning!$D$11:$CV$18="Foo";(ADDRESS(ROW(Planning!$D$11:$CV$18);COLUMN(Planning!$D$11:$CV$18)+1;;;"Planning"));"$U$19"))))
IF: Create the array with references to the Planning sheet if the string is found. If it's not found, add the reference $U$19.
Using INDIRECT, replace all references with the values in the Planning sheet. $U$19 contains the value 0.
Then use SUM twice to sum up all the values. I don't know why, but see
Is it possible to have array as an argument to INDIRECT(), so INDIRECT() returns array?
https://superuser.com/questions/1196243/simplify-a-sum-of-indirect-cell-values
Indirect doest work in most array formulas. If you give it a string that refers to an array, like "A1:A10" it it returns those cells as expected but thats about it. You can use that array as the input to another function but you cant send an array output from another function to INDIRECT(). (Or at least i have not figured out a way)
Try using the INDEX function with the ROW function.
INDIRECT("A1:A10") is similar to
INDEX(A:A,ROW(A1:A10))
However the former is less flexible.
Comsider:
INDEX(A:A,FILTER(ROW(A1:A10),NOT(ISBLANK(A1:A10))*ISNUMBER(A1:A10)))
This returns an array containing the numerical values in the range but does not treat an empty cell as zero. Watch your order of operations and parenthesis.
The product NOT(ISBLANK(A1:A10)*ISNUMBER(A1:A10) is the inner product of two vectors of boolean values.
ROW(A1:A10) creates a vector of row values of the of the elements in that range. Then filter throws out any where the corespinsing element of the boolean vector is 0. Index then returns an array of values of the cells in its range coresponding to those rows. The range given to INDEX could be any row in fact. Not just the one your selecting on. Using the entire column (for example A:A) allows excel to automatically update the references if you move the source data, for instance if you insert a header row. If you use a specific range you will need to add an offset to the row value and it will not automatically update refernces. (Without a far more complex formula)

VLOOKUP return an array for SMALL function

I am attempting to use VLOOKUP to return an array for the SMALL function so that I can evaluate the X smallest values based on the lookup. The lookup table is as the image shows.
The image above is from the 'How Many to Use' worksheet. I'm using the following formula =VLOOKUP(COUNTA($N9:AA9),'How Many to Use'!A2:C14,3) but Excel chokes (#VALUE) on my formula because it returns the array expected but returns it as text, not the true array I'm hoping for for the SMALL function. Is there a way to convert this to an actual array?
The full formula in context is trying to average use a certain number of values within the array in my main worksheet and averaging the smallest X values based on the full count in the row's values.
=IF(COUNT($N9:Z9)<>0,IF(COUNT($N9:Z9)<=3,AVERAGE($N9:Z9),SUM(SMALL(INDEX($N9:Z9,MATCH(TRUE,COLUMN($N9:Z9)=LARGE(NOT(ISBLANK($N9:Z9))*COLUMN($N9:Z9),COUNTA($N9:AA9)),0)):Z9,VLOOKUP(COUNTA($N9:AA9),'How Many to Use'!A2:C14,3)))/VLOOKUP(COUNTA($N9:AA9),'How Many to Use'!A2:C14,2)),"")
Starting narrowly and working outward:
(1) To directly answer your question, you could convert the string to an array using VBA, eg:
Function Eval(formulaText As String, Optional stable As Boolean = False) As Variant
Application.Volatile Not stable
Eval = Evaluate(formulaText)
End Function
=Eval(VLOOKUP(COUNTA($N9:AA9),'How Many to Use'!A2:C14,3),true)
(Note that I included the volatile mechanism even though you don't need it here, since the way you'd be using it is a special case where volatile needn't be declared. This function should be considered volatile unless you know better.)
But, this is not really necessary.
(2) My usual way to generate a 1-k array of integers is to do something like:
=COLUMN(A1:INDEX(1:1,k))
(Following your lead, uses a row array, though I tend to use columns. Must be in an array formula, obviously.)
(3) But what you're really trying to do is average the k smallest values in an array. For that I'd just do this:
=AVERAGE(IF(RANK(A1:Z1,A1:Z1,1)+COLUMN(A1:Z1)/COLUMNS(A1:Z1)<=SMALL(RANK(A1:Z1,A1:Z1,1)+COLUMN(A1:Z1)/COLUMNS(A1:Z1),10),A1:Z1,""))
where A1:Z1 is the array and 10 is the number of items to include, entered as an array formula.
The reason you can't just use SMALL as a test for inclusion, as you no doubt already know, is that the kth item might be one of several with the same value. So, I use RANK to assign each value an integer (if they're already integers, this isn't necessary) then I add a fraction based on order of appearance to produce a id for each value that maintains its position: RANK(A1:Z1,A1:Z1,1)+COLUMN(A1:Z1)/COLUMNS(A1:Z1) If a value's id is one of the smallest (in my example, the smallest 10) the value is included, otherwise I pass "" to the AVERAGE function.
To get the number of items to use (in place of the '10' in my example), you could either use your table-lookup or use a rule such as MAX(1,MIN(10,COUNT(A1:Z1)-1)).
So, the final formula, using your range, might be:
=AVERAGE(IF(RANK($N9:AA9,$N9:AA9,1)+COLUMN($N9:AA9)/COLUMNS($N9:AA9)<=SMALL(RANK($N9:AA9,$N9:AA9,1)+COLUMN($N9:AA9)/COLUMNS($N9:AA9),MAX(1,MIN(10,COUNT($N9:AA9)-1))),$N9:AA9,""))

How to make nested array computations with INDEX()?

Imagine I have several (i.e. > 100) column vectors of numbers. Vectors are large with equal length (e.g. 20k items). The vectors are not adjacent, so they don't make a matrix.
What I want, is to get some row-wise computation with the vectors, for instance
For each row what is the first non zero value among all vectors?
or
For each row what is the maximal value among all vectors?
See this simplified example, that should get the maximal value for all vectors, which would be 3 for all row (in reality the displayed value is 1):
It would be easy, if I could copy the vectors as a matrix and get the column of row ranges that spans all vectors for a given row, instead of the column ranges. But that is not the option due to the size of the data. I think it is related to other SO question: Is it possible to have array as an argument to INDIRECT(), so INDIRECT() returns array?.
You can use CHOOSE to combine equal sized columns into a single range, e.g. for your 3 range example:
=CHOOSE({1,2,3},$B$1:$B$4,$B$5:$B$8,$A$3:$A$6)
Then use that directly in a formula, e.g. in G2 copied down to get the MAX in each row for your example
=MAX(INDEX(CHOOSE({1,2,3},$B$1:$B$4,$B$5:$B$8,$A$3:$A$6),F2,0))
or you can define the CHOOSE part as a named range [especially useful if you have 100 ranges], e.g. name that Matrix and use
=MAX(INDEX(Matrix,F2,0))
You need to modify the {1,2,3} part based on the number of ranges, to shortcut when you have 100 ranges you can use
=CHOOSE(TRANSPOSE(ROW(INDIRECT("1:100"))),Range1, Range2.....Range100)
Now needs to be confirmed with CTRL+SHIFT+ENTER
To get the first non-zero value you can use this version
=INDEX(INDEX(Matrix,F2,0),MATCH(TRUE,INDEX(Matrix,F2,0)<>0,0))
also confirmed with CTRL+SHIFT+ENTER
I've found that you actually "can" return an array from INDIRECT().
However it must be in "R1C1" syntax AND you cannot create your R1C1 syntax with a formula (not with something like "R" & ROW() & "C" & COLUMN()".
You have to enter the ROW & COLUMN numbers as absolute and then it works.
Apparently excel puts {} around the numbers when they are returned by ROW() or COLUMN() function, and I guess that's why it doesn't work (try debugging, you'll see).

When using the IF function, how to use dynamic set for comparison in the "Logical_test" argument?

I am using an array formula (in Excel 2003) to count the number of strings meeting a specific condition. I am using a column of strings instead of a table of cells to store my data because of file size limitations.
The formula is below:
{=SUM(IF((VALUE(MID(INDIRECT(CONCATENATE(D1,"test")),6,1))*VALUE(MID(INDIRECT(CONCATENATE(D1,"test")),1,1)))=VLOOKUP(D2,t.lkup,2,FALSE),1,0))}
The expression VALUE(MID(INDIRECT(CONCATENATE(D1,"test")),6,1)) looks through the cells in a named range to return a value. This value is multiplied by another value returned by the expression VALUE(MID(INDIRECT(CONCATENATE(D1,"test")),1,1)). The resulting product is then looked for in a set of numbers given by VLOOKUP(D2,t.lkup,2,FALSE), which returns a string like "{1,2,3,4}". If the product is an element of set, then 1 is added to the sum, else 0 is added to the sum.
When I use the array formula above (with the dynamic lookup set), a value of zero is returned. If I use the following formula,
{=SUM(IF((VALUE(MID(INDIRECT(CONCATENATE(D1,"test")),6,1))*VALUE(MID(INDIRECT(CONCATENATE(D1,"test")),1,1)))={1,2,3,4},1,0))}
then the correct sum is returned. Does anyone know how to get Excel to treat the set lookup as a static set instead of a string?
An array formula performs multiple clalculations and returns either a single result or an array. All array arguments in the formula must be of equal size.
in your first example you compare a single value of something (as the most outer function within IF is a VALUE() function) to a VLOOKUP which returns a string ... that must go wrong ... the fact that your string contains curly brackets does not convert it into an array.
in your second example you compare a single value to an array containing elements {1,2,3,4}, so actually you do four comparisons, and if one of them resolves to TRUE you add 1.
I don't know any way to convert a comma delimited string "{1,2,3,4}" into an array {1,2,3,4} without use of VBA, but maybe you can change your tactic by converting your VALUE(...)*VALUE(...) number into a string and use the FIND() function to identify number as a substring of the condition string.
e.g. say your MID_CONCATENATE_TIMES_BLA_BLAH results in 7, and your
condition string = "{1, 3, 5, 7, 9}", a FIND(MID_BLA, CONX_STR) = TRUE
condition string = "{1, 2, 3, 4}", a FIND(MID_BLA, CONDX_STR) = FALSE
This will work as long as your results are 1 digit. With more than 1 digit you would need to include a SPACE before the number in both MID_BLA and CONDX_STR; also not too difficult, but adding even more complexity to the formulae.
If you want to do VBA, you can use the Split() function to create a zero-based array from a seperated string
Function StrToArray(Arg As String) As Variant
StrToArray = Split(Arg, ",")
End Function
and surrond your VLOOKUP in (1) by StrToArray(VLOOKUP(...)), in which case you must remove the curly braces from your condition strings.
Hope that helps - good luck

Resources