I'm looking for a excel formula which will help me calculate the medians of different data.
1 45
2 54
3 26
4 12
1 34
2 23
3 9
Now, I need to calculate the median of data from B1:B4 and then B5:B8, and print whether the number is lesser/equal/greater than the median..
I've come up with preliminary formula
=IF(MEDIAN($B$1:$B$4)<B1;"g";IF(MEDIAN($B$1:$B$4)=B1;"e";"l"))
But, this won't help for calculating the median for different sets.
What should i do?
Thanks for the help!
To check if B1 is greater, less than, or equal the median of both the medians of B1:B4 and B5:B8 (in this case would be 29.25), then you could use something like this:
=IF(B1>(MEDIAN(MEDIAN(B1:B4),MEDIAN(B5:B7))),"g",IF(B1=(MEDIAN(MEDIAN(B1:B4),MEDIAN(B5:B7))),"e","l"))
If you just want to check against B1:B4 (as in your example) you can use:
=IF(B1>MEDIAN(B1:B4),"g",IF(B1=MEDIAN(B1:B4),"e","l"))
UPDATE:
According to your comment below, here is what you can write in C1 and drag down to C4:
=IF(B1>MEDIAN($B$1:$B$4),B1&">"&MEDIAN($B$1:$B$4),IF(B1=MEDIAN($B$1:$B$4),B1&"="&MEDIAN($B$1:$B$4),B1&"<"&MEDIAN($B$1:$B$4)))
You have three problems here:
your 1st column is a sequence rather than a group identifier
there is no =MEDIANIF()
Pivot tables don't support Medians either
not a good starting point ....
ad #1, you could change your 1,2,3,4,1,2,3, ... towards 1,1,1,1, 2,2,2, ... to indicate what belongs together
ad #2, #3 ... I would suggest to define a function =MEDIANIF() in VBA; example:
Function MedianIf(R As Range, Cr As Variant) As Variant
Dim Idx As Integer, Jdx As Integer, A() As Variant, CCnt As Integer, Tmp As Variant
' find array size
CCnt = 0
For Idx = 1 To R.Rows.Count
If R(Idx, 1) = Cr Then CCnt = CCnt + 1
Next Idx
'dim array
ReDim A(CCnt - 1)
' load from range into array
CCnt = 0
For Idx = 1 To R.Rows.Count
If R(Idx, 1) = Cr Then
A(CCnt) = R(Idx, 2)
CCnt = CCnt + 1
End If
Next Idx
' bubble sort
For Jdx = CCnt - 1 To 0 Step -1
For Idx = 0 To Jdx - 1
If A(Idx) > A(Idx + 1) Then
Tmp = A(Idx)
A(Idx) = A(Idx + 1)
A(Idx + 1) = Tmp
End If
Next Idx
Next Jdx
' get Median
If CCnt Mod 2 = 1 Then
MedianIf = A(Int(CCnt / 2))
Else
MedianIf = (A(CCnt / 2 - 1) + A(CCnt / 2)) / 2
End If
End Function
Use this function by selecting a range 2 col's wide and x rows down, like
=MedianIF($A$1:$B$7,A1)
and there we go ... now you can use the new function in your =IF(.... , "g", ...)
so one single formula for all the range .... attention: bubble sort is not very economic for large ranges
Related
I have the following data:
Value
Percentages
Rank
A
67%
3
B
57%
4
C
43%
5
D
38%
1
E
67%
2
F
57%
6
In Excel (either formulas or VBA), how would I be able to generate a top three based on first the percentage value - and if the percentages of two or more are equal, then based on rank?
So for example 67% is found twice in column Percentages. However, the second time 67% is found it has a rank of 2, so the first value for the top three would be E and the complete top three would look like this:
1: E
2: A
3: B
I don't even have an idea to start with. I can use the =LARGE formula, but that will only give me the value of the highest and if the value is found in there multiple times, then I don't know from which one it took it. And it also doesn't take into account the custom ranking..
If you have Excel-365 then can try-
=SEQUENCE(3)&":"&INDEX(SORTBY(A2:A7,B2:B7,-1,C2:C7,1),SEQUENCE(3))
Here is a UDF that should work in most versions of Excel (tested in Excel 365). I have chosen to avoid sorting and just repeatedly find the maximum subject to the two conditions. Assumes all percentages and ranks are positive values but can tolerate blank rows.
Function ListTopValues(r As Range, Optional topN As Integer = 3)
Dim arr() As Variant, used() As Boolean
Dim percent As Double, rank As Double
Dim maxPercent As Double, maxRank As Double
Dim value As String, maxValue As String, result As String
Dim rows As Long, n As Long, i As Long, u As Long
' Transer range to array
rows = r.rows.Count
ReDim used(rows)
arr = r.value
' Loop over number of values required
For n = 1 To topN
maxValue = ""
maxRank = 0
maxPercent = 0
u = 0
' Loop over rows of data
For i = 1 To rows
value = arr(i, 1)
percent = arr(i, 2)
rank = arr(i, 3)
' Check if row already used and copy maximum so far
If Not used(i) Then
If percent > maxPercent Or percent = maxPercent And rank < maxRank Then
maxPercent = percent
maxRank = rank
maxValue = value
u = i
End If
End If
Next i
If maxValue <> "" Then _
result = result & maxValue & ","
If u > 0 Then used(u) = True
Next n
If Len(result) > 0 Then _
result = Left(result, Len(result) - 1)
ListTopValues = result
End Function
I'm wanting to build a conditional linear interpolation. I have over 31 unique identifiers. Where the range changes based on the identifier it selects. I was thinking I could do a select based on case criteria but that doesn't seem like the most efficient.
Data looks like this. (Where the currency is the identifier)
AED 1 4
AED 2 6
AUD 1 1
AED 3 12
AUD 2 6
AED 4 13
AUD 3 8
Below is the original linear interpolation formula.(Without any conditions). Any ideas what would be the best way to tackle this?
Function Linterp2(rX As Range, rY As Range, x As Double) As Double
' linear interpolator / extrapolator
' R is a two-column range containing known x, known y
Dim lR As Long, l1 As Long, l2 As Long
Dim nR As Long
'If x = 1.5 Then Stop
nR = rX.Rows.Count
If nR < 2 Then Exit Function
If x < rX(1) Then ' x < xmin, extrapolate
l1 = 1: l2 = 2: GoTo Interp
ElseIf x > rX(nR) Then ' x > xmax, extrapolate
l1 = nR - 1: l2 = nR: GoTo Interp
Else
' a binary search would be better here
For lR = 1 To nR
If rX(lR) = x Then ' x is exact from table
Linterp2 = rY(lR)
Exit Function
ElseIf rX(lR) > x Then ' x is between tabulated values, interpolate
l1 = lR: l2 = lR - 1: GoTo Interp
End If
Next
End If
Interp:
Linterp2 = rY(l1) _
+ (rY(l2) - rY(l1)) _
* (x - rX(l1)) _
/ (rX(l2) - rX(l1))
End Function
mmm "you want to build a conditional linear interpolation" and said "Say ID is AED" let me speculate what you really want,
First you have a Function (that do linear interpolate)
Second you have (for easy explain) a table that have 3 columns:
Column 1: ID (value that identify which ranges will use)
Column 2: Number of values have varibale X (range X)
Column 2: Number of values have varibale Y (range Y)
thats to mean if you select ID=AED, Function() will take a range.size for X and range.size for Y (for example taken first row you wrote only take AED)
1: you want "select case" insde your function if this is the case:
*first you select all columns for your ranges (select all column 2 for range X and select all column 3 for range y) and X value.
*then when your function runs; function have to identify Which ID have (you want to know)and resize your ranges X,Y and only take values that have ID like indentify.
So you need change variables in your Function because you need obligatorily a relationship between ID, X-value and y-value for each point.
so need a matrix
Function Linterp2(Mtr As Range, x As Double, ID as String) As Double
in this case your range have 3 columns and n rows (need to select all table)
then do a "For" where you search for ID in Range
dim MtrP(0,2)
for i=1 to mtr.rows.count
if MtrP(0,0)=nothing then /*get first value*/
if Mtr.cells(i,1).value="ID" then
MtrP(0,0)=Mtr.cells(i,1)
MtrP(0,1)=Mtr.cells(i,1)
MtrP(0,2)=Mtr.cells(i,1)
j=0
end if
elseif Mtr.cells(i,1).value="ID" then
j=j+1
redim preserve MtrP(j,2)
MtrP(j,0)=Mtr.cells(i,1)
MtrP(j,1)=Mtr.cells(i,1)
MtrP(j,2)=Mtr.cells(i,1)
end if
next
In this moment your new array with all data you need is MtrP and you can work with it for do your linear interpolate
Check this out,
Note that arrays are 0 indexed but ranges are 1 indexed.
Function Linterp3(rX As Range, rY As Range, rID As Range, x As Double, id As String) As Double
' Linear interpolator / extrapolator with index criteria
' Inputs:
' rX - 1 column range of x Values
' rY - 1 column range of y Values
' rID - 1 column range of index criteria
' x - x value criterion
' id - index criterion
' Select the relevant parts of the X,Y ranges based on the id criteria
Dim rX_selected() As Double, rY_selected() As Double, i As Integer, j As Integer
j = 0
For i = 1 To rX.Worksheet.UsedRange.Rows.Count
If rID.Cells(i).Value = id Then
ReDim Preserve rX_selected(j)
ReDim Preserve rY_selected(j)
rX_selected(j) = rX(i).Cells.Value
rY_selected(j) = rY(i).Cells.Value
j = j + 1
End If
Next
'Linearly interpolate
Dim lR As Long, l1 As Long, l2 As Long
Dim nR As Long
nR = j
If nR < 2 Then Exit Function
If x < rX_selected(0) Then ' x < xmin, extrapolate
l1 = 1: l2 = 2: GoTo Interp
ElseIf x > rX_selected(nR - 1) Then ' x > xmax, extrapolate
l1 = nR - 1: l2 = nR: GoTo Interp
Else
' a binary search would be better here
For lR = 1 To nR - 1
If rX_selected(lR) = x Then ' x is exact from table
Linterp3 = rY_selected(lR)
Exit Function
ElseIf rX_selected(lR) > x Then ' x is between tabulated values, interpolate
l1 = lR: l2 = lR - 1: GoTo Interp
End If
Next
End If
Interp:
Linterp3 = rY_selected(l1) _
+ (rY_selected(l2) - rY_selected(l1)) _
* (x - rX_selected(l1)) _
/ (rY_selected(l2) - rX_selected(l1))
End Function
Does anyone know a routine on how to get a data set composed by 7 columns into all possible combinations?
the combination is composed by 7 numbers like this--> 1|3|8|10|35|40|50
The routine needs to look into the first table and make a list of all possible combination excluding the duplicate numbers from the combination in the second table. Please see picture.
The table on the left contains the combination which need to be reshuffled, into the right table which contain all possible combinations.
I would do something like:
The number of options are 6^7 so there will be alot of cases: 279936
To get all of it, you should loop through them.
First we should find all the options.
To generate all the possible combinations including duplicates, the probles is the same as get all the may 7 digit long numbers in base 6 ( as we have 6 number in each column)
in newer excels you can use the BASE funtion, but if you can not access it you can use this:
if you cange a code a bit you can call the value of the original table instead of the 0-5 numbers.
Then just remove duplicates.
Sub generateAllBase6()
Dim i As Double 'number tries
Dim n As String ' the number of item from the column 1-7
For i = 0 To 279936 - 1
n = ConvertBase10(i, "012345")
For k = 1 To 7
If Len(n) < k Then
Cells(i + 2, k) = 0
Else
Cells(i + 2, k) = Right(Left(n, k), 1)
End If
Next k
Next i
End Sub
Public Function ConvertBase10(ByVal d As Double, ByVal sNewBaseDigits As String) As String
Dim S As String, tmp As Double, i As Integer, lastI As Integer
Dim BaseSize As Integer
BaseSize = Len(sNewBaseDigits)
Do While Val(d) <> 0
tmp = d
i = 0
Do While tmp >= BaseSize
i = i + 1
tmp = tmp / BaseSize
Loop
If i <> lastI - 1 And lastI <> 0 Then S = S & String(lastI - i - 1, Left(sNewBaseDigits, 1)) 'get the zero digits inside the number
tmp = Int(tmp) 'truncate decimals
S = S + Mid(sNewBaseDigits, tmp + 1, 1)
d = d - tmp * (BaseSize ^ i)
lastI = i
Loop
S = S & String(i, Left(sNewBaseDigits, 1)) 'get the zero digits at the end of the number
ConvertBase10 = S
End Function
I found the funcion here: http://www.freevbcode.com/ShowCode.asp?ID=6604
I am looking for a way to repeat a set of cells horizontally a certain number of times before moving on to the next set of cells. For example:
If I have this in 3 columns:
5 4 3
0 1 2
and I have 3 columns which dictate how many times I want the values iterated:
4 2 3
This function should give me this when dragged over a range:
5 5 5 5 4 4 3 3 3
0 0 0 0 1 1 2 2 2
Does anyone know the best manner to do this?
I have been using some convoluted reasoning to get through this with an array formula ( has the "{}" brackets around it and you have to use Shift+Enter). I am using SUMIF and COUNTIF functions to do some things, but it never really works out.
Here's a hacky VBA solution. This assumes the "repeat counts" are on the first row (4, 2, 3) and the "values to repeat" are on the second row (5, 4, 3, 0, 1, 2).
Sub outputRepeatedValues()
Dim xStart As Integer, yStart As Integer
Dim i As Integer, j As Integer
Dim valueToRepeat As Integer, numTimesRepeat As Integer
Dim xOffset As Integer, yOffset As Integer
xStart = ActiveCell.Column
yStart = ActiveCell.Row
i = 1
j = 1
xOffset = 0
yOffset = 0
While Cells(2, j) <> ""
If Cells(1, i) = "" Then
i = 1
yOffset = yOffset + 1
xOffset = 0
End If
numTimesRepeat = Cells(1, i)
valueToRepeat = Cells(2, j)
For k = 1 To numTimesRepeat
Cells(yStart + yOffset, xStart + xOffset) = valueToRepeat
xOffset = xOffset + 1
Next k
j = j + 1
i = i + 1
Wend
End Sub
Stick this in a new module. To use this code, select the cell representing the upper left corner of the output region. Then press Alt+F8 to bring up the Macro box, and then you can run the macro.
I want to calculate a moving average of the last, say 20, numbers of a column. A problem is that some of the cells of the column may be empty, they should be ignored. Example:
A
175
154
188
145
155
167
201
A moving average of the last three would be (155+167+201)/3. I've tried to implement this using average, offset, index, but I simply don't know how. I'm a little bit familiar with macros, so such a solution would work fine: =MovingAverage(A1;3)
Thanks for any tips or solutions!
{=SUM(($A$1:A9)*(ROW($A$1:A9)>LARGE((ROW($A$1:A9))*(NOT(ISBLANK($A$1:A9))),3+1)))/3}
Enter this with control+shift+enter to make it an array formula. This will find the latest three values. If you want more or less, change the two instances of '3' in the formula to whatever you want.
LARGE((ROW($A$1:A9))*(NOT(ISBLANK($A$1:A9))),3+1)
This part returns the 4th highest row number of all the cells that have a value, or 5 in your example because rows 6, 8, and 9 are the 1st through 3rd highest rows with a value.
(ROW($A$1:A9)>LARGE((ROW($A$1:A9))*(NOT(ISBLANK($A$1:A9))),3+1))
This part returns 9 TRUEs or FALSEs based on whether the row number is larger than the 4th largest.
($A$1:A9)*(ROW($A$1:A9)>LARGE((ROW($A$1:A9))*(NOT(ISBLANK($A$1:A9))),3+1))
This multiplies the values in A1:A9 by those 9 TRUEs or FALSEs. TRUEs are converted to 1 and FALSEs to zero. This leaves a SUM function like this
=SUM({0;0;0;0;0;155;0;167;201})/3
Because all the values above 155 don't satisfy the row number criterion, the get multiplied by zero.
If you are going to use a UDF it will only recalculate correctly when you change the data if the parameters include all the range of data you want to handle.
Here is a moving average UDF that handles entire columns and contains some error handling. You can call it using by entering the formula =MovingAverage(A:A,3) into a cell.
Function MovingAverage(theRange As Range, LastN As Long) As Variant
Dim vArr As Variant
Dim j As Long
Dim nFound As Long
Dim dSum As Double
On Error GoTo Fail
MovingAverage = CVErr(xlErrNA)
'
' handle entire column reference
'
vArr = Intersect(Application.Caller.Parent.UsedRange, theRange).Value2
If IsArray(vArr) And LastN > 0 Then
For j = UBound(vArr) To 1 Step -1
' skip empty/uncalculated
If Not IsEmpty(vArr(j, 1)) Then
' look for valid numbers
If IsNumeric(vArr(j, 1)) Then
If Len(Trim(CStr(vArr(j, 1)))) > 0 Then
nFound = nFound + 1
If nFound <= LastN Then
dSum = dSum + CDbl(vArr(j, 1))
Else
Exit For
End If
End If
End If
End If
Next j
If nFound >= LastN Then MovingAverage = dSum / LastN
End If
Exit Function
Fail:
MovingAverage = CVErr(xlErrNA)
End Function
Just a quick solution:
Supposing your numbers are on the cells A2:A10, put in B10 the following formula:
=IF(COUNT(A8:A10)=3,AVERAGE(A8:A10),IF(COUNT(A7:A10)=3,AVERAGE(A7:A10),"too many blanks"))
Dragging up the formula you get the moving average
If there is the possibility of two consecutive blank you could nest another if, more than that and this solution became too complicated
I have written a short script in VBA. Hopefull it does what you want. Here you are:
Function MovingAverage(ByVal r As String, ByVal i As Integer) As Double
Dim rng As Range, counter As Long, j As Integer, tmp As Double
Set rng = Range(r)
counter = 360
j = 0
tmp = 0
While j < i + 1 And counter > 0
If Len(rng.Offset(j, 0)) > 0 Then
tmp = tmp + rng.Offset(j, 0).Value
End If
j = j + 1
counter = counter - 1
Wend
MovingAverage = CDbl(tmp / i)
End Function
1) I have set limit to 360 cells. It means that the script will not look for more than 360 cells. If you want to change it then change the initial value of counter.
2) The script returns not rounded average. Change the last row to
MovingAverage = Round(CDbl(tmp / i),2)
3) The use is just like you wanted, so just type =MovingAverage("a1";3) into the cell.
Any comments are welcome.