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"))
Related
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.
I have a table on Excel with data as the following:
Meaning, I have different JPH based on the %SMALL unit and the number of active stations.
I need to create a matrix like the following (with %SMALL on horizontal and STATIONS on vertical axes):
And the formula for each cell should:
Take the input of Stations (column "B")
Check, for that specific Stations number, the amount of data on the other table (like make a filter on STATIONS for the specific number)
Perform an VLOOKUP for checking the JPH based on the %SMALL value on row 2
Interpolate for the exact JPH value, if not found on table
For now, I was able to create the last part (the VLOOKUP and the interpolation), with the following:
=IFERROR(VLOOKUP(C2;'EARLY-STATIONS'!$F:$H;3;FALSE);AVERAGE(OFFSET(INDEX('EARLY-STATIONS'!$H:$H;MATCH(C2;'EARLY-STATIONS'!$F:$F;1));0;0;2;1)))
The problem I'm facing is than with this, the calculation is not checking the number of stations, so the Iteration is not accurate.
Unfortunately I cannot use VBA macros to solve this.
Any clue?
This is an attempt because more clarity is needed in terms of all possible scenarios to consider, based on different input data and how to understand the "extrapolation" process. This approach understands as extrapolation the average of two values (lower and greater), but the idea can be customized to any other way to calculate it. Per tags listed in the question I assume there is no Excel version constraint. This is O365 solution:
=LET(sm, A2:A10, st, B2:B10, jph, C2:C10, smx, F1:J1, sty, E2:E4, NULL, "",
GETLk, LAMBDA(x,y,mode, FILTER(jph, (st=y)
* (sm = INDEX(sm, XMATCH(x, sm, mode))), NULL)),
GET, LAMBDA(x,y, LET(f, FILTER(jph, (jph=GETLk(x,y, 1))
+ (jph=GETLk(x,y, -1)), NULL), IF(#f=NULL, NULL, AVERAGE(f)))),
HREDUCE, LAMBDA(yi, DROP(REDUCE("", smx, LAMBDA(ac,x,
HSTACK(ac, GET(x, yi)))),,1)),
DROP(REDUCE("", sty, LAMBDA(ac,y, VSTACK(ac, HREDUCE(y)))),1))
The above formula spills the entire result, I don't think for this case you can use a LOOKUP-like function.
Here is the output:
The highlighted cells where the average is calculated.
Explanation
The main idea is to use DROP/REDUCE/HSTACK/VSTACK pattern to generate the grid. Check my answer to the following question: how to transform a table in Excel from vertical to horizontal but with different length on how to apply it.
We use two user LAMBDA functions to abstract some calculations:
GETLk(x,y,mode), filters jph name based on %SMALL and Stations columns values, based on input values x (x-axis value from the grid), y (y-axis value form the grid) respectively. The third input argument mode, is for doing the approximate search in XMATCH (1-next largest, -1 next smallest). In case the value exist in the input table, XMATCH returns the same value in both cases.
GET(x,y) has the logic to find the value or if the value doesn't exist to calculate the average. It uses the previous LAMBDA function GETLk. We filter for jph values that match the input values (x,y), but we use an OR condition in the FILTER (+), to select both lower or greater values. If the value exist, returns just one value otherwise two values are returned by FILTER (f). Finally if f is not empty we return the average, otherwise the value we setup as NULL.
HREDUCE: Concatenate the result by columns for a given row of the grid. Check the referred question for more information about it.
I want to consolidate the data of column B into a single cell ONLY IF the index (ie., Column A) is duplicated.
For example:
Currently, I'm doing manually for each duplicated index by using the following formula:
=TEXTJOIN(", ",TRUE,B4:B6)
Is there a better way to do this all at once?
Any help is appreciated.
There may easier way but you can try this formula-
=BYROW(A2:A17,LAMBDA(p,IF(INDEX(MAP(A2:A17,LAMBDA(x,SUM(--(A2:INDEX(A2:A17,ROW(x)-1)=x)))),ROW(p)-1,1)=1,TEXTJOIN(", ",1,FILTER(B2:B17,A2:A17=p)),"")))
Using REDUCE might be possible for a more succinct solution, though try this for now:
=BYROW(A2:A17,LAMBDA(ζ,LET(α,A2:A17,IF((COUNTIF(α,ζ)>1)*(COUNTIF(INDEX(α,1):ζ,ζ)=1),TEXTJOIN(", ",,FILTER(B2:B17,α=ζ)),""))))
For the sake of alternatives about how to solve it:
Using XMATCH/UNIQUE
=LET(A, A2:A17, ux, UNIQUE(A),idx, FILTER(XMATCH(ux, A), COUNTIF(A, ux)>1),
MAP(SEQUENCE(ROWS(A)), LAMBDA(s, IF(ISNA(XMATCH(s, idx)), "", TEXTJOIN(",",,
FILTER(B2:B17, A=INDEX(A,s)))))))
or using SMALL/INDEX to identify the first element of the repetition:
=LET(A, A2:A17, n, ROWS(A), s, SEQUENCE(n),
MAP(A, s, LAMBDA(aa,ss, LET(f, FILTER(B2:B17, A=aa), IF((ROWS(f)>1)
* (INDEX(s, SMALL(IF(A=aa, s, n+1),1))=ss), TEXTJOIN(",",, f), "")))))
Here is the output:
Explanation
XMATCH and UNIQUE
The main idea here is to identify the first unique elements of column A via ux, and find their corresponding index position in A via XMATCH(ux, A). It is an array of the same size as ux. Then COUNTIF(A, ux)>1) returns an array of the same size as XMATCH output indicating where we have a repetition.
Here is the intermediate result:
XMATCH(ux, A) COUNTIF(A, ux)>1)
1 FALSE
2 FALSE
3 TRUE
6 FALSE
7 TRUE
9 TRUE
11 FALSE
12 TRUE
15 FALSE
16 FALSE
so FILTER takes only the rows form the first column where the second column is TRUE, i.e the index position (idx) where the repetition starts. For our sample it will be: {3;7;9;12}.
Now we iterate over the sequence of index positions (s) via MAP . If s is found in idx via XMATCH (also XLOOKUP(s, idx, TRUE, FALSE) can be used for the same purpose) then we join the values of column B filtered by column A equal to INDEX(A,s).
SMALL and INDEX
This is a more flexible approach because in the case we want to do the concatenation in another position of the repetition you just need to specify the order and the formula doesn't change.
We iterate via MAP through elements of column A and index position (s). The name f has the filtered values from column B where column A is equal to a given value of the iteration aa. We need to identify only filtered rows with repetition, so the first condition ROWS(f) > 1 ensures it.
The second condition identifies only the first element of the repetition:
INDEX(s, SMALL(IF(A=aa, s, n+1),1))=ss
The second argument of SMALL indicates we want the first smallest value, but it could be the second, third, etc.
Where A is equal to aa, IF assigns the corresponding value of the sequence (remember IF works as an array formula), if not then it assigns a value that will never be the smallest one, for example, n+1, where n represents the number of rows of column B. SMALL returns the smallest index position. If the current index position ss is not the smallest one, the conditions FALSE.
Finally, we do a TEXTJOIN only when both conditions are met (we multiply them to ensure an AND condition).
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.
I'm a teacher and trying to create a mark sheet the problem I have is that assignments are marked using different schemes (i.e. % [1 - 100], Level [1 -4], Letter Grade [F - A]). The problem I have is that I only want to report using one marking scheme and need to convert or calculate the other marking schemes based on the scheme there is it is marked.
I've made an excel worksheet which contains a student names and assignments. The sheet then has 3 other columns, one representing each scheme "Letter", "Level", Percentage a column for each scheme. Now, at any given time there is going to be a value in one of the 'scheme' columns - I need excel to automatically or by macro calculate the appropriate value into the other cells.
For example, there are three assignments each marked with a different scheme on the same sheet.
Assignment 1 - %
Assignment 2 - Level
Assignment 3 - Letter Grade
How can I get excel to check which mark value exists and then populate/calculate the other two the corresponding values.
Mike got 80% (%) on Assignment 1, but I want excel to populate the level column and letter column with the corresponding values at the same time Level 3 on Assignment 2 excel to calculate corresponding percentage and letter and at the same time for a Letter A on Assignment 3 the corresponding % and level should be calculated.
Don't know if this is possible or makes sense, but I tried VLOOKUP and was not successful because I have to do it all manually - I'm aiming for an automated process.
Sorry if it is not clear or confusing...but I've been at this for two days with no luck and not even sure if what I'm doing is possible.
of course it should be first assessed the translation of one rating system to the other and then assume those algorithms to shift between rating grades types
one of the possible approaches could be the following:
Option Explicit
Sub main()
Dim grades As Variant, levels As Variant
Dim cell As Range
Dim index As Double
grades = Array("F", "E", "D", "C", "B", "A")
levels = Array(1, 2, 3, 4)
For Each cell In Intersect(Range("A:C"), ActiveSheet.UsedRange).Offset(1).SpecialCells(xlCellTypeConstants)
Select Case cell.Column
Case 1 '%
index = cell.value / 100
cell.Offset(, 1).value = GetRate(index, levels)
cell.Offset(, 2).value = GetRate(index, grades)
Case 2 ' Level
index = InStr(Join(levels, ""), cell.value) / Len(Join(levels, ""))
cell.Offset(, -1).value = index * 100
cell.Offset(, 1).value = GetRate(index, grades)
Case 3
index = InStr(Join(grades, ""), cell.value) / Len(Join(grades, ""))
cell.Offset(, -2).value = index * 100
cell.Offset(, -1).value = GetRate(index, levels)
End Select
Next
End Sub
Function GetRate(index As Double, rates As Variant)
GetRate = rates(WorksheetFunction.Max(Round(index * (UBound(rates) - LBound(rates) + 1), 0) - 1, 0))
End Function
due to the simplicity of the translation algorithm it doesn't provide a bijective relationship between grading systems with different spans, but it can get you closer to your goal
Create a table like so (you can add more rows if you need to define +/- scores like "B+", etc.). The key here is sort the table ascending by %, with each row being the minimum score needed for the corresponding Level/Letter Grade.
Then, you can use VLOOKUP formula against this table, like so to get the Level:
=VLOOKUP(F2,$A$1:$C$7,2,TRUE)
And like so, to get the Letter Grade:
=VLOOKUP(F2,$A$1:$C$7,3,TRUE)
This takes advantage of the True parameter in the VLOOKUP function, which will return an approximate match (and assumes data is sorted ascending). So, when you enter a value like "81%", it returns the nearest row which is not greater than 81%, so it will return data from row 5. Likewise, 12% will return data from row 2, 75% from row 4, etc.
And your results:
You may need to use two tables if you can't 1:1 map the Level/Letter Grade to a Percent row, but the idea would be the same.