Excel: Find Which Range A Value Belongs To If Any (Sparse Ranges) - excel

I know one can use LOOKUP or VLOOKUP to find range from a list of contiguous ranges a value belongs to. I know you can do some functional but awful things with IF statements to do similar things.
What I'm looking to do:
I have a calculated value between 1 and 100% (represents how far along a lunar synodic orbital cycle the moon is).
I need to essentially identify if the calculated value falls in:
0 - 0.6 Full
11.9 - 13.1 3/4 Waning
24.4 - 25.6 1/2 Waning
36.9 - 38.1 1/4 Waning
49.4 - 50.6 No Moon
61.9 - 63.1 1/4 Waxing
74.4 - 75.6 1/2 Waxing
86.9 - 88.1 3/4 Waxing
99.4 - 100 Full
So I need to check if the calculated value falls within any of the calculated ranges and if so, return the associated text. If it does not, it would be desirable to return a blank ("").
I'm wondering if I have to just use a really ugly nested IF statement or if there is some graceful way to use one or two lookup functions to accomplish what I want. The fact the overall range one is testing against is sparse (parts of the range should return a blank) is the challenge.
One approach I can see is not using a sparse range - just filling in between each of the existing ranges a range that returns blank, then using a LOOKUP or VLOOKUP. Is that my best option or is there a better solution?
For example:
0 - 0.6 Full
0.7 - 11.8 <blank>
11.9 - 13.1 3/4 Waning
13.2 - 24.3 <blank>
24.4 - 25.6 1/2 Waning
25.7 - 36.8 <blank>
36.9 - 38.1 1/4 Waning
38.2 - 49.3 <blank>
49.4 - 50.6 No Moon
50.7 - 61.8 <blank>
61.9 - 63.1 1/4 Waxing
63.2 - 74.3 <blank>
74.4 - 75.6 1/2 Waxing
75.7 - 86.8 <blank>
86.9 - 88.1 3/4 Waxing
88.2 - 99.3 <blank>
99.4 - 100 Full

Given your original data, split into three columns and formatted as a table named PhaseTbl, with column headers Min, Max and Phase, I believe the following will do what you require, with the value to be tested in A2:
=IFERROR(INDEX(PhaseTbl,AGGREGATE(15,6,1/((A2>=PhaseTbl[Min])*(A2<=PhaseTbl[Max]))*ROW(PhaseTbl)-ROW(PhaseTbl[#Headers]),1),3),"")
Phase Table
Sample Results
You can examine how this formula works by using the formula evaluation tool.
In brief, working from the inside --> out
(A2>=PhaseTbl[Min])*(A2<=PhaseTbl[Max])
We take our value and, by multiplying the Boolean comparisons, we return an array of 1's and 0's depending on whether we satisfy the condition that in the same row the tested value is both greater than (or equal to) the Minimum and less than or equal to the maximum.
1/{1,0,0,...}
Converts the 0's into error values.
The array form of the AGGREGATE function, with the ignore errors argument, will then return the row number of the match. We adjust that for the table location to return the value from column 3 within the INDEX function.

As well as Ron's answer, you can use an array formula:
=IFERROR(INDEX(PhaseTbl[State],MATCH(1,([#Value]>=PhaseTbl[Min])*([#Value]<=PhaseTbl[Max]),0)),"")

I have another option with three basic formulas IF, COUNTIFS and INDEX(MATCH()) and no arrays. As other answers have suggested, I'd firstly recommend you split your data into 3 columns: Min, Max and Phase, so it looks like the following example.
Input Data:
A B C
1|Min |Max | Phase |
+---+-----+-----------+
| 0 |0.6 | Full |
|11.9|13.1|3/4 Waning |
|24.4|25.6|1/2 Waning |
|36.9|38.1|1/4 Waning |
|49.4|50.6| No Moon |
|61.9|63.1|1/4 Waxing |
|74.4|63.1|1/2 Waxing |
|86.9|88.1|3/4 Waxing |
|99.4|100 | Full |
With the above data starting in A1, your output data starting in F1 would look like the below example, with the below formula in G2.
Formula:
=IF(COUNTIFS(A:A,"<="&F2,B:B,">="&F2)=1,INDEX(C:C,MATCH(F2,A:A,1)),"")
Output Data:
F G
1|Value|Result |
+-----+----------+
| 0 |Full |
|0.6 |Full |
|0.7 | |
|11.8 | |
|11.9 |3/4 Waning|
|13.1 |3/4 Waning|
|13.2 | |
|24.3 | |
|24.4 |1/2 Waning|
|25.6 |1/2 Waning|
|25.7 | |
|99.3 | |
|99.4 |Full |
|100 |Full |
|110 | |
Formula Explained:
=IF(COUNTIFS(A:A,"<="&F2,B:B,">="&F2)=1,INDEX(C:C,MATCH(F2,A:A,1)),"")
Count if any of the values in the "Min" list are less than or equal to F2 AND any of the values in the "Max" list are greater than or equal to F2
If the count returns 1, return the "Phase" on the same row as the "Min" value that is less than F2 (this is done in the MATCH part of the INDEX(MATCH()) formula, with the match type set to "1: Less than")
If the count does not return 1, return a blank cell.

Related

How to reference another cell in a chart based on an aggregating total in another cell

So, the title might be confusing, so I'll outline like this:
I am making a weightloss chart. One of the clients gets to open a bag of legos as a reward for every 2lbs that he loses, as long as he does it based on a goal progression. For instance, if he weights 260, and loses 2lb, he gets his reward. However, if he gains a lb, now he has to lose 3lb to get his reward.
Currently, I have charts that look like this:
Column O
Column P
Current Weight
Amount Lost
263
8
Column L
Column M
Next Lego Bag
261
Lbs until next bag
2
After he hits 261, I want that cell that says 261 in Col M to say "259". So if he weighs in again, I want it to look like this automatically.
Column O
Column O
Current Weight
Amount Lost
260.5
10.5
Column L
Column M
Next Lego Bag
259
Lbs until next bag
1.5
What is the best way to automatically make that cell in Column M change when he hits the 2lb goal? I have a table that basically states all the goal weighs he needs to hit for each reward. It looks like this:
| Column Z | Column AA | Column AB | (formatting is being weird)
| -------- | -------- | -------- |
| Bag | Target Weight | Amount Lost |
| Bag 5 | 261 | 8 |
| Bag 6 | 259 | 10 |
| Bag 7 | 257 | 12 |
| Bag 8 | 255 | 14 |
| Bag 9 | 253 | 16 |
etc
I've tried a few things, but I'm coming up blank, because it won't always be in whole numbers the amount he loses, so matching it to the target weight has been tough.
In really, really simple terms, I need it to basically say this:
If current weight > goal 1, then A1 = goal 1. If current weight < Goal 1, then A1 = Goal 2, and all the way to Goal 21. However, A1 can't change to the next goal until current weight is less than that goal.
Thanks all
I have tried IF statements and Floor statements to get an ongoing changing thing, but it's not working.
In M2: =IF(MOD(O2+1,2)=0,2,MOD(O2+1,2))
In M1:
=O2-M2
Or using O365 in M1:
=LET(m,MOD(O2+1,2),
lbs,IF(m=0,2,m),
VSTACK(O2-lbs,lbs))

Need some kind of pivotal table without agregation in Excel

I struck with problem of getting reports from table, that look like this:
C1| C2 | C3 | C4
A | 2015-05-15 | 34 | 4
A | 2015-03-12 | -4 | 5
A | 2014-03-12 | 24 | 8
B | 2015-11-10 | -4 | 5
B | 2015-06-12 | 3 | 5
C | 2013-05-12 | 3 | 5
...
600+ rows
...
So I need to make a diagram by different value columns (C3 and C4) grouping by values in the first column. In usual case it is achieved with to separate table which a looks like this (e.g. for col3):
A | B | C | ....
34 | -4 | 3 | ....
-4 | 3 | | ....
24 | | | ....
For col4, I need a table with the similar layout. So in short, I need to make some pivotal table by without aggregation on value per term. Is it possible to get such small table with the original table? If you can offer some other layout for original data which will be more suitable (and easier, in ideal with standard excel functions) for this task, fell free to offer - with some Python script I can resave it.
Find a simple solution: just download tool Tableau. It affords to create very flexible graphs based on raw data, freely place any col as row and vice versa, groping also available.
First, I will assume that C1,C2,C3,C4 are column headings in A1:D1 and the data is in A2:D1000
In F1:H1 place C3,C3,C3
In F2:H2 place A,B,C
In J1:L1 place C4,C4,C4
In J2:L2 place A,B,C
In F3 place the following array formula:
=IFERROR(INDEX(OFFSET($A$2:$A$1000,0,MATCH(F$1,$A$1:$D$1,0)-1),SMALL(IF($A$2:$A$1000=F$2,ROW($A$2:$A$1000)-ROW(A$2)+1),ROWS(F$2:F2))),"")
Don't forget to use Ctrl+Shift+Enter to make it an array formula instead of just using Enter for a normal formula.
Copy F3 to F4:F7, G3:H7 and J3:L7
You end up with something that looks like this:
| A B C D E F G H I J K L
----|--------------------------------------------------------------------
1 | C1 C2 C3 C4 C3 C3 C3 C4 C4 C4
2 | A 15/05/2015 34 4 A B C A B C
3 | A 12/03/2015 -4 5 34 -4 3 4 5 5
4 | A 12/03/2014 24 8 -4 3 5 5
5 | B 10/11/2015 -4 5 24 8
6 | B 12/06/2015 3 5
7 | C 12/05/2013 3 5
Decomposing the formula in F3:
=IFERROR(INDEX(source column,SMALL(filtered row numbers,ranking)),"")
Where:
source column is OFFSET($A$2:$A$1000,0,MATCH(F$1,$A$1:$D$1,0)-1)
filtered row numbers is IF($A$2:$A$1000=F$2,row numbers)
row numbers is ROW($A$2:$A$1000)-ROW(A$2)+1
ranking is ROWS(F$2:F2)
How the formula works is a multipart explanation:
IFERROR(formula,"") If the embedded formula produces an error then we display an empty cell instead. This is useful since we don't know how many results we will get.
INDEX(range,row) Get the value in the row we want to see. The row number here is dynamically generated based on whether the data matches the criteria.
SMALL(array,k) Extract the kth smallest value in the array. The array is the filtered values. k is just a number depending on which row in F we put the formula in.
IF(criteria,value) Based on the criteria either produces the value or FALSE.
ROW(cell) The row number of the cell
ROWS(range) The size of the range
So in the array formula:
ROW($A$2:$A$1000)-ROW(A$2)+1 is a list of all the row numbers in the range starting at 1
IF($A$2:$A$1000=F$2,ROW($A$2:$A$1000)-ROW(A$2)+1) is a list of just those row numbers that match the criteria
SMALL takes the filtered list and returns the kth smallest filtered row number which means that F3 gets the first one, F4 gets the second one, etc
INDEX grabs the data at that row number and displays it
I got the idea for this from http://www.exceltactics.com/make-filtered-list-sub-arrays-excel-using-small/ and on that page is a really good explanation of how it all works.

Sumifs, but only on once for each set of matching values

I am extracting sums from a table, and there is one thing I can't figure out how to do: I need to get the results only once no matter how many times there is a match.
For example, I need to sum the total of column C for all rows where A and B both match the values of current row, but I need to get the answer only ONCE, not on every row where they match, although it doesn't matter which instance it is (first, last, middle, etc.).
Sample Table:
1 | 100 | 4.0
1 | 100 | 7.3
1 | 200 | 9.5
2 | 100 | 4.7
2 | 200 | 9.0
2 | 100 | 9.1
2 | 100 | 7.4
I would want to get the results (assuming they showed at the first match):
11.3
0.0
9.5
21.2
9.0
0.0
0.0
How do I do this?
I've tested this and it works:
=IF(COUNTIFS($A$2:$A$8,A2,$B$2:$B$8,B2)=COUNTIFS($A2:$A$8,A2,$B2:$B$8,B2),SUMIFS($C$2:$C$8,$A$2:$A$8,A2,$B$2:$B$8,B2),0)
regarding the comments:
oh true.... messed up the true/false part...
=IF(COUNTIFS(A$1:A1,A2,B$1:B1,B2),0,"here comes your sumifs")
so for your whole formula:
=IF(COUNTIFS(A$1:A1,A2,B$1:B1,B2),0,SUMIFS(C:C,A:A,A2,B:B,B2))
to get the solution always at the first line. ;)

Scaling values with a known upper limit

I have a column of values in Excel that I need to modify by a scale factor. Original column example:
| Value |
|:-----:|
| 75 |
| 25 |
| 25 |
| 50 |
| 0 |
| 0 |
| 100 |
Scale factor: 1.5
| Value |
|:-----:|
| 112.5 |
| 37.5 |
| 37.5 |
| 75 |
| 0 |
| 0 |
| 150 |
The problem is I need them to be within a range of 0-100. My first thought was take them as percentages of 100, but then quickly realized that this would be going in circles.
Is there some sort of mathematical method or Excel formula I could use to handle this so that I actually make meaningful changes to the values, such that when these numbers are modified, 150 is 100 but 37.5 might not be 25 and I'm not just canceling out my scale factor?
Assuming your data begin in cell A1, you can use this formula:
=MIN(100,A1*1.5)
Copy downward as needed.
You could do something like:
ScaledValue = (v - MIN(AllValues)) / (MAX(AllValues) - MIN(AllValues)) * (SCALE_MAX - SCALE_MIN) + SCALE_MIN
Say your raw data (a.k.a. AllValues) ranges from a MIN of 15 to a MAX of 83, and you want to scale it to a range of 0 to 100. To do that you would set SCALE_MIN = 0 and SCALE_MAX = 100. In the above equation, v is any single value in the data.
Hope that helps
Another option is:
ScaledValue = PERCENTRANK.INC(AllValues, v)
In contrast to my earlier suggestion, (linear --- preserves relative spacing of the data points), this preserves the order of the data but not spacing. Using PERCENTRANK.INC will have the effect that sparse data will get compressed closer together, and bunched data will get spread out.
You could also do a weighted combination of the two methods --- give the linear method a weight of say 0.5 so that relative spacing is partially preserved.

Calculate Average based on multiple condition in Excel

I am back with my new excel question.
Lets say I have table like this.
| A | B
------------------------------------------
1 | ENV | Value
------------------------------------------
2 | ABC - 10/1/2014 1:38:32 PM | 4
3 | XYZ - 10/1/2014 1:38:32 PM | 6
4 | ABC - 9/1/2014 1:38:32 PM | 1
5 | XYZ - 10/1/2014 1:38:32 PM | 10
6 | ABC - 10/1/2014 1:38:32 PM | 7
7 | XYZ - 9/1/2014 1:38:32 PM | 1
8 | ABC - 9/1/2014 1:38:32 PM | 10
9 | ABC - 10/1/2014 1:38:32 PM | 7
10 | XYZ - 10/1/2014 1:38:32 PM | 7
Now, in Cell C2, I've selected ABC.
So in cell D2, I want the average (from col B) of all the "ABC" (col A) where Month = 10 (col A) and in cell E2, Max (from col B) of all the "ABC" where Month = 10 (col A).
So, my result in cells D2 and E2 would be 6 and 7 respectively.
I hope my question and example make sense.
UPDATE:
Thank you all for all your help.
Now let's say I am not sure how many rows I'll have on this spreadsheet, so I came up with this formula, but its not working, giving me #DIV/0! error.
*Note: I am using formula to get "ABC" and "10" from cell C2.
=AVERAGEIFS(
(OFFSET($A$1,1,1,COUNTA($B:$B)-1,1)),
OFFSET($A$1,1,0,COUNTA($A:$A)-1,1), (MID(C2,1,(FIND("-",C2))-2)),
OFFSET($A$1,1,0,COUNTA($A:$A)-1,1), (MID(C2,(FIND("-",C2)+1),(FIND("/",C2))-(FIND("-",C2)+1))))
Even tried this, but same error:
=SUMPRODUCT(((MID(A2:A10,1,(FIND("-",A2:A10))-1))=(MID(C2,(FIND("-",C2)+1),(FIND("/",C2))-(FIND("-",C2)+1))))*
(MONTH(DATEVALUE(MID(A2:A10,7,99)))=(MID(C2,(FIND("-",C2)+1),(FIND("/",C2))-(FIND("-",C2)+1))))*
(B2:B10))/SUMPRODUCT(((MID(A2:A10,1,(FIND("-",A2:A10))-1))=(MID(C2,(FIND("-",C2)+1),(FIND("/",C2))-(FIND("-",C2)+1))))*
(MONTH(DATEVALUE(MID(A2:A10,7,99)))=(MID(C2,(FIND("-",C2)+1),(FIND("/",C2))-(FIND("-",C2)+1)))))
Can you help me with this...?
Solution with Intermediary Values
To solve the issue (I tested the average only) I first used 2 intermediary values: this solution is not optimal and there will be many smarter ways to address the issue (e.g. pivot tables).
ENV Value Intermediary 1 Intermediary 2
ABC - 10/1/2014 1:38:32 PM 4 ABC 10
XYZ - 10/1/2014 1:38:32 PM 6 XYZ 10
ABC - 9/1/2014 1:38:32 PM 1 ABC 9
XYZ - 10/1/2014 1:38:32 PM 10 XYZ 10
The first intermediary column contains the first 3 chars of ENV column (=LEFT(A9,3)), while the second intermediary column contains the month (=MID(A9,7,2)). This works only if your ENV records are fixed size and homogeneous (e.g. your env name has exactly 3 chars).
With this layout, you can compute the average putting in any cell the following formula:
=AVERAGEIFS(D9:D12, F9:F12,"=ABC", G9:G12, "=10")
Where D9:D12 is the values interval, F9:F12 is the 1st intermediary column and G9:G12 the second intermediary column.
One Shot Compact Solution (Arrays)
An optimized solution can be found relying on arrays. For instance, to calculate the average and the max of an interval based on 2 "vectorial" conditions you can write this one liners:
= MAX(IF((LEFT(A9:A12,3)="ABC")*(MID(A9:A12,7,2)="10"),D9:D12))
= AVERAGE(IF((LEFT(A9:A12,3)="ABC")*(MID(A9:A12,7,2)="10"),D9:D12))
With A9:A12 your original records, and D9:D12 is the values interval.
The advantages of this solution are that you don't need any intermediary column and that you can extend this approach to all the other formulas that don't have 'xxxxxIFS' (it's the case for MAX).
NOTE: you have to confirm this formula with CTRL + SHIFT + RETURN or your formula will fail with #VALUE error.
Live Demo
Live demo available here.
You can start by spiting column A into a date and letters using - Data > Text to Columns with the delimiter " - ".
after you have the new two columns (let say F and G) you can use the function "AVERAGEIF" with a condition that check is the value of the cell in "F" is ABC and the Moth(cell in "G") = 10.
as for the max, you can do the same with MAX(IF....) for column E.
SUMPRODUCT will allow you to parse the left-most and date characters from your combined string. A pseudo-MAXIF() can be similarly constructed using MAX() and INDEX().
In D2 use =SUMPRODUCT((LEFT(A2:A10,3)="ABC")*(MONTH(DATEVALUE(MID(A2:A10,7,99)))=10)*(B2:B10))/SUMPRODUCT((LEFT(A2:A10,3)="ABC")*(MONTH(DATEVALUE(MID(A2:A10,7,99)))=10))
In E2 use =MAX(INDEX((LEFT(A2:A10,3)="ABC")*(MONTH(DATEVALUE(MID(A2:A10,7,99)))=10)*(B2:B10),,))
Both SUMPRODUCT and INDEX like to choke on anything remotely resembling an error when parsing text so keep the cell range references to what your actual data is and avoid blanks.
Your results should look like the following.
            

Resources