1

The table below shows a list of employees and number of hours they have worked in that week. I am trying to come up with formula that would return the total number of hours worked per employee per month (sum the weekly numbers) to fill in the lower table. I have tried combining the XLOOKUP and SUMIFS formula but so far no luck. Does anyone have any tips by chance?

enter image description here

Resource Name Resource Level 7/22/2024 7/29/2024 8/5/2024 8/12/2024 8/19/2024 8/26/2024 9/2/2024 9/9/2024
Rob Manager 40 20 37 29 23 31 33 32
Tom Analyst 30 25 26 27 19 39 19 31
Jessica Senior Analyst 20 34 30 35 34 30 29 24
Julia Business Analyst 15 34 28 22 27 36 38 19
Resource Name Resource Level July August September
Rob Manager
Tom Analyst
Jessica Senior Analyst
Julia Business Analyst
0

4 Answers 4

3

Instead of using XLOOKUP() use either SUM() or SUMPRODUCT(). The following is the simplest of all and remember a SUM() function will be always efficient and quicker, there is no match to it.

When you have output data laid out where you have the resource name and resource level on the left, and the headers with the formatting as mmm-e you can simply use the SUM() function performing a Boolean Logical Operation, no fancy formulas or functions are required. It is way easy to understand and easy to debug, moreover it will be very efficient as well. Just try !

Otherwise if you are attempting for second dynamic array formula then use the Option Two which I have proposed.

enter image description here


    =SUM((TEXT($C$1:$J$1,"mmm-e")=TEXT(C$8,"mmm-e"))*
     ($A9=$A$2:$A$5)*($B9=$B$2:$B$5)*
     $C$2:$J$5)

OPTION TWO:

Instead of using multiple LAMBDA() iterations using functions like REDUCE() or Custom LAMBDA() with a combination of another LAMBDA() helper like MAP() you can easily transform your data into one single dynamic array using MMULT() :

enter image description here


=LET(
     _Data, Table1[#All],
     _Rname, DROP(TAKE(_Data,,1),1),
     _Rlevel, DROP(INDEX(_Data,,2),1),
     _Headers, TEXT(DROP(TAKE(_Data,1),,2),"mmm-e"),
     _Uniq, UNIQUE(_Headers,1),
     _Body, DROP(_Data,1,2),
     _Output, HSTACK(_Rname,_Rlevel,MMULT(_Body,N(_Uniq=TOCOL(_Headers)))),
     VSTACK(HSTACK(TAKE(_Data,1,2),_Uniq),_Output))

Please ensure to convert your range into Structured References aka Tables. There are many reasons to use this feature of excel. Two such reasons are it makes easier to read the formulas as well as it will auto resize the formula when there is a change in dimensions of the source data.


To explain more clearly please read:

  • _Data Variable refers to the entire table Table1[#All]
  • Rname Variable extracts the first columns from the source excluding the headers using TAKE() and DROP() --> TAKE() helps in taking the first column while DROP() involves in dropping the headers.
  • _Rlevel Variable Similarly extracts the second column from the source likewise above using the same methodology by changing the column_index in the INDEX()
  • _Headers Variable extracts the first row using TAKE() function while DROP() helps in removing first two columns, and lastly using TEXT() function which formats the dates as a three-letter abbreviation of the month followed by a dash/hyphen and the year.The formatting notation for the full name of the month is mmmm while for only Jul or Apr etc it would be like mmm which already been used, e refers to the year which is universal.
  • _Uniq Variable refers to unique list of headers.
  • _Body Variable is the values areas, extracted using DROP() function.
  • _Output Variable which combines the arrays of _Rname and Rlevel with the matrix multiplication of _Body and a Binary array created between the _Uniqlist and the transposed_Headers, enclosed within HSTACK()` to return one single output.
  • Lastly, using VSTACK() to give some better look we are placing the headers!.

Sign up to request clarification or add additional context in comments.

2 Comments

MMULT is perfect here!
Even I think So @P.b Sir!
2

With Excel for MS365, one possible single-cell array formula could be:

=LET(
    table, A1:J5,
    names, TAKE(DROP(table, 1),, 1),
    levels, INDEX(DROP(table, 1),, 2),
    row_labels, names & "|" & levels,
    col_labels, EOMONTH(--DROP(TAKE(table, 1),, 2), 0),
    values, DROP(table, 1, 2),
    arr, LAMBDA(n, CHOOSE(n, row_labels, UNIQUE(SORT(col_labels,,, 1), 1))),
    results, MAP(arr({1}), arr({2}), LAMBDA(r,c, SUM(FILTER(FILTER(values, row_labels = r), col_labels = c)))),
    VSTACK(
        HSTACK(TAKE(table, 1, 2), TEXT(arr(2), "mmmyy")),
        HSTACK(names, levels, results)
    )
)

map_3D_array.png

Adjust the table range reference as needed, and change the output format of the month-end labels as desired (e.g. "mmmm" instead of "mmmyy").

Alternatively, you could use SUM((row_labels = r)*(col_labels = c)*values) instead of SUM(FILTER(FILTER(values, row_labels = r), col_labels = c)), if preferred.

Also, row_labels could be defined as SEQUENCE(ROWS(names)) instead of names & "|" & levels in this particular scenario. Then, you could use SUM(FILTER(CHOOSEROWS(values, r), col_labels = c)) or SUM(CHOOSEROWS(values, r)*(col_labels = c)).

EDIT: In its simplest form, the above-mentioned formula could be reduced to the following:

=LET(
    table, A1:J5,
    col_labels, EOMONTH(--DROP(TAKE(table, 1),, 2), 0),
    values, DROP(table, 1, 2),
    arr, LAMBDA(n, CHOOSE(n, SEQUENCE(ROWS(values)), UNIQUE(SORT(col_labels,,, 1), 1))),
    results, MAP(arr({1}), arr({2}), LAMBDA(r,c, SUM(FILTER(CHOOSEROWS(values, r), col_labels = c)))),
    HSTACK(TAKE(table,, 2), VSTACK(TEXT(arr(2), "mmmyy"), results))
)

Explanation of arr({1}):

Basically, CHOOSE was used as a custom LAMBDA function to simulate a 3D array. When an array object is passed to the index_num argument of CHOOSE (e.g. {1} instead of 1), all of the value arguments are automatically resized via broadcasting. A value argument containing a single column of data (vertical vector) is broadcast across to fill the same number of columns as the argument with the most columns, whereas a value argument containing a single row of data (horizontal vector) is broadcast down to fill the same number of rows as the argument with the most rows. The MAP function is then used to loop through the layers together, left-to-right, top-to-bottom.

Note: in complex scenarios, this method can be more efficient than the MAKEARRAY function (with INDEX), which is known to perform very poorly on larger datasets. However, in this relatively simple scenario, a MAKEARRAY equivalent (without having to INDEX the col_labels) could be:

=LET(
    table, A1:J5,
    col_labels, EOMONTH(--DROP(TAKE(table, 1),, 2), 0),
    periods, UNIQUE(SORT(col_labels,,, 1), 1),
    col_ID, XMATCH(col_labels, periods,, 2),
    values, DROP(table, 1, 2),
    results, MAKEARRAY(ROWS(values), COLUMNS(periods), LAMBDA(r,c, SUM(FILTER(CHOOSEROWS(values, r), col_ID = c)))),
    HSTACK(TAKE(table,, 2), VSTACK(TEXT(periods, "mmmyy"), results))
)

Comments

2

You could use two XLOOKUP's and a SUM formula.

If you use actual dates in C9:E9 - 01/07/2024, 01/08/2024 and 01/09/2024.
Give these dates a custom number format of 'mmmm' so they just show the month name.

The formula =XLOOKUP(C9,$C$1:$J$1,$C2:$J2,,1) will look up the first cell that is greater or equal to the value in C9.
The formula =XLOOKUP(EOMONTH(C9,0),$C$1:$J$1,$C2:$J2,,-1) will look up the first cell that is less than or equal to the last day of the month.

Stick these two formula together and wrap with a sum to return the totals for the month. =SUM(XLOOKUP(C$9,$C$1:$J$1,$C2:$J2,,1):XLOOKUP(EOMONTH(C$9,0),$C$1:$J$1,$C2:$J2,,-1))

Result of formula

Comments

2

Sum-up Matching Columns (By Month)

In C9:

=LET(data,A1:J5,row_labels_count,2,
    rl,TAKE(data,,row_labels_count),
    d,DROP(data,,row_labels_count),
    h,TAKE(d,1),
    v,DROP(d,1),
    m,MONTH(h),
    mu,UNIQUE(m,1),
    dh,XLOOKUP(mu,m,TEXT(UNIQUE(h,1),"mmmm")),
    dv,DROP(REDUCE("",mu,LAMBDA(rr,r,
        HSTACK(rr,BYROW(FILTER(v,m=r),LAMBDA(r,SUM(r)))))),,1),
    r,HSTACK(rl,VSTACK(dh,dv)),
    r)

enter image description here

In C16

=LET(lookup_data,C1:J5,months,C15:F15,if_no_month,NA(),
    h,TAKE(lookup_data,1),
    v,DROP(lookup_data,1),
    m,TEXT(h,"mmmm"),
    nm,IF(SEQUENCE(ROWS(v)),if_no_month),
    r,DROP(REDUCE("",months,LAMBDA(rr,r,
        HSTACK(rr,IFERROR(BYROW(FILTER(v,m=r),
        LAMBDA(r,SUM(r))),nm)))),,1),
    r)
  • To force e.g. US (English) months, replace mmmm with [$-409]mmmm.
  • If the ETA functions are available, replace LAMBDA(r,SUM(r)) with SUM.

3 Comments

Instead of making the solution so complicated, why not use MMULT() to make it more efficient ? Does REDUCE() and multiple LAMBDA() make formulas any faster or efficient ? do you have any example where it has.
@MayukhBhattacharya Thanks for the tip. I see no problem here even with 10 years (+520 columns) worth of data but you prove me wrong. We recently answered "How to return number placement based on criteria from a table?" (not linking because it's not related to this question). You answered somewhat 'statically' while I did 'dynamically' using REDUCE. It might not be obvious (scared away by REDUCE) but on a large data set (e.g. 10k records), our 'complicated (longer)' answers are far more efficient than the accepted 'simple (shorter)' answer. I'll leave it to you to figure out why.
I agree the accepted answer is way complicated and will immensely slow, I am not sure why OP has accepted, are they aware of or not. But REDUCE() is not going to be faster any ways. I have recently posted a solution using MAKEARRAY() REDUCE() and MMULT() where OP has confirmed that MMULT() was faster and was instant actions where as the other two were very slow, infact any other LAMBDA() helper. OP was using data with 72K rows

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.