arraysexcelexcel-formulaexcel-lambda

Excel: #CALC! error (Nested Array) when using MAP functions for counting interval overlaps


I am struggling with the following formula, it works for some scenarios but not in all of them. The name input has the data set that is failing, getting an #CALC! error with the description "Nested Array":

=LET(input, {"N1",0,0;"N1",0,10;"N1",10,20},
  names, INDEX(input,,1), namesUx, UNIQUE(names), dates, FILTER(input, {0,1,1}),
  byRowResult, BYROW(namesUx, LAMBDA(name,
    LET(set, FILTER(dates, names=name),
      startDates, INDEX(set,,1), endDates, INDEX(set,,2), onePeriod, IF(ROWS(startDates)=1, TRUE, FALSE),
      IF(onePeriod, IF(startDates <= IF(endDates > 0, endDates, startDates + 1),0, 1),
        LET(seq, SEQUENCE(ROWS(startDates)),
          mapResult, MAP(startDates, endDates, seq, LAMBDA(start,end,idx,
            LET(incIdx, 1-N(ISNUMBER(XMATCH(seq,idx))),
              startInc, FILTER(startDates, incIdx), endInc, FILTER(endDates, incIdx),
              MAP(startInc, endInc,LAMBDA(ss,ee, N(AND(start <= ee, end >= ss))))
              ))),
              SUM(mapResult)))
    ))), HSTACK(namesUx, byRowResult)
)

If we replace the input values in previous formula with the following range: A2:C4, in G1:H1 would be the expected output:

sample input and output

Provided also a graphical representation to visualize the intervals and their corresponding overlap. From the screenshot, we have 2 overlaps.

If we use the above formula for the same range we get the following output:

sample excel input output with error

If we hover the #CALC! cell, it informs about the specific error:

Error description

Let's explain the input data and what the formula does:

Input data

Formula

The purpose of the formula is to identify for each unique names, how many intervals overlap. The calculation goes by each row (BYROW) of the unique names and for each pair of start-end values, counts the overlaps with respect to the other start-end values. I use FILTER to exclude the current start-end pair with the following condition: FILTER(startDates, incIdx) and I tested it works properly.

The condition to exclude the start data of the current name of the iteration of BYROW is the following:

1-N(ISNUMBER(XMATCH(seq,idx)))

and used as second input argument of the FILTER function.

The rest is just to check the overlap range condition.

I separate the logic when a name has only one interval, from the rest because the calculation is different, For a single interval I just want to check that the end date comes after start date and treat the special case of 0. This particular case I tested it works.

Testing and workarounds

I already isolated where is the issue and when it happens. The problem happens in the following call:

MAP(startInc, endInc,LAMBDA(ss,ee, N(AND(start <= ee, end >= ss))))

when startInc and endInc has more than one row. It has nothing to do with the content of the LAMBDA function. I can use:

MAP(startInc, endInc,LAMBDA(ss,ee, 1))

and still fails. The problem is with the input arrays: startInc, endInc. If I use any other array for example the following ones it doesn't works:

MAP(seq,LAMBDA(ss, 1))

Similar result using names, startDates, etc, even if I use: {1;2;3} fails. If use use idx it works, because it is not an array. Therefore the error happens with any type of array or range.

I have also tested that the input arguments are correct having the correct shape and values. For example replacing the MAP function with: TEXTJOIN(",",, startInc)&" ; " (and also with endInc) and replacing SUM with CONCAT to concatenate the result.

In terms of input data I tested the following scenarios:

{"N1",0,0;"N1",0,10} -> Works
{"N1",0,0;"N1",0,10;"N2",10,0;"N2",10,20;"N3",20,10} -> Works
{"N1",0,0;"N1",0,10;"N1",10,20} -> Error
{"N1",0,0;"N1",0,10;"N1",10,0} -> Error
{"N1",0,0;"N1",0,10;"N1",10,0;"N1",20,10} -> Error
{"N1",0,0;"N1",0,10;"N2",10,0;"N2",10,20;"N2",20,10} -> Error

The cases that work are because it goes to the MAP function an array of size 1 (number of duplicated names is less than 3)

I did some research on internet about #CALC! error, but there is no too much details about this error and it is provided only a very trivial case. I didn't find any indication in the limit of nested calls of the new arrays functions: BYROW, MAP, etc.

Conclusion, it seems that the following nested structure produce this error:

=MAP({1;2;3}, LAMBDA(n, MAP({4;5;6}, LAMBDA(s, TRUE))))

even for a trivial case like this.

On contrary the following situation works:

=MAP({1;2;3}, LAMBDA(n, REDUCE("",{4;5;6}, LAMBDA(a,s, TRUE))))

because the output of REDUCE is not an array.

Any suggestion on how to circumvent this limitation in my original formula?, Is this a real situation of an array that cannot use another array as input?, Is it a bug?


Solution

  • As @JosWoolley pointed out:

    LAMBDA's calculation parameter should return a single value and not an array

    I haven't seen that way, or deduced it from #CALC! Nested Array error definition:

    The nested array error occurs when you try to input an array formula that contains an array. To resolve the error, try removing the second array...For example, =MUNIT({1,2}) is asking Excel to return a 1x1 array, and a 2x2 array, which isn't currently supported. =MUNIT(2) would calculate as expected

    so the alternative is then to remove this second MAP call. The following link gave me an idea about how to do it: Identify overlapping dates and times in Excel, therefore using SUMPRODUCT or SUM can serve the purpose.

    =LET(input, {"N1",0,0;"N1",0,10;"N1",10,20},
      names, INDEX(input,,1), namesUx, UNIQUE(names), dates, FILTER(input, {0,1,1}),
      byRowResult, BYROW(namesUx, LAMBDA(name,
        LET(set, FILTER(dates, names=name),
          startDates, INDEX(set,,1), endDates, INDEX(set,,2), 
          onePeriod, IF(ROWS(startDates)=1, TRUE, FALSE),
          IF(onePeriod, IF(startDates <= IF(endDates > 0, endDates, startDates + 1),0, 1),
            LET(seq, SEQUENCE(ROWS(startDates)),
              mapResult, MAP(startDates, endDates, seq, LAMBDA(start,end,idx,
                LET(incIdx, 1-N(ISNUMBER(XMATCH(seq,idx))),
                  startInc, FILTER(startDates, incIdx), endInc, FILTER(endDates, incIdx),
                  SUMPRODUCT((startInc <= end) * (endInc >= start ))
                  ))),SUM(mapResult)))/2
        ))), HSTACK(namesUx, byRowResult)
    )
    

    We need to divide by 2 the result, because we are counting the overlapping in both directions. A overlaps with B and vice versa.

    It can be further simplified because there is no need to build the names: startInc, endInc to exclude the range itself we are checking for overlap. We can include it and subtract one overlap. This is the way to do it:

    =LET(input, {"N1",0,0;"N1",0,10;"N1",10,20},
      names, INDEX(input,,1), namesUx, UNIQUE(names), dates, FILTER(input, {0,1,1}),
      byRowResult, BYROW(namesUx, LAMBDA(name,
        LET(set, FILTER(dates, names=name),
          startDates, INDEX(set,,1), endDates, INDEX(set,,2), 
          onePeriod, IF(ROWS(startDates)=1, TRUE, FALSE),
          IF(onePeriod, IF(startDates <= IF(endDates > 0, 
            endDates, startDates + 1),0, 1),
            SUM(MAP(startDates, endDates, LAMBDA(start,end,
              SUMPRODUCT((startDates <= end) * (endDates >= start ))-1)))/2)
        ))), HSTACK(namesUx, byRowResult)
    )
    

    Here, the output, removing the array as input and using the corresponding range A2:C4. Providing also a graphical representations of the intervals (highlighted) and in cell G2 putting the corresponding previous formula:

    sample excel file

    Note: Since we are using SUMPRODUCT with a single input, it can be replaced with SUM.