I have defined, inside an Excel workbook, a function that calculates a physical property taking as an input a Temperature and a Pressure.
It operates interpolating discrete data from a T/P matrix on a dedicated spreadsheet. The matrix can be modified as needed.
In case one of the given input data falls outside the data matrix range, the function is forced to calculate using the boundary. For example: If discrete Temperatures data in the matrix are from 40°C to 90°C, in the case that a temperature below 40°C is given to the function as input it will proceed to calculate using 40°C.
What I would like to do is to give a warning inside the cell containing the function call. In order to warn the user that the result is extrapolated and may not be accurate. Something similar to the warnings that excel gives to you, inside the cells, when a number is formatted as text or a formula inside a cell is different from the formulas in cells of the same column or row.
Is it possible to do it?
For sake of clarity, I add the code I wrote:
Function Z(T As Double, P As Double)
Dim i As Integer, j As Integer, r As Integer, c As Integer
Dim T1 As Double, T2 As Double, P1 As Double, P2 As Double, Z1 As Double, Z2 As Double
Dim ckp As Boolean, ckt As Boolean
Dim wks As Worksheet
Set wks = ThisWorkbook.Sheets("Z")
i = 2
j = 2
ckp = False
cht = False
'check if T < T min; if yes, set T = T min
If T < wks.Cells(1, 2) Then
i = 3
T = wks.Cells(1, 2).Value
ckt = True
End If
'check if P < P min; if yes, set P = P min
If P < wks.Cells(2, 1) Then
j = 3
P = wks.Cells(2, 1).Value
ckp = True
End If
'if temperature is not below the minimum, find the column containing the T_
immediately above the given one.
If ckt = False Then
Do
i = i + 1
'Check if T is > T max; if a blank cell is found during seeking, T_
is set at maximum.
If wks.Cells(1, i) = "" Then
T = wks.Cells(1, i - 1)
i = i - 1
ckt = True
End If
Loop While wks.Cells(1, i) < T And ckt = False
End If
'if pressure is not below the minimum, find the row containing the P_
immediately above the given one.
If ckp = False Then
Do
j = j + 1
'Check if P is > P max; if a blank cell is found during seeking, P_
is set at maximum.
If wks.Cells(j, 1) = "" Then
P = wks.Cells(j - 1, 1)
j = j - 1
ckp = True
End If
Loop While wks.Cells(j, 1) < P And ckp = False
End If
'Calculate the function by sequentially using Line Passing Through Two_
Points (RDP)user defined function.
'T1 is the temperature immediately below the given one and T2 is the_
temperature immediately above the given one
'T will be between T1 and T2
'P1 is the pressure immediately below the given one and P2 is the pressure_
immediately above the given one
'P will be between P1 and P2
T1 = wks.Cells(1, i - 1)
T2 = wks.Cells(1, i)
P1 = wks.Cells(j - 1, 1)
P2 = wks.Cells(j, 1)
'Calculate function at T1 and P
Z1 = RDP(P1, P2, wks.Cells(j - 1, i - 1), wks.Cells(j, i - 1), P)
'Calculate function at T2 and P
Z2 = RDP(P1, P2, wks.Cells(j - 1, i), wks.Cells(j, i), P)
'Calculate function at T and P
Z = RDP(T1, T2, Z1, Z2, T)
End Function
I also add a snapshot of the "Z" spreadsheet. Data are calculated by means of a process simulation software. In this way I can use the same spreadsheet in calculations where T and P vary between different ranges and steps, being the function still able to operate. All I need is the function to tell me: "I gave you a result but consider that you gave me an out of range parameter, so pay attention", without stopping processing data (it is used in hundreds of cells, together with other similar functions calculating other parameters)
In the "Z" spreadsheet snapshot, T are in the first row and P in the first column. In "J2" cell you can see the result inputing a Temperature of 100°C and a pressure of 42.5 bar (average between 40 and 45). The result is an average between the Z "90°C and 40 bar" and the Z "90°C and 45 bar".
I hope the issue to be more clear, now.
I found a solution to the posted problem and I share it, hoping that it could be useful. Even if it does not involve warning messages, the user is warned that something is not optimal and the given result could be not accurate. I proceeded in this way:
1) I modified the function to give a variant (1,2) array as a result. The first data is the actual function output, while the second one is a boolean value which can be "TRUE" if the calculation has been forced (not accurate) or "FALSE" otherwise.
2) I asked excel to evaluate "INDEX(Z(cellA,cellB),2)" to conditionally format the cell where the function is used, using the same arguments of the function inside the cell.
In this way, every time the function has been forced to give an output the cell results to be formatted in a different way (for example, with bold and red character).
Here below the modified Function:
Function Z(T As Double, P As Double)
Dim i As Integer, j As Integer, r As Integer, c As Integer
Dim T1 As Double, T2 As Double, P1 As Double, P2 As Double, Z1 As Double, _
Z2 As Double
Dim ckp As Boolean, ckt As Boolean
Dim wks As Worksheet
Dim returnval(2)
Set wks = ThisWorkbook.Sheets("Z")
i = 2
j = 2
ckp = False
ckt = False
'check if T < T min; if yes, set T = T min
If T < wks.Cells(1, 2) Then
i = 3
T = wks.Cells(1, 2).Value
ckt = True
End If
'check if P < P min; if yes, set P = P min
If P < wks.Cells(2, 1) Then
j = 3
P = wks.Cells(2, 1).Value
ckp = True
End If
'if temperature is not below the minimum, find the column containing the T_
immediately above the given one.
If ckt = False Then
Do
i = i + 1
'Check if T is > T max; if a blank cell is found during seeking, T _
is set at maximum.
If wks.Cells(1, i) = "" Then
T = wks.Cells(1, i - 1)
i = i - 1
ckt = True
End If
Loop While wks.Cells(1, i) < T And ckt = False
End If
'if pressure is not below the minimum, find the row containing the P_
immediately above the given one.
If ckp = False Then
Do
j = j + 1
'Check if P is > P max; if a blank cell is found during seeking, P_
is set at maximum.
If wks.Cells(j, 1) = "" Then
P = wks.Cells(j - 1, 1)
j = j - 1
ckp = True
End If
Loop While wks.Cells(j, 1) < P And ckp = False
End If
'Calculate the function by sequentially using Line Passing Through Two_
Points (RDP)user defined function.
'T1 is the temperature immediately below the given one and T2 is the_
temperature immediately above the given one
'T will be between T1 and T2
'P1 is the pressure immediately below the given one and P2 is the_
pressure immediately above the given one
'P will be between P1 and P2
T1 = wks.Cells(1, i - 1)
T2 = wks.Cells(1, i)
P1 = wks.Cells(j - 1, 1)
P2 = wks.Cells(j, 1)
'Calculate function at T1 and P
Z1 = RDP(P1, P2, wks.Cells(j - 1, i - 1), wks.Cells(j, i - 1), P)
'Calculate function at T2 and P
Z2 = RDP(P1, P2, wks.Cells(j - 1, i), wks.Cells(j, i), P)
'Calculate function at T and P
returnval(0) = RDP(T1, T2, Z1, Z2, T)
If ckt = True Or ckp = True Then
returnval(1) = True
Else
returnval(1) = False
End If
Z = returnval
End Function
Related
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
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
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:
I'm trying to figure the cleanest way of showing, as an example, an initial value say 300 as x and a critical path say 1.5 as y. Both of these values can change, via input on the sheet.
Together with these values we have a resource of a and b. In this scenario a will fill 200 cells within a row and b will fill 100.
As I alluded to before, x and y can change, say, if y is 2, a fills 150 and b fills 150. And if y is 1 then only a fills 300.
Currently i'm using If statements, but I feel this is messy and could potentially lead to endless code in order to cover every possible outcome and I'm in need of a better solution.
Here is a simplistic example of what I'm currently achieving:
Private Sub Example()
Dim ActiveWB As Worksheet
Set ActiveWB = ActiveWorkbook.Sheets("Sheet1")
Dim cell As Range
Dim a, b, x, y As Double
x = ActiveWB.Range("A1").Value
y = ActiveWB.Range("A2").Value
a = x / y
b = x - a
For Each cell In ActiveWB.Range(Cells(3, 1), Cells(3, a))
If (cell.Column >= 0) And (cell.Column <> x) Then
If (y = 1) And (a > 0) Then
cell.Value = "a"
a = a - 1
ElseIf (y > 1) And (y < 2) And (a > 0) Then
cell.Value = "a"
a = a - 1
If (b > 0) Then
cell.Offset(1, 0).Value = "b"
b = b - 1
End If
ElseIf (y >= 2) And (y < 2.5) And (a > 0) Then
cell.Value = "a"
a = a - 1
If (b > 0) Then
cell.Offset(1, 0).Value = "b"
b = b - 1
End If
'..... and so on......
End If
End If
Next cell
End Sub
Any suggestions would be much appreciated. Thank you for your time. Y.
First of all declaring types should be done for each variable separately:
Dim a, b, x, y As Double
Becomes:
Dim a As Double, b As Double, x As Double, y As Double
Or (this is what I prefer):
Dim a As Double
Dim b As Double
Dim x As Double
Dim y As Double
Second, if your a and b are only used for determining the range width, then they are preferably not of a floating point type. In stead use Integer (under 2^15) or Long:
Dim a As Integer
Dim b As Integer
Dim x As Double
Dim y As Double
Then your value assignment to a and b cannot stay the way they are now, but should read something like:
a = Int(x / y)
b = Int(x - a)
Then I hope your x and y values are restricted to values > 0 and x > y in your sheet. If not then first test for that in your script...
Now last (and your original question), you can assign a value to a complete range of cells if you like:
If a > 0 Then Range(Cells(3, 1), Cells(3, a)).Value = "a"
If b > 0 Then Range(Cells(4, 1), Cells(4, b)).Value = "b"
I dont understand why you take of 1 from a and b, so if that really add something, please elaborate a bit more on the general logic...
I probably don't completely understand the complexity, but based upon what you shared you can replace your for loop with the following to achieve the same result:
ActiveWB.Range(Cells(3, 1), Cells(3, a)).Value = "a"
If b > 0 then
ActiveWB.Range(Cells(4, 1), Cells(4, b)).Value = "b"
End If
I want to simulate a binomial distribution where the price of something can only go up or down with the probability of p that is 50% this time.
My variables:
S=100 (the basic value)
u=1,1 (how much the value goes up in each experiment if it goes up)
d=1/u (how much the value goes down in each experiment if it goes down)
p=0.5 (probability)
n=400 (number of experiments)
I did not declare these variables, because I want to read these values from specific cells.
My code (for the first step):
Sub BINOM()
S = Range("L4").Value
u = Range("M4").Value
d = Range("N4").Value
p = Range("O4").Value
n = Range("P4").Value
v = Rnd()
If v > p Then
Range("B2").Value = S * u
Else
Range("B2").Value = S * d
End If
End Sub
The result of the second experiment (that should be written in the B3 cell) has to be calculated from the result of the first experiment and so on but not with using the same random number.
I'll try my best but I just removed Excel in favor of Calc which doesn't support the same type of language as far as I can tell.
Sub BINOM()
Dim intCounter, v
S = Range("L4").Value
u = Range("M4").Value
d = Range("N4").Value
p = Range("O4").Value
n = Range("P4").Value
Range("B1").Value = s
For intCounter = 2 to n
'//If this creates an error then just remove it.
'//It should keep the same random number from appearing over and over.
Randomize
'//Create new Random number in v
v = Rnd()
If v > p Then
Range("B" & intCounter).Value = Range("B" & (intCounter - 1)).Value * u
Else
Range("B" & intCounter).Value = Range("B" & (intCounter - 1)).Value * d
End If
Next intCounter
End Sub
Let me know if that works out for you or if any errors appear. Updated to base each cell from data given of previous cell.