I'm trying to use structured references to the current columns the same as CountIf does for my UDF function. While
=COUNTIF(Data[Team];Overview[Team])
works, my new function
=CONCATENATEIF(Data[Team];Overview[Team];Data[Data])
doesn't work, since the Overview[Team] criteria Range can't be cast to a single value which is [#This Row].
I tried to change the parameter "criteria" As String as well as different methods. Calling
=CONCATENATEIF(Data[Team];Overview[#Team];Data[Data])
with "#" works as intended. But CountIf can handle [#Team], [Team] and normal ranges like [A1:A4]. So how they do it?
Public Function CONCATENATEIF(check_range As Range, criteria As Range, data_range As Range) As Variant
Dim mydic As Object
Dim L As Long
Set mydic = CreateObject("Scripting.Dictionary")
For L = 1 To check_range.Count
If check_range(L) = criteria Then
mydic(L) = data_range(L)
End If
Next
CONCATENATEIF= Join(mydic.items, ", ")
End Function
What cast does criteria need to work like CountIf's criteria? How can i transform the structured Reference [Team] to [#Team] vba-wise, so it selects the same row, where the Formular is used later.
The table for the problem (sadly can't embed images yet)
COUNTIF works due to inferred reference¹.
If you put a bunch of values in column A and then use =INDEX(A:A, , ) (Index(<column_A>, <all_rows>, <all_columns>)) in an unused column to the right of the data then the result will be from the common row in column A. Since you haven't provided a specific row reference where a single cell reference is expected, the associated (or inferred) row is used. This is why COUNTIF works; it is using an inferred reference from the Overview[Team] column to reference a single cell for criteria; e.g. the cell in Overview[Team] that is on the same row as the formula (also known as Overview[#Team]).
The VBA code is not using an inferred reference. It is referencing the whole column of Overview[Team] where it needs a single cell for criteria (e.g. Overview[#Team]).
You could try to artificially parse the column of criteria down to a single cell with something like Application.Caller.Row or you could just use Overview[#Team] as the criteria like it was intended.
¹ I hope I got that term right. I use it so little that I have a hard time remembering the correct term sometimes.
Related
I am using the new dynamic array functions introduced in excel in 2018 (e. g. SEQUENCE, UNIQUE etc. functions).
I have a list of cell references that are that are generated dynamically, and would like to apply the INDIRECT function to these list items. A simplified example:
cell A1: =SEQUENCE(5) (results in rows column A values 1,2,3,4,5 as expected)
cell B1: ="A"&A1# (results in rows column B values A1, A2, A3, A4, A5 as expected)
cell C1: =INDIRECT(B1#) this should give me rows in column C values 1,2,3,4,5, but in fact gives me #VALUE ,#VALUE ,#VALUE ,#VALUE ,#VALUE
So the formula properly recognizes the number of rows of the original dynamic array, but for some reason does not dereference the cells properly. The strings seem to be of the proper format - a simple string function such as LEN also works: setting C1 to =LEN(B1#) results in 5 rows of the value 2.
The syntax per se seems to be OK.. for the special case of =SEQUENCE(1) in cell A1 everything works as intended. I tried the R1C1 reference format also, same result
EDIT
Overall I am trying to achieve the following
import a list form a non-Excel data source list is not a dynamic array, it's just a TSV import. I don't now beforehand how many items are in this list, and it can vary a lot
do several different calculations on values of this list.
so far my approach was to use the COUNT function to determine the number of items in the imported list, and then use that to create the second list using SEQUENCE and INDEX to retrieve values.
the problem arises for some calculations where the data contains references to other rows so I have to use indirect addressing to get at that data
The INDIRECT function cannot accept an array for an argument.
In other words:
=INDIRECT({"a1","a2"}) --> #VALUE! | #VALUE!
So you could, for example, refer to each cell in column B as a single cell:
eg:
C1: =INDIRECT(B1)
and fill down.
Depending on how you are using this, you could also use the INDEX function to return an individual element
To return the third element in the array generated by B1#:
=INDIRECT(INDEX(B1#,3))
EDIT:
After reading your comment, and depending on details you have not shared, you may be able to use a variation of the INDEX function.
For example, to return the contents of A1:A5, based on your SEQUENCE function, you can use:
=INDEX($A:$A, SEQUENCE(5))
but exactly how to apply this to your actual situation depends on the details.
As Rosenfeld points out, INDIRECT() does not accept an array as an input. If you need a function that:
"acts" like INDIRECT()
can accept an array as an input
can return an array as an output
Then we can make our own:
Public Function Indirect_a(rng As Range)
Dim arr, i As Long, j As Long
Dim rngc As Long, rngr As Long
rngc = rng.Columns.Count
rngr = rng.Rows.Count
ReDim arr(1 To rngr, 1 To rngc)
For i = 1 To rngc
For j = 1 To rngr
arr(j, i) = Range(rng(j, i).Value)
Next j
Next i
Indirect_a = arr
End Function
and use it like:
Since it creates a "column-compatible" array, it will spill-down dynamically in Excel 365.It can be used in versions of Excel prior to 365, but it must be array-entered into the block it occupies.
You can use the following formula
=BYROW(B1#,LAMBDA(a,INDIRECT(a)))
Having the nest Formula:
"=IF(Hoja1!$A$4=$A$15:$A$22),IF($B$4=$B$15:$B$22),IF($F$15:$F$22=0,$A$15:$A$22)))"
The resulting array is like so:
{FALSE\FALSE\FALSE\FALSE\FALSE\"Title 6"\FALSE\FALSE}
get an array that is set of booleans, and in this case I get String, but what I want to get is Ranges so I can know the position of that gotten non False result in the resulting array.
I know I could do the same using loops in VBA but my goal was to make it using formulas.
I don't know, maybe there is some built-in function that retrieves ranges that I don't know of.
Alternatively, I thought that having a known Range like $A$15:$F$22 I could get the index numbers of the resulting array that has a non false value and make a Range.Cells(index1, index2) using the 2 indexes of the array.
Also, I thought that using the MATCH function could do it.
For example
Match(<>False, {false\"string value"},0)
And so retrieve the row number that I can then use in a .Cell().
But It does not work. Can I do also a excluding match?
What do you suggest? Any easy/fast solution for this?
Reference to get the date:
Data source
I found a solution that was almost in front of my eyes.
I haven't tried using more than one possible search in an array constant (result of an aray formula).
It works for sure in array constant results that has one non empty/non-false results.
Using MATCH() was the solution.
Using the previous conditional formula I got an array with all results FALSE, except for one. Since the non empty/false result is a String and I want to know what is the range that result is in, using the data source range I extract that cell using the row index taken from the MATCH function, and then with simple VBA I have the range I wanted, like so.
Dim F As String
Dim Res As Integer
Dim R As Range
Set R = Range("$A$15:$A$22")
F = "=MATCH($A$4,IF($A$4=$A$15:$A$22),IF($B$4=$B$15:$B$22),IF($F$15:$F$22=0,$A$15:$A$22)))),0)"
'We get the index number of the desired value in the array that
we got in *IF* parts of the array formula.
Res = Hoja1.Evaluate(F)
'Since The position in the array of the element we want is the same as the row number inside the range the value we searched for is in, we can get that range/cell easily.
Set R = R.Cells(Res, 1) 'Or the column I want.
Debug.Print R.Address
The result is $A$18.
As we expected it matches, since inside the $A$15$:$A$22 the value we looked for is in the 4th row inside that source range.
We can get other columns for that match as well.
I have a global range, let's say for instance A1:A18, that contains value of the same type (only integers or only real numbers) and maybe also empty cells.
Data is dynamically outputted (through an excel function fetching data) in this range : sometimes the whole range contains non empty cells, sometimes not, with the following properties :
both ranges of empty cells (range 2) and non-empty cells (range 1) are "one-piece" ranges
the global range starts with the range 1
Now, in some cell not in the global range, I have the formula =F(A1:18) where F works only for ranges with non empty cells. If my range A1:A18 finishes with empty-cell (and this may well happen), the formula is going to throw an error.
Imagine range 1 is A1:A4. By which (necessarily matrix) formula G(A1:A18,...) depending only on range A1:18, on official excel functions (that is not user-defined excel functions) and not on VBA could I replace the range A1:18 in =F(A1:18) so that =F(G(A1:A18,...)) would in fact calculate =F(A1:A4) ? In others terms : which formula (with aforementioned conditions) gives dynamically A1:A4 from A1:A18 ?
I have also the same question with "#N/A" instead of empty and with "#VALUE!" instead of "empty".
G(A1:A18)=OFFSET(A1,0,0):OFFSET(A1,COUNTA(A1:18)-1,0)does the job
I'm using two cells with Data Validation - the first cell (E9) simply creates a drop down menu based on the range A2:A6, and the second cell (E10) validation uses the source INDIRECT(E9), which will always refer to one of five different named ranges.
When I have the named ranges fixed, (i.e A2:A250), the second drop down works, but I really need the ranges to be dynamic, so far I've been creating named ranges with the following "source" formula:
=OFFSET(LookupLists!$B$2,0,0,COUNTA(LookupLists!$B:$B),1)
With the other ranges being the exact same only in columns C-F.
When I write out this formula it highlights the correct area on the screen, but the drop down button in cell E10 is completely unresponsive, when the drop down list should show the exact area that's being highlighted.
As a note, the lists themselves are created using an array formula and some VBA code to create a sorted unique list based on another part of the spreadsheet, so I've been unable to use tables to create the ranges as some other websites have suggested.
INDIRECT doesn't work with dynamic ranges. Credit to these guys for the solution:
http://chandoo.org/forum/threads/passing-a-named-range-to-a-formula-with-indirect.5854/#post-32423
First, insert a module into you sheet and paste in the UDF:
Option Explicit
Function RetrieveRangeForName(psRange As String) As String
RetrieveRangeForName = Range(psRange).Address
End Function
Then you will need a helper cell, since I don't think UDFs work in the Data Validation dialog. In E11, enter =RetrieveRangeForName(E9).
Then in the Data Validation, set to List, you can enter: =INDIRECT(E11)
The reason it doesn't work as discussed here is that INDIRECT expects a string that it can evaluate to give a reference. However your named range is already a reference. Logically, the only way to use it in INDIRECT is to convert it into a string first which you can do with a UDF:-
Function GetAddress(Name As String) As String
Dim rng As Range, addr As String
Set rng = Worksheets("Sheet1").Range(Name)
addr = rng.Address
GetAddress = addr
End Function
Then use this to define a range called NewRange:-
=INDIRECT(GetAddress(Sheet1!$E$9))
Finally this can be used in the validation for E10 (Named Range ListB is defined as in the question, ListA etc. correspondingly for columns A to E).
I'm looking for the most elegant way to count the same number values in a noncontiguous range (I'll refer to it as just 'range'). This is the range:
=$C$2:$C$31,$E$2:$E$31,$G$2:$G$31,$I$2:$I$31,$K$2:$K$31,$M$2:$M$31,$O$2:$O$31,$Q$2:$Q$31,$S$2:$S$7
These are the parameters:
The range contains non-adjacent columns.
The columns differ in height.
The cells in the range are either empty or contain integers.
I'm checking for how many cells equal '1', how many equal '2' etc. in the range. (Not in one go, but in seperate formulas).
I've used a named range to reference the range. I'd really like to use this named range in the formula, in one way or another.
I hope I've given you enough info... Thanks in advance!
I agree with Kartik that a VBA solution is required. However the solution offered is a little inefficient in that it loops over every cell in the ranged passed into the function. It also limits the key parameter to a range reference, and can only count up to 32767 matches. Here's an alternative addresses these shortcomings
Function CountIf_N(rng As Range, key As Variant) As Variant
Dim r As Range
Dim count As Long
count = 0
For Each r In rng.Areas
count = count + WorksheetFunction.CountIfs(r, key)
Next
CountIf_N = count
End Function
Note: assumes Excel 07 or later. If using with an ealier version replace CountIfs with CountIf
One approach is to use excel built in function Countif, but it won't work with non-contigous range. The other way (the easy way) will be to use VBA to create your own custom function, and then use it in excel.
I've presented that technique here.
Goto visual basic editor in excel by pressing Alt+F11, in the project window insert a new module and paste the below code:
Function countif_n(rng As Range, key As Range) As Integer
Dim count As Integer
count = 0
For Each cell In rng
If cell.Value = key.Value Then
count = count + 1
End If
Next cell
countif_n = count
End Function
Here rng is your non-contigous range, and key represent the "range"(cell) which contains the value you want to count. For eg., to check for 1 enter 1 in any cell lets suppose "F2", and your non-contigous range is "testrange"
Then use the above function by entering the following in any blank cell:
=countif_n(testrange, F2)
Although COUNTIF can't handle non-contiguous ranges some functions can, for example RANK and COUNT so for a range called Range this formula will give the number of instances of a number in Z2 within Range
=IFERROR(COUNT(Range)-SUM(RANK(Z2,Range,{1,0}))+2,0)
assumes Excel 2007 or later but can be amended to work in earlier versions
This doesn't quite work if there's stuff below S7 that can't be counted, but you may be able to modify. It also doesn't incorporate the named range.
=SUM(IF(MOD(COLUMN(A2:S31),2)=0,IF(A2:S31=2,1,0)))
This example counts the number of 2's.
This needs to be array-entered with ctrl-shift-enter. It's based on the fact that you're counting in every other column, at least in your example. Also, although you mention the columns are different heights, it looks like all except S are the same height. So maybe there's a way to work around that.