Excel Conditional Linear Interpolation - excel

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

Related

Excel/VBA - How to check the top three and if equal then check based on custom rank?

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

Subtracting Variants

I am having trouble getting Variants to subtract. I am pulling data from a spreadsheet and if one cell states a phrase then I need the code to subtract one cell from another. If the cell does not state a phrase then I need it to copy one cell to another. I can get the code to run but nothing happens.
Private Sub CommandButton1_Click()
Dim x As Variant, y As Variant, z As Variant, a As Integer, B As String
'getting values for data
x = Range("D2:D48").Value
y = Range("I2:I48").Value
z = Range("E2:E48").Value
B = "Total ISU Days: "
'The the cells are empty then subtract. This is not what I wanted to do but I can't think of extracting strings from variants.
If IsEmpty(Range("D2:D48").Value) = True Then
a = y - z
End If
Range("N2:N48").Value = a
Range("M2:M48").Value = B
End Sub
x = Range("D2:D48").Value
y = Range("I2:I48").Value
z = Range("E2:E48").Value
A Variant contains metadata about its subtype. In this case, x, y, and z are all arrays of variants.
a = y - z
The right-hand side of this expression simply cannot be evaluated, because {array1} - {array2} means nothing: operators (arithmetic or logical) work off values, not array of values.
What is a supposed to be? It's declared As Integer, so its value is capped at 32,767 (should probably be a Long). If you mean to add up all the values in y and subtract that total from the sum of all values in z, then you need to be more explicit about how you do that - you could use Application[.WorksheetFunction].Sum to add things up:
sumOfY = Application.Sum(Range("I2:I48"))
sumOfZ = Application.Sum(Range("E2:E48"))
a = sumOfY - sumOfZ
And then...
Range("N2:N48").Value = a
That will put the value of a in every single cell in the N2:N48 range - is that really what you mean to do?
Or maybe you meant to do this instead?
Range("N2:N48").Formula = "=IF(D2="""",I2-E2,0)"
That would make each cell in N2:N48 calculate the difference between I and E for each row where D is empty... and there's not really any need for any VBA code to do this.
Let's simplify a bit the task and say that the idea is to substract the values in Range("C1:C6") from the corresponding values in the left - Range("B1:B6"). Then write the corresponding results in column E:
Of course, this would be done only in case that all values in column A are empty. This is one way to do it:
Sub TestMe()
Dim checkNotEmpty As Boolean: checkNotEmpty = False
Dim substractFrom As Range: Set substractFrom = Worksheets(1).Range("B1:B6")
Dim substractTo As Range: Set substractTo = Worksheets(1).Range("C1:C6")
Dim MyCell As Range
Dim result() As Variant
ReDim result(substractFrom.Cells.Count - 1)
Dim areCellsEmpty As Boolean
For Each MyCell In substractFrom
If Len(MyCell) > 0 Then checkNotEmpty = True
Next
Dim i As Long
For i = LBound(result) + 1 To UBound(result) + 1
result(i - 1) = substractFrom.Cells(i) - substractTo.Cells(i)
Next
Worksheets(1).Range("E1").Resize(UBound(result) + 1) = Application.Transpose(result)
End Sub
The code could be improved further, saving all ranges to an Array, but it works quite ok so far.
The part with the +1 and -1 in the For-loop is needed as a workaround:
For i = LBound(result) + 1 To UBound(result) + 1
result(i - 1) = substractFrom.Cells(i) - substractTo.Cells(i)
Next
because the arrays start from index 0, but the Cells in a range start with row 1.
Worksheets(1).Range("E1").Resize(UBound(result) + 1) = Application.Transpose(result) is needed, to write the values of the result array to the column E, without defining the length of the range in E.

Excel find 17 Cells with highest value, 5 of which are the highest in a specific row

I'm struggling with a complex excel problem, and I would be amazed by any solution.
I have a table with 4 columns and the following values
The highest |13|12|12|12|
The two highest|11|12|11|11|
The two highest|12|12|12|12|
|12|11|11|11|
|12|11|11|11|
|12|11|11|11|
My problem requires from the first three rows to select the highest respectively the two highest values. Over the complete matrix there should be a sum of 12 values.
The required 5 plus whatever are the remaining 7 highest values. My current approach is to do a sum of the required rows and add the rest together, but that is obviouly not working.
|13|12|12|12|[MAX(B10:E10)]13|
|11|12|11|11|[LARGE(B11:E11;1)+LARGE(B11:E11;2)23|
|12|12|12|12|[LARGE(B12:E12;1)+LARGE(B12:E12;2)24|
|12|11|11|11|
|12|11|11|11|
|12|11|11|11|
Any ideas or suggestions are highly appreciated. Also a more understandable title for references would be great. Thanks!
Explanation:
It's sloppy VBA, but this works and the structure is generally expandable if you need it to be. You can just paste this in a VBA module, run Sum57(), and the result will be in the debug window (Ctl + G). To modify this for other array sizes, change the following :
the size of the used array in line 1
the values of arrR and arrC in lines 5 and 6 which define the start of the array
the pattern of the function calls in the body of Sum57()
The base pattern is:
For i = 1 To N
x = x + LargeOfRange([rStart], [rEnd], [cStart], [cEnd])
Next
where N is top N largest numbers from the range.
VBA:
Public used(5, 3) As Boolean
Public arrR, arrC As Integer
Public Sub Sum57()
arrR = 10
arrC = 2
For a = LBound(used, 1) To UBound(used, 1)
For b = LBound(used, 2) To UBound(used, 2)
used(a, b) = False
Next
Next
Dim x As Integer
x = x + LargeOfRange(10, 10, 2, 5)
For i = 1 To 2
x = x + LargeOfRange(11, 11, 2, 5)
Next
For i = 1 To 2
x = x + LargeOfRange(12, 12, 2, 5)
Next
For i = 1 To 7
x = x + LargeOfRange(10, 15, 2, 5)
Next
Debug.Print x
End Sub
Public Function LargeOfRange(rStart As Integer, rEnd As Integer, _
cStart As Integer, cEnd As Integer) As Integer
On Error GoTo SkipVal
Dim l, x, xR, xC As Integer
x = 0
For r = rStart To rEnd
For c = cStart To cEnd
If x < Cells(r, c).Value And used(r - arrR, c - arrC) = False Then
xR = r
xC = c
x = Cells(r, c).Value
End If
Next
Next
used(xR - arrR, xC - arrC) = True
LargeOfRange = x
Exit Function
SkipVal:
LargeOfRange = 0
End Function
Why not just extend the range and add more elements to the Large() calc?
=LARGE(B13:E15,1)+LARGE(B13:E15,2)+LARGE(B13:E15,3)+LARGE(B13:E15,4)+
LARGE(B13:E15,5)+LARGE(B13:E15,6)+LARGE(B13:E15,7)
This returns 80

Searching a table both horizontally and vertically and printing the values

sorry for the ambiguous title. I'm not quite sure how to name what I'm trying to do.
I have data in a worksheet that looks like this:
I would like to search for the smallest number in the range and write out the name on the y axis and the number. It then ignores this number and searches for the smallest number on the x axis as well. In that same row, it searches for the smallest value horizontally, excludes the number and then looks vertically as well. It continues this way until all possibilities are exhausted. Is this possible with Excel?
A sample output will be:
y5 : 40
x3: 60
y3: 90
x4: 80
y2 : 85
x3: 75
y1 : 70
and so on.
Interesting problem. You should be able to modify the following. To run it you need to include a reference to Microsoft Scripting Runtime (Under Tools/References in the VBA editor) since it uses a dictionary data structure -- the natural choice to keep track of already picked numbers:
'The following code assumes than Nums is a 1-based 2-dimensional array
Function MinPath(Nums As Variant) As Variant
Dim counter As Long
Dim mins As Variant
Dim PickedNums As New Dictionary
Dim i As Long, j As Long, m As Long, n As Long
Dim report As String
Dim direction As String
Dim num As Variant
Dim min As Variant, min_i As Long, min_j As Long
m = UBound(Nums, 1)
n = UBound(Nums, 2)
ReDim mins(1 To m * n)
min = Nums(1, 1)
min_i = 1
min_j = 1
For i = 1 To m
For j = 1 To n
If Nums(i, j) < min Then
min = Nums(i, j)
min_i = i
min_j = j
End If
Next j
Next i
PickedNums.Add min, 0
counter = 1
mins(counter) = Array(min_i, min_j, min)
direction = "vertical"
min = Empty
Do While True
If direction = "vertical" Then
For i = 1 To m
num = Nums(i, min_j)
If Not PickedNums.Exists(num) Then
If IsEmpty(min) Then
min = num
min_i = i
ElseIf num < min Then
min = num
min_i = i
End If
End If
Next i
If IsEmpty(min) Then
ReDim Preserve mins(1 To counter)
MinPath = mins
Exit Function
Else
PickedNums.Add min, 0
counter = counter + 1
mins(counter) = Array(min_i, min_j, min)
direction = "horizontal"
End If
Else
'direction = horizontal case
For j = 1 To n
num = Nums(min_i, j)
If Not PickedNums.Exists(num) Then
If IsEmpty(min) Then
min = num
min_j = j
ElseIf num < min Then
min = num
min_j = j
End If
End If
Next j
If IsEmpty(min) Then
ReDim Preserve mins(1 To counter)
MinPath = mins
Exit Function
Else
PickedNums.Add min, 0
counter = counter + 1
mins(counter) = Array(min_i, min_j, min)
direction = "vertical"
End If
End If
min = Empty
Loop
End Function
The function repeatedly searches a either a row or a column (depending on the search direction) to find the smallest non-picked number. At the beginning of each pass the variable min is set to Empty until a non-picked number is encountered. If after a pass min is still Empty the function returns. This function returns an array of arrays where each array is of the form Array(i,j,min) (e.g. the values (5,3,40) in the first step). VBA's Array function returns a 0-based array so the i (row) coordinate is at index 0 and the j coordinate is at index 1. What you do with this data is up to you. For example:
Sub test()
Dim i As Long, n As Long
Dim mins As Variant
mins = MinPath(Range("B2:F6").Value)
n = UBound(mins)
For i = 1 To n
If i Mod 2 = 1 Then 'odd step
Range("A7").Offset(i).Value = "y" & mins(i)(0) & ":"
Else 'even step
Range("A7").Offset(i).Value = "x" & mins(i)(1) & ":"
End If
Range("B7").Offset(i).Value = mins(i)(2)
Next i
End Sub
Which results in:

calculate median excel having conditions

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

Resources