File indexing completed on 2026-04-19 07:25:00
0001 /* 0002 File : StatisticalPlotsTest.cpp 0003 Project : LabPlot 0004 Description : Tests for statistical plots like Q-Q plot, KDE plot, etc. 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2023 Alexander Semke <alexander.semke@web.de> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "StatisticalPlotsTest.h" 0012 #include "backend/core/Project.h" 0013 #include "backend/core/column/Column.h" 0014 #include "backend/spreadsheet/Spreadsheet.h" 0015 #include "backend/worksheet/Worksheet.h" 0016 #include "backend/worksheet/plots/cartesian/Histogram.h" 0017 #include "backend/worksheet/plots/cartesian/KDEPlot.h" 0018 #include "backend/worksheet/plots/cartesian/QQPlot.h" 0019 0020 #include <gsl/gsl_randist.h> 0021 #include <gsl/gsl_rng.h> 0022 0023 #include <QUndoStack> 0024 0025 // ############################################################################## 0026 // ############################## Histogram ##################################### 0027 // ############################################################################## 0028 /*! 0029 * \brief create and add a new Histogram, undo and redo this step 0030 */ 0031 void StatisticalPlotsTest::testHistogramInit() { 0032 Project project; 0033 auto* ws = new Worksheet(QStringLiteral("worksheet")); 0034 project.addChild(ws); 0035 0036 auto* p = new CartesianPlot(QStringLiteral("plot")); 0037 ws->addChild(p); 0038 0039 auto* histogram = new Histogram(QStringLiteral("histogram")); 0040 p->addChild(histogram); 0041 0042 auto children = p->children<Histogram>(); 0043 0044 QCOMPARE(children.size(), 1); 0045 0046 project.undoStack()->undo(); 0047 children = p->children<Histogram>(); 0048 QCOMPARE(children.size(), 0); 0049 0050 project.undoStack()->redo(); 0051 children = p->children<Histogram>(); 0052 QCOMPARE(children.size(), 1); 0053 } 0054 0055 /*! 0056 * \brief create and add a new Histogram, duplicate it and check the number of children 0057 */ 0058 void StatisticalPlotsTest::testHistogramDuplicate() { 0059 Project project; 0060 auto* ws = new Worksheet(QStringLiteral("worksheet")); 0061 project.addChild(ws); 0062 0063 auto* p = new CartesianPlot(QStringLiteral("plot")); 0064 ws->addChild(p); 0065 0066 auto* histogram = new Histogram(QStringLiteral("kdeplot")); 0067 p->addChild(histogram); 0068 0069 histogram->duplicate(); 0070 0071 auto children = p->children<Histogram>(); 0072 QCOMPARE(children.size(), 2); 0073 } 0074 0075 /*! 0076 * \brief create a Histogram for 3 values check the plot ranges. 0077 */ 0078 void StatisticalPlotsTest::testHistogramRangeBinningTypeChanged() { 0079 // prepare the data 0080 Spreadsheet sheet(QStringLiteral("test"), false); 0081 sheet.setColumnCount(1); 0082 sheet.setRowCount(100); 0083 auto* column = sheet.column(0); 0084 column->setValueAt(0, 1.); 0085 column->setValueAt(1, 2.); 0086 column->setValueAt(2, 3.); 0087 0088 // prepare the worksheet + plot 0089 auto* ws = new Worksheet(QStringLiteral("worksheet")); 0090 auto* p = new CartesianPlot(QStringLiteral("plot")); 0091 ws->addChild(p); 0092 0093 auto* histogram = new Histogram(QStringLiteral("kdeplot")); 0094 histogram->setBinningMethod(Histogram::BinningMethod::ByNumber); 0095 histogram->setBinCount(3); 0096 histogram->setDataColumn(column); 0097 p->addChild(histogram); 0098 0099 // the x-range is defined by the min and max values in the data [1, 3] 0100 // because of the bin count 3 we have one value in every bin and the y-range is [0,1] 0101 const auto& rangeX = p->range(Dimension::X); 0102 const auto& rangeY = p->range(Dimension::Y); 0103 QCOMPARE(rangeX.start(), 1); 0104 QCOMPARE(rangeX.end(), 3); 0105 QCOMPARE(rangeY.start(), 0); 0106 QCOMPARE(rangeY.end(), 1); 0107 0108 // set the bin number to 1, the values 1 and 2 fall into the same bin 0109 histogram->setBinCount(1); 0110 QCOMPARE(rangeX.start(), 1); 0111 QCOMPARE(rangeX.end(), 3); 0112 QCOMPARE(rangeY.start(), 0); 0113 QCOMPARE(rangeY.end(), 2); 0114 } 0115 0116 /*! 0117 * \brief create a Histogram for 3 values check the plot ranges after a row was removed in the source spreadsheet. 0118 */ 0119 void StatisticalPlotsTest::testHistogramRangeRowsChanged() { 0120 Project project; 0121 0122 // prepare the data 0123 auto* sheet = new Spreadsheet(QStringLiteral("test"), false); 0124 sheet->setColumnCount(1); 0125 sheet->setRowCount(3); 0126 project.addChild(sheet); 0127 auto* column = sheet->column(0); 0128 column->setValueAt(0, 1.); 0129 column->setValueAt(1, 2.); 0130 column->setValueAt(2, 3.); 0131 0132 // worksheet 0133 auto* ws = new Worksheet(QStringLiteral("worksheet")); 0134 project.addChild(ws); 0135 0136 auto* p = new CartesianPlot(QStringLiteral("plot")); 0137 ws->addChild(p); 0138 0139 auto* histogram = new Histogram(QStringLiteral("kdeplot")); 0140 histogram->setBinningMethod(Histogram::BinningMethod::ByNumber); 0141 histogram->setBinCount(3); 0142 histogram->setDataColumn(column); 0143 p->addChild(histogram); 0144 0145 // remove the last row and check the ranges, the x-range should become [1,2] 0146 sheet->setRowCount(2); 0147 const auto& rangeX = p->range(Dimension::X); 0148 const auto& rangeY = p->range(Dimension::Y); 0149 QCOMPARE(rangeX.start(), 1); 0150 QCOMPARE(rangeX.end(), 2); 0151 QCOMPARE(rangeY.start(), 0); 0152 QCOMPARE(rangeY.end(), 1); 0153 0154 // undo the row removal and check again, the x-range should become [1,3] again 0155 project.undoStack()->undo(); 0156 QCOMPARE(rangeX.start(), 1); 0157 QCOMPARE(rangeX.end(), 3); 0158 QCOMPARE(rangeY.start(), 0); 0159 QCOMPARE(rangeY.end(), 1); 0160 0161 // add more (empty) rows in the spreadsheet, the ranges should be unchanged 0162 sheet->setRowCount(5); 0163 QCOMPARE(rangeX.start(), 1); 0164 QCOMPARE(rangeX.end(), 3); 0165 QCOMPARE(rangeY.start(), 0); 0166 QCOMPARE(rangeY.end(), 1); 0167 } 0168 0169 void StatisticalPlotsTest::testHistogramColumnRemoved() { 0170 Project project; 0171 auto* ws = new Worksheet(QStringLiteral("worksheet")); 0172 project.addChild(ws); 0173 0174 auto* p = new CartesianPlot(QStringLiteral("plot")); 0175 ws->addChild(p); 0176 0177 auto* histogram = new Histogram(QStringLiteral("histogram")); 0178 p->addChild(histogram); 0179 0180 auto* c = new Column(QStringLiteral("TestColumn")); 0181 project.addChild(c); 0182 0183 histogram->setDataColumn(c); 0184 c->setName(QStringLiteral("NewName")); 0185 QCOMPARE(histogram->dataColumnPath(), QStringLiteral("Project/NewName")); 0186 0187 c->remove(); 0188 0189 QCOMPARE(histogram->dataColumn(), nullptr); 0190 QCOMPARE(histogram->dataColumnPath(), QStringLiteral("Project/NewName")); 0191 0192 c->setName(QStringLiteral("Another new name")); // Shall not lead to a crash 0193 0194 QCOMPARE(histogram->dataColumn(), nullptr); 0195 QCOMPARE(histogram->dataColumnPath(), QStringLiteral("Project/NewName")); 0196 } 0197 0198 // ############################################################################## 0199 // ############################## KDE Plot ###################################### 0200 // ############################################################################## 0201 0202 /*! 0203 * \brief create and add a new KDEPlot, undo and redo this step 0204 */ 0205 void StatisticalPlotsTest::testKDEPlotInit() { 0206 Project project; 0207 auto* ws = new Worksheet(QStringLiteral("worksheet")); 0208 project.addChild(ws); 0209 0210 auto* p = new CartesianPlot(QStringLiteral("plot")); 0211 ws->addChild(p); 0212 0213 auto* kdePlot = new KDEPlot(QStringLiteral("kdeplot")); 0214 p->addChild(kdePlot); 0215 0216 auto children = p->children<KDEPlot>(); 0217 0218 QCOMPARE(children.size(), 1); 0219 0220 project.undoStack()->undo(); 0221 children = p->children<KDEPlot>(); 0222 QCOMPARE(children.size(), 0); 0223 0224 // TODO: crash!!! 0225 // project.undoStack()->redo(); 0226 // children = p->children<KDEPlot>(); 0227 // QCOMPARE(children.size(), 1); 0228 } 0229 0230 /*! 0231 * \brief create and add a new KDEPlot, duplicate it and check the number of children 0232 */ 0233 void StatisticalPlotsTest::testKDEPlotDuplicate() { 0234 Project project; 0235 auto* ws = new Worksheet(QStringLiteral("worksheet")); 0236 project.addChild(ws); 0237 0238 auto* p = new CartesianPlot(QStringLiteral("plot")); 0239 ws->addChild(p); 0240 0241 auto* kdePlot = new KDEPlot(QStringLiteral("kdeplot")); 0242 p->addChild(kdePlot); 0243 0244 kdePlot->duplicate(); 0245 0246 auto children = p->children<KDEPlot>(); 0247 QCOMPARE(children.size(), 2); 0248 } 0249 0250 /*! 0251 * \brief create a KDE plot for 3 values check the plot ranges. 0252 */ 0253 void StatisticalPlotsTest::testKDEPlotRange() { 0254 // prepare the data 0255 Spreadsheet sheet(QStringLiteral("test"), false); 0256 sheet.setColumnCount(1); 0257 sheet.setRowCount(100); 0258 auto* column = sheet.column(0); 0259 column->setValueAt(0, 2.); 0260 column->setValueAt(1, 4.); 0261 column->setValueAt(2, 6.); 0262 0263 // prepare the worksheet + plot 0264 auto* ws = new Worksheet(QStringLiteral("worksheet")); 0265 auto* p = new CartesianPlot(QStringLiteral("plot")); 0266 ws->addChild(p); 0267 0268 auto* kdePlot = new KDEPlot(QStringLiteral("kdeplot")); 0269 kdePlot->setKernelType(nsl_kernel_gauss); 0270 kdePlot->setBandwidthType(nsl_kde_bandwidth_custom); 0271 kdePlot->setBandwidth(0.3); 0272 p->addChild(kdePlot); 0273 kdePlot->setDataColumn(column); 0274 0275 // validate with R via: 0276 // data <- c(2,4,6); 0277 // kd <- density(data,kernel="gaussian", bw=0.3) 0278 // plot(kd, col='blue', lwd=2) 0279 0280 // check the x-range of the plot which should be [1, 7] (subtract/add 3 sigmas from/to min and max, respectively). 0281 const auto& rangeX = p->range(Dimension::X); 0282 QCOMPARE(rangeX.start(), 1); 0283 QCOMPARE(rangeX.end(), 7); 0284 0285 // check the y-range of the plot which should be [0, 0.45] 0286 const auto& rangeY = p->range(Dimension::Y); 0287 QCOMPARE(rangeY.start(), 0.); 0288 QCOMPARE(rangeY.end(), 0.45); 0289 } 0290 0291 // ############################################################################## 0292 // ############################## Q-Q Plot ###################################### 0293 // ############################################################################## 0294 0295 /*! 0296 * \brief create and add a new QQPlot, undo and redo this step 0297 */ 0298 void StatisticalPlotsTest::testQQPlotInit() { 0299 Project project; 0300 auto* ws = new Worksheet(QStringLiteral("worksheet")); 0301 project.addChild(ws); 0302 0303 auto* p = new CartesianPlot(QStringLiteral("plot")); 0304 ws->addChild(p); 0305 0306 auto* qqPlot = new QQPlot(QStringLiteral("qqplot")); 0307 p->addChild(qqPlot); 0308 0309 auto children = p->children<QQPlot>(); 0310 0311 QCOMPARE(children.size(), 1); 0312 0313 project.undoStack()->undo(); 0314 children = p->children<QQPlot>(); 0315 QCOMPARE(children.size(), 0); 0316 0317 // TODO: crash!!! 0318 // project.undoStack()->redo(); 0319 // children = p->children<QQPlot>(); 0320 // QCOMPARE(children.size(), 1); 0321 } 0322 0323 /*! 0324 * \brief create and add a new QQPlot, duplicate it and check the number of children 0325 */ 0326 void StatisticalPlotsTest::testQQPlotDuplicate() { 0327 Project project; 0328 auto* ws = new Worksheet(QStringLiteral("worksheet")); 0329 project.addChild(ws); 0330 0331 auto* p = new CartesianPlot(QStringLiteral("plot")); 0332 ws->addChild(p); 0333 0334 auto* qqPlot = new QQPlot(QStringLiteral("qqplot")); 0335 p->addChild(qqPlot); 0336 0337 qqPlot->duplicate(); 0338 0339 auto children = p->children<QQPlot>(); 0340 QCOMPARE(children.size(), 2); 0341 } 0342 0343 /*! 0344 * \brief create QQPlot for 100 normaly distributed values and check the plot ranges. 0345 */ 0346 void StatisticalPlotsTest::testQQPlotRange() { 0347 // prepare the data 0348 Spreadsheet sheet(QStringLiteral("test"), false); 0349 sheet.setColumnCount(1); 0350 sheet.setRowCount(100); 0351 auto* column = sheet.column(0); 0352 0353 // create a generator chosen by the environment variable GSL_RNG_TYPE 0354 gsl_rng_env_setup(); 0355 const gsl_rng_type* T = gsl_rng_default; 0356 gsl_rng* r = gsl_rng_alloc(T); 0357 const double sigma = 1.; 0358 0359 for (int i = 0; i < 100; ++i) 0360 column->setValueAt(i, gsl_ran_gaussian(r, sigma)); 0361 0362 // prepare the worksheet + plot 0363 auto* ws = new Worksheet(QStringLiteral("worksheet")); 0364 auto* p = new CartesianPlot(QStringLiteral("plot")); 0365 ws->addChild(p); 0366 0367 auto* qqPlot = new QQPlot(QStringLiteral("qqplot")); 0368 p->addChild(qqPlot); 0369 qqPlot->setDataColumn(column); 0370 0371 // check the x-range of the plot which should be [-2.5, 2.5] for the theoretical quantiles 0372 const auto& range = p->range(Dimension::X); 0373 QCOMPARE(range.start(), -2.5); 0374 QCOMPARE(range.end(), 2.5); 0375 } 0376 0377 QTEST_MAIN(StatisticalPlotsTest)