diff --git a/src/backend/generalTest/CorrelationCoefficient.cpp b/src/backend/generalTest/CorrelationCoefficient.cpp index c10e5dff8..fb9143bf1 100644 --- a/src/backend/generalTest/CorrelationCoefficient.cpp +++ b/src/backend/generalTest/CorrelationCoefficient.cpp @@ -1,119 +1,197 @@ /*************************************************************************** File : CorrelationCoefficient.cpp Project : LabPlot Description : Finding Correlation Coefficient on data provided -------------------------------------------------------------------- Copyright : (C) 2019 Devanshu Agarwal(agarwaldevanshu8@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CorrelationCoefficient.h" #include "GeneralTest.h" #include "kdefrontend/generalTest/CorrelationCoefficientView.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/core/column/Column.h" #include "backend/lib/macros.h" #include #include #include #include #include #include #include #include #include -#include #include +#include extern "C" { #include "backend/nsl/nsl_stats.h" } CorrelationCoefficient::CorrelationCoefficient(const QString &name) : GeneralTest (name, AspectType::CorrelationCoefficient) { } CorrelationCoefficient::~CorrelationCoefficient() { } void CorrelationCoefficient::performTest(Test test, bool categoricalVariable) { m_statsTable = ""; m_tooltips.clear(); for (int i = 0; i < 10; i++) m_resultLine[i]->clear(); switch (test) { case CorrelationCoefficient::Test::Pearson: { m_currTestName = "

" + i18n("Pearson's r Correlation Test") + "

"; performPearson(categoricalVariable); break; } case CorrelationCoefficient::Test::Kendall: m_currTestName = "

" + i18n("Kendall's Correlation Test") + "

"; performKendall(); break; case CorrelationCoefficient::Test::Spearman: { m_currTestName = "

" + i18n("Spearman Correlation Test") + "

"; performSpearman(); break; } } emit changed(); } double CorrelationCoefficient::correlationValue() { return m_correlationValue; } /*************************************************************************************************************************** * Private Implementations * ************************************************************************************************************************/ /*********************************************Pearson r ******************************************************************/ +// variables: +// N = total number of observations +// sumColx = sum of values in colx +// sumSqColx = sum of square of values in colx +// sumColxColy = sum of product of values in colx and coly + +//TODO: support for col1 is categorical. +//TODO: add symbols in stats table header. void CorrelationCoefficient::performPearson(bool categoricalVariable) { - Q_UNUSED(categoricalVariable); + if (m_columns.count() != 2) { + printError("Select only 2 columns "); + return; + } + + if (categoricalVariable) { + printLine(1, "currently categorical variable not supported", "blue"); + return; + } + + QString col1Name = m_columns[0]->name(); + QString col2Name = m_columns[1]->name(); + + + if (!isNumericOrInteger(m_columns[1])) { + printError("Column " + col2Name + " should contain only numeric or interger values"); + } + + + int N = findCount(m_columns[0]); + if (N != findCount(m_columns[1])) { + printError("Number of data values in Column: " + col1Name + "and Column: " + col2Name + "are not equal"); + return; + } + + double sumCol1 = findSum(m_columns[0], N); + double sumCol2 = findSum(m_columns[1], N); + double sumSqCol1 = findSumSq(m_columns[0], N); + double sumSqCol2 = findSumSq(m_columns[1], N); + + double sumCol12 = 0; + + for (int i = 0; i < N; i++) + sumCol12 += m_columns[0]->valueAt(i) * + m_columns[1]->valueAt(i); + + // printing table; + // cell constructor structure; data, level, rowSpanCount, m_columnspanCount, isHeader; + QList rowMajor; + int level = 0; + + // horizontal header + rowMajor.append(new Cell("", level, true)); + rowMajor.append(new Cell("N", level, true, "Total Number of Observations")); + rowMajor.append(new Cell("Sigma", level, true, "Sum of Scores in each column")); + rowMajor.append(new Cell("Sigma x2", level, true, "Sum of Squares of scores in each column")); + rowMajor.append(new Cell("Sigma xy", level, true, "Sum of Squares of scores in each column")); + + //data with vertical header. + level++; + rowMajor.append(new Cell(col1Name, level, true)); + rowMajor.append(new Cell(N, level)); + rowMajor.append(new Cell(sumCol1, level)); + rowMajor.append(new Cell(sumSqCol1, level)); + + rowMajor.append(new Cell(sumCol12, level, false, "", 2, 1)); + + level++; + rowMajor.append(new Cell(col2Name, level, true)); + rowMajor.append(new Cell(N, level)); + rowMajor.append(new Cell(sumCol2, level)); + rowMajor.append(new Cell(sumSqCol2, level)); + + m_statsTable += getHtmlTable3(rowMajor); + + + m_correlationValue = (N * sumCol12 - sumCol1*sumCol2) / + sqrt((N * sumSqCol1 - gsl_pow_2(sumCol1)) * + (N * sumSqCol2 - gsl_pow_2(sumCol2))); + + printLine(0, QString("Correlation Value is %1").arg(m_correlationValue), "green"); } /***********************************************Kendall ******************************************************************/ void CorrelationCoefficient::performKendall() { } /***********************************************Spearman ******************************************************************/ void CorrelationCoefficient::performSpearman() { } // Virtual functions QWidget* CorrelationCoefficient::view() const { if (!m_partView) { m_view = new CorrelationCoefficientView(const_cast(this)); m_partView = m_view; } return m_partView; } diff --git a/src/backend/generalTest/GeneralTest.cpp b/src/backend/generalTest/GeneralTest.cpp index 7ebcfc477..5a8b8c278 100644 --- a/src/backend/generalTest/GeneralTest.cpp +++ b/src/backend/generalTest/GeneralTest.cpp @@ -1,484 +1,557 @@ -/*************************************************************************** +/*************************************************************************** File : GeneralTest.cpp Project : LabPlot Description : Doing Hypothesis-Test on data provided -------------------------------------------------------------------- Copyright : (C) 2019 Devanshu Agarwal(agarwaldevanshu8@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "GeneralTest.h" #include "kdefrontend/generalTest/HypothesisTestView.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/core/column/Column.h" #include "backend/lib/macros.h" //#include //#include //#include #include #include //#include //#include //#include #include #include #include extern "C" { #include "backend/nsl/nsl_stats.h" } GeneralTest::GeneralTest(const QString &name, const AspectType& type) : AbstractPart(name, type), m_summaryLayout(new QVBoxLayout()) { for (int i = 0; i < 10; i++) { m_resultLine[i] = new QLabel(); m_summaryLayout->addWidget(m_resultLine[i]); } } GeneralTest::~GeneralTest() { } void GeneralTest::setDataSourceType(DataSourceType type) { if (type != m_dataSourceType) m_dataSourceType = type; } GeneralTest::DataSourceType GeneralTest::dataSourceType() const { return m_dataSourceType; } void GeneralTest::setDataSourceSpreadsheet(Spreadsheet* spreadsheet) { m_dataSourceSpreadsheet = spreadsheet; for (auto* col : m_dataSourceSpreadsheet->children()) m_allColumns << col->name(); } QString GeneralTest::testName() { return m_currTestName; } QString GeneralTest::statsTable() { return m_statsTable; } QMap GeneralTest::tooltips() { return m_tooltips; } QVBoxLayout* GeneralTest::summaryLayout() { return m_summaryLayout; } void GeneralTest::setColumns(QStringList cols) { m_columns.clear(); Column* column = new Column("column"); for (QString col : cols) { if (!cols.isEmpty()) { column = m_dataSourceSpreadsheet->column(col); m_columns.append(column); } } delete[] column; } void GeneralTest::setColumns(const QVector &cols) { m_columns = cols; } /******************************************************************************************************************** * Protected functions implementations [Helper Functions] ********************************************************************************************************************/ QString GeneralTest::round(QVariant number, int precision) { if (number.userType() == QMetaType::Double || number.userType() == QMetaType::Float) { double multiplierPrecision = gsl_pow_int(10, precision); int tempNum = int(number.toDouble()*multiplierPrecision*10); if (tempNum % 10 < 5) return QString::number((tempNum/10) / multiplierPrecision); else return QString::number((tempNum/10 + 1) / multiplierPrecision); } return i18n("%1", number.toString()); } -bool GeneralTest::isNumericOrInteger(Column* column) { +bool GeneralTest::isNumericOrInteger(const Column* column) { return (column->columnMode() == AbstractColumn::Numeric || column->columnMode() == AbstractColumn::Integer); } -GeneralTest::ErrorType GeneralTest::findStats(const Column* column, int& count, double& sum, double& mean, double& std) { - sum = 0; - mean = 0; - std = 0; - count = column->rowCount(); - for (int i = 0; i < count; i++) { - double row = column->valueAt(i); - if ( std::isnan(row)) { - count = i; - break; - } - sum += row; +int GeneralTest::findCount(const Column *column) { + int N = column->rowCount(); + switch (column->columnMode()) { + case (AbstractColumn::Numeric): + case (AbstractColumn::Integer): { + for (int i = 0; i < N; i++) + if (std::isnan(column->valueAt(i))) { + N = i; + break; + } + break; + } + case (AbstractColumn::Month): + case (AbstractColumn::Day): + case (AbstractColumn::Text): { + for (int i = 0; i < N; i++) + if (column->textAt(i).isEmpty()) { + N = i; + break; + } + break; } + case (AbstractColumn::DateTime): + break; + } + return N; +} - if (count < 1) - return GeneralTest::ErrorEmptyColumn; +double GeneralTest::findSum(const Column *column, int N) { + if (!isNumericOrInteger(column)) + return 0; - mean = sum / count; + if (N < 0) + N = findCount(column); - for (int i = 0; i < count; i++) { + double sum = 0; + for (int i = 0; i < N; i++) + sum += column->valueAt(i); + return sum; +} + +double GeneralTest::findSumSq(const Column *column, int N) { + if (!isNumericOrInteger(column)) + return 0; + + if (N < 0) + N = findCount(column); + + double sumSq = 0; + for (int i = 0; i < N; i++) + sumSq += gsl_pow_2(column->valueAt(i)); + return sumSq; +} + +double GeneralTest::findMean(const Column *column, int N) { + if (!isNumericOrInteger(column)) + return 0; + + if (N < 0) + N = findCount(column); + + double sum = findSum(column, N); + return sum / N; +} + +double GeneralTest::findStd(const Column *column, int N, double mean) { + if (!isNumericOrInteger(column)) + return 0; + + double std = 0; + for (int i = 0; i < N; i++) { double row = column->valueAt(i); std += gsl_pow_2( (row - mean)); } - if (count > 1) - std = std / (count-1); + if (N > 1) + std = std / (N-1); std = sqrt(std); + return std; +} + +double GeneralTest::findStd(const Column *column, int N) { + if (!isNumericOrInteger(column)) + return 0; + + if (N < 0) + N = findCount(column); + + double mean = findMean(column, N); + return findStd(column, N, mean); +} + +GeneralTest::ErrorType GeneralTest::findStats(const Column* column, int& count, double& sum, double& mean, double& std) { + count = findCount(column); + sum = findSum(column, count); + mean = findMean(column, count); + std = findStd(column, count, mean); + + if (count < 1) + return GeneralTest::ErrorEmptyColumn; return GeneralTest::NoError; } GeneralTest::ErrorType GeneralTest::findStatsPaired(const Column* column1, const Column* column2, int& count, double& sum, double& mean, double& std) { sum = 0; mean = 0; std = 0; int count1 = column1->rowCount(); int count2 = column2->rowCount(); count = qMin(count1, count2); double cell1, cell2; for (int i = 0; i < count; i++) { cell1 = column1->valueAt(i); cell2 = column2->valueAt(i); if (std::isnan(cell1) || std::isnan(cell2)) { if (std::isnan(cell1) && std::isnan(cell2)) count = i; else return GeneralTest::ErrorUnqualSize; break; } sum += cell1 - cell2; } if (count < 1) return GeneralTest::ErrorEmptyColumn; mean = sum / count; double row; for (int i = 0; i < count; i++) { cell1 = column1->valueAt(i); cell2 = column2->valueAt(i); row = cell1 - cell2; std += gsl_pow_2( (row - mean)); } if (count > 1) std = std / (count-1); std = sqrt(std); return GeneralTest::NoError; } void GeneralTest::countPartitions(Column* column, int& np, int& totalRows) { totalRows = column->rowCount(); np = 0; QString cellValue; QMap discoveredCategoricalVar; AbstractColumn::ColumnMode originalColMode = column->columnMode(); column->setColumnMode(AbstractColumn::Text); for (int i = 0; i < totalRows; i++) { cellValue = column->textAt(i); if (cellValue.isEmpty()) { totalRows = i; break; } if (discoveredCategoricalVar[cellValue]) continue; discoveredCategoricalVar[cellValue] = true; np++; } column->setColumnMode(originalColMode); } GeneralTest::ErrorType GeneralTest::findStatsCategorical(Column* column1, Column* column2, int n[], double sum[], double mean[], double std[], QMap& colName, const int& np, const int& totalRows) { Column* columns[] = {column1, column2}; for (int i = 0; i < np; i++) { n[i] = 0; sum[i] = 0; mean[i] = 0; std[i] = 0; } AbstractColumn::ColumnMode originalColMode = columns[0]->columnMode(); columns[0]->setColumnMode(AbstractColumn::Text); int partitionNumber = 1; for (int i = 0; i < totalRows; i++) { QString name = columns[0]->textAt(i); double value = columns[1]->valueAt(i); if (std::isnan(value)) { columns[0]->setColumnMode(originalColMode); return GeneralTest::ErrorUnqualSize; } if (colName[name] == 0) { colName[name] = partitionNumber; partitionNumber++; } n[colName[name]-1]++; sum[colName[name]-1] += value; } for (int i = 0; i < np; i++) mean[i] = sum[i] / n[i]; for (int i = 0; i < totalRows; i++) { QString name = columns[0]->textAt(i); double value = columns[1]->valueAt(i); std[colName[name]-1] += gsl_pow_2( (value - mean[colName[name]-1])); } for (int i = 0; i < np; i++) { if (n[i] > 1) std[i] = std[i] / (n[i] - 1); std[i] = sqrt(std[i]); } columns[0]->setColumnMode(originalColMode); if (isNumericOrInteger(columns[0])) { } return GeneralTest::NoError; } QString GeneralTest::getHtmlTable(int row, int column, QVariant* rowMajor) { if (row < 1 || column < 1) return QString(); QString table; table = "" "" " "; QString bg = "tg-0pky"; bool pky = true; QString element; table += " "; for (int j = 0; j < column; j++) { element = rowMajor[j].toString(); table += " "; } table += " "; if (pky) bg = "tg-0pky"; else bg = "tg-btxf"; pky = !pky; for (int i = 1; i < row; i++) { table += " "; QString element = round(rowMajor[i*column]); table += " "; for (int j = 1; j < column; j++) { element = round(rowMajor[i*column+j]); table += " "; } table += " "; if (pky) bg = "tg-0pky"; else bg = "tg-btxf"; pky = !pky; } table += "
" + i18n("%1", element) + "
" + i18n("%1", element) + "" + i18n("%1", element) + "
"; return table; } QString GeneralTest::getHtmlTable3(const QList& rowMajor) { m_tooltips.clear(); int rowMajorSize = rowMajor.size(); if (rowMajorSize == 0) return QString(); QString table; table = ""; table += ""; table += " "; int prevLevel = 0; for (int i = 0; i < rowMajorSize; i++) { Cell* currCell = rowMajor[i]; if (currCell->level != prevLevel) { table += " "; table += " "; prevLevel = currCell->level; } QString cellStartTag = ""; table += "
isHeader) { cellStartTag = "" + i18n("%1", currCell->data) + cellEndTag; if (!currCell->tooltip.isEmpty()) m_tooltips.insert(currCell->data, currCell->tooltip); } table += "
"; return table; } QString GeneralTest::getLine(const QString& msg, const QString& color) { return "

" + i18n("%1", msg) + "

"; } void GeneralTest::printLine(const int& index, const QString& msg, const QString& color) { if (index < 0 || index >= 10) return; m_resultLine[index]->setText(getLine(msg, color)); return; } void GeneralTest::printTooltip(const int &index, const QString &msg) { if (index < 0 || index >= 10) return; m_resultLine[index]->setToolTip(i18n("%1", msg)); } void GeneralTest::printError(const QString& errorMsg) { printLine(0, errorMsg, "red"); } /******************************************************************************************************************** * virtual functions implementations ********************************************************************************************************************/ /*! Saves as XML. */ void GeneralTest::save(QXmlStreamWriter* writer) const { writer->writeStartElement("GeneralTest"); writeBasicAttributes(writer); writeCommentElement(writer); writer->writeEndElement(); } /*! Loads from XML. */ bool GeneralTest::load(XmlStreamReader* reader, bool preview) { Q_UNUSED(preview); if (!readBasicAttributes(reader)) return false; return !reader->hasError(); } Spreadsheet *GeneralTest::dataSourceSpreadsheet() const { return m_dataSourceSpreadsheet; } bool GeneralTest::exportView() const { return true; } bool GeneralTest::printView() { return true; } bool GeneralTest::printPreview() const { return true; } /*! Constructs a primary view on me. This method may be called multiple times during the life time of an Aspect, or it might not get called at all. Aspects must not depend on the existence of a view for their operation. */ //QWidget* GeneralTest::view() const { // if (!m_partView) { // m_view = new HypothesisTestView(const_cast(this)); // m_partView = m_view; // } // return m_partView; //} /*! Returns a new context menu. The caller takes ownership of the menu. */ QMenu* GeneralTest::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); // Q_ASSERT(menu); // emit requestProjectContextMenu(menu); return menu; } diff --git a/src/backend/generalTest/GeneralTest.h b/src/backend/generalTest/GeneralTest.h index 97085b470..6e313ad73 100644 --- a/src/backend/generalTest/GeneralTest.h +++ b/src/backend/generalTest/GeneralTest.h @@ -1,135 +1,146 @@ /*************************************************************************** File : GeneralTest.h Project : LabPlot Description : Doing Hypothesis-Test on data provided -------------------------------------------------------------------- Copyright : (C) 2019 Devanshu Agarwal(agarwaldevanshu8@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef GENERALTEST_H #define GENERALTEST_H #include "backend/core/AbstractPart.h" #include "backend/lib/macros.h" class GeneralTestView; class Spreadsheet; class QString; class Column; class QVBoxLayout; class QLabel; class GeneralTest : public AbstractPart { Q_OBJECT public: explicit GeneralTest(const QString& name, const AspectType& type); ~GeneralTest() override; enum DataSourceType {DataSourceSpreadsheet, DataSourceDatabase}; struct Cell { QString data; int level; bool isHeader; QString tooltip; int rowSpanCount; int columnSpanCount; Cell(QVariant data = "", int level = 0, bool isHeader = false, QString tooltip = "", int rowSpanCount = 1, int columnSpanCount = 1) { this->data = data.toString(); this->level = level; this->isHeader = isHeader; this->tooltip = tooltip; this->rowSpanCount = rowSpanCount; this->columnSpanCount = columnSpanCount; } }; enum ErrorType {ErrorUnqualSize, ErrorEmptyColumn, NoError}; void setDataSourceType(DataSourceType type); DataSourceType dataSourceType() const; void setDataSourceSpreadsheet(Spreadsheet* spreadsheet); Spreadsheet* dataSourceSpreadsheet() const; void setColumns(const QVector& cols); void setColumns(QStringList cols); QStringList allColumns(); QString testName(); QString statsTable(); QMap tooltips(); QVBoxLayout* summaryLayout(); //virtual methods // QIcon icon() const override; QMenu* createContextMenu() override; // QWidget* view() const override; bool exportView() const override; bool printView() override; bool printPreview() const override; void save(QXmlStreamWriter*) const override; bool load(XmlStreamReader*, bool preview) override; signals: void changed(); void requestProjectContextMenu(QMenu*); void dataSourceTypeChanged(GeneralTest::DataSourceType); void dataSourceSpreadsheetChanged(Spreadsheet*); protected: DataSourceType m_dataSourceType{GeneralTest::DataSourceSpreadsheet}; Spreadsheet* m_dataSourceSpreadsheet{nullptr}; QVector m_columns; QStringList m_allColumns; QString m_currTestName{"Result Table"}; QString m_statsTable; QVBoxLayout* m_summaryLayout{nullptr}; QLabel* m_resultLine[10]; QMap m_tooltips; - bool isNumericOrInteger(Column* column); + bool isNumericOrInteger(const Column* column); QString round(QVariant number, int precision = 3); + int findCount(const Column* column); + double findSum(const Column* column, int N = -1); + double findSumSq(const Column* column, int N = -1); + double findMean(const Column* column, int N = -1); + double findStd(const Column* column, int N, double mean); + double findStd(const Column* column, int N = -1); + void countPartitions(Column* column, int& np, int& totalRows); + + +// double findSumProducts(const Column* columns[], int N = -1); + ErrorType findStats(const Column* column,int& count, double& sum, double& mean, double& std); ErrorType findStatsPaired(const Column* column1, const Column* column2, int& count, double& sum, double& mean, double& std); ErrorType findStatsCategorical(Column* column1, Column* column2, int n[], double sum[], double mean[], double std[], QMap& colName, const int& np, const int& totalRows); QString getHtmlTable(int row, int column, QVariant* rowMajor); QString getHtmlTable3(const QList& rowMajor); QString getLine(const QString& msg, const QString& color = "black"); void printLine(const int& index, const QString& msg, const QString& color = "black"); void printTooltip(const int& index, const QString& msg); void printError(const QString& errorMsg); bool m_dbCreated{false}; mutable GeneralTestView* m_view{nullptr}; }; #endif // GeneralTest_H diff --git a/src/kdefrontend/dockwidgets/CorrelationCoefficientDock.cpp b/src/kdefrontend/dockwidgets/CorrelationCoefficientDock.cpp index 650341cfe..391fba7f4 100644 --- a/src/kdefrontend/dockwidgets/CorrelationCoefficientDock.cpp +++ b/src/kdefrontend/dockwidgets/CorrelationCoefficientDock.cpp @@ -1,518 +1,520 @@ /*************************************************************************** File : CorrelationCoefficientDock.cpp Project : LabPlot Description : widget for correlation test properties -------------------------------------------------------------------- Copyright : (C) 2019 Devanshu Agarwal(agarwaldevanshu8@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CorrelationCoefficientDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/AbstractAspect.h" #include "backend/core/Project.h" #include "backend/spreadsheet/Spreadsheet.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/datasources/DatabaseManagerDialog.h" #include "kdefrontend/datasources/DatabaseManagerWidget.h" #include "kdefrontend/TemplateHandler.h" #include #include #include #include #include #include #include #include /*! \class CorrelationCoefficientDock \brief Provides a dock (widget) for correlation testing: \ingroup kdefrontend */ //TODO: To add tooltips in docks for non obvious widgets. //TODO: Add functionality for database along with spreadsheet. CorrelationCoefficientDock::CorrelationCoefficientDock(QWidget* parent) : QWidget(parent) { ui.setupUi(this); ui.cbDataSourceType->addItem(i18n("Spreadsheet")); ui.cbDataSourceType->addItem(i18n("Database")); cbSpreadsheet = new TreeViewComboBox; ui.gridLayout->addWidget(cbSpreadsheet, 5, 4, 1, 3); ui.bDatabaseManager->setIcon(QIcon::fromTheme("network-server-database")); ui.bDatabaseManager->setToolTip(i18n("Manage connections")); m_configPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).constFirst() + "sql_connections"; ui.cbTest->addItem( i18n("Pearson r"), CorrelationCoefficient::Test::Pearson); ui.cbTest->addItem( i18n("Kendall"), CorrelationCoefficient::Test::Kendall); ui.cbTest->addItem( i18n("Spearman"), CorrelationCoefficient::Test::Spearman); // adding item to tests and testtype combo box; // making all test blocks invisible at starting. ui.lCategorical->hide(); ui.chbCategorical->hide(); ui.lCol1->hide(); ui.cbCol1->hide(); ui.lCol2->hide(); ui.cbCol2->hide(); ui.pbPerformTest->setEnabled(false); ui.pbPerformTest->setIcon(QIcon::fromTheme("run-build")); // readConnections(); connect(ui.cbDataSourceType, static_cast(&QComboBox::currentIndexChanged), this, &CorrelationCoefficientDock::dataSourceTypeChanged); connect(cbSpreadsheet, &TreeViewComboBox::currentModelIndexChanged, this, &CorrelationCoefficientDock::spreadsheetChanged); // connect(ui.cbConnection, static_cast(&QComboBox::currentIndexChanged), // this, &CorrelationCoefficientDock::connectionChanged); // connect(ui.cbTable, static_cast(&QComboBox::currentIndexChanged), // this, &CorrelationCoefficientDock::tableChanged); // connect(ui.bDatabaseManager, &QPushButton::clicked, this, &CorrelationCoefficientDock::showDatabaseManager); // connect(ui.bAddRow, &QPushButton::clicked, this, &CorrelationCoefficientDock::addRow); // connect(ui.bRemoveRow, &QPushButton::clicked, this,&CorrelationCoefficientDock::removeRow); // connect(ui.bAddColumn, &QPushButton::clicked, this, &CorrelationCoefficientDock::addColumn); // connect(ui.bRemoveColumn, &QPushButton::clicked, this,&CorrelationCoefficientDock::removeColumn); // connect(ui.cbCol1, static_cast(&QComboBox::currentIndexChanged), this, &CorrelationCoefficientDock::doTTest); // connect(ui.cbCol2, static_cast(&QComboBox::currentIndexChanged), this, &CorrelationCoefficientDock::doTTest); // connect(ui.lwFields, &QListWidget::itemSelectionChanged, this, [=]() { // bool enabled = !ui.lwFields->selectedItems().isEmpty(); // ui.bAddRow->setEnabled(enabled); // ui.bAddColumn->setEnabled(enabled); // }); // connect(ui.lwRows, &QListWidget::doubleClicked, this,&CorrelationCoefficientDock::removeRow); // connect(ui.lwRows, &QListWidget::itemSelectionChanged, this, [=]() { // ui.bRemoveRow->setEnabled(!ui.lwRows->selectedItems().isEmpty()); // }); // connect(ui.lwColumns, &QListWidget::doubleClicked, this,&CorrelationCoefficientDock::removeColumn); // connect(ui.lwColumns, &QListWidget::itemSelectionChanged, this, [=]() { // ui.bRemoveColumn->setEnabled(!ui.lwColumns->selectedItems().isEmpty()); // }); connect(ui.cbTest, static_cast(&QComboBox::currentIndexChanged), this, &CorrelationCoefficientDock::showCorrelationCoefficient); connect(ui.chbCategorical, &QCheckBox::stateChanged, this, &CorrelationCoefficientDock::changeCbCol2Label); connect(ui.pbPerformTest, &QPushButton::clicked, this, &CorrelationCoefficientDock::doCorrelationCoefficient); connect(ui.cbCol1, static_cast(&QComboBox::currentIndexChanged), this, &CorrelationCoefficientDock::col1IndexChanged); ui.cbTest->setCurrentIndex(0); emit ui.cbTest->currentIndexChanged(0); } void CorrelationCoefficientDock::setCorrelationCoefficient(CorrelationCoefficient* CorrelationCoefficient) { m_initializing = true; m_CorrelationCoefficient = CorrelationCoefficient; m_aspectTreeModel = new AspectTreeModel(m_CorrelationCoefficient->project()); QList list{AspectType::Folder, AspectType::Workbook, AspectType::Spreadsheet, AspectType::LiveDataSource}; cbSpreadsheet->setTopLevelClasses(list); list = {AspectType::Spreadsheet, AspectType::LiveDataSource}; m_aspectTreeModel->setSelectableAspects(list); cbSpreadsheet->setModel(m_aspectTreeModel); //show the properties ui.leName->setText(m_CorrelationCoefficient->name()); ui.leComment->setText(m_CorrelationCoefficient->comment()); ui.cbDataSourceType->setCurrentIndex(m_CorrelationCoefficient->dataSourceType()); if (m_CorrelationCoefficient->dataSourceType() == CorrelationCoefficient::DataSourceType::DataSourceSpreadsheet) setModelIndexFromAspect(cbSpreadsheet, m_CorrelationCoefficient->dataSourceSpreadsheet()); // else // ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(m_CorrelationCoefficient->dataSourceConnection())); setColumnsComboBoxModel(m_CorrelationCoefficient->dataSourceSpreadsheet()); this->dataSourceTypeChanged(ui.cbDataSourceType->currentIndex()); //setting rows and columns in combo box; //undo functions // connect(m_CorrelationCoefficient, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(CorrelationCoefficientDescriptionChanged(const AbstractAspect*))); m_initializing = false; } void CorrelationCoefficientDock::showCorrelationCoefficient() { if (ui.cbTest->count() == 0) return; m_test = CorrelationCoefficient::Test(ui.cbTest->currentData().toInt()); ui.lCol1->show(); ui.cbCol1->show(); ui.lCol2->show(); ui.cbCol2->show(); ui.lCategorical->show(); ui.chbCategorical->show(); setColumnsComboBoxView(); ui.pbPerformTest->setEnabled(nonEmptySelectedColumns()); } void CorrelationCoefficientDock::doCorrelationCoefficient() { QVector cols; if (ui.cbCol1->count() == 0) return; cols << reinterpret_cast(ui.cbCol1->currentData().toLongLong()); + cols << reinterpret_cast(ui.cbCol2->currentData().toLongLong()); + m_CorrelationCoefficient->setColumns(cols); switch(m_test) { case (CorrelationCoefficient::Test::Pearson): { m_CorrelationCoefficient->performTest(m_test, ui.chbCategorical->isChecked()); break; } case (CorrelationCoefficient::Test::Kendall): { break; } case (CorrelationCoefficient::Test::Spearman): { break; } } m_CorrelationCoefficient->setColumns(cols); m_CorrelationCoefficient->performTest(m_test, ui.chbCategorical->isChecked()); } void CorrelationCoefficientDock::setModelIndexFromAspect(TreeViewComboBox* cb, const AbstractAspect* aspect) { if (aspect) cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(aspect)); else cb->setCurrentModelIndex(QModelIndex()); } ////************************************************************* ////****** SLOTs for changes triggered in CorrelationCoefficientDock ******* ////************************************************************* //void CorrelationCoefficientDock::nameChanged() { // if (m_initializing) // return; // m_CorrelationCoefficient->setName(ui.leName->text()); //} //void CorrelationCoefficientDock::commentChanged() { // if (m_initializing) // return; // m_CorrelationCoefficient->setComment(ui.leComment->text()); //} void CorrelationCoefficientDock::dataSourceTypeChanged(int index) { //QDEBUG("in dataSourceTypeChanged"); CorrelationCoefficient::DataSourceType type = static_cast(index); bool showDatabase = (type == CorrelationCoefficient::DataSourceType::DataSourceDatabase); ui.lSpreadsheet->setVisible(!showDatabase); cbSpreadsheet->setVisible(!showDatabase); ui.lConnection->setVisible(showDatabase); ui.cbConnection->setVisible(showDatabase); ui.bDatabaseManager->setVisible(showDatabase); ui.lTable->setVisible(showDatabase); ui.cbTable->setVisible(showDatabase); if (m_initializing) return; m_CorrelationCoefficient->setComment(ui.leComment->text()); } void CorrelationCoefficientDock::spreadsheetChanged(const QModelIndex& index) { //QDEBUG("in spreadsheetChanged"); auto* aspect = static_cast(index.internalPointer()); Spreadsheet* spreadsheet = dynamic_cast(aspect); setColumnsComboBoxModel(spreadsheet); m_CorrelationCoefficient->setDataSourceSpreadsheet(spreadsheet); } void CorrelationCoefficientDock::col1IndexChanged(int index) { if (index < 0) return; changeCbCol2Label(); } //void CorrelationCoefficientDock::connectionChanged() { // if (ui.cbConnection->currentIndex() == -1) { // ui.lTable->hide(); // ui.cbTable->hide(); // return; // } // //clear the previously shown tables // ui.cbTable->clear(); // ui.lTable->show(); // ui.cbTable->show(); // const QString& connection = ui.cbConnection->currentText(); // //connection name was changed, determine the current connections settings // KConfig config(m_configPath, KConfig::SimpleConfig); // KConfigGroup group = config.group(connection); // //close and remove the previos connection, if available // if (m_db.isOpen()) { // m_db.close(); // QSqlDatabase::removeDatabase(m_db.driverName()); // } // //open the selected connection // //QDEBUG("CorrelationCoefficientDock: connecting to " + connection); // const QString& driver = group.readEntry("Driver"); // m_db = QSqlDatabase::addDatabase(driver); // const QString& dbName = group.readEntry("DatabaseName"); // if (DatabaseManagerWidget::isFileDB(driver)) { // if (!QFile::exists(dbName)) { // KMessageBox::error(this, i18n("Couldn't find the database file '%1'. Please check the connection settings.", dbName), // appendRow i18n("Connection Failed")); // return; // } else // m_db.setDatabaseName(dbName); // } else if (DatabaseManagerWidget::isODBC(driver)) { // if (group.readEntry("CustomConnectionEnabled", false)) // m_db.setDatabaseName(group.readEntry("CustomConnectionString")); // else // m_db.setDatabaseName(dbName); // } else { // m_db.setDatabaseName(dbName); // m_db.setHostName( group.readEntry("HostName") ); // m_db.setPort( group.readEntry("Port", 0) ); // m_db.setUserName( group.readEntry("UserName") ); // m_db.setPassword( group.readEntry("Password") ); // } // WAIT_CURSOR; // if (!m_db.open()) { // RESET_CURSOR; // KMessageBox::error(this, i18n("Failed to connect to the database '%1'. Please check the connection settings.", ui.cbConnection->currentText()) + // QLatin1String("\n\n") + m_db.lastError().databaseText(), // i18n("Connection Failed")); // return; // } // //show all available database tables // if (m_db.tables().size()) { // for (auto table : m_db.tables()) // ui.cbTable->addItem(QIcon::fromTheme("view-form-table"), table); // ui.cbTable->setCurrentIndex(0); // } // RESET_CURSOR; // if (m_initializing) // return; //// m_CorrelationCoefficient->setDataSourceConnection(connection); //} //void CorrelationCoefficientDock::tableChanged() { // const QString& table = ui.cbTable->currentText(); // //show all attributes of the selected table //// for (const auto* col : spreadsheet->children()) { //// QListWidgetItem* item = new QListWidgetItem(col->icon(), col->name()); //// ui.lwFields->addItem(item); //// } // if (m_initializing) // return; //// m_CorrelationCoefficient->setDataSourceTable(table); //} ////************************************************************* ////******** SLOTs for changes triggered in Spreadsheet ********* ////************************************************************* void CorrelationCoefficientDock::CorrelationCoefficientDescriptionChanged(const AbstractAspect* aspect) { if (m_CorrelationCoefficient != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) ui.leName->setText(aspect->name()); else if (aspect->comment() != ui.leComment->text()) ui.leComment->setText(aspect->comment()); m_initializing = false; } void CorrelationCoefficientDock::changeCbCol2Label() { if (ui.cbCol1->count() == 0) return; QString selected_text = ui.cbCol1->currentText(); Column* col1 = m_CorrelationCoefficient->dataSourceSpreadsheet()->column(selected_text); if (!ui.chbCategorical->isChecked() && (col1->columnMode() == AbstractColumn::Integer || col1->columnMode() == AbstractColumn::Numeric)) { ui.lCol2->setText( i18n("Independent Var. 2")); ui.chbCategorical->setChecked(false); ui.chbCategorical->setEnabled(true); } else { ui.lCol2->setText( i18n("Dependent Var. 1")); if (!ui.chbCategorical->isChecked()) ui.chbCategorical->setEnabled(false); else ui.chbCategorical->setEnabled(true); ui.chbCategorical->setChecked(true); } } ////************************************************************* ////******************** SETTINGS ******************************* ////************************************************************* //void CorrelationCoefficientDock::load() { //} //void CorrelationCoefficientDock::loadConfigFromTemplate(KConfig& config) { // Q_UNUSED(config); //} ///*! // loads saved matrix properties from \c config. // */ //void CorrelationCoefficientDock::loadConfig(KConfig& config) { // Q_UNUSED(config); //} ///*! // saves matrix properties to \c config. // */ //void CorrelationCoefficientDock::saveConfigAsTemplate(KConfig& config) { // Q_UNUSED(config); //} void CorrelationCoefficientDock::setColumnsComboBoxModel(Spreadsheet* spreadsheet) { m_onlyValuesCols.clear(); m_twoCategoricalCols.clear(); m_multiCategoricalCols.clear(); for (auto* col : spreadsheet->children()) { if (col->columnMode() == AbstractColumn::Integer || col->columnMode() == AbstractColumn::Numeric) m_onlyValuesCols.append(col); else { int np = 0, n_rows = 0; countPartitions(col, np, n_rows); if (np <= 1) continue; else if (np == 2) m_twoCategoricalCols.append(col); else m_multiCategoricalCols.append(col); } } setColumnsComboBoxView(); showCorrelationCoefficient(); } //TODO: change from if else to switch case: void CorrelationCoefficientDock::setColumnsComboBoxView() { ui.cbCol1->clear(); ui.cbCol2->clear(); QList::iterator i; switch (m_test) { case (CorrelationCoefficient::Test::Pearson): { for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) { ui.cbCol1->addItem( (*i)->name(), qint64(*i)); ui.cbCol2->addItem( (*i)->name(), qint64(*i)); } for (i = m_twoCategoricalCols.begin(); i != m_twoCategoricalCols.end(); i++) ui.cbCol1->addItem( (*i)->name(), qint64(*i)); break; } case CorrelationCoefficient::Test::Kendall: { for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) { ui.cbCol1->addItem( (*i)->name(), qint64(*i)); ui.cbCol2->addItem( (*i)->name(), qint64(*i)); } for (i = m_twoCategoricalCols.begin(); i != m_twoCategoricalCols.end(); i++) ui.cbCol1->addItem( (*i)->name(), qint64(*i)); break; } case CorrelationCoefficient::Test::Spearman: { for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) { ui.cbCol1->addItem( (*i)->name(), qint64(*i)); ui.cbCol2->addItem( (*i)->name(), qint64(*i)); } for (i = m_twoCategoricalCols.begin(); i != m_twoCategoricalCols.end(); i++) ui.cbCol1->addItem( (*i)->name(), qint64(*i)); break; } } } bool CorrelationCoefficientDock::nonEmptySelectedColumns() { if (ui.cbCol1->isVisible() && ui.cbCol1->count() < 1) return false; if (ui.cbCol2->isVisible() && ui.cbCol2->count() < 1) return false; return true; } void CorrelationCoefficientDock::countPartitions(Column *column, int &np, int &total_rows) { total_rows = column->rowCount(); np = 0; QString cell_value; QMap discovered_categorical_var; AbstractColumn::ColumnMode original_col_mode = column->columnMode(); column->setColumnMode(AbstractColumn::Text); for (int i = 0; i < total_rows; i++) { cell_value = column->textAt(i); if (cell_value.isEmpty()) { total_rows = i; break; } if (discovered_categorical_var[cell_value]) continue; discovered_categorical_var[cell_value] = true; np++; } column->setColumnMode(original_col_mode); }