3

This is a followup question from this earlier thread

I have output that comes from a sequence() function. I want to run a calculation that sums up the output from the sequence() function. In particular, I want it to be structured so that my calculation will be right, even if the underlying data gets additional rows. I'll describe this in more detail with example data.

Say my output from the sequence() function looks like this

name      source 
banana    fruits
orange    fruits
peach     fruits
dog       pets
fish      pets
cat       pets

Now let's say I want to create a calculation that summarizes the collated dataset with a simple condition.

If this were a table, I would do something like this- countifs(tblCollated[name], tblCollated[source], "pets")

Anyway, the point is that I cannot do this because I cannot make the output of a sequence() into a table.

Cauder
  • 2,157
  • 4
  • 30
  • 69
  • So the result of your `SEQUENCE` function is never output into actual worksheet cells? Is that your point, i.e. that you want it to remain in-formula within some additional, external function(s)? Might be useful if you give an example of your `SEQUENCE` function. – Jos Woolley Nov 07 '21 at 05:54
  • There are no Mountains nor Heights in the example in that link. – Jos Woolley Nov 07 '21 at 11:15
  • I want to do one thing. I want to add a calculated column that operates on the results of the sequence function. With a table, it's great because it is something like `=tblMountain[@mountains]`. I'm concerned that if my calculated column is like =E25, then my results will be wrong if I sort the table or do something with it – Cauder Nov 07 '21 at 11:15
  • Updated to match the other data – Cauder Nov 07 '21 at 11:17
  • But you didn't answer my question as to whether you are outputting the results of your SEQUENCE function to the worksheet somewhere. If so, this spilled range can be referenced appropriately to give you your desired results. – Jos Woolley Nov 07 '21 at 11:58
  • Sorry, I am not sure I understand your question. I have three worksheets: one has `pets`, one has `fruits` and the other has the sequence() function `=LET( a,CHOOSE({1,2},tblFruits[name],"Fruits"), b,CHOOSE({1,2},tblPets[name],"Pets"), rowIndex,SEQUENCE(ROWS(a) + ROWS(b)), colIndex,SEQUENCE(1,COLUMNS(a)), IF(rowIndex<=ROWS(a), INDEX(a,rowindex,colIndex), INDEX(b,rowindex-ROWS(a),colIndex) ) ) ` – Cauder Nov 07 '21 at 14:20
  • So that LET function is currently creating a spilled range within the worksheet, which you can then reference as desired. The important point is that it's a spilled range, so by referencing it in other formulas you ensure that you capture all results from that spilled range as it changes. – Jos Woolley Nov 07 '21 at 14:21
  • Ah I see. I am able to reference it (eg, I can do `=countifs(countifs(E1:E10, F1:F10, "pets"`) but I want to do a structured reference (eg, `=countifs(tblCollated[name], tblCollated[source], "pets")`. My concern is that, in the first case, the result will be inaccurate as my spilled range gets more rows – Cauder Nov 07 '21 at 14:23
  • A spilled range ends in a #. If you highlight the range spilled by your LET formula it should end in that symbol. There's no need for structured referencing here; in any case, dynamic array formulas cannot be entered into tables. If you only want to reference one column from a multi-column spilled range then INDEX it appropriately. – Jos Woolley Nov 07 '21 at 14:26
  • I am not sure that we're on the same page here. I feel like we may be talking about two separate things. Please let me know in case I can be more clear on my end – Cauder Nov 07 '21 at 14:26
  • You don't put E1:E10 into your formula but `INDEX(E1#;;1)` for the first column and `INDEX(E1#;;2)` for the second column of the spill result – Ike Nov 07 '21 at 14:52
  • Oh interesting, can you add that as an answer to this question? – Cauder Nov 08 '21 at 00:08
  • I tried that and got this result "There's a problem with this formula." – Cauder Nov 08 '21 at 00:19
  • Assuming that your spill-result starts in A2 and you want to check for the values in the second column - this should work: =COUNTIFS(INDEX(A2#,,2),"Pets") - regardless of how many Pets are returned by the formula. – Ike Nov 15 '21 at 09:39

3 Answers3

3

Using LET and spill range references you can create larger complex functions that self-reference;

For example, assigning the column of data you want to col = 2 then indexing the second column from the spill range reference $A$2# gives a spill list of just that data you can manipulate

=LET(col,2,list,INDEX($A$2#,,col),list)

You can then further manipulate that list such that the count matching "Pets" is:

=LET(col,2,list,INDEX($A$2#,,col),COUNTIF(list,"Pets"))

Using a more dynamic LET formula, makes it a bit easier to quickly alter your formula if you need to, and also makes it a bit more readable.

Or you could use a filter on that list:

=LET(col,2,list,INDEX($A$2#,,col),ROWS(FILTER(list,list="Pets")))

And allowing for error handling (Thanks @T.M.):

=LET(col,2,list,INDEX($A$2#,,col),IFERROR(ROWS(FILTER(list,list="Pets")),0))
Tragamor
  • 3,594
  • 3
  • 15
  • 32
  • 1
    Nice ':) - Friendly hint: your last formula fails in case of non-findings; had a comparable issue applying `FilterXML` in my post. – T.M. Nov 15 '21 at 18:24
2

For versions of Excel that suppot LET

=LET(dat,YourFormulaThatReturnsTable,HdrCol,MATCH("source",INDEX(dat,1,),0),COUNTIFS(INDEX(dat,,HdrCol),"pets"))

YourFormulaThatReturnsTable can be either

  • the actual formula that returns the collated data, or
  • the cell reference to the cell that contains your spill formula followed by #
    Eg if your collating formula is in D4 then it would be D4#

Obviously, you can replace "source" and/or "pets" with cell references

chris neilsen
  • 52,446
  • 10
  • 84
  • 123
2

Approach via FilterXML()

For versions supporting LET (MS 365) let's develop a basic formula logic based on ►FilterXML() (assuming e.g. A1# as spill range reference):

    =LET(xml,"<all><i>"&TEXTJOIN("</i><i>",TRUE,A1#)&"</i></all>",xpath,"//i[.='pets']",COUNTA(FILTERXML(xml,Xpath)))

where

  • xml creates a wellformed xml string (roughly comparable to a hmtl tag hierarchy)
  • xpath defines the search expression string matching the value of each <i> node against 'pets' via "//i[.='pets']"
  • COUNTA(FILTERXML(xml,Xpath)) counts the found elements of filterXML()

Caveats:

a) Hypothetical caveat

The formula above assumes that no individual name equals the generic name pets, i.e.g that there isn't any specific animal breed that is named "pets" as individual identifier.

If you want to exclude this improbable case, too just enter the following formula where the xpath expression refers to the neighbour column elements:

    =LET(xml,"<all><i>"&TEXTJOIN("</i><i>",TRUE,A1#)&"</i></all>",Xpath,"//i[.='pets' and position() mod 2 = 0]",COUNTA(FILTERXML(xml,Xpath)))

b) Integrate non-findings

Non-findings result in a #Value! error, which COUNTA, however would accept as 1 found element. On the other hand, enclosing FilterXML in an IF(IsError(FilterXML(..)),0,..) condition would spill the whole result if there are findings n times, which can be avoided by integrating an error check like IF(err,0,COUNTA(FILTERXML(xml,Xpath):

Refined formula

=LET(spill,A1#,srch,"pets",xml,"<all><i>"&TEXTJOIN("</i><i>",TRUE,spill)&"</i></all>",Xpath,"//i[.='" & srch & "' and position() mod 2 = 0]",err,IsError(MATCH(srch,INDEX(spill,,2),0)),IF(err,0,COUNTA(FILTERXML(xml,Xpath))))

In order to make the formula more readable, I display it with line breaks:

=LET(
    spill, A1#,
    srch,  "pets",
    xml,   "<all><i>"&TEXTJOIN("</i><i>",TRUE,spill)&"</i></all>",
    Xpath, "//i[.='" & srch & "' and position() mod 2 =0]",
    err,   ISERROR(MATCH(srch,INDEX(spill,,2),0)),
    IF(err, 0, COUNTA(FILTERXML(xml,Xpath)))
)

Recommended links

Alternative //edit as of 2021-11-16

Just for the sake of the art another approach translating all matches into the numeric value 1, which allows to sum them up. Note the unconventional double array argument src in the second MATCH(src,src,0):

=LET(src,INDEX(A1#,,2),srch,"pets",row,MATCH(srch,src,0),err,ISERROR(row),IF(err,0,SUM(IF(MATCH(src,src,0)=row,1,0))))

T.M.
  • 9,436
  • 3
  • 33
  • 57