0% found this document useful (0 votes)
187 views6 pages

XL Interpolators

Download as txt, pdf, or txt
Download as txt, pdf, or txt
Download as txt, pdf, or txt
You are on page 1/ 6

Attribute VB_Name = "xlInterpolators"

Option Explicit
' Linear and bilinear inter/extrapolators
' shg 1997-2012
'
'
'

Function
Use
---------- -------LInterp
UDF only
of ranges)
' dLInterp
VBA only

Purpose
------UDF wrapper for dLinterp (linear inter/extrapolation

' BiLInterp UDF only


tion of ranges)
' dBiLInterp VBA only

UDF wrapper for dBiLInterp (bilinear inter/extrapola

Linear inter/extrapolator

VBA bilinear inter/extrapolator

'

bInterp
VBA only
Interpolates the non-numeric values of a 1D array in
situ
' InterpArr UDF or VBA UDF wrapper for bInterp
' Sub Interpolate
Interpolates the selected range in situ
' Internal functions
' Frac
VBA only
' NumDim
VBA only

Computes an interpolation fraction


Returns the number of dimensions of an array

Private Function Frac(d As Double, _


avd As Variant, _
ByRef i As Long, _
ByRef dF As Double, _
iMatchType As Long)
' shg 1997-0606, 2009-0419
'
2009-0604 added option for descending sort
' Returns an index to avd in i and an interpolation fraction in dF
' avd must be a 2+ element vector sorted {a|de}scending if iMatchType={1|-1}
If iMatchType = 1 And d <= avd(1) Or _
iMatchType = -1 And d >= avd(1) Then
i = 1
Else
' this can generate an error, handled by caller
i = WorksheetFunction.Match(d, avd, iMatchType)
If i = UBound(avd) Then i = UBound(avd) - 1
End If
dF = (d - avd(i)) / (avd(i + 1) - avd(i))
End Function
Function LInterp(x As Double, rX As Range, rY As Range) As Variant
' UDF wrapper for dLInterp
' shg 1997-0606, 2009-0419
'
2009-0604 added option for descending sort
' rX and rY must be equal-length vectors
' rX must be sorted (ascending or descending, doesn't matter)
Dim avdX
Dim avdY

As Variant
As Variant

On Error GoTo Oops


With WorksheetFunction
If rX.Areas.Count > 1 Then Err.Raise xlErrValue
If rX.Rows.Count <> 1 And rX.Columns.Count <> 1 Then Err.Raise xlErrValu
e
If .Count(rX) <> rX.Cells.Count Then Err.Raise xlErrValue
If rY.Areas.Count > 1 Then Err.Raise xlErrValue
If rY.Rows.Count <> 1 And rY.Columns.Count <> 1 Then Err.Raise xlErrValu
e
If .Count(rY) <> rY.Cells.Count Then Err.Raise xlErrValue
If rX.Cells.Count < 2 Then Err.Raise xlErrValue
If rX.Cells.Count <> rY.Cells.Count Then Err.Raise xlErrValue
If rX.Rows.Count > 1 Then avdX = .Transpose(rX.Value2) Else avdX = .Tran
spose(.Transpose(rX.Value2))
If rY.Rows.Count > 1 Then avdY = .Transpose(rY.Value2) Else avdY = .Tran
spose(.Transpose(rY.Value2))
LInterp = dLInterp(x, avdX, avdY)
End With
Exit Function
Oops:
LInterp = CVErr(Err.Number)
End Function
Function dLInterp(x As Double, avdX As Variant, avdY As Variant) As Double
' Linear interpolator / extrapolator
' {Inter|extra}polates avdX to return the value of avdY corresponding to the
given x
Dim i
Dim dF

As Long
As Double

' index to rY
' interpolation fraction

Frac x, avdX, i, dF, Sgn(avdX(UBound(avdX)) - avdX(1))


dLInterp = avdY(i) * (1 - dF) + avdY(i + 1) * dF
End Function
Public Function BiLInterp(x As Double, _
y As Double, _
rTbl As Range) As Variant
' shg 2007
' UDF wrapper for BiLInterp
Dim rX
Dim rY

As Range
As Range

Dim avdX
Dim avdY
Dim avdZ

As Variant
As Variant
As Variant

' top row of rTbl


' left column of rTbl

On Error GoTo Oops


If rTbl.Areas.Count > 1 Then Err.Raise xlErrValue
If rTbl.Rows.Count < 3 Or rTbl.Columns.Count < 3 Then Err.Raise xlErrValue
With WorksheetFunction
' If .Count(rTbl) - .Count(rTbl(1)) <> rTbl.Cells.Count - 1 Then Err.Rai

se xlErrValue
Set rX = Range(rTbl(1, 2), rTbl(1, rTbl.Columns.Count))
Set rY = Range(rTbl(2, 1), rTbl(rTbl.Rows.Count, 1))
avdX = .Transpose(.Transpose(rX.Value))
avdY = .Transpose(rY.Value)
avdZ = Range(rTbl(2, 2), rTbl(rTbl.Rows.Count, rTbl.Columns.Count)).Valu
e2
BiLInterp = dBiLInterp(x, y, avdX, avdY, avdZ)
End With
Exit Function
Oops:
BiLInterp = CVErr(Err.Number)
End Function
Function dBiLInterp(x As Double, y As Double, _
avdX As Variant, avdY As Variant, _
avdZ As Variant)
' shg 1997-0606, 2007-0307, 2009-0419
'
2009-0615 added support for ascending or descending sort
'
2009-1021 changed implementation of weighted sum
'
2011-1228 fix a bug in computing iMatchType!
' Returns the bilinear interpolation of rTbl with
' o x interpolated across the top row (rX)
' o y interpolated down the left column (rY)
' x and y must be within the upper and lower limits of rX and rY
' rTbl must be sorted
' o
left to right by the top row
' o
top to bottom by the left column
' The sort orders can be ascending or descending,
' each independent of the other.
' All values in rTbl must be numeric (other than the UL corner,
' which is ignored).
Dim
Dim
Dim
Dim

iRow
iCol
dRF
dCF

As
As
As
As

Long
Long
Double
Double

' row fraction


' column fraction

Frac x, avdX, iCol, dCF, Sgn(avdX(UBound(avdX)) - avdX(1))


Frac y, avdY, iRow, dRF, Sgn(avdY(UBound(avdY)) - avdY(1))
' weighted sum of four
dBiLInterp = avdZ(iRow
avdZ(iRow
avdZ(iRow
avdZ(iRow
End Function

corners
+ 0, iCol
+ 0, iCol
+ 1, iCol
+ 1, iCol

+
+
+
+

0)
1)
0)
1)

*
*
*
*

(1# - dRF)
(1# - dRF)
(dRF - 0#)
(dRF - 0#)

*
*
*
*

(1# - dCF) + _
(dCF - 0#) + _
(1# - dCF) + _
(dCF - 0#)

'===============================================================================
Function bLInterp(avInp As Variant) As Boolean
' shg 2010, 2012
' VBA only

'
'
'
'

Interpolates between the numeric values in 1D array avInp in situ


Non-numeric values (Empty, text, logical, or errors)
are replaced by linear interpolation or extrapolation of the adjacent
numeric values.

' Returns False if avInp is not 1D, or contains fewer than two numbers.
' UDF or VBA
' For example =InterpArr({"", "", 1, "", 3, "", "", 9, ""})
'
returns
{-1, 0, 1, 2, 3, 5, 7, 9, 11}
' Requires NumDim
Dim
Dim
Dim
Dim
Dim
Dim

iLB
iUB
iInp
nNum
aiNum()
iNum

Dim dF

As
As
As
As
As
As

Long
Long
Long
Long
Long
Long

As Double

'
'
'
'
'
'

lower bound of avInp


upper bound of avInp
index to avInp
count of numbers in avInp
stores the indices of numeric values in avInp
index to aiNum

' interpolating fraction

If NumDim(avInp) > 1 Then Exit Function


With WorksheetFunction
nNum = .Count(avInp)
If nNum < 2 Then Exit Function
ReDim aiNum(1 To nNum)
iLB = LBound(avInp)
iUB = UBound(avInp)
For iInp = iLB To iUB
If .IsNumber(avInp(iInp)) Then
iNum = iNum + 1
aiNum(iNum) = iInp
End If
Next iInp
End With
iNum = 1
For iInp = iLB To iUB
If iInp > aiNum(iNum + 1) Then
If iNum < nNum - 1 Then iNum = iNum + 1
End If
dF = (iInp - aiNum(iNum)) / (aiNum(iNum + 1) - aiNum(iNum))
avInp(iInp) = (1 - dF) * avInp(aiNum(iNum)) + dF * avInp(aiNum(iNum + 1)
)
Next iInp
bLInterp = True
End Function
Function InterpArray(avInp As Variant) As Variant
' shg 2010, 2012
' UDF wrapper for bInterp
Dim av

As Variant

Dim r

As Range

Set r = Application.Caller
If r.Areas.Count > 1 Or (r.Rows.Count > 1 And r.Columns.Count > 1) Then
InterpArray = CVErr(xlErrValue)
Else
With WorksheetFunction
av = avInp
If NumDim(av) > 1 Then av = .Transpose(av)
If NumDim(av) > 1 Then av = .Transpose(av)
If NumDim(av) > 1 Then
InterpArray = CVErr(xlErrValue)
Else
bLInterp av
If r.Rows.Count = 1 Then
InterpArray = av
Else
InterpArray = .Transpose(av)
End If
End If
End With
End If
End Function
Sub Interpolate()
' shg 2012
' Interpolates the selected range in situ
' Requires bLInterp
Dim r
As Range
Dim av
As Variant
If TypeOf Selection Is Range Then
Set r = Selection
If r.Areas.Count > 1 Or (r.Rows.Count > 1 And r.Columns.Count > 1) Then
MsgBox "Must select a single-area, single-row or single-column range
!"
Else
With WorksheetFunction
If r.Rows.Count = 1 Then
av = .Transpose(.Transpose(r.Value))
bLInterp av
r.Value = av
Else
av = .Transpose(r.Value)
bLInterp av
r.Value = .Transpose(av)
End If
End With
End If
Else
MsgBox "Must select a range!"
End If
End Sub

Private Function NumDim(av As Variant) As Long


Dim i
As Long
If TypeOf av Is Range Then
If av.Count = 1 Then NumDim = 1 Else NumDim = 2
ElseIf IsArray(av) Then
On Error GoTo Done
For NumDim = 0 To 6000
i = LBound(av, NumDim + 1)
Next NumDim
Done:
Err.Clear
End If
End Function

You might also like