What oval goes through two points? Or, better, the best-fit oval: X²/a² + Y²/b² = 1 ε = X²/a² + Y²/b² - 1 ε² = X⁴/a⁴ + Y⁴/b⁴ + 1 + 2X²Y²/a²b² - 2X²/a² - 2Y²/b² ∑ε² = ∑X⁴/a⁴ + ∑Y⁴/b⁴ + ∑1 + 2∑X²Y²/a²b² - 2∑X²/a² - 2∑Y²/b² d∑/da = -4∑X⁴/a⁵ + 0 + 0 + -4∑X²Y²/a³b² + 4∑X²/a³ + 0 d∑/db = 0 + -4∑Y⁴/b⁵ + 0 + -4∑X²Y²/a²b³ + 0 + 4∑Y²/b³ (d∑da = 0) and (d∑db = 0): -4∑X⁴/a⁵ -4∑X²Y²/a³b² + 4∑X²/a³ = 0 -4∑Y⁴/b⁵ -4∑X²Y²/a²b³ + 4∑Y²/b³ = 0 divide by -4 and move the the negative term to the zero side: ∑X⁴/a⁵ + ∑X²Y²/a³b² = ∑X²/a³ ∑Y⁴/b⁵ + ∑X²Y²/a²b³ = ∑Y²/b³ multiply by param³, which gets the right side without any parameter ∑X⁴/a² + ∑X²Y²/b² = ∑X² ∑Y⁴/b² + ∑X²Y²/a² = ∑Y² Reorder the second to get it closer to matrix form: (∑X⁴ )/a² + (∑X²Y²)/b² = ∑X² (∑X²Y²)/a² + (∑Y⁴ )/b² = ∑Y² Matrix form: [ ∑X⁴ ∑X²Y² ] [1/a²] = [ ∑X² ] [ ∑X²Y² ∑Y⁴ ] [1/b²] = [ ∑Y² ] Also tried a derivation (not shown) where A=1/a² and B=1/b² , and it comes to the same answer with the column vector(A,B). So the p⁻² in the original equation, which generates powers of -2,-3,-4,and -5 in the various stages, they all cancel out except for the final p⁻², so the two are equivalent (except the p⁻² form emphasizes that a and b cannot be zero and still be a valid ellipse). Other hyperbolic best fits could be done similarly. Actually, any implicit equation f(𝕩 ∈ 𝕏|𝕡 ∈ ℙ)=k, where 𝕩 ∈ 𝕏 are the measured variables and 𝕡 ∈ ℙ are all the parameters, can be attempted to be solved with that same method, assuming you can isolate each 𝕡 ∈ ℙ after you've set ∂(∑ε²)/∂𝕡=0
Wiki lists the arbitrary conic section as
Ax² + Bxy + Cy² + Dx + Ey + F = 0
When I tried the process above, and generated a matrix of sums, there weren't any "lonely" terms
without 𝕡 ∈ {A,B,C,D,E,F}, so when I tried to do the M*𝕏=Q and solve for 𝕏, the Q was the
zero-column, which meant that 𝕏 was the zero-column as well. That's not useful.
In using [Geogebra CAS](https://www.geogebra.org/calculator "in CAS mode"), I compared the partial
derivatives from the my original working oval to the one here, and found there were no negative
terms, and I realized that it was because there was no -1 term coming from the =1 in the original
oval equation.
However, if you normalize everything in the generic equation by -F, that is equivalent to
Ax² + Bxy + Cy² + Dx + Ey = 1
... the F wasn't contributing anything meaningful. Re-solve the partial derivatives for the
5-parameter version, and now everything looks much more reasonable:
__GEOGEBRA__
err2(x, y)=Expand((A x^(2)+B x y+C y^(2)+D x+E y-1)^(2))
ddA(x,y)=Derivative(Expand(Simplify(err2(x,y)/2))=0,A)
ddB(x,y)=Derivative(Expand(Simplify(err2(x,y)/2))=0,B)
ddC(x,y)=Derivative(Expand(Simplify(err2(x,y)/2))=0,C)
ddD(x,y)=Derivative(Expand(Simplify(err2(x,y)/2))=0,D)
ddE(x,y)=Derivative(Expand(Simplify(err2(x,y)/2))=0,E)
# divide each by 2 because that is a common factor throughout
__END__
I then manipulated the results into clean matrix form in Notepad++:
[ ∑X⁴ ∑X³Y ∑X²Y² ∑X³ ∑X²Y ] [ A ] [ ∑X² ]
[ ∑X³Y ∑X²Y² ∑XY³ ∑X²Y ∑XY² ] [ B ] [ ∑XY ]
[ ∑X²Y² ∑XY³ ∑Y⁴ ∑XY² ∑Y³ ] * [ C ] = [ ∑Y² ]
[ ∑X³ ∑X²Y ∑XY² ∑X² ∑XY ] [ D ] [ ∑X ]
[ ∑X²Y ∑XY² ∑Y³ ∑XY ∑Y² ] [ E ] [ ∑Y ]
Note in the 5x5 matrix M that M{i,j}==M{j,i}, so that's computationally easy.
To do the best fit in Excel, list the measured X and Y, and calculate the sums, then use
MMULT(MINVERSE(A1:E5),G1:G5) => column vector of the ℙaram space
For doing a gradient descent to get from an arbitrary (x,y) to some point P on the conic section,
the ddX(x,y) and ddY(x,y) will give the gradient with respect to the coordinates, and err2/-ddX or
err2/-ddY will give the coordinate adjustment needed to get closer to the local minimum for err2,
which should be on the conic section. This allows a computationally-expensive way of doing the
graphing of an implicit conic section. (My Excel experiments showed that with clamping the deltaX
and deltaY to +/-1, it usually converged to the point within 5-10 gradient-descent steps.) You
graph a point (x,y) if the err2 is within some threshold of 0.
But with this, I should be able to best-fit any conic section I run across.
_______________
update: I was realizing that maybe the reason it didn't have "lonely" terms was because I had tried
Ax² + Bxy + Cy² + Dx + Ey + F = 0
But if I instead use
Ax² + Bxy + Cy² + Dx + Ey + F = 1
Then the -1 should give me "lonely" terms again. Use the same GEOGEBRA, except with +F and add ddF(x,y):
__GEOGEBRA__
err2(x, y)=Expand((A x^(2)+B x y+C y^(2)+D x+E y + F - 1)^(2))
...
ddF(x,y)=Derivative(Expand(Simplify(err2(x,y)/2))=0,F)
__END__
Convert their output to a matrix:
[ ∑X⁴ ∑X³Y ∑X²Y² ∑X³ ∑X²Y ∑X² ] [ A ] [ ∑X² ]
[ ∑X³Y ∑X²Y² ∑XY³ ∑X²Y ∑XY² ∑XY ] [ B ] [ ∑XY ]
[ ∑X²Y² ∑XY³ ∑Y⁴ ∑XY² ∑Y³ ∑Y² ] * [ C ] = [ ∑Y² ]
[ ∑X³ ∑X²Y ∑XY² ∑X² ∑XY ∑X ] [ D ] [ ∑X ]
[ ∑X²Y ∑XY² ∑Y³ ∑XY ∑Y² ∑Y ] [ E ] [ ∑Y ]
[ ∑X² ∑XY ∑Y² ∑X ∑Y ∑1 ] [ F ] [ ∑1 ]
It's still M{i,j}==M{j,i}, so still easy to compute.
The Excel best-fit will just be 6x6 instead of 5x5.
This should be able to show me F as well, now, so I don't always have the downscaled version.
And if I really want
Ax² + Bxy + Cy² + Dx + Ey + F = 0
then I can just subtract 1 from the F I calculated above.
OUT = [ a b c ] [ x1 x2 x3 ... xN ] = [ v1 v2 v3 ... vN ]
[ d e f ] * [ y1 y2 y3 ... yN ] [ w1 w2 w3 ... wN ]
[ 0 0 1 ] [ 1 1 1 ... 1 ] [ 1 1 1 ... 1 ]
So if you want to figure out the expanded M, you want to best fit on OUT = M * IN (with the extra rows)
To figure out the best fit, you basically have the equations
v_i = a*Xi + b*Yi + c*1
w_i = d*Xi + e*Yi + f*1
Best fit is least-sum-squared-error, so
err_v_i = a*Xi + b*Yi + c*1 - Vi
err_w_i = d*Xi + e*Yi + f*1 - Wi
And the sq error
esq_Vi = a²*Xi² + b²*Yi² + c²*1 + Vi² + 2ab*Xi*Yi + 2ac*Xi - 2a*Xi*Vi + 2bc*Yi - 2b*Yi*Vi - 2c*Vi
esq_Wi = exact same form (I will just show derivation on the Vi terms for now)
Sum up the total error:
SSEv = ∑(a²*Xi² + b²*Yi² + c²*1 + Vi² + 2ab*Xi*Yi + 2ac*Xi - 2a*Xi*Vi + 2bc*Yi - 2b*Yi*Vi - 2c*Vi)
= ∑(a²*Xi²) + ∑(b²*Yi²) + ∑(c²*1) + ∑(Vi²) + ∑(2ab*Xi*Yi) + ∑(2ac*Xi) - ∑(2a*Xi*Vi) + ∑(2bc*Yi) - ∑(2b*Yi*Vi) - ∑(2c*Vi)
= a²*∑(Xi²) + b²*∑(Yi²) + c²*∑(1) + ∑(Vi²) + 2ab*∑(Xi*Yi) + 2ac*∑(Xi) - 2a*∑(Xi*Vi) + 2bc*∑(Yi) - 2b*∑(Yi*Vi) - 2c*∑(Vi)
Need to find the minimum with respect to each of a, b, c (and equivalently d,e,f) by setting each partial derivative to 0
dSSEv/da = 2a*∑(Xi²) + 0 + 0 + 0 + 2b*∑(Xi*Yi) + 2c*∑(Xi) - 2*∑(Xi*Vi) + 0 - 0 - 0 = 0
dSSEv/db = 0 + 2b*∑(Yi²) + 0 + 0 + 2a*∑(Xi*Yi) + 0 - 0 + 2c*∑(Yi) - 2*∑(Yi*Vi) - 0 = 0
dSSEv/dc = 0 + 0 + 2c*∑(1) + 0 + 0 + 2a*∑(Xi) - 0 + 2b*∑(Yi) - 0 - 2*∑(Vi) = 0
Divide by two and rearrange those three, grouping into a,b,c on one side and coefficientless on the other
dSSEv/da: a*∑(Xi²) + b*∑(Xi*Yi) + c*∑(Xi) = ∑(Xi*Vi)
dSSEv/db: a*∑(Xi*Yi) + b*∑(Yi²) + c*∑(Yi) = ∑(Yi*Vi)
dSSEv/dc: a*∑(Xi) + b*∑(Yi) + c*∑(1) = ∑(Vi)
But that is a matrix multiplication:
[ ∑(Xi²) ∑(Xi*Yi) ∑(Xi) ] [ a ] = [ ∑(Xi*Vi) ]
[ ∑(Xi*Yi) ∑(Yi²) ∑(Yi) ] [ b ] = [ ∑(Yi*Vi) ]
[ ∑(Xi) ∑(Yi) ∑(1) ] [ c ] = [ ∑(Vi) ]
And similarly from the esq_Wi, you get:
[ ∑(Xi²) ∑(Xi*Yi) ∑(Xi) ] [ d ] = [ ∑(Xi*Wi) ]
[ ∑(Xi*Yi) ∑(Yi²) ∑(Yi) ] [ e ] = [ ∑(Yi*Wi) ]
[ ∑(Xi) ∑(Yi) ∑(1) ] [ f ] = [ ∑(Wi) ]
Note that the matrix on the left is the same for both. So we can merge those two into a single matrix multiplication
[ ∑(Xi²) ∑(Xi*Yi) ∑(Xi) ] [ a d ] = [ ∑(Xi*Vi) ∑(Xi*Wi) ]
[ ∑(Xi*Yi) ∑(Yi²) ∑(Yi) ] [ b e ] = [ ∑(Yi*Vi) ∑(Yi*Wi) ]
[ ∑(Xi) ∑(Yi) ∑(1) ] [ c f ] = [ ∑(Vi) ∑(Wi) ]
If we call those G x P = Q, then we can solve for the parameter matrix P by P = inv(G) x Q
But you can get the original abcdef001 matrix by transposing P and adding a row of (001)
Then to find the blue outputs, just do
BLUE_OUT = abcdef001 * BLUE_IN
Y = C x X, where Y is the [3x1] extended 2d output, and X is the [4x1] extended 3d input,
but using X and Y are confusing when dealing with real space
Translate that to XYZ for input and RU for output (right and up, in screen coordinates)
[ R ] [a b c d] [ X ]
[ U ] = [e f g h] . [ Y ]
[ 1 ] [0 0 1 0] [ Z ]
[ 1 ]
R = a*X + b*Y + c*Z + d
U = e*X + f*Y + g*Z + h
errx = a*X + b*Y + c*Z + d - R
erry = e*X + f*Y + g*Z + h - U
esqx = a²*X² + b²*Y² + c²*Z² + d² + R² + 2ab*XY + 2ac*XZ + 2ad*X - 2a*XR + 2bc*YZ + 2bd*Y - 2b*YR + 2cd*Z - 2c*ZR - 2d*R
similar for esqy
technically all the terms with X,Y,Z,R,U have ∑ in front of them, but to save typing, will just apply it at the end
desqx/da = 2a*X² + 0 + 0 + 0 + 0 + 2b*XY + 2c*XZ + 2d*X - 2*XR + 0 + 0 + 0 + 0 + 0 - 0 = 0
desqx/db = 0 + 2b*Y² + 0 + 0 + 0 + 2a*XY + 0 + 0 - 0 + 2c*YZ + 2b*Y - 2*YR + 0 + 0 - 0 = 0
desqx/dc = 0 + 0 + 2c*Z² + 0 + 0 + 0 + 2a*XZ + 0 - 0 + 2b*YZ + 0 + 0 + 2d*Z - 2*ZR - 0 = 0
desqx/dd = 0 + 0 + 0 + 2d + 0 + 0 + 0 + 2a*X - 0 + 0 + 2b*Y + 0 + 2c*Z + 0 - 2*R = 0
desqx/da = 2a*X² + 2b*XY + 2c*XZ + 2d*X = 2*XR
desqx/db = 2b*Y² + 2a*XY + 2c*YZ + 2b*Y = 2*YR
desqx/dc = 2c*Z² + 2a*XZ + 2b*YZ + 2d*Z = 2*ZR
desqx/dd = 2d + 2a*X + 2b*Y + 2c*Z = 2*R
desqx/da: a*X² + b*XY + c*XZ + d*X = XR
desqx/db: a*XY + b*Y² + c*YZ + b*Y = YR
desqx/dc: a*XZ + b*YZ + c*Z² + d*Z = ZR
desqx/dd: a*X + b*Y + c*Z + d*1 = R
[ X² XY XZ X ] [a] = [ XR ]
[ XY Y² YZ Y ] [b] = [ YR ]
[ XZ YZ Z² Z ] [c] = [ ZR ]
[ X Y Z 1 ] [d] = [ R ]
Extend for the equivalent on efgh, and mark the sums correctly
[ ∑X² ∑XY ∑XZ ∑X ] [ a e ] = [ ∑XR ∑XU ]
[ ∑XY ∑Y² ∑YZ ∑Y ] [ b f ] = [ ∑YR ∑YU ]
[ ∑XZ ∑YZ ∑Z² ∑Z ] [ c g ] = [ ∑ZR ∑ZU ]
[ ∑X ∑Y ∑Z ∑1 ] [ d h ] = [ ∑R ∑U ]
M x P = S
Do the various X Y Z R U sums for M and S, then solve with P = inv(M) x S
Then C = transpose(P)->append(ones(1))
Option Explicit
' excel: LINEST() = array formula for best-fit line for
' fn = m_[n]*x_[n] + m_[n-1]*x_[n-1] + ... m_1*x_1 + b
' POLYNOMIAL: to get best-fit polynomial, LINEST(ydata, xdata^{1,2,3,...n}), where xdata must be single-column
' excel: LOGEST(): fn = m_[n]^(x_[n]) * ... * m_[1]^(x_[1]) * b
'
' for fitting fn = a * exp(b*x), most will recommend
' fnl = log(fn)
' = log( a * exp(b*x) )
' = log( a ) + log( exp(b*x) )
' = log( a ) + b*x
' = A' + b*x
' then you can just fit LINEST( log(y), log(x) ), where b = slope(), A' = offset(), and a = exp(A')
' => note, this is equivalent to LOGEST, because b*m^x = b*exp(log(m)*x), but LOGEST brings the a back to the right value
'
' However, neither LOGEST() nor LINEST() methods work right for an exponential with dc offset:
' fn = a + b * exp(cx) (ie, an exponential with dc offset)
' or
' fn = a + b * base^x (where base = exp(c))
'
' Hence, my EXPEST(), which (after some googling) does one linear approximation to get the exponential constant (or, equivalently, base)
' and then using that, a second linear approximation to get the amplitude (b) and offset (a)
'
Function expest(yData As Range, xData As Range) As Variant
' [Array Function]: Estimate y = a*exp(m*x) + b using "best fit" methodology
'
' row#: data columns
' 1: ampl:a , slpe:m , offs:b best-fit parameter results
' 2: sea, sem, seb [FUTURE] Standard errors of the input parameters
' 3: r^2, sey [FUTURE] correlation factor, std err for y-estimator
' 4: F, df [FUTURE] f-statistic, degrees of freedom
' 5: ssreg, ssresid [FUTURE] regression sum-squares, residual sum-squares
' ssresid = sum( (y_est_i - y_data_i)^2 )
' sstotal = sum( (y_data_i - avg_y)^2 )
' ssreg = sum( (y_est_i - avg_y)^2 ) = sstotal - ssresid
' r^2 = ssreg / sstotal
'
' _similar_ to LINEST ( y = m1*x1 + b ) or LOGEST ( y = (m1^x1)*b )
' but using y = a + b*exp(c*x)
' for now, NOT going to handle slpeiple columns of X data
' REFERENCES:
' * Return Array: SEE http://www.cpearson.com/excel/returningarraysfromvba.aspx
' * somewhat-best-fit for a + b*e^(cx):
' => NOTE that this function is using nomenclature b + a*exp(m*x)
' https://stackoverflow.com/questions/3938042/fitting-exponential-decay-with-no-initial-guessing
' https://fr.scribd.com/doc/14674814/Regressions-et-equations-integrales
' * Residuals and Diagnostics: http://www.informit.com/articles/article.aspx?p=2019171
' -- gives pure-excel formulas to mimic each of the elements of LINEST(), so is helping me make equivalent
' -- taught me DEVSQ():
' akin to SUMSQ(range) == SUM( range^2 ),
' there is DEVSQ(range) == SUMSQ( range - average(range) ) == SUM( (range - average(range))^2 )
'
' I did my best to keep things parallel to the LINEST(), but I make no guarantees as to the statistical validity of anything
' * HIGHEST CONFIDENCE: ampl, slpe, offs => these are as the fr.scribd document derived, so quite confident
' * HIGH CONFIDENCE: r^2, sey, ssreg, ssresid => I understood these derivations better, and cannot see a reason they wouldn't be analogous
' * MEDIUM CONFIDENCE: F, df => I see no reason for the analog to the imformit derivation to not be analogous
' * LOWEST CONFIDENCE: se_ampl, se_slpe, se_offs => these require the SSCP, and I don't know how they might influence each other...
' for the example dataset I derived from, se_slpe was bigger than I expected... but maybe it's right, because of the assumptions of the pseudo-integral
Dim output() As Variant, x As Variant, y As Variant
If xData.Columns.Count > 1 Then
expest = "#ERR! Only one column of X (for now)"
Exit Function
End If
Dim orows, ocols
orows = 5
ocols = 3
ReDim output(1 To orows, 1 To ocols)
Dim ny, nx, k
ny = yData.Rows.Count
nx = xData.Rows.Count
If ny <> nx Then
expest = "#ERR! X and Y are not the same size"
Exit Function
End If
' the integral only works if data is sorted by x...
' S.O. has a single-function QuickSort at https://stackoverflow.com/a/152325/5508606
' I will adapt this for sorting both the X and Y
x = xData.value
y = yData.value
Call QuickSort2d(x, y)
Dim colIdx
colIdx = LBound(x, 2)
' do the first fit to find the slope 'slpe' (the constant power of the exponential)
Dim integral, sum_dx, sum_dy, sum_dx_s, sum_dy_s, sum_dx_dx, sum_dy_dx, sum_s_s
integral = 0
sum_dx = 0
sum_dy = 0
sum_dx_s = 0
sum_dy_s = 0
sum_dx_dx = 0
sum_dy_dx = 0
sum_s_s = 0
For k = 1 To nx
Dim dx, dy
If k = 1 Then
integral = 0
Else
integral = integral + 0.5 * (y(k, colIdx) + y(k - 1, colIdx)) * (x(k, colIdx) - x(k - 1, colIdx)) ' += avg(y)*delta(x)
End If
dx = x(k, colIdx) - x(1, colIdx)
dy = y(k, colIdx) - y(1, colIdx)
sum_s_s = sum_s_s + integral * integral
sum_dx = sum_dx + dx
sum_dx_s = sum_dx_s + dx * integral
sum_dx_dx = sum_dx_dx + dx * dx
sum_dy = sum_dy + dy
sum_dy_s = sum_dy_s + dy * integral
sum_dy_dx = sum_dy_dx + dy * dx
Next k
' Using ANS = MMult( MINVERSE(MAT), VEC) from VBA
' see https://stackoverflow.com/a/7307992/5508606
Dim MAT(1 To 2, 1 To 2), VEC(1 To 2, 1 To 1), ANS As Variant
Dim I1 As Variant, I2 As Variant
MAT(1, 1) = sum_dx_dx
MAT(1, 2) = sum_dx_s
MAT(2, 1) = sum_dx_s
MAT(2, 2) = sum_s_s
VEC(1, 1) = sum_dy_dx
VEC(2, 1) = sum_dy_s
I1 = WorksheetFunction.MInverse(MAT)
ANS = WorksheetFunction.MMult(I1, VEC)
' offs + ampl*exp(slpe*x)
' S.O.: d = IM11 * V1 + IM12 * V2 ' ignored
' S.O.: c = IM21 * V1 + IM22 * V2 ' SO:c => 'slpe'
Dim offs, ampl, slpe
slpe = ANS(2, 1)
' second matrix
Dim sum_ec1x, sum_ec2x, sum_y, sum_y_ec1x
For k = 1 To nx
Dim ec1x, ec2x
ec1x = Exp(slpe * 1 * x(k, colIdx))
ec2x = Exp(slpe * 2 * x(k, colIdx))
sum_ec1x = sum_ec1x + ec1x
sum_ec2x = sum_ec2x + ec2x
sum_y = sum_y + y(k, colIdx)
sum_y_ec1x = sum_y_ec1x + y(k, colIdx) * ec1x
Next k
MAT(1, 1) = nx
MAT(1, 2) = sum_ec1x
MAT(2, 1) = sum_ec1x
MAT(2, 2) = sum_ec2x
VEC(1, 1) = sum_y
VEC(2, 1) = sum_y_ec1x
I2 = WorksheetFunction.MInverse(MAT)
ANS = WorksheetFunction.MMult(I2, VEC)
' S.O.: a = IM11 * V1 + IM12 * V2 ' SO:a => 'offs'
' S.O.: b = IM21 * V1 + IM22 * V2 ' SO:b => 'ampl'
offs = ANS(1, 1)
ampl = ANS(2, 1)
' DF = #datapoints - (#variables+1) ' LINEST().df ' degrees of freedom of the RESIDUAL
' in LINEST(), the degrees of freedom (df) is the number of data points,
' minus the TOTAL number of variables, which is really the number of obvious x colums,
' plus the implied 1s column for the DC offset.
' Thus, for EXPEST(), since the X is limited to 1 column, it should be
' nx - (1+1) = nx-2
Dim DF As Long
DF = nx - 2 ' TODO = if I ever allow multiple X columns, edit this formula
' In the second-matrix calcs, need to get
' my nomenclature ' LINEST() nomenclature
' SSE = Sum((y_i - fn(x_i)) ^ 2) ' ssresid ' sum of squares residual
' SST = SUM( (y_i - avg(y_*)^2 ) ' sstotal ' sum of squares total
' SSR = SST - SSE ' ssreg ' sum of squares regression
Dim avg_y, SSE, SSR, SST, yfn
avg_y = sum_y / ny
SSE = 0 ' sum( (y_data-yfn)^2 ) = EXCEL.DEVSQ(predicted-data)
SSR = 0 ' sum( (yfn - avg_y)^2 ) = EXCEL.DEVSQ(predicted)
SST = 0 ' sum( (y_data-avg_y)^2) = EXCEL.DEVSQ(data)
For k = 1 To ny
yfn = ampl * Exp(slpe * x(k, colIdx)) + offs
SSE = SSE + (y(k, colIdx) - yfn) ^ 2
'SSR = SSR + (yfn - avg_y) ^ 2
SST = SST + (y(k, colIdx) - avg_y) ^ 2
Next k
SSR = SST - SSE
' my nomenclature ' LINEST() nomenclature
' RSQ = 1 - SSE / SST = SSR / SST ' ' correlation factor
' SE_Y = sqr( SSE / DF ) ' sqrt(ssresid/df) ' standard error of the estimate
Dim RSQ, SE_Y
RSQ = 1 - SSE / SST ' == SSR / SST
SE_Y = Sqr(SSE / DF)
' for figuring out the F ratio, need a different degrees of freedom:
' total number of variables (again, number of X columns, including the 1s DC-offset column) minus 1
' so here, (1+1)-1 = 1 = dfx
' There are two ways of calculating the F_RATIO: one involves the RSQ, the other the sum-squares (SSR and SSE), but are mathematically equivalent
' F_RATIO_RSQ = (RSQ/dfx) / ( (1-RSQ)/DF )
' (SSR/SST/dfx) / (1-(1-SSE/SST))/DF)
' ((SSR/SST)/dfx) / ( (SSE/SST) / DF ) ' subst SSR/SST on left, and 1-SSE/SST on right
' ((SSR/---)/dfx) / ( (SSE/---) / DF ) ' cancel the common SST
' F_RATIO_SS = (SSR/dfx) / (SSE/DF)
Dim dfx, F_RATIO
dfx = 1
F_RATIO = (SSR / dfx) / (SSE / DF)
' the last (the "Standard errors" for row 2 of the output)
' are the most complicated... still trying to understand the inform.it description...
' However, it starts with the MSResid (Mean Squared Residual), which is what I'd call the MSE
Dim MSE
MSE = SSE / DF ' msresid = ssresid / df
' For linear regression, it's the GRID=MInverse( MMult( TRANSPOSE(X), X ) ) * MSE,
' then the se_param are the sqr(GRID(1,1)), sqr(GRID(2,2)), ... sqr(GRID(n,n))
' The problem is, for the non-linear version, I just don't see what the equivalent would be,
' because I don't know what the 'X' would be for each param...
' The "second matrix" somewhat implies that for the offset and amplitude,
' the X are 1s (offset) and exp(slpe*x)
' Compare the form of the first matrix to the second
' second first
' M22 = sum(exp^2) M22 = sum(integral^2)
' M11 = sum(1^2) M11 = sum((x-x1)^2)
' M12 = sum(1*exp) M12 = sum((x-x1)*integral)
' Note that in the second, the Xi data is the element that's squared in Mii: so Xs1 = 1, Xs2 = exp()...
' Thus, I would say in the first, Xf1 = (xj-x1) and Xf2 = (integral)
' so, I need to build XMERGE, where
' XMERGE(col:1) = x_r - x_1
' XMERGE(col:2) = integral
' XMERGE(col:3) = 1
' XMERGE(col:4) = exp(slpe*x)
' Then
' SSCP = MMULT( TRANSPOSE( XMERGE ), XMERGE )
' ISCP = INVERSE(SSCP)
' MSCP = ISCP * MSE
' se_ignored = sqr(MSCP(1,1))
' se_slpe = sqr(MSCP(2,2))
' se_offs = sqr(MSCP(3,3))
' se_ampl = sqr(MSCP(4,4))
' TODO = when mimicing the inform.it figure 7, see if the order of columns matters for XMERGE ... it shouldn't
' TODO = if I've understood things correctly, then see if it makes sense when I implement it for this one
' also, it might be good to compare the results to splitting it into two SSCP, just like the
' the original matrixes were...
' Oh, wait... that might be even better, because the ISCP is what the MINVERSE(MAT) is in both the 2x2 matrixes...
' yeah, so if I saved a copy of I1 = INVERSE(MAT#1) and I2 = INVERSE(MAT#2), then I could get MSCP = (I1,I2)*MSE
' I think that's how I'll do it, without the XMERGE version
' sqr(MSE*I1(1,1)) = se_ignored sqr(MSE*I1(2,2)) = se_slpe
' sqr(MSE*I2(1,1)) = se_offs sqr(MSE*I2(2,2)) = se_ampl
' => that does _something_, but it shows that the slpe error is pretty big (se=0.49 vs slpe=-3.88 = 13% in the S.O. data)
' populate the output structure
output(1, 1) = ampl
output(1, 2) = slpe
output(1, 3) = offs
output(2, 1) = Sqr(MSE * I2(2, 2)) ' se_ampl
output(2, 2) = Sqr(MSE * I1(2, 2)) ' se_slpe
output(2, 3) = Sqr(MSE * I2(1, 1)) ' se_offs
output(3, 1) = RSQ
output(3, 2) = SE_Y
output(3, 3) = " - "
output(4, 1) = F_RATIO
output(4, 2) = DF
output(4, 3) = " - "
output(5, 1) = SSR ' ssreg = SSR = SST - SSE
output(5, 2) = SSE ' ssresid = SSE
output(5, 3) = " - "
expest = output
End Function
Cubic Bézier Curve: (1-t)³⋅P0 + 3⋅(1-t)²⋅t¹⋅P1 + 3⋅(1-t)¹⋅t²⋅P2 + t³⋅P3
Tᵢ are the inputs
Xᵢ are the outputs
Pₙ are the control points that I'm trying to solve for (the "parameters")
Mᵢₙ are the multipliers, which are a function of Tᵢ
Mᵢ₀=1⋅(1-Tᵢ)³
Mᵢ₁=3⋅(1-Tᵢ)²⋅Tᵢ¹
Mᵢ₂=3⋅(1-Tᵢ)¹⋅Tᵢ²
Mᵢ₃=1 ⋅Tᵢ³
(Skipping summation notation for now)
Eᵢ = Xᵢ - (1-Tᵢ)³⋅P₀ - (1-Tᵢ)²⋅Tᵢ¹⋅P₁ - (1-Tᵢ)¹⋅Tᵢ²⋅P₂ - Tᵢ³⋅P₃
Eᵢ = Xᵢ - Mᵢ₀⋅P₀ - Mᵢ₁⋅P₁ - Mᵢ₂⋅P₂ - Mᵢ₃⋅P₃
Eᵢ² = Xᵢ² + Mᵢ₀²⋅P₀² + Mᵢ₁²⋅P₁² + Mᵢ₂²⋅P₂² + Mᵢ₃²⋅P₃²
- 2⋅Xᵢ⋅(Mᵢ₀⋅P₀ + Mᵢ₁⋅P₁ + Mᵢ₂⋅P₂ + Mᵢ₃⋅P₃)
+ 2⋅(Mᵢ₀⋅P₀⋅Mᵢ₁⋅P₁ + Mᵢ₀⋅P₀⋅Mᵢ₂⋅P₂ + Mᵢ₀⋅P₀⋅Mᵢ₃⋅P₃ + Mᵢ₁⋅P₁⋅Mᵢ₂⋅P₂ + Mᵢ₁⋅P₁⋅Mᵢ₃⋅P₃ + Mᵢ₂⋅P₂⋅Mᵢ₃⋅P₃)
S = ∑Eᵢ²
dS/dP₀ = 0 = 2⋅(Mᵢ₀²⋅P₀ - Xᵢ⋅Mᵢ₀ + Mᵢ₀⋅Mᵢ₁⋅P₁ + Mᵢ₀⋅Mᵢ₂⋅P₂ + Mᵢ₀⋅Mᵢ₃⋅P₃)
dS/dP₁ = 0 = 2⋅(Mᵢ₁²⋅P₁ - Xᵢ⋅Mᵢ₁ + Mᵢ₀⋅P₀⋅Mᵢ₁ + Mᵢ₁⋅Mᵢ₂⋅P₂ + Mᵢ₁⋅Mᵢ₃⋅P₃)
dS/dP₂ = 0 = 2⋅(Mᵢ₂²⋅P₂ - Xᵢ⋅Mᵢ₂ + Mᵢ₀⋅P₀⋅Mᵢ₂ + Mᵢ₁⋅P₁⋅Mᵢ₂ + Mᵢ₂⋅Mᵢ₃⋅P₃)
dS/dP₃ = 0 = 2⋅(Mᵢ₃²⋅P₃ - Xᵢ⋅Mᵢ₃ + Mᵢ₀⋅P₀⋅Mᵢ₃ + Mᵢ₁⋅P₁⋅Mᵢ₃ + Mᵢ₂⋅P₂⋅Mᵢ₃ )
Clean up into matrix form and add the summation symbols
[ ∑Mᵢ₀² ∑Mᵢ₀⋅Mᵢ₁ ∑Mᵢ₀⋅Mᵢ₂ ∑Mᵢ₀⋅Mᵢ₃ ] ⋅ [ P₀ ] = [ ∑Xᵢ⋅Mᵢ₀ ]
[ ∑Mᵢ₀⋅Mᵢ₁ ∑Mᵢ₁² ∑Mᵢ₁⋅Mᵢ₂ ∑Mᵢ₁⋅Mᵢ₃ ] [ P₁ ] [ ∑Xᵢ⋅Mᵢ₁ ]
[ ∑Mᵢ₀⋅Mᵢ₂ ∑Mᵢ₁⋅Mᵢ₂ ∑Mᵢ₂² ∑Mᵢ₂⋅Mᵢ₃ ] [ P₂ ] [ ∑Xᵢ⋅Mᵢ₂ ]
[ ∑Mᵢ₀⋅Mᵢ₃ ∑Mᵢ₁⋅Mᵢ₃ ∑Mᵢ₂⋅Mᵢ₃ ∑Mᵢ₃² ] [ P₃ ] [ ∑Xᵢ⋅Mᵢ₃ ]
Since the matrix A on the left is a square, and given column vectors p for the parameters and q for the RHS of the matrix equation:
A⋅p = q
Then solve for p using:
p = A⁻¹⋅q
(And note, to solve for the x and y coordinates of Pₙ simultaneously, just change the p and q to two-column vectors with the x in the first column, y in the second. Matrix algebra is great!)
If you want the quadratic Bézier curve instead, just remove the Mᵢ₃ column and row, and set the coefficients as follows:
Mᵢ₀=1⋅(1-Tᵢ)²
Mᵢ₁=2⋅(1-Tᵢ)¹⋅Tᵢ¹
Mᵢ₂=1 ⋅Tᵢ²
That's all great... except if trying to do it from an image, you don't have the Tᵢ for a given point.
When I tried to estimate Tᵢ based on the proportional distance along the curve (summing the √(ΔXᵢ²+ΔYᵢ²), and doing partial-sum divided by total-sum), the Tᵢ estimators were really bad, and it didn't get very close to the right p solution.
For quadratic, the tangent line to each point (Xᵢ,Yᵢ) should hit the baseline at P₀ + t⋅(P₁-P₀), so I should be able to solve:
Xᵢ + s⋅dXᵢ = Px₀ + t⋅(Px₁-Px₀)
Yᵢ + s⋅dYᵢ = Py₀ + t⋅(Py₁-Py₀)
[ dXᵢ -(Px₁-Px₀) ] ⋅ [ s ] = [ Px₀-Xᵢ ]
[ dYᵢ -(Py₁-Py₀) ] [ t ] = [ Py₀-Yᵢ ]
So if I solve for s and t, then plug in the equation for t into all the above formulas, I could (in theory) translate the t into something dependent only on the control points and the input variables. But since Pₙ have to stay variables, it would need to be algebraic solution rather than st = []⁻¹⋅eq, which would be a lot of if statements for solving. I'm not sure I could make that work in the best-fit solution.
And the cubic adds an extra layer of complexity, because the tangent lines actually hit an implied control line, and then those hit the real control line -- and I don't think I could solve that one.
Studying the question and answers in this SO discussion, I have thought of an iterative approach:
But that has divide-by-0 errors at tᵢ=0. I remember from ANN that the backpropagation of errors has the activation slope in the numerator instead of the denominator, which always confuses me. Looking at the Wiki:Backprop discussion, I think I've figured it out. Translating it into nomenclature similar to what I'm using here (and dropping the x-or-y and i subscripts):
In Excel, you could of course use their best-fit routines, but where's the fun in that? (Besides, it's limited to linear and a few others.)
For the square matrix A, with column vectors b (parameters) and y (results):
A⋅b = yThen solve for b using
b = A⁻¹⋅y
Unfortunately, for best-fit purposes, your input data (or input data augmented by the column of 1s for offset term) rarely is square (and when it is, you solve not for the best-fit but for the exact solution, using the technique above).
However, https://people.revoledu.com/kardi/tutorial/Regression/GeneralizedInverse.html reminded me of the workaround (which I know I've found before, but have forgotten, because it's not here):
At⋅A is a square matrix. And multiply both sides of an equation by the same thing doesn't change the equality, so:
At⋅A⋅b = At⋅y
(At⋅A)⋅b = At⋅y
To solve for b, multiply both sides by the inverse of that parenthetical term:
(At⋅A)⁻¹⋅(At⋅A)⋅b = (At⋅A)⁻¹⋅At⋅y
The left includes multiplying something by its inverse, which is the identity, so:
b = (At⋅A)⁻¹⋅At⋅y
The article goes on to show that the At⋅A and At⋅y are just the normal ∑x-style terms in the standard best-fit derivations above, showing that it works in the normal case.
So, in Excel, if you want to best-fit for some polynomial, y = ∑j(bj⋅xj), just make columns for xⁿ and 1 for the A matrix:
xⁿ ... x² x¹ 1 | y
_ _ * _ | _
Then use matrix formulas of the right dimension for
L = MMULT(MINVERSE(MMULT(TRANSPOSE(A),A)),TRANSPOSE(A))
solve for b = MMULT(L, y)