From aa93bf92851ee0546074521ef6749a1916f089f5 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 15 Mar 2024 21:07:02 +0100 Subject: [PATCH] Library: cover statistic functions and linear regression --- src/lib/random.hpp | 2 +- src/lib/stat/statistic.hpp | 16 ++- tests/library/stat/statistic-test.cpp | 141 +++++++++++++++++------ wiki/thinkPad.ichthyo.mm | 156 ++++++++++++++++++++++---- 4 files changed, 252 insertions(+), 63 deletions(-) diff --git a/src/lib/random.hpp b/src/lib/random.hpp index 04f510b29..914d1a868 100644 --- a/src/lib/random.hpp +++ b/src/lib/random.hpp @@ -100,7 +100,7 @@ namespace lib { inline int rani() { return defaultGen.i32(); } inline uint64_t ranu() { return defaultGen.u64(); } - inline double rado() { return defaultGen.uni(); } + inline double runi() { return defaultGen.uni(); } /** inject true randomness into the #defaultGen */ diff --git a/src/lib/stat/statistic.hpp b/src/lib/stat/statistic.hpp index 3661b9bda..b0452580c 100644 --- a/src/lib/stat/statistic.hpp +++ b/src/lib/stat/statistic.hpp @@ -37,6 +37,7 @@ #include "lib/error.hpp" #include "lib/nocopy.hpp" +#include "lib/iter-adapter.hpp" #include "lib/format-string.hpp" #include "lib/util.hpp" @@ -119,14 +120,17 @@ namespace stat{ using iterator = const D*; + using const_iterator = iterator; size_t size() const { return e_ - b_; } bool empty() const { return b_ == e_;} iterator begin() const { return b_; } iterator end() const { return e_; } + friend const_iterator begin (DataSpan const& span){ return span.begin();} + friend const_iterator end (DataSpan const& span){ return span.end(); } - D const& operator[](size_t i) const { return b_ + i; } + D const& operator[](size_t i) const { return *(b_ + i); } D const& at(size_t i) const { if (i >= size()) @@ -136,6 +140,12 @@ namespace stat{ } }; + /** deduction guide: derive content from container. */ + template + DataSpan (CON const& container) -> DataSpan::value_type>; + + + /** summation of variances, for error propagation: √Σe² */ @@ -318,7 +328,7 @@ namespace stat{ inline auto computeLinearRegression (RegressionData const& points) { - return computeLinearRegression(DataSpan{points}); + return computeLinearRegression (DataSpan{points}); } @@ -359,7 +369,7 @@ namespace stat{ inline auto computeTimeSeriesLinearRegression (VecD const& series) { - return computeTimeSeriesLinearRegression(DataSpan{series}); + return computeTimeSeriesLinearRegression (DataSpan{series}); } }} // namespace lib::stat diff --git a/tests/library/stat/statistic-test.cpp b/tests/library/stat/statistic-test.cpp index 3cb6c14cd..f332e61a7 100644 --- a/tests/library/stat/statistic-test.cpp +++ b/tests/library/stat/statistic-test.cpp @@ -28,39 +28,26 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" #include "lib/stat/statistic.hpp" -//#include "lib/time/timevalue.hpp" -//#include "lib/error.hpp" -//#include "lib/util-foreach.hpp" +#include "lib/iter-explorer.hpp" +#include "lib/format-util.hpp" +#include "lib/random.hpp" +#include "lib/util.hpp" #include "lib/format-cout.hpp" ///////////////////////TODO #include "lib/test/diagnostic-output.hpp" ///////////////////////TODO -//#include -//#include - -//using util::for_each; -//using lumiera::Error; -//using lumiera::LUMIERA_ERROR_EXCEPTION; -//using lumiera::error::LUMIERA_ERROR_ASSERTION; -//using lib::time::TimeVar; -//using lib::time::Time; - -//using boost::algorithm::is_lower; -//using boost::algorithm::is_digit; -//using std::function; -//using std::string; namespace lib { namespace stat{ namespace test{ - template - class Wrmrmpft - { - T tt_; - }; - - struct Murpf { }; + namespace { + const size_t NUM_POINTS = 1'000; + } + + using lib::test::roughEQ; + using util::isnil; + using error::LUMIERA_ERROR_INVALID; /**************************************************************//** @@ -82,36 +69,118 @@ namespace test{ } - /** @test prints "sizeof()" including some type name. */ - void - check_baseStatistics () - { - } - - - + /** @test a simplified preview on C++20 ranges */ void demonstrate_DataSpan() { + auto dat = VecD{0,1,2,3,4,5}; + + DataSpan all{dat}; + CHECK (not isnil (all)); + CHECK (dat.size() == all.size()); + + auto i = all.begin(); + CHECK (i != all.end()); + CHECK (0 == *i); + ++i; + CHECK (1 == *i); + + DataSpan innr{*i, dat.back()}; + CHECK (util::join(innr) == "1, 2, 3, 4"_expect); + CHECK (2 == innr.at(1)); + CHECK (2 == innr[1]); + CHECK (4 == innr[3]); + CHECK (5 == innr[4]); // »undefined behaviour« + + VERIFY_ERROR (INVALID, innr.at(4) ) + + CHECK (1+2+3+4 == lib::explore(innr).resultSum()); } - /** @test check the VERIFY_ERROR macro, - * which ensures a given error is raised. + /** @test helpers to calculate mean and standard derivation */ + void + check_baseStatistics () + { + auto dat = VecD{4,2,5,8,6}; + DataSpan all = lastN(dat, dat.size()); + DataSpan rst = lastN(dat, 4); + CHECK (2 == *rst.begin()); + CHECK (4 == rst.size()); + CHECK (5 == all.size()); + + CHECK (5.0 == average (all)); + CHECK (5.25 == average(rst)); + + // Surprise : divide by N-1 since it is a guess for the real standard derivation + CHECK (sdev (all, 5.0) == sqrt(20/(5-1))); + + CHECK (5.0 == averageLastN (dat,20)); + CHECK (5.0 == averageLastN (dat, 5)); + CHECK (5.25 == averageLastN (dat, 4)); + CHECK (7.0 == averageLastN (dat, 2)); + CHECK (6.0 == averageLastN (dat, 1)); + CHECK (0.0 == averageLastN (dat, 0)); + } + + + /** @test attribute a weight to each data point going into linear regression + * - using a simple scenario with three points + * - a line with gradients would run through the end points (1,1) ⟶ (5,5) + * - but we have a middle point, offset by -2 and with double weight + * - thus the regression line is overall shifted by -1 + * - standard derivation is √3 and correlation 81% + * (both plausible and manually checked */ void check_wightedLinearRegression() { + RegressionData points{{1,1, 1} + ,{5,5, 1} + ,{3,1, 2} + }; + + auto [socket,gradient + ,predicted,deltas + ,correlation + ,maxDelta + ,sdev] = computeLinearRegression (points); + + CHECK (socket == -1); + CHECK (gradient == 1); + CHECK (util::join (predicted) == "0, 4, 2"_expect ); + CHECK (util::join (deltas) == "1, 1, -1"_expect ); + CHECK (maxDelta == 1); + CHECK (correlation == "0.81649658"_expect ); + CHECK (sdev == "1.7320508"_expect ); } - /** @test check a local manipulations, - * which are undone when leaving the scope. + + /** @test regression over a series of measurement data + * - use greater mount of data generated with randomness + * - actually a power function is _hidden in the data_ */ void check_TimeSeriesLinearRegression() { + auto dirt = [] { return runi() - 0.5; }; + auto fun = [&](uint i){ auto x = double(i)/NUM_POINTS; + return x*x; + }; + VecD data; + data.reserve (NUM_POINTS); + for (uint i=0; i 0.65); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 52fc6b94f..3f199f15f 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -57412,6 +57412,7 @@ + @@ -57441,6 +57442,11 @@ + + + + + @@ -57942,6 +57948,25 @@ + + + + + + + + +

+ ...ich brauche ein Ausführungs-Framework, damit man die einzelnen Fälle zu Greifen bekommt. +

+

+ Das impliziert leider auch eine Überarbeitung des Test-Frameworks +

+ +
+ +
+
@@ -111983,6 +112008,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ @@ -112040,16 +112066,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

was auch immer das ist....

- -
+
@@ -112059,16 +112082,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

empirisch(debug) ⟹ 17 Stellen

- -
+ @@ -112129,6 +112149,12 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + @@ -112136,16 +112162,13 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

Das ist naheliegend und kompromittiert das Design nicht wesentlich; File-Operationen können ohnehin immer Fehler auslösen, und da ein Pfad invalid sein kann, ist auch das Original-Design der Tabelle in dieser Hinsicht inhärent stateful.

- -
+ @@ -112165,8 +112188,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -112205,12 +112228,99 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...das ist grundsätzlicher Natur; da ich die eingebetteten Daten-Vectoren bewußt und explizit zugänglich gehalten habe (um auf weitergehende Zugriffsfunktionen verzichten zu können), kann man die Invarianten des Containers jederzeit unterlaufen. Ich habe jetzt nur eine Mitigation eingebaut, insofern das Hinzufügen neuer Zeilen die eingebetteten Daten beschneidet +

+ +
+
+ + + + +

+ Denn hier erfolgt der Feldzugriff sozusagen naïv, unter den Annahme daß niemand an den Einzeldaten manipuliert hat. Da die gesamte Semantik der Tabelle von der letzten Zeile her aufgebaut ist, wäre es korrekter, wenn man hier den Index an der nominellen DataFile::size() rückwärts verankern würde. Diese Überlegung zeigt aber, daß auch schon diese DataFile::size() unter der gleichen Annahme operiert, und damit zu einer Menge weiterere Inkonsistenzen führt +

+ +
+ + +
+ + + + +

+ Das Desig ist bewußt minimalistisch (und damit handwerklich orientiert): Es ist ein Werkzeug, keine Komponente aus dem Baukasten. +

+
    +
  • + entweder man müßte die Daten-Vectoren komplett einkapseln, würde dann aber ein gutes Stück der Eleganz in Statistik-Berechnungen verlieren +
  • +
  • + zudem müßte man dann das Konzept eines »Cursors« einführen +
  • +
  • + oder man müßte überall komplexen Prüfcode einbauen, der auch explizit Performance kostet +
  • +
+ + +
+ + +
+
+ + + + +