How to make a formula shorter - excel

I have three pieces of information: quantity, weight per piece and a limit. What I need is for the weight per piece to multiple without passing the limit and the quantity.
I made a code, but the thing is the data of quantity varies and the code used is very long.
=ROUND(IFS((F13*G13)<H13,F13*G13,((F13-1)*G13)<H13,((F13-1)*G13),
((F13-2)*G13)<H13,,((F13-3)*G13)<H13,,((F13-4)*G13)<H13,,((F13-5)*G13)<H13,,
((F13-6)*G13)<H13,(F13-6)*G13,((F13-7)*G13)<H13,(F13-7)*G13,
((F13-8)*G13)<H13,(F13-8)*G13,((F13-9)*G13)<H13,(F13-9)*G13,
((F13-10)*G13)<H13,(F13-10)*G13,((F13-11)*G13)<H13, (F13-11)*G13,
((F13-12)*G13)<H13,(F13-12)*G13,((F13-13)*G13)<H13,(F13-13)*G13,
((F13-14)*G13)<H13,(F13-14)*G13,((F13-15)*G13)<H13,(F13-15)*G13,
((F13-16)*G13)<H13, (F13-16)*G13,((F13-17)*G13)<H13,(F13-17)*G13,
((F13-18)*G13)<H13,(F13-18)*G13),-2)+200
Here is the input and expected result, if no condition matches returns #N/A.

I don't know if the +200 at the end of the formula is supposed to be included in the limit or not so just adjust the formula accordingly
=ROUND(IF(G13*F13+200>=H13,H13,F13*G13+200),-2)
Multiply the quantity and the weight and add 200.
If the result is equal to or greater than the limit, set the result to the limit.
Otherwise use the results of the quantity * weight + 200
And then round it according to your initial formula

This is what your IFS() looks like:
IFS(((F13-0)*G13)<H13,(F13-0)*G13,
((F13-1)*G13)<H13,(F13-1)*G13,
((F13-2)*G13)<H13,,
((F13-3)*G13)<H13,,
((F13-4)*G13)<H13,,
((F13-5)*G13)<H13,,
((F13-6)*G13)<H13,(F13-6)*G13,
((F13-7)*G13)<H13,(F13-7)*G13,
((F13-8)*G13)<H13,(F13-8)*G13,
((F13-9)*G13)<H13,(F13-9)*G13,
((F13-10)*G13)<H13,(F13-10)*G13,
((F13-11)*G13)<H13,(F13-11)*G13,
((F13-12)*G13)<H13,(F13-12)*G13,
((F13-13)*G13)<H13,(F13-13)*G13,
((F13-14)*G13)<H13,(F13-14)*G13,
((F13-15)*G13)<H13,(F13-15)*G13,
((F13-16)*G13)<H13,(F13-16)*G13,
((F13-17)*G13)<H13,(F13-17)*G13,
((F13-18)*G13)<H13,(F13-18)*G13)
I see three issues:
the cases for 2 up to 5 are missing. Are you sure this is correct?
it looks like (for a general 'x'):
IF ((F13-x)*G13)<H13 THEN (F13-x)*G13, you can calculate the resulting value quite easily, isn't it?
why do you stop at 18?

You can simplify it as follows:
=LET(A, A2, B, B2, C, C2, seq, SEQUENCE(19,,0),
out, IF((seq > 1) * (seq < 6), 0, (A - seq)*B),
ROUND(#FILTER(out, (A - seq)*B < C, NA()), -2) + 200)
and extend it down, or use the array version as follow in cell D2:
=LET(A, A2:A3, B, B2:B3, C, C2:C3, seq, SEQUENCE(19,,0),
MAP(A,B,C, LAMBDA(x,y,z, LET(out, IF((seq > 1) * (seq < 6), 0, (x - seq)*y),
ROUND(#FILTER(out, (x - seq)*y < z, NA()), -2) + 200))))
Here is the output:
The implicit intersection operator (#) ensures to get the first element of FILTER result, which is equivalent to get the first condition that matches. If no condition matches, then it returns NA(). The name out, has the result in the same order it should be tested via IFS. Using SEQUENCE allows to simplify the process.

Related

Identifying Specific Time Segments in Excel

I have 10,000 rows of data in Excel and the first column is the time of a data recording (hh:mm:ss). I want to filter/extract (in the front end) only the rows where the readings are at least six consecutive seconds - these are signal strength recordings and I only consider readings of at least six consecutive seconds to be valid for my purposes. I would only want to extract the rows in green in the attached image. Separate ask - what if I wanted segments of only six to eight seconds? Thanks!
With O365 (assuming no excel version constraints per tags used in the question) you can try the following in cell D1:
=LET(rng, A1:B32, ref, SCAN("", INDEX(rng,,1), LAMBDA(ac,a, IF(ac="", 1,
IF((a - TIME(0,0,1)) = OFFSET(a,-1,0), 0, 1)))), size, ROWS(ref),
seq, SEQUENCE(size), start, FILTER(seq, ref=1),
end, VSTACK(DROP(start-1, 1), size), DROP(REDUCE("", start, LAMBDA(ac,s,
LET(e, XLOOKUP(s, start, end), f, FILTER(rng, (seq >= s) * (seq <= e)),
IF(ROWS(f) > 5, VSTACK(ac, f), ac)))),1))
Note: The same result can be achieved to avoid using the volatile function OFFSET, not using SCAN, and instead calculating the ref value using comparison as follow, where A represents the first column of rng (we keep the same logic as previous formula, but it can be simplified changing the logic to check 0 instead 1 to remove 1-N()):
=LET(rng,A1:B32,A,INDEX(rng,,1), ref,VSTACK(1,1-N(DROP(A,1)
=(DROP(A,-1)+TIME(0,0,1)))), size, ROWS(ref), seq, SEQUENCE(size),
start, FILTER(seq, ref=1), end, VSTACK(DROP(start-1, 1), size),
DROP(REDUCE("", start, LAMBDA(ac,s, LET(e, XLOOKUP(s, start, end),
f, FILTER(rng, (seq >= s) * (seq <= e)),
IF(ROWS(f) > 5, VSTACK(ac, f), ac)))),1))
Here is the output:
Then adjust the input range (rng) to your real case.
Explanation
Using LET for easy reading and composition. The name ref identifies with 1, the start of each group of values with consecutive seconds of the first column from rng. It has the same number of rows as the input data.
We use DROP/REDUCE/VSTACK pattern to generate iteratively the output with the data that satisfies the conditions the group has more than 5 consecutive elements. Check the following answer to this question: how to transform a table in Excel from vertical to horizontal but with different length
Via REDUCE we iterate over all start group positions (start). For each start group position (s), finds the corresponding end group position (e) via XLOOKUP. Filter the range (rng) for rows (seq) between start (s) and end (e) rows via FILTER. Append the filter result (f) only if the number of rows is bigger than 5 via the VSTACK function.
Conditional Format
The formula provided is too large to be used for a conditional format formula (255 maximum number of characters). A possible workaround could be to use a helper column (you can hide it). It returns TRUE if the row belongs to a valid group, otherwise returns FALSE. If no valid group were found it returns FALSE too (we use this trick: NOT(SEQUENCE(size,,1,0)=1) to generate a constant column with FALSE values). Then you can highlight the column A values that correspond with TRUE values in the helper column.
=LET(A, TOCOL(A:A,1), ref, VSTACK(1,N(DROP(A,1) = (DROP(A,-1)+TIME(0,0,1)))),
size, ROWS(ref), seq, SEQUENCE(size), start, FILTER(seq, ref=0),
end, VSTACK(DROP(start-1, 1), size), gr, FILTER(HSTACK(start, end),
(end-start +1) > 5, -1), sgr, INDEX(gr,,1), egr, INDEX(gr,,2),
IF(#gr=-1, NOT(SEQUENCE(size,,1,0)=1), MAP(seq, LAMBDA(x,
LET(overlaps, SUM((sgr <= x) * (egr >= x)), IF(overlaps = 1, TRUE, FALSE))))))
as input, we use the entire column and filter by nonblank values via the TOCOL function (named A) using the second input argument of this function. In case of more than one overlapping interval, it returns FALSE too, just for testing purposes, it indicates some error calculating start and end names because per design it should never happen.
Tip: The previous formula can be used for the original purpose, using its output as a condition for a FILTER function to select only the input range where the value is TRUE. It is a matter of taste which route you want to go. For example, as follow:
=LET(rng,A1:B32,A,INDEX(rng,,1), ref, VSTACK(1,N(DROP(A,1)
= (DROP(A,-1)+TIME(0,0,1)))), size, ROWS(ref), seq, SEQUENCE(size),
start, FILTER(seq, ref=0), end, VSTACK(DROP(start-1, 1), size),
gr, FILTER(HSTACK(start, end), (end-start +1) > 5, -1),
sgr, INDEX(gr,,1), egr, INDEX(gr,,2),
incl, IF(#gr=-1, NOT(SEQUENCE(size,,1,0)=1), MAP(seq, LAMBDA(x,
LET(overlaps, SUM((sgr <= x) * (egr >= x)), IF(overlaps = 1, TRUE, FALSE))))),
FILTER(rng, incl=TRUE, "No Group found"))

What's the logic behind PERCENTILE.INC Excel function?

I would like to know how does Excel think to calculate the values on the function PERCENTILE.INC. I'm making some studies on Percentile and Quartile, I got the below results:
How does Excel think to calculate the values on column F?
Here's the formulas I'm using:
=PERCENTILE.INC(B2:B21; 0,75) ==> F2
=PERCENTILE.INC(B2:B21; 0,50) ==> F3
=PERCENTILE.INC(B2:B21; 0,25) ==> F4
=PERCENTILE.INC(B2:B21; 0,00) ==> F5
Short answer - the position of a given percentile when the data is sorted in ascending order, using percentile.inc, is given by
(N-1)p+1
where p is the required percentile as a fraction from 0 to 1 and N is the number of points.
If this expression gives a whole number, you take the value at this position (e.g. percentile zero gives 1, so its value is exactly 22). If it's not a whole number, you interpolate between the value at the position given by the whole number part (e.g. for p=0.25 it's 5 and the value at this position is 52) and the value at the position one higher (in this case position 6 so the number is 55), then multiply the difference of the two values (3) by the fraction part (0.75) giving you 2.25 and finally add this to the lower of the two values giving you 54.25. A shorter way of saying this is that you go a quarter of the way between the two nearest values. So you have:
If you wished to show the logic as an Excel formula, you could implement the expression shown here on the right (where h, in the second column of the table, is the position calculated from the formula above and x is the value at that position)
like this:
=LET(p,J3,
range,I$2:I$21,
N,COUNT(range),
position,(N-1)*p+1,
lower,FLOOR(position,1),
fraction,MOD(position,1),
upper,CEILING(position,1),
lowerValue,INDEX(range,lower),
upperValue,INDEX(range,upper),
difference,upperValue-lowerValue,
lowerValue+fraction*difference)

Excel Dynamic Array formula to create a running product of a column

I need to create a running product from a column of numbers (I could use a row, but a column is easier to demonstrate here.) The input might be any arbitrary array. In fact, in the application where I would deploy this, it will not be a range, but rather another dynamic array within a LAMBDA formula. Here is an example of the Input column of numbers and the desired Output from the formula:
Inputs
Expected Dynamic Array Output
10
10
8
80
3
240
4
960
5
4800
The formula would spill the results.
There are lots of solutions for a running total, but I've found no solution for a running product. I have tried a few different approaches, including SUBTOTAL and AGGREGATE with no success. I have also built a number of approaches that get the result, but are hard-coded to a fixed number of rows. I need the formula to adapt to any arbitrarily sized number of rows. The following formula is the closest I have gotten so far.
This LET formula delivers the result, but, as you can see is fixed to 5 rows:
=LET( a, {10;8;3;4;5},
v, SEQUENCE( ROWS(a) ), h, TRANSPOSE( v ),
stagr, (v - h + 1) * (v >= h),
m, IFERROR(INDEX( a, IF(stagr>0,stagr,-1), ), 1),
almost, INDEX(m,v,h) * INDEX(m,v,h+1) * INDEX(m,v,h+2) * INDEX(m,v,h+3) * INDEX(m,v,h+4),
result, INDEX( almost, , 1 ),
result )
The arbitrary array of numbers input is placed in the variable a.
The next step is to create some indexes that will be used to address these numbers: v is a sequence of vertical rows for each number in a and h is a the same sequence, but transposed into columns. stagr is an index matrix that is created from v and h that will later be used to address each item in a to form it into a multiplication matrix. If you replace the last result with stagr, you can see the shape of stagr. It just shifts a column down by one row until they are shifted all the way down.
Now we create the mulitplication matrix m using stagr by simply using INDEX, like this: INDEX(a,stagr). But this is not exactly what is needed because it takes the first row value (10) and replicates it because an INDEX of 0 is treated the same as 1. To get what we want, I forced an error by using and internal IF statement like this: INDEX( a, IF(stagr>0,stagr,-1) ) to replace the 0 results with -1. i.e. it will produce this:
Now, replace the errors with 1's by using IFERROR, so this explains how m is created and why. The result is a matrix like this:
and by multiplying m row-wise, we get the output we want, but this is where I fail.
For illustration, I created a variable almost that shows how I am trying to do a row-wise multiplication.
almost, INDEX(m,v,h) * INDEX(m,v,h+1) * INDEX(m,v,h+2) * INDEX(m,v,h+3) * INDEX(m,v,h+4)
You can see that I crudely multiplied one column times the next and the next... and using h + offset to get there. This produces the almost matrix and result just delivers the first column of that matrix, which contains the answer.
While an answer might be a good replacement for almost that would be dynamically sized, that is not my real question. I want a running product and I suspect that there is a wholly different approach than simply replacing my almost.
Just to be clear, the result must be a dynamic array that spills with no helper cells or CSE drag-down.
oh... and no VBA. (#stackoverflow - please add a no-VBA tag)
The only way I can find is to use DPRODUCT with OFFSET, but that requires a title row. It does not matter what is in the title row(it can even be empty), just that it is included.
=DPRODUCT(OFFSET(A1,0,0,SEQUENCE(COUNT(A:A),,2)),1,$ZZ1:$ZZ2)
The $ZZ1:$ZZ2 can be any empty cell reference.
If the values in A are dynamic then we can do:
=DPRODUCT(OFFSET(A1,0,0,SEQUENCE(ROWS(A2#),,2)),1,$ZZ:$ZZ)
There are plenty of interesting answers here. But, if summation is easy why not take logarithms of the number you want to multiply, sum those logarithms and then calculate the exponent of your sum to return to the product of the original numbers.
i.e. exploit the fact that ln(a * b) = ln(a) + ln(b)
Whilst not available to everybody (yet) we can use SCAN()
Formula in A1:
=SCAN(1,{10,8,3,4,5},LAMBDA(a,b,a*b))
The 1st parameter is our starting value, meaning the 1st calculation in the nested LAMBDA() is '1*10'.
The 2nd parameter can both take a 1D- & 2D-array (written or range-reference).
The 3rd parameter is a nested LAMBDA() where the result of our recursive function will then be used for the 2nd calculation; '10*8'. And the 3rd...etc. etc.
In the above sample a vertical array is spilled but when horizontal input is used this will obviously result in an horizontal spilled output. When a 2D-array is used this will spill a 2D-array as result.

Excel comparison with multiple IFs

I have a scale, based on which I decide the value of the coefficient for the multiplication. The scale looks as following:
Which means that:
for Category1: when value>=1.000.000 then coef is 1, when value>=500.000 then coef is 0.8 and etc.
Same logic applies for Category2;
Then I have input data in the following format:
Company !MainCat|Sales Amount|
Company1|T1 | 6.500.000|
Company2|T2 | 70.000|
I need to find corresponding coefficient, ratio of the coeffitient and the value (=ratio*MaxCoef). Currently, I am finding coef the following way:
- for company1:
=IF(C8>=$D2;$D$1;IF(C8>=$E2;$E$1;IF(C8>=$F2;$F$1;IF(C8>=$G2;$G$1;IF(C8>=$H2;$H$1;IF(C8>=$I2;$I$1))))))
That is literally hardcoded and doesn't look good. Maybe there is a better way of doing ? Any suggestions?
Formula view:
You can COUNTIF(range, [criteria] < value) * 0.2 as your add 0.2 per coef stage.
To you data do: =COUNTIF(D2:H2, "<"&C8) * 0.2, count how many stages the value passes * the value per stage.
Your count if range needs to be until H2 as I2 is 0, so inferior to value and gets counted.
To combine the COUNTIF() with a dynamic search for the right category based on MainCat you can MATCH() the MainCat with Code which will give the row where the Code is located and utilize INDIRECT() to apply it as range.
=COUNTIF(INDIRECT("D"&MATCH(B8,B:B,0)&":H"&MATCH(B8,B:B,0)),"<"&C8)*0.2
MATCH(B8,B:B,0) - will match the value on B8 (lets say T1) and return the row 2.
INDIRECT("D"&MATCH(B8,B:B,0)&":H"&MATCH(B8,B:B,0) = INDIRECT("D"&2&":H"&2) - will turn the text into an actual range to be use by the COUNTIF().
Create a table ‚Mapping’ that contains two columns, ‚Category’ and ‚Coefficient‘, then use INDEX-MATCH on it as described in https://www.deskbright.com/excel/using-index-match/.
=INDEX(Mapping[Category]; MATCH([Coefficient]; Mapping[Coefficient]; -1))
This example assumes that you put this formula into a table that has a column named ‚Coefficient‘ with the input value to your multiple IFs.
The trick is that as a match_type argument, provide either -1 or 1, according to your needs.
You can do this in VBA. Write your own function which ends in something like that
=MyOwnScale(C8; B8; A2:I3)
The first parameter of your VBA-function is the value, the second the category and the third is the range with the thresholds. So you can move your cascading IF-loops in VBA-Code and you (and your users) see only a clean function call in the cell.

How to exclude zero entry from a dynamic formula (SUBTOTAL + SUMPRODUCT) in excel

I'm working on a formula to get the standard deviation. It has been working not until I encountered a zero value which makes the result into #DIV/0!.
This is the screenshot of the expected value.
However, when I used my formula, the Game Time SD returned 0.
How do I exclude it in the calculation if the value in F column is zero? I tried IF(F5:F9 <> 0) but it won't work.
This is the formula I used.
F3 = IFERROR(SUBTOTAL(1,F5:F9),0)
G3 = IFERROR(SUMPRODUCT(SUBTOTAL(2,OFFSET(F5:F9,ROW(F5:F9)-MIN(ROW(F5:F9)),,1))*(G5:G9*F5:F9))/SUBTOTAL(9,F5:F9),0)
H3 = IFERROR(((SUBTOTAL(9,F5:F9)*(SUMPRODUCT(SUBTOTAL(2,OFFSET(F5:F9,ROW(F5:F9)-MIN(ROW(F5:F9)),,1)) *
((H5:H9^2*F5:F9*(F5:F9-1)+(G5:G9*F5:F9)^2)/F5:F9)))-(SUMPRODUCT(SUBTOTAL(9,OFFSET(G5:G9,ROW(G5:G9)-MIN(ROW(G5:G9)),,1)),SUBTOTAL(9,OFFSET(F5:F9,ROW(F5:F9)-MIN(ROW(F5:F9)),,1))))^2)/(SUBTOTAL(9,F5:F9)*(SUBTOTAL(9,F5:F9)-1)))^(1/2),0)
I know the problem is somewhere in F5:F9, since the divisor used is zero
The part you suspected in the code involves dividing by a denominator that happens to be a factor in the numerator. You can avoid a division by zero by simplifying that fraction.
((H5:H9^2*F5:F9*(F5:F9-1)+(G5:G9*F5:F9)^2)/F5:F9)))
can be reduced to
(H5:H9^2*(F5:F9-1) + (G5:G9^2*F5:F9))
Resulting in the formula (3rd line modified)
=IFERROR(((SUBTOTAL(9,F5:F9)*
(SUMPRODUCT(SUBTOTAL(2,OFFSET(F5:F9,ROW(F5:F9)-MIN(ROW(F5:F9)),,1))*
(H5:H9^2*(F5:F9-1) + (G5:G9^2*F5:F9))))-
(SUMPRODUCT(SUBTOTAL(9,OFFSET(G5:G9,ROW(G5:G9)-
MIN(ROW(G5:G9)),,1)),SUBTOTAL(9,OFFSET(F5:F9,ROW(F5:F9)-
MIN(ROW(F5:F9)),,1))))^2)/(SUBTOTAL(9,F5:F9)*
(SUBTOTAL(9,F5:F9)-1)))^(1/2), 0)
In my tests without the enclosing IFERROR, I could set some rows to zero and get values. Only when the square rooted subtotal was negative (which logically should not happen) was the result #NUM.
Hope this helps.

Resources