1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <PolynomialRegressionCurveCalculator.hxx>
21 #include <RegressionCalculationHelper.hxx>
25 #include <rtl/math.hxx>
26 #include <rtl/ustrbuf.hxx>
28 #include <SpecialCharacters.hxx>
30 using namespace com::sun::star
;
35 static double lcl_GetDotProduct(std::vector
<double>& aVec1
, std::vector
<double>& aVec2
)
38 assert(aVec1
.size() == aVec2
.size());
39 for (size_t i
= 0; i
< aVec1
.size(); ++i
)
40 fResult
+= aVec1
[i
] * aVec2
[i
];
44 PolynomialRegressionCurveCalculator::PolynomialRegressionCurveCalculator()
47 PolynomialRegressionCurveCalculator::~PolynomialRegressionCurveCalculator()
50 void PolynomialRegressionCurveCalculator::computeCorrelationCoefficient(
51 RegressionCalculationHelper::tDoubleVectorPair
& rValues
,
52 const sal_Int32 aNoValues
,
55 double aSumError
= 0.0;
56 double aSumTotal
= 0.0;
57 double aSumYpred2
= 0.0;
59 for( sal_Int32 i
= 0; i
< aNoValues
; i
++ )
61 double xValue
= rValues
.first
[i
];
62 double yActual
= rValues
.second
[i
];
63 double yPredicted
= getCurveValue( xValue
);
64 aSumTotal
+= (yActual
- yAverage
) * (yActual
- yAverage
);
65 aSumError
+= (yActual
- yPredicted
) * (yActual
- yPredicted
);
67 aSumYpred2
+= (yPredicted
- mInterceptValue
) * (yPredicted
- mInterceptValue
);
70 double aRSquared
= 0.0;
73 if (auto const div
= aSumError
+ aSumYpred2
)
75 aRSquared
= aSumYpred2
/ div
;
78 else if (aSumTotal
!= 0.0)
80 aRSquared
= 1.0 - (aSumError
/ aSumTotal
);
84 m_fCorrelationCoefficient
= std::sqrt(aRSquared
);
86 m_fCorrelationCoefficient
= 0.0;
89 // ____ XRegressionCurveCalculator ____
90 void SAL_CALL
PolynomialRegressionCurveCalculator::recalculateRegression(
91 const uno::Sequence
< double >& aXValues
,
92 const uno::Sequence
< double >& aYValues
)
94 m_fCorrelationCoefficient
= std::numeric_limits
<double>::quiet_NaN();
96 RegressionCalculationHelper::tDoubleVectorPair
aValues(
97 RegressionCalculationHelper::cleanup( aXValues
, aYValues
, RegressionCalculationHelper::isValid()));
99 const sal_Int32 aNoValues
= aValues
.first
.size();
101 const sal_Int32 aNoPowers
= mForceIntercept
? mDegree
: mDegree
+ 1;
103 mCoefficients
.clear();
104 mCoefficients
.resize(aNoPowers
, 0.0);
106 double yAverage
= 0.0;
108 std::vector
<double> yVector
;
109 yVector
.resize(aNoValues
, 0.0);
111 for(sal_Int32 i
= 0; i
< aNoValues
; i
++)
113 double yValue
= aValues
.second
[i
];
115 yValue
-= mInterceptValue
;
121 yAverage
/= aNoValues
;
124 // Special case for single variable regression like in LINEST
125 // implementation in Calc.
128 std::vector
<double> xVector
;
129 xVector
.resize(aNoValues
, 0.0);
130 double xAverage
= 0.0;
132 for(sal_Int32 i
= 0; i
< aNoValues
; ++i
)
134 double xValue
= aValues
.first
[i
];
140 xAverage
/= aNoValues
;
143 if (!mForceIntercept
)
145 for (sal_Int32 i
= 0; i
< aNoValues
; ++i
)
147 xVector
[i
] -= xAverage
;
148 yVector
[i
] -= yAverage
;
151 double fSumXY
= lcl_GetDotProduct(xVector
, yVector
);
152 double fSumX2
= lcl_GetDotProduct(xVector
, xVector
);
154 double fSlope
= fSumXY
/ fSumX2
;
156 if (!mForceIntercept
)
158 mInterceptValue
= ::rtl::math::approxSub(yAverage
, fSlope
* xAverage
);
159 mCoefficients
[0] = mInterceptValue
;
160 mCoefficients
[1] = fSlope
;
164 mCoefficients
[0] = fSlope
;
165 mCoefficients
.insert(mCoefficients
.begin(), mInterceptValue
);
168 computeCorrelationCoefficient(aValues
, aNoValues
, yAverage
);
172 std::vector
<double> aQRTransposed
;
173 aQRTransposed
.resize(aNoValues
* aNoPowers
, 0.0);
175 for(sal_Int32 j
= 0; j
< aNoPowers
; j
++)
177 sal_Int32 aPower
= mForceIntercept
? j
+1 : j
;
178 sal_Int32 aColumnIndex
= j
* aNoValues
;
179 for(sal_Int32 i
= 0; i
< aNoValues
; i
++)
181 double xValue
= aValues
.first
[i
];
182 aQRTransposed
[i
+ aColumnIndex
] = std::pow(xValue
, static_cast<int>(aPower
));
186 // QR decomposition - based on org.apache.commons.math.linear.QRDecomposition from apache commons math (ASF)
187 sal_Int32 aMinorSize
= std::min(aNoValues
, aNoPowers
);
189 std::vector
<double> aDiagonal
;
190 aDiagonal
.resize(aMinorSize
, 0.0);
192 // Calculate Householder reflectors
193 for (sal_Int32 aMinor
= 0; aMinor
< aMinorSize
; aMinor
++)
195 double aNormSqr
= 0.0;
196 for (sal_Int32 x
= aMinor
; x
< aNoValues
; x
++)
198 double c
= aQRTransposed
[x
+ aMinor
* aNoValues
];
204 if (aQRTransposed
[aMinor
+ aMinor
* aNoValues
] > 0.0)
205 a
= -std::sqrt(aNormSqr
);
207 a
= std::sqrt(aNormSqr
);
209 aDiagonal
[aMinor
] = a
;
213 aQRTransposed
[aMinor
+ aMinor
* aNoValues
] -= a
;
215 for (sal_Int32 aColumn
= aMinor
+ 1; aColumn
< aNoPowers
; aColumn
++)
218 for (sal_Int32 aRow
= aMinor
; aRow
< aNoValues
; aRow
++)
220 alpha
-= aQRTransposed
[aRow
+ aColumn
* aNoValues
] * aQRTransposed
[aRow
+ aMinor
* aNoValues
];
222 alpha
/= a
* aQRTransposed
[aMinor
+ aMinor
* aNoValues
];
224 for (sal_Int32 aRow
= aMinor
; aRow
< aNoValues
; aRow
++)
226 aQRTransposed
[aRow
+ aColumn
* aNoValues
] -= alpha
* aQRTransposed
[aRow
+ aMinor
* aNoValues
];
232 // Solve the linear equation
233 for (sal_Int32 aMinor
= 0; aMinor
< aMinorSize
; aMinor
++)
235 double aDotProduct
= 0;
237 for (sal_Int32 aRow
= aMinor
; aRow
< aNoValues
; aRow
++)
239 aDotProduct
+= yVector
[aRow
] * aQRTransposed
[aRow
+ aMinor
* aNoValues
];
241 aDotProduct
/= aDiagonal
[aMinor
] * aQRTransposed
[aMinor
+ aMinor
* aNoValues
];
243 for (sal_Int32 aRow
= aMinor
; aRow
< aNoValues
; aRow
++)
245 yVector
[aRow
] += aDotProduct
* aQRTransposed
[aRow
+ aMinor
* aNoValues
];
250 for (sal_Int32 aRow
= aDiagonal
.size() - 1; aRow
>= 0; aRow
--)
252 yVector
[aRow
] /= aDiagonal
[aRow
];
253 double yRow
= yVector
[aRow
];
254 mCoefficients
[aRow
] = yRow
;
256 for (sal_Int32 i
= 0; i
< aRow
; i
++)
258 yVector
[i
] -= yRow
* aQRTransposed
[i
+ aRow
* aNoValues
];
264 mCoefficients
.insert(mCoefficients
.begin(), mInterceptValue
);
267 // Calculate correlation coefficient
268 computeCorrelationCoefficient(aValues
, aNoValues
, yAverage
);
271 double SAL_CALL
PolynomialRegressionCurveCalculator::getCurveValue( double x
)
273 if (mCoefficients
.empty())
274 return std::numeric_limits
<double>::quiet_NaN();
276 sal_Int32 aNoCoefficients
= static_cast<sal_Int32
>(mCoefficients
.size());
279 double fResult
= 0.0;
280 for (sal_Int32 i
= aNoCoefficients
- 1; i
>= 0; i
--)
282 fResult
= mCoefficients
[i
] + (x
* fResult
);
287 OUString
PolynomialRegressionCurveCalculator::ImplGetRepresentation(
288 const uno::Reference
< util::XNumberFormatter
>& xNumFormatter
,
289 sal_Int32 nNumberFormatKey
, sal_Int32
* pFormulaMaxWidth
/* = nullptr */ ) const
291 OUStringBuffer
aBuf( mYName
+ " = " );
293 sal_Int32 nValueLength
=0;
294 sal_Int32 aLastIndex
= mCoefficients
.size() - 1;
296 if ( pFormulaMaxWidth
&& *pFormulaMaxWidth
> 0 )
298 sal_Int32 nCharMin
= aBuf
.getLength(); // count characters different from coefficients
299 double nCoefficients
= aLastIndex
+ 1.0; // number of coefficients
300 for (sal_Int32 i
= aLastIndex
; i
>= 0; i
--)
302 double aValue
= mCoefficients
[i
];
304 { // do not count coefficient if it is 0
308 if ( rtl::math::approxEqual( fabs( aValue
) , 1.0 ) )
309 { // do not count coefficient if it is 1
311 if ( i
== 0 ) // intercept = 1
314 if ( i
!= aLastIndex
)
315 nCharMin
+= 3; // " + "
318 nCharMin
+= mXName
.getLength() + 1; // " x"
320 nCharMin
+=1; // "^i"
322 nCharMin
++; // 2 digits for i
325 nValueLength
= ( *pFormulaMaxWidth
- nCharMin
) / nCoefficients
;
326 if ( nValueLength
<= 0 )
330 bool bFindValue
= false;
331 sal_Int32 nLineLength
= aBuf
.getLength();
332 for (sal_Int32 i
= aLastIndex
; i
>= 0; i
--)
334 double aValue
= mCoefficients
[i
];
335 OUStringBuffer
aTmpBuf(""); // temporary buffer
340 else if (aValue
< 0.0)
342 if ( bFindValue
) // if it is not the first aValue
343 aTmpBuf
.append( " " );
344 aTmpBuf
.append( OUStringChar(aMinusSign
) + " ");
349 if ( bFindValue
) // if it is not the first aValue
350 aTmpBuf
.append( " + " );
354 // if nValueLength not calculated then nullptr
355 sal_Int32
* pValueLength
= nValueLength
? &nValueLength
: nullptr;
356 OUString aValueString
= getFormattedString( xNumFormatter
, nNumberFormatKey
, aValue
, pValueLength
);
357 if ( i
== 0 || aValueString
!= "1" ) // aValueString may be rounded to 1 if nValueLength is small
359 aTmpBuf
.append( aValueString
);
360 if ( i
> 0 ) // insert blank between coefficient and x
361 aTmpBuf
.append( " " );
366 aTmpBuf
.append( mXName
);
369 if (i
< 10) // simple case if only one digit
370 aTmpBuf
.append( aSuperscriptFigures
[ i
] );
373 OUString aValueOfi
= OUString::number( i
);
374 for ( sal_Int32 n
= 0; n
< aValueOfi
.getLength() ; n
++ )
376 sal_Int32 nIndex
= aValueOfi
[n
] - u
'0';
377 aTmpBuf
.append( aSuperscriptFigures
[ nIndex
] );
382 addStringToEquation( aBuf
, nLineLength
, aTmpBuf
, pFormulaMaxWidth
);
384 if ( std::u16string_view(aBuf
) == Concat2View( mYName
+ " = ") )
387 return aBuf
.makeStringAndClear();
392 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */