diff --git a/src/backend/generalTest/CorrelationCoefficient.cpp b/src/backend/generalTest/CorrelationCoefficient.cpp index f69118e79..3cdc7b734 100644 --- a/src/backend/generalTest/CorrelationCoefficient.cpp +++ b/src/backend/generalTest/CorrelationCoefficient.cpp @@ -1,604 +1,660 @@ /*************************************************************************** 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 "backend/generalTest/MyTableModel.h" #include #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(int test, bool categoricalVariable, bool calculateStats) { m_statsTable = ""; m_correlationValue = 0; m_statisticValue.clear(); m_pValue.clear(); for (int i = 0; i < RESULTLINESCOUNT; i++) m_resultLine[i]->clear(); switch (testType(test)) { case CorrelationCoefficient::Pearson: { m_currTestName = "

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

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

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

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

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

"; performSpearman(); break; } case CorrelationCoefficient::ChiSquare: if (testSubtype(test) == CorrelationCoefficient::IndependenceTest) { m_currTestName = "

" + i18n("Chi Square Independence Test") + "

"; performChiSquareIndpendence(calculateStats); } break; } emit changed(); } void CorrelationCoefficient::initInputStatsTable(int test, bool calculateStats, int nRows, int nColumns) { m_inputStatsTableModel->clear(); if (!calculateStats) { if (testSubtype(test) == IndependenceTest) { m_inputStatsTableModel->setRowCount(nRows + 1); m_inputStatsTableModel->setColumnCount(nColumns + 1); } } for (int i = 1; i < nRows + 1; i++) m_inputStatsTableModel->setData(m_inputStatsTableModel->index(i, 0), i18n("Row %1", i)); for (int i = 1; i < nColumns + 1; i++) m_inputStatsTableModel->setData(m_inputStatsTableModel->index(0, i), i18n("Column %1", i)); emit changed(); } double CorrelationCoefficient::correlationValue() const { return m_correlationValue; } QList CorrelationCoefficient::statisticValue() const { return m_statisticValue; } QList CorrelationCoefficient::pValue() const { return m_pValue; } void CorrelationCoefficient::setInputStatsTableNRows(int nRows) { int nRows_old = m_inputStatsTableModel->rowCount(); m_inputStatsTableModel->setRowCount(nRows + 1); for (int i = nRows_old; i < nRows + 1; i++) m_inputStatsTableModel->setData(m_inputStatsTableModel->index(i, 0), i18n("Row %1", i)); } void CorrelationCoefficient::setInputStatsTableNCols(int nColumns) { int nColumns_old = m_inputStatsTableModel->columnCount(); m_inputStatsTableModel->setColumnCount(nColumns + 1); for (int i = nColumns_old; i < nColumns + 1; i++) m_inputStatsTableModel->setData(m_inputStatsTableModel->index(0, i), i18n("Column %1", i)); } void CorrelationCoefficient::exportStatTableToSpreadsheet() { if (m_dataSourceSpreadsheet == nullptr) return; int rowCount = m_inputStatsTableModel->rowCount(); int columnCount = m_inputStatsTableModel->columnCount(); int spreadsheetColCount = m_dataSourceSpreadsheet->columnCount(); m_dataSourceSpreadsheet->insertColumns(spreadsheetColCount, 3); Column* col1 = m_dataSourceSpreadsheet->column(spreadsheetColCount); Column* col2 = m_dataSourceSpreadsheet->column(spreadsheetColCount + 1); Column* col3 = m_dataSourceSpreadsheet->column(spreadsheetColCount + 2); col1->setName("Independent Var. 1"); col2->setName("Independent Var. 2"); col3->setName("Data Values"); col1->setColumnMode(AbstractColumn::Text); col2->setColumnMode(AbstractColumn::Text); col3->setColumnMode(AbstractColumn::Numeric); int index = 0; for (int i = 1; i < rowCount; i++) for (int j = 1; j < columnCount; j++) { col1->setTextAt(index, m_inputStatsTableModel->data( m_inputStatsTableModel->index(i, 0)).toString()); col2->setTextAt(index, m_inputStatsTableModel->data( m_inputStatsTableModel->index(0, j)).toString()); col3->setValueAt(index, m_inputStatsTableModel->data( m_inputStatsTableModel->index(i, j)).toDouble()); index++; } } /*************************************************************************************************************************** * Private Implementations * ************************************************************************************************************************/ /*********************************************Pearson r ******************************************************************/ //Formulaes are taken from https://www.statisticssolutions.com/correlation-pearson-kendall-spearman/ // 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 tooltip for correlation value result //TODO: find p value void CorrelationCoefficient::performPearson(bool 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 (!m_columns[1]->isNumeric()) 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; // HtmlCell constructor structure; data, level, rowSpanCount, m_columnspanCount, isHeader; QList rowMajor; int level = 0; // horizontal header QString sigma = UTF8_QSTRING("Σ"); rowMajor.append(new HtmlCell("", level, true)); rowMajor.append(new HtmlCell("N", level, true, "Total Number of Observations")); rowMajor.append(new HtmlCell(QString(sigma + "Scores"), level, true, "Sum of Scores in each column")); rowMajor.append(new HtmlCell(QString(sigma + "Scores2"), level, true, "Sum of Squares of scores in each column")); rowMajor.append(new HtmlCell(QString(sigma + "(" + UTF8_QSTRING("∏") + "Scores)"), level, true, "Sum of product of scores of both columns")); //data with vertical header. level++; rowMajor.append(new HtmlCell(col1Name, level, true)); rowMajor.append(new HtmlCell(N, level)); rowMajor.append(new HtmlCell(sumCol1, level)); rowMajor.append(new HtmlCell(sumSqCol1, level)); rowMajor.append(new HtmlCell(sumCol12, level, false, "", 2, 1)); level++; rowMajor.append(new HtmlCell(col2Name, level, true)); rowMajor.append(new HtmlCell(N, level)); rowMajor.append(new HtmlCell(sumCol2, level)); rowMajor.append(new HtmlCell(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(round(m_correlationValue)), "green"); } /***********************************************Kendall ******************************************************************/ // used knight algorithm for fast performance O(nlogn) rather than O(n^2) // http://adereth.github.io/blog/2013/10/30/efficiently-computing-kendalls-tau/ // TODO: Change date format type to original for numeric type; // TODO: add tooltips. // TODO: Compute tauB for ties. // TODO: find P Value from Z Value void CorrelationCoefficient::performKendall() { if (m_columns.count() != 2) { printError("Select only 2 columns "); return; } QString col1Name = m_columns[0]->name(); QString col2Name = m_columns[1]->name(); 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; } QVector col2Ranks(N); if (m_columns[0]->isNumeric()) { if (m_columns[0]->isNumeric() && m_columns[1]->isNumeric()) { for (int i = 0; i < N; i++) col2Ranks[int(m_columns[0]->valueAt(i)) - 1] = int(m_columns[1]->valueAt(i)); } else { printError(QString("Ranking System should be same for both Column: %1 and Column: %2
" "Hint: Check for data types of columns").arg(col1Name).arg(col2Name)); return; } } else { AbstractColumn::ColumnMode origCol1Mode = m_columns[0]->columnMode(); AbstractColumn::ColumnMode origCol2Mode = m_columns[1]->columnMode(); m_columns[0]->setColumnMode(AbstractColumn::Text); m_columns[1]->setColumnMode(AbstractColumn::Text); QMap ValueToRank; for (int i = 0; i < N; i++) { if (ValueToRank[m_columns[0]->textAt(i)] != 0) { printError("Currently ties are not supported"); m_columns[0]->setColumnMode(origCol1Mode); m_columns[1]->setColumnMode(origCol2Mode); return; } ValueToRank[m_columns[0]->textAt(i)] = i + 1; } for (int i = 0; i < N; i++) col2Ranks[i] = ValueToRank[m_columns[1]->textAt(i)]; m_columns[0]->setColumnMode(origCol1Mode); m_columns[1]->setColumnMode(origCol2Mode); } int nPossiblePairs = (N * (N - 1)) / 2; int nDiscordant = findDiscordants(col2Ranks.data(), 0, N - 1); int nCorcordant = nPossiblePairs - nDiscordant; m_correlationValue = double(nCorcordant - nDiscordant) / nPossiblePairs; m_statisticValue.append((3 * (nCorcordant - nDiscordant)) / sqrt(N * (N- 1) * (2 * N + 5) / 2)); printLine(0, QString("Number of Discordants are %1").arg(nDiscordant), "green"); printLine(1, QString("Number of Concordant are %1").arg(nCorcordant), "green"); printLine(2, QString("Tau a is %1").arg(round(m_correlationValue)), "green"); printLine(3, QString("Z Value is %1").arg(round(m_statisticValue[0])), "green"); return; } /***********************************************Spearman ******************************************************************/ // All formulaes and symbols are taken from : https://www.statisticshowto.datasciencecentral.com/spearman-rank-correlation-definition-calculate/ void CorrelationCoefficient::performSpearman() { if (m_columns.count() != 2) { printError("Select only 2 columns "); return; } QString col1Name = m_columns[0]->name(); QString col2Name = m_columns[1]->name(); 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; } QMap col1Ranks; convertToRanks(m_columns[0], N, col1Ranks); QMap col2Ranks; convertToRanks(m_columns[1], N, col2Ranks); double ranksCol1Mean = 0; double ranksCol2Mean = 0; for (int i = 0; i < N; i++) { ranksCol1Mean += col1Ranks[int(m_columns[0]->valueAt(i))]; ranksCol2Mean += col2Ranks[int(m_columns[1]->valueAt(i))]; } ranksCol1Mean /= N; ranksCol2Mean /= N; double s12 = 0; double s1 = 0; double s2 = 0; for (int i = 0; i < N; i++) { double centeredRank_1 = col1Ranks[int(m_columns[0]->valueAt(i))] - ranksCol1Mean; double centeredRank_2 = col2Ranks[int(m_columns[1]->valueAt(i))] - ranksCol2Mean; s12 += centeredRank_1 * centeredRank_2; s1 += gsl_pow_2(centeredRank_1); s2 += gsl_pow_2(centeredRank_2); } s12 /= N; s1 /= N; s2 /= N; m_correlationValue = s12 / std::sqrt(s1 * s2); printLine(0, QString("Spearman Rank Correlation value is %1").arg(m_correlationValue), "green"); } /***********************************************Chi Square Test for Indpendence******************************************************************/ -// TODO: Implement this function when data is given in spreadsheet: +// TODO: Find P value from chi square test statistic: void CorrelationCoefficient::performChiSquareIndpendence(bool calculateStats) { int rowCount; int columnCount; QVector sumRows; QVector sumColumns; int overallTotal = 0; QVector> observedValues; + QStringList horizontalHeader; + QStringList verticalHeader; + if (!calculateStats) { rowCount = m_inputStatsTableModel->rowCount() - 1; columnCount = m_inputStatsTableModel->columnCount() - 1; sumRows.resize(rowCount); sumColumns.resize(columnCount); - observedValues.resize(rowCount); for (int i = 1; i <= rowCount; i++) { observedValues[i - 1].resize(columnCount); for (int j = 1; j <= columnCount; j++) { int cellValue = m_inputStatsTableModel->data(m_inputStatsTableModel->index(i, j)).toInt(); sumRows[i - 1] += cellValue; sumColumns[j - 1] += cellValue; overallTotal += cellValue; observedValues[i - 1][j - 1] = cellValue; } } + + for (int i = 0; i < columnCount + 1; i++) + horizontalHeader.append(m_inputStatsTableModel->data(m_inputStatsTableModel->index(0, i)).toString()); + + for (int i = 0; i < rowCount + 1; i++) + verticalHeader.append(m_inputStatsTableModel->data(m_inputStatsTableModel->index(i, 0)).toString()); } else { - printError("Missing Feature: Can't calculate Statistics from Spreadsheet for now:"); - return; + if (m_columns.count() != 3) { + printError("Select only 3 columns "); + return; + } + + int nRows = findCount(m_columns[0]); + + rowCount = 0; + columnCount = 0; + + horizontalHeader.append(QString()); + verticalHeader.append(QString()); + + QMap independentVar1; + QMap independentVar2; + for (int i = 0; i < nRows; i++) { + QString cell1Text = m_columns[0]->textAt(i); + QString cell2Text = m_columns[1]->textAt(i); + + if (independentVar1[cell1Text] == 0) { + independentVar1[cell1Text] = ++columnCount; + horizontalHeader.append(cell1Text); + } + + if (independentVar2[cell2Text] == 0) { + independentVar2[cell2Text] = ++rowCount; + verticalHeader.append(cell2Text); + } + } + + sumRows.resize(rowCount); + sumColumns.resize(columnCount); + observedValues.resize(rowCount); + for (int i = 0; i < rowCount; i++) + observedValues[i].resize(columnCount); + + + for (int i = 0; i < nRows; i++) { + QString cell1Text = m_columns[0]->textAt(i); + QString cell2Text = m_columns[1]->textAt(i); + int cellValue = int(m_columns[2]->valueAt(i)); + + int partition1Number = independentVar1[cell1Text] - 1; + int partition2Number = independentVar2[cell2Text] - 1; + + sumRows[partition1Number] += cellValue; + sumColumns[partition2Number] += cellValue; + overallTotal += cellValue; + observedValues[partition1Number][partition2Number] = cellValue; + } } if (overallTotal == 0) printError("Enter some data: All columns are empty"); QVector> expectedValues(rowCount, QVector(columnCount)); for (int i = 0; i < rowCount; i++) for (int j = 0; j < columnCount; j++) expectedValues[i][j] = (double(sumRows[i]) * double(sumColumns[j])) / overallTotal; m_statsTable += "

" + i18n("Observed Value Table") + "

"; QList rowMajor; int level = 0; // horizontal header for (int i = 0; i < columnCount + 1; i++) - rowMajor.append(new HtmlCell(m_inputStatsTableModel->data(m_inputStatsTableModel->index(0, i)).toString(), level, true)); + rowMajor.append(new HtmlCell(horizontalHeader[i], level, true)); rowMajor.append(new HtmlCell("Total", level, true)); //data with vertical header. for (int i = 1; i < rowCount + 1; i++) { level++; - rowMajor.append(new HtmlCell(m_inputStatsTableModel->data(m_inputStatsTableModel->index(i, 0)).toString(), level, true)); + rowMajor.append(new HtmlCell(verticalHeader[i], level, true)); for (int j = 0; j < columnCount; j++) rowMajor.append(new HtmlCell(round(observedValues[i - 1][j]), level)); rowMajor.append(new HtmlCell(round(sumRows[i - 1]), level)); } level++; rowMajor.append(new HtmlCell("Total", level, true)); for (int i = 0; i < columnCount; i++) rowMajor.append(new HtmlCell(round(sumColumns[i]), level)); rowMajor.append(new HtmlCell(round(overallTotal), level)); m_statsTable += getHtmlTable3(rowMajor); m_statsTable += "
"; m_statsTable += "

" + i18n("Expected Value Table") + "

"; rowMajor.clear(); level = 0; // horizontal header for (int i = 0; i < columnCount + 1; i++) - rowMajor.append(new HtmlCell(m_inputStatsTableModel->data(m_inputStatsTableModel->index(0, i)).toString(), level, true)); + rowMajor.append(new HtmlCell(horizontalHeader[i], level, true)); rowMajor.append(new HtmlCell("Total", level, true)); //data with vertical header. for (int i = 1; i < rowCount + 1; i++) { level++; - rowMajor.append(new HtmlCell(m_inputStatsTableModel->data(m_inputStatsTableModel->index(i, 0)).toString(), level, true)); + rowMajor.append(new HtmlCell(verticalHeader[i], level, true)); for (int j = 0; j < columnCount; j++) rowMajor.append(new HtmlCell(round(expectedValues[i - 1][j]), level)); rowMajor.append(new HtmlCell(round(sumRows[i - 1]), level)); } level++; rowMajor.append(new HtmlCell("Total", level, true)); for (int i = 0; i < columnCount; i++) rowMajor.append(new HtmlCell(round(sumColumns[i]), level)); rowMajor.append(new HtmlCell(round(overallTotal), level)); m_statsTable += getHtmlTable3(rowMajor); double chiSquareVal = 0; // finding chi-square value; for (int i = 0; i < rowCount; i++) for (int j = 0; j < columnCount; j++) chiSquareVal += gsl_pow_2(observedValues[i][j] - expectedValues[i][j]) / expectedValues[i][j]; m_statisticValue.append(chiSquareVal); int df = (rowCount - 1) * (columnCount - 1); printLine(0, "Degree of Freedom is " + QString::number(df), "blue"); printLine(1, "Chi Square Statistic Value is " + round(chiSquareVal), "green"); } /***********************************************Helper Functions******************************************************************/ int CorrelationCoefficient::findDiscordants(int *ranks, int start, int end) { if (start >= end) return 0; int mid = (start + end) / 2; int leftDiscordants = findDiscordants(ranks, start, mid); int rightDiscordants = findDiscordants(ranks, mid + 1, end); int len = end - start + 1; int leftLen = mid - start + 1; int rightLen = end - mid; int leftLenRemain = leftLen; QVector leftRanks(leftLen); QVector rightRanks(rightLen); for (int i = 0; i < leftLen; i++) leftRanks[i] = ranks[start + i]; for (int i = leftLen; i < leftLen + rightLen; i++) rightRanks[i - leftLen] = ranks[start + i]; int mergeDiscordants = 0; int i = 0, j = 0, k =0; while (i < len) { if (j >= leftLen) { ranks[start + i] = rightRanks[k]; k++; } else if (k >= rightLen) { ranks[start + i] = leftRanks[j]; j++; } else if (leftRanks[j] < rightRanks[k]) { ranks[start + i] = leftRanks[j]; j++; leftLenRemain--; } else if (leftRanks[j] > rightRanks[k]) { ranks[start + i] = rightRanks[k]; mergeDiscordants += leftLenRemain; k++; } i++; } return leftDiscordants + rightDiscordants + mergeDiscordants; } void CorrelationCoefficient::convertToRanks(const Column* col, int N, QMap &ranks) { if (col->isNumeric()) return; double* sortedList = new double[N]; for (int i = 0; i < N; i++) sortedList[i] = col->valueAt(i); std::sort(sortedList, sortedList + N, std::greater()); ranks.clear(); for (int i = 0; i < N; i++) ranks[sortedList[i]] = i + 1; delete[] sortedList; } void CorrelationCoefficient::convertToRanks(const Column* col, QMap &ranks) { convertToRanks(col, findCount(col), ranks); } /***********************************************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/kdefrontend/dockwidgets/CorrelationCoefficientDock.cpp b/src/kdefrontend/dockwidgets/CorrelationCoefficientDock.cpp index deedba6e6..e69fefbd4 100644 --- a/src/kdefrontend/dockwidgets/CorrelationCoefficientDock.cpp +++ b/src/kdefrontend/dockwidgets/CorrelationCoefficientDock.cpp @@ -1,616 +1,625 @@ /*************************************************************************** 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::Pearson); ui.cbTest->addItem( i18n("Kendall"), CorrelationCoefficient::Kendall); ui.cbTest->addItem( i18n("Spearman"), CorrelationCoefficient::Spearman); ui.cbTest->addItem( i18n("Chi Square"), CorrelationCoefficient::ChiSquare); ui.leNRows->setText("2"); ui.leNColumns->setText("2"); ui.leNRows->setValidator(new QIntValidator(this)); ui.leNColumns->setValidator(new QIntValidator(this)); ui.lTestType->hide(); ui.cbTestType->hide(); ui.lCalculateStats->hide(); ui.chbCalculateStats->hide(); ui.lNRows->hide(); ui.leNRows->hide(); ui.lNColumns->hide(); ui.leNColumns->hide(); ui.lCategorical->hide(); ui.chbCategorical->hide(); ui.lCol1->hide(); ui.cbCol1->hide(); ui.lCol2->hide(); ui.cbCol2->hide(); + ui.lCol3->hide(); + ui.cbCol3->hide(); ui.lAlpha->hide(); ui.leAlpha->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::showTestType); connect(ui.cbTestType, static_cast(&QComboBox::currentIndexChanged), this, &CorrelationCoefficientDock::showCorrelationCoefficient); connect(ui.chbCategorical, &QCheckBox::stateChanged, this, &CorrelationCoefficientDock::changeCbCol2Label); connect(ui.chbCalculateStats, &QCheckBox::stateChanged, this, &CorrelationCoefficientDock::chbColumnStatsStateChanged); connect(ui.leNRows, &QLineEdit::textChanged, this, &CorrelationCoefficientDock::leNRowsChanged); connect(ui.leNColumns, &QLineEdit::textChanged, this, &CorrelationCoefficientDock::leNColumnsChanged); connect(ui.pbExportToSpreadsheet, &QPushButton::clicked, this, &CorrelationCoefficientDock::exportStatsTableToSpreadsheet); connect(ui.pbPerformTest, &QPushButton::clicked, this, &CorrelationCoefficientDock::findCorrelationCoefficient); connect(ui.cbCol1, static_cast(&QComboBox::currentIndexChanged), this, &CorrelationCoefficientDock::col1IndexChanged); ui.cbTest->setCurrentIndex(0); emit ui.cbTest->currentIndexChanged(0); ui.cbTestType->setCurrentIndex(0); emit ui.cbTestType->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::showTestType() { if (ui.cbTest->count() == 0) return; m_test = ui.cbTest->currentData().toInt(); ui.cbTestType->clear(); switch (m_test) { case CorrelationCoefficient::ChiSquare: ui.lTestType->show(); ui.cbTestType->show(); ui.cbTestType->addItem( i18n("Test for Independence"), CorrelationCoefficient::IndependenceTest); break; case CorrelationCoefficient::Pearson: case CorrelationCoefficient::Spearman: case CorrelationCoefficient::Kendall: case CorrelationCoefficient::IndependenceTest: ui.lTestType->hide(); ui.cbTestType->hide(); showCorrelationCoefficient(); break; } } void CorrelationCoefficientDock::showCorrelationCoefficient() { m_test |= ui.cbTestType->currentData().toInt(); ui.lCalculateStats->setVisible(testType(m_test) == CorrelationCoefficient::ChiSquare); ui.chbCalculateStats->setVisible(testType(m_test) == CorrelationCoefficient::ChiSquare); if (testType(m_test) != CorrelationCoefficient::ChiSquare) ui.chbCalculateStats->setChecked(true); chbColumnStatsStateChanged(); } void CorrelationCoefficientDock::findCorrelationCoefficient() { QVector cols; if (ui.chbCategorical->isChecked() && ui.cbCol1->count() == 0) return; cols << reinterpret_cast(ui.cbCol1->currentData().toLongLong()); cols << reinterpret_cast(ui.cbCol2->currentData().toLongLong()); + if (testSubType(m_test) == CorrelationCoefficient::IndependenceTest) + cols << reinterpret_cast(ui.cbCol3->currentData().toLongLong()); + m_correlationCoefficient->setColumns(cols); m_correlationCoefficient->performTest(m_test, ui.chbCategorical->isChecked(), ui.chbCalculateStats->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 (m_test == (CorrelationCoefficient::Kendall | CorrelationCoefficient::Spearman) || (!ui.chbCategorical->isChecked() && col1->isNumeric())) { 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); } } void CorrelationCoefficientDock::chbColumnStatsStateChanged() { bool chbChecked = ui.chbCalculateStats->isChecked(); ui.lVariables->setVisible(chbChecked); ui.lCol1->setVisible(chbChecked); ui.cbCol1->setVisible(chbChecked); ui.lCol2->setVisible(chbChecked); ui.cbCol2->setVisible(chbChecked); + ui.lCol3->setVisible(chbChecked); + ui.cbCol3->setVisible(chbChecked); ui.lCategorical->setVisible(chbChecked && testType(m_test) == CorrelationCoefficient::Pearson); ui.chbCategorical->setVisible(chbChecked && testType(m_test) == CorrelationCoefficient::Pearson); ui.lNRows->setVisible(!chbChecked); ui.leNRows->setVisible(!chbChecked); ui.lNColumns->setVisible(!chbChecked); ui.leNColumns->setVisible(!chbChecked); ui.pbExportToSpreadsheet->setVisible(!chbChecked); if (chbChecked) { setColumnsComboBoxView(); ui.pbPerformTest->setEnabled(nonEmptySelectedColumns()); } else ui.pbPerformTest->setEnabled(true); if (m_correlationCoefficient != nullptr) m_correlationCoefficient->initInputStatsTable(m_test, chbChecked, ui.leNRows->text().toInt(), ui.leNColumns->text().toInt()); } void CorrelationCoefficientDock::leNRowsChanged() { if (m_correlationCoefficient != nullptr) m_correlationCoefficient->setInputStatsTableNRows(ui.leNRows->text().toInt()); } void CorrelationCoefficientDock::leNColumnsChanged() { if (m_correlationCoefficient != nullptr) m_correlationCoefficient->setInputStatsTableNCols(ui.leNColumns->text().toInt()); } void CorrelationCoefficientDock::exportStatsTableToSpreadsheet() { if (ui.chbCalculateStats->isVisible() && !ui.chbCalculateStats->isChecked()) m_correlationCoefficient->exportStatTableToSpreadsheet(); } ////************************************************************* ////******************** 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->isNumeric()) 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(); + ui.cbCol3->clear(); QList::iterator i; switch (testType(m_test)) { case (CorrelationCoefficient::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::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)); ui.cbCol2->addItem( (*i)->name(), qint64(*i)); } for (i = m_multiCategoricalCols.begin(); i != m_multiCategoricalCols.end(); i++) { ui.cbCol1->addItem( (*i)->name(), qint64(*i)); ui.cbCol2->addItem( (*i)->name(), qint64(*i)); } break; } case CorrelationCoefficient::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; } case CorrelationCoefficient::ChiSquare: { - switch (testSubType(m_test)) { - case CorrelationCoefficient::IndependenceTest: + if (testSubType(m_test) == CorrelationCoefficient::IndependenceTest) { for (i = m_twoCategoricalCols.begin(); i != m_twoCategoricalCols.end(); i++) { ui.cbCol1->addItem( (*i)->name(), qint64(*i)); ui.cbCol2->addItem( (*i)->name(), qint64(*i)); } for (i = m_multiCategoricalCols.begin(); i != m_multiCategoricalCols.end(); i++) { ui.cbCol1->addItem( (*i)->name(), qint64(*i)); ui.cbCol2->addItem( (*i)->name(), qint64(*i)); } - break; + for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) { + ui.cbCol3->addItem( (*i)->name(), qint64(*i)); + } } break; } } } bool CorrelationCoefficientDock::nonEmptySelectedColumns() { if ((ui.cbCol1->isVisible() && ui.cbCol1->count() < 1) || (ui.cbCol2->isVisible() && ui.cbCol2->count() < 1)) return false; return true; } int CorrelationCoefficientDock::testType(int test) { return test & 0x0F; } int CorrelationCoefficientDock::testSubType(int test) { return test & 0xF0; } 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); } diff --git a/src/kdefrontend/ui/dockwidgets/correlationcoefficientdock.ui b/src/kdefrontend/ui/dockwidgets/correlationcoefficientdock.ui index 6aedb064e..6fb20e006 100644 --- a/src/kdefrontend/ui/dockwidgets/correlationcoefficientdock.ui +++ b/src/kdefrontend/ui/dockwidgets/correlationcoefficientdock.ui @@ -1,437 +1,447 @@ CorrelationCoefficientDock 0 0 557 1293 Form - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 32 - 18 - - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 32 - 18 - - - - - - - - Spreadsheet: - - - - - + + - Calculate Statistics <br> From Spreadsheet - - - - - - - - 75 - true - - - - Data + Dependent Var. - - - - Recalculate - - - - - - - - - - - - - - - + + - Connection: + Type - + QFrame::NoFrame QFrame::Raised 0 0 0 0 - - + + + + Spreadsheet: + + - - + + Qt::Horizontal - - QSizePolicy::Fixed - - - - 13 - 23 - - - + - - + + + + + 75 + true + + - Number of Columns + Data - - + + - Qt::Horizontal + Qt::Vertical - 150 - 20 + 18 + 38 - - + + + + + 75 + true + + - Number of Rows + Test - - - - Qt::Vertical + + + + Source: - - QSizePolicy::Fixed + + + + + + + 0 + 0 + - - - 32 - 18 - + + Name: - + - - + + + + + Qt::Vertical QSizePolicy::Fixed 32 18 - - + + + + + - Subtype + - - + + - + 0 0 + + + - - + + - - - - - 75 - true - - + + - Variables + alpha - - + + + + Qt::Horizontal + + + + 150 + 20 + + + + + + - + 0 0 - - Name: - - - + + - - + + - Independent Var. 1 + Independent Var. 1 Cateogical - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + Subtype - - + + - + Independent Var. 2 - - - - - 0 - 0 - + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 32 + 18 + + + + + + + + + 75 + true + - + Variables - - + + + + Calculate Statistics <br> From Spreadsheet + + + + + + + Number of Columns + + - + QFrame::NoFrame QFrame::Raised 0 0 0 0 0 - - + + + + + 0 + 0 + + + + + + + + + - Source: + Recalculate - - + + + + + + - - + + + + Export To Spreadsheet + + + + + + + Number of Rows + + + + + Qt::Vertical + + QSizePolicy::Fixed + - 18 - 38 + 32 + 18 - - + + - Qt::Horizontal - - - - - - - - 75 - true - + Qt::Vertical - - Test + + QSizePolicy::Fixed - - - - - - alpha + + + 32 + 18 + - + - - + + - + Comment: - - + + - Type + Connection: - - - - - - + + - - + + - - + + - Comment: + Independent Var. 1 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - + + - Independent Var. 1 Cateogical + Table: - - - - Variable + + + + Qt::Horizontal - - - - - - - 0 - 0 - + + QSizePolicy::Fixed - + + + 13 + 23 + + + - - + + - Export To Spreadsheet + + + +