diff --git a/src/backend/hypothesis_test/HypothesisTest.cpp b/src/backend/hypothesis_test/HypothesisTest.cpp index 69c601251..fabb88ba9 100644 --- a/src/backend/hypothesis_test/HypothesisTest.cpp +++ b/src/backend/hypothesis_test/HypothesisTest.cpp @@ -1,1166 +1,1172 @@ /*************************************************************************** File : HypothesisTest.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 "HypothesisTest.h" #include "HypothesisTestPrivate.h" #include "kdefrontend/hypothesis_test/HypothesisTestView.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/core/column/Column.h" #include "backend/lib/macros.h" #include "QDebug" extern "C" { #include "backend/nsl/nsl_stats.h" } #include #include #include #include #include #include #include #include #include #include HypothesisTest::HypothesisTest(const QString &name) : AbstractPart(name), d(new HypothesisTestPrivate(this)) { } HypothesisTest::~HypothesisTest() { delete d; } void HypothesisTest::setDataSourceType(DataSourceType type) { if (type != d->dataSourceType) { d->dataSourceType = type; } } HypothesisTest::DataSourceType HypothesisTest::dataSourceType() const { return d->dataSourceType; } void HypothesisTest::setDataSourceSpreadsheet(Spreadsheet *spreadsheet) { if (spreadsheet != d->dataSourceSpreadsheet) d->setDataSourceSpreadsheet(spreadsheet); } void HypothesisTest::setColumns(QVector cols) { d->m_columns = cols; } void HypothesisTest::setColumns(QStringList cols) { return d->setColumns(cols); } QStringList HypothesisTest::allColumns() { return d->all_columns; } void HypothesisTest::setTailType(HypothesisTest::TailType tailType) { d->tail_type = tailType; } HypothesisTest::TailType HypothesisTest::tailType() { return d->tail_type; } void HypothesisTest::setPopulationMean(QVariant populationMean) { d->m_population_mean = populationMean.toDouble(); } void HypothesisTest::setSignificanceLevel(QVariant alpha) { d->m_significance_level = alpha.toDouble(); } QString HypothesisTest::testName() { return d->m_currTestName; } QString HypothesisTest::statsTable() { return d->m_stats_table; } void HypothesisTest::performTwoSampleIndependentTTest(bool categorical_variable, bool equal_variance) { d->m_currTestName = i18n( "

Two Sample Independent T Test

"); d->performTwoSampleIndependentTest(HypothesisTestPrivate::TestT, categorical_variable, equal_variance); } void HypothesisTest::performTwoSamplePairedTTest() { d->m_currTestName = i18n( "

Two Sample Paried T Test

"); d->performTwoSamplePairedTest(HypothesisTestPrivate::TestT); } void HypothesisTest::performOneSampleTTest() { d->m_currTestName = i18n( "

One Sample T Test

"); d->performOneSampleTest(HypothesisTestPrivate::TestT); } void HypothesisTest::performTwoSampleIndependentZTest() { d->m_currTestName = i18n( "

Two Sample Independent Z Test

"); d->performTwoSampleIndependentTest(HypothesisTestPrivate::TestZ); } void HypothesisTest::performTwoSamplePairedZTest() { d->m_currTestName = i18n( "

Two Sample Paired Z Test

"); d->performTwoSamplePairedTest(HypothesisTestPrivate::TestZ); } void HypothesisTest::performOneSampleZTest() { d->m_currTestName = i18n( "

One Sample Z Test

"); d->performOneSampleTest(HypothesisTestPrivate::TestZ); } void HypothesisTest::performOneWayAnova() { d->m_currTestName = i18n( "

One Way Anova

"); d->performOneWayAnova(); } void HypothesisTest::performLeveneTest(bool categorical_variable) { d->m_currTestName = i18n( "

Levene Test for Equality of Variance

"); d->performLeveneTest(categorical_variable); } /****************************************************************************** * Private Implementations * ****************************************************************************/ //TODO: round off numbers while printing +//TODO: backend of z test; + HypothesisTestPrivate::HypothesisTestPrivate(HypothesisTest* owner) : q(owner) { } HypothesisTestPrivate::~HypothesisTestPrivate() { } void HypothesisTestPrivate::setDataSourceSpreadsheet(Spreadsheet *spreadsheet) { dataSourceSpreadsheet = spreadsheet; //setting rows and columns count; m_rowCount = dataSourceSpreadsheet->rowCount(); m_columnCount = dataSourceSpreadsheet->columnCount(); for (auto* col : dataSourceSpreadsheet->children()) { all_columns << col->name(); } } void HypothesisTestPrivate::setColumns(QStringList cols) { m_columns.clear(); Column* column = new Column("column"); for (QString col : cols) { if (!cols.isEmpty()) { column = dataSourceSpreadsheet->column(col); m_columns.append(column); } } } /**************************Two Sample Independent *************************************/ void HypothesisTestPrivate::performTwoSampleIndependentTest(TestType test,bool categorical_variable, bool equal_variance) { QString test_name; double value; int df = 0; double p_value = 0; double sp = 0; - clearGlobalVariables(); + clearTestView(); if (m_columns.size() != 2) { printError("Inappropriate number of columns selected"); emit q->changed(); return; } int n[2]; double sum[2], mean[2], std[2]; QString col1_name = m_columns[0]->name(); QString col2_name = m_columns[1]->name(); if (!categorical_variable && (m_columns[0]->columnMode() == AbstractColumn::Integer || m_columns[0]->columnMode() == AbstractColumn::Numeric)) { for (int i = 0; i < 2; i++) { findStats(m_columns[i], n[i], sum[i], mean[i], std[i]); if (n[i] < 1) { printError("At least one of selected column is empty"); emit q->changed(); return; } } } else { QMap col_name; QString base_col_name = ""; int np; int total_rows; countPartitions(m_columns[0], np, total_rows); if (np != 2) { printError( i18n("Number of Categorical Variable in Column %1 is not equal to 2", m_columns[0]->name())); emit q->changed(); return; } if (m_columns[0]->columnMode() == AbstractColumn::Integer || m_columns[0]->columnMode() == AbstractColumn::Numeric) base_col_name = m_columns[0]->name(); ErrorType error_code = findStatsCategorical(m_columns[0], m_columns[1], n, sum, mean, std, col_name, np, total_rows); switch (error_code) { case ErrorUnqualSize: { printError( i18n("Unequal size between Column %1 and Column %2", m_columns[0]->name(), m_columns[1]->name())); emit q->changed(); return; }case ErrorEmptyColumn: { printError("At least one of selected column is empty"); emit q->changed(); return; } case NoError: break; } QMapIterator i(col_name); while (i.hasNext()) { i.next(); if (i.value() == 1) col1_name = base_col_name + " " + i.key(); else col2_name = base_col_name + " " + i.key(); } } QVariant row_major[] = {"", "N", "Sum", "Mean", "Std", col1_name, n[0], sum[0], mean[0], std[0], col2_name, n[1], sum[1], mean[1], std[1]}; m_stats_table = getHtmlTable(3, 5, row_major); switch (test) { case TestT: { test_name = "T"; if (equal_variance) { df = n[0] + n[1] - 2; sp = qSqrt( ((n[0]-1)*gsl_pow_2(std[0]) + (n[1]-1)*gsl_pow_2(std[1]))/df); value = (mean[0] - mean[1])/(sp*qSqrt(1.0/n[0] + 1.0/n[1])); printLine(9, "Assumption: Equal Variance b/w both population means"); } else { double temp_val; temp_val = gsl_pow_2( gsl_pow_2(std[0])/n[0] + gsl_pow_2(std[1])/n[1]); temp_val = temp_val / ( (gsl_pow_2( (gsl_pow_2(std[0])/n[0]))/(n[0]-1)) + (gsl_pow_2( (gsl_pow_2(std[1])/n[1]))/(n[1]-1))); df = qRound(temp_val); value = (mean[0] - mean[1]) / (qSqrt( (gsl_pow_2(std[0])/n[0]) + (gsl_pow_2(std[1])/n[1]))); printLine(9, "Assumption: UnEqual Variance b/w both population means"); } break; } case TestZ: { test_name = "Z"; sp = qSqrt( ((n[0]-1)*gsl_pow_2(std[0]) + (n[1]-1)*gsl_pow_2(std[1]))/df); value = (mean[0] - mean[1])/(sp*qSqrt(1.0/n[0] + 1.0/n[1])); p_value = gsl_cdf_gaussian_P(value, sp); } } m_currTestName = i18n( "

Two Sample Independent %1 Test for %2 vs %3

", test_name, col1_name, col2_name); p_value = getPValue(test, value, col1_name, col2_name, (mean[0] - mean[1]), sp, df); printLine(2, i18n("Significance level is %1", m_significance_level), "blue"); printLine(4, i18n("%1 Value is %2 ", test_name, value), "green"); printLine(5, i18n("P Value is %1 ", p_value), "green"); printLine(6, i18n("Degree of Freedom is %1", df), "green"); if (p_value <= m_significance_level) q->m_view->setResultLine(5, i18n("We can safely reject Null Hypothesis for significance level %1", m_significance_level), Qt::ToolTipRole); else q->m_view->setResultLine(5, i18n("There is a plausibility for Null Hypothesis to be true"), Qt::ToolTipRole); emit q->changed(); return; } /********************************Two Sample Paired ***************************************/ void HypothesisTestPrivate::performTwoSamplePairedTest(TestType test) { QString test_name; int n; double sum, mean, std; double value; int df = 0; double p_value = 0; - clearGlobalVariables(); + clearTestView(); if (m_columns.size() != 2) { printError("Inappropriate number of columns selected"); emit q->changed(); return; } for (int i = 0; i < 2; i++) { if (!(m_columns[i]->columnMode() == AbstractColumn::Numeric || m_columns[i]->columnMode() == AbstractColumn::Integer)) { printError("select only columns with numbers"); emit q->changed(); return; } } ErrorType error_code = findStatsPaired(m_columns[0], m_columns[1], n, sum, mean, std); switch (error_code) { - case ErrorUnqualSize: { - printError("both columns are having different sizes"); - emit q->changed(); - return; - } case ErrorEmptyColumn: { - printError("columns are empty"); - emit q->changed(); - return; - } case NoError: - break; + case ErrorUnqualSize: { + printError("both columns are having different sizes"); + emit q->changed(); + return; + } case ErrorEmptyColumn: { + printError("columns are empty"); + emit q->changed(); + return; + } case NoError: + break; } if (n == -1) { printError("both columns are having different sizes"); emit q->changed(); return; } if (n < 1) { printError("columns are empty"); emit q->changed(); return; } QVariant row_major[] = {"", "N", "Sum", "Mean", "Std", "difference", n, sum, mean, std}; m_stats_table = getHtmlTable(2, 5, row_major); switch (test) { case TestT: { value = mean / (std/qSqrt(n)); df = n - 1; test_name = "T"; printLine(6, i18n("Degree of Freedom is %1name(), i18n("%1",m_population_mean), mean, std, df); m_currTestName = i18n( "

One Sample %1 Test for %2 vs %3

", test_name, m_columns[0]->name(), m_columns[1]->name()); printLine(2, i18n("Significance level is %1 ", m_significance_level), "blue"); printLine(4, i18n("%1 Value is %2 ", test_name, value), "green"); printLine(5, i18n("P Value is %1 ", p_value), "green"); if (p_value <= m_significance_level) q->m_view->setResultLine(5, i18n("We can safely reject Null Hypothesis for significance level %1", m_significance_level), Qt::ToolTipRole); else q->m_view->setResultLine(5, i18n("There is a plausibility for Null Hypothesis to be true"), Qt::ToolTipRole); emit q->changed(); return; } /******************************** One Sample ***************************************/ void HypothesisTestPrivate::performOneSampleTest(TestType test) { QString test_name; double value; int df = 0; double p_value = 0; - clearGlobalVariables(); + clearTestView(); if (m_columns.size() != 1) { printError("Inappropriate number of columns selected"); emit q->changed(); return; } if ( !(m_columns[0]->columnMode() == AbstractColumn::Numeric || m_columns[0]->columnMode() == AbstractColumn::Integer)) { printError("select only columns with numbers"); emit q->changed(); return; } int n; double sum, mean, std; ErrorType error_code = findStats(m_columns[0], n, sum, mean, std); switch (error_code) { case ErrorUnqualSize: { printError("column is empty"); emit q->changed(); return; } case NoError: break; case ErrorEmptyColumn: { emit q->changed(); return; } } QVariant row_major[] = {"", "N", "Sum", "Mean", "Std", m_columns[0]->name(), n, sum, mean, std}; m_stats_table = getHtmlTable(2, 5, row_major); switch (test) { case TestT: { test_name = "T"; value = (mean - m_population_mean) / (std/qSqrt(n)); df = n - 1; printLine(6, i18n("Degree of Freedom is %1", df), "blue"); break; } case TestZ: { test_name = "Z"; df = 0; value = (mean - m_population_mean) / (std/qSqrt(n)); }} p_value = getPValue(test, value, m_columns[0]->name(), i18n("%1",m_population_mean), mean - m_population_mean, std, df); m_currTestName = i18n( "

One Sample %1 Test for %2

", test_name, m_columns[0]->name()); printLine(2, i18n("Significance level is %1", m_significance_level), "blue"); printLine(4, i18n("%1 Value is %2", test_name, value), "green"); printLine(5, i18n("P Value is %1", p_value), "green"); if (p_value <= m_significance_level) q->m_view->setResultLine(5, i18n("We can safely reject Null Hypothesis for significance level %1", m_significance_level), Qt::ToolTipRole); else q->m_view->setResultLine(5, i18n("There is a plausibility for Null Hypothesis to be true"), Qt::ToolTipRole); emit q->changed(); return; } /*************************************One Way Anova***************************************/ // all standard variables and formulas are taken from this wikipedia page: // https://en.wikipedia.org/wiki/One-way_analysis_of_variance // b stands for b/w groups // w stands for within groups void HypothesisTestPrivate::performOneWayAnova() { - clearGlobalVariables(); + clearTestView(); int np, total_rows; // np is number of partition i.e., number of classes countPartitions(m_columns[0], np, total_rows); int* ni = new int[np]; double* sum = new double[np]; double* mean = new double[np]; double* std = new double[np]; QString* col_names = new QString[np]; QMap classname_to_index; QString base_col_name = ""; if (m_columns[0]->columnMode() == AbstractColumn::Integer || m_columns[0]->columnMode() == AbstractColumn::Numeric) base_col_name = m_columns[0]->name(); findStatsCategorical(m_columns[0], m_columns[1], ni, sum, mean, std, classname_to_index, np, total_rows); double y_bar = 0; // overall mean double s_b = 0; // sum of squares of (mean - overall_mean) between the groups int f_b = 0; // degree of freedom between the groups double ms_b = 0; // mean sum of squares between the groups double s_w = 0; // sum of squares of (value - mean of group) within the groups int f_w = 0; // degree of freedom within the group double ms_w = 0; // mean sum of squares within the groups double f_value = 0; double p_value = 0; // now finding mean of each group; for (int i = 0; i < np; i++) y_bar += mean[i]; y_bar = y_bar / np; for (int i = 0; i < np; i++) { s_b += ni[i] * gsl_pow_2( ( mean[i] - y_bar)); if (ni[i] > 1) s_w += gsl_pow_2( std[i])*(ni[i] - 1); else s_w += gsl_pow_2( std[i]); f_w += ni[i] - 1; } f_b = np - 1; ms_b = s_b / f_b; ms_w = s_w / f_w; f_value = ms_b / ms_w; p_value = nsl_stats_fdist_p(f_value, static_cast(np-1), f_w); QMapIterator i(classname_to_index); while (i.hasNext()) { i.next(); col_names[i.value()-1] = base_col_name + " " + i.key(); } // now printing the statistics and result; int row_count = np + 1, column_count = 5; QVariant* row_major = new QVariant[row_count*column_count]; // header data; row_major[0] = ""; row_major[1] = "Ni"; row_major[2] = "Sum"; row_major[3] = "Mean"; row_major[4] = "Std"; // table data for (int row_i = 1; row_i < row_count ; row_i++) { row_major[row_i*column_count] = col_names[row_i - 1]; row_major[row_i*column_count + 1] = ni[row_i - 1]; row_major[row_i*column_count + 2] = sum[row_i - 1]; row_major[row_i*column_count + 3] = QString::number( mean[row_i - 1], 'f', 3); row_major[row_i*column_count + 4] = QString::number( std[row_i - 1], 'f', 3); } m_stats_table = i18n( "

Group Summary Statistics

"); m_stats_table += getHtmlTable(row_count, column_count, row_major); m_stats_table += getLine(""); m_stats_table += getLine(""); m_stats_table += i18n( "

Grand Summary Statistics

"); m_stats_table += getLine(""); m_stats_table += getLine(i18n("Overall Mean is %1", y_bar)); row_count = 4; column_count = 3; row_major->clear(); row_major[0] = ""; row_major[1] = "Between Groups"; row_major[2] = "Within Groups"; int base_index = 0; base_index = 1*column_count; row_major[base_index + 0] = "Sum of Squares"; row_major[base_index + 1] = s_b; row_major[base_index + 2] = s_w; base_index = 2*column_count; row_major[base_index + 0] = "Degree of Freedom"; row_major[base_index + 1] = f_b; row_major[base_index + 2] = f_w; base_index = 3*column_count; row_major[base_index + 0] = "Mean Square Value"; row_major[base_index + 1] = ms_b; row_major[base_index + 2] = ms_w; m_stats_table += getHtmlTable(row_count, column_count, row_major); printLine(1, i18n("F Value is %1", f_value), "blue"); printLine(2, i18n("P Value is %1 ", p_value), "green"); if (p_value <= m_significance_level) q->m_view->setResultLine(2, i18n("We can safely reject Null Hypothesis for significance level %1", m_significance_level), Qt::ToolTipRole); else q->m_view->setResultLine(2, i18n("There is a plausibility for Null Hypothesis to be true"), Qt::ToolTipRole); emit q->changed(); return; } /**************************************Levene Test****************************************/ +// TODO: Fix: Program crashes when n = np; void HypothesisTestPrivate::performLeveneTest(bool categorical_variable) { QString test_name; double f_value; int df = 0; // degree of freedom double p_value = 0; int np = 0; // number of partitions int n = 0; int total_rows = 0; - clearGlobalVariables(); + clearTestView(); if (m_columns.size() != 2) { printError("Inappropriate number of columns selected"); emit q->changed(); return; } if (!categorical_variable && (m_columns[0]->columnMode() == AbstractColumn::Integer || m_columns[0]->columnMode() == AbstractColumn::Numeric)) np = m_columns.size(); else countPartitions(m_columns[0], np, n); if (np < 2) { printError("select atleast two columns/ classes"); emit q->changed(); return; } double* yi_bar = new double[np]; double* zi_bar = new double[np]; double zi_bar_bar = 0; double* ni = new double[np]; for (int i = 0; i < np; i++) { yi_bar[i] = 0; zi_bar[i] = 0; ni[i] = 0; } QString* col_names = new QString[np]; if (!categorical_variable && (m_columns[0]->columnMode() == AbstractColumn::Integer || m_columns[0]->columnMode() == AbstractColumn::Numeric)) { total_rows = m_columns[0]->rowCount(); double value = 0; for (int j = 0; j < total_rows; j++) { int number_nan_cols = 0; for (int i = 0; i < np; i++) { value = m_columns[i]->valueAt(j); if (std::isnan(value)) { number_nan_cols++; continue; } yi_bar[i] += value; ni[i]++; n++; } if (number_nan_cols == np) { total_rows = j; break; } } for (int i = 0; i < np; i++) { if (ni[i] > 0) yi_bar[i] = yi_bar[i] / ni[i]; } for (int j = 0; j < total_rows; j++) { for (int i = 0; i < np; i++) { value = m_columns[i]->valueAt(j); if (!(std::isnan(value))) zi_bar[i] += abs(value - yi_bar[i]); } } for (int i = 0; i < np; i++) { zi_bar_bar += zi_bar[i]; if (ni[i] > 0) zi_bar[i] = zi_bar[i] / ni[i]; } zi_bar_bar = zi_bar_bar / n; double numerator_value = 0; double denominator_value = 0; for (int j = 0; j < total_rows; j++) { for (int i = 0; i < np; i++) { value = m_columns[i]->valueAt(j); if (!(std::isnan(value))) { double zij = abs(value - yi_bar[i]); denominator_value += gsl_pow_2( (zij - zi_bar[i])); } } } for (int i = 0; i < np; i++) { col_names[i] = m_columns[i]->name(); numerator_value += ni[i]*gsl_pow_2( (zi_bar[i]-zi_bar_bar)); } f_value = ((n - np) / (np - 1)) * (numerator_value / denominator_value); // qDebug() << "n is " << n; // qDebug() << "fvalue is " << f_value; // qDebug() << "numerator is " << numerator_value; // qDebug() << "denominator is " << denominator_value; } else { QMap classname_to_index; AbstractColumn::ColumnMode original_col_mode = m_columns[0]->columnMode(); m_columns[0]->setColumnMode(AbstractColumn::Text); int partition_number = 1; QString name; double value; int class_index; for (int j = 0; j < n; j++) { name = m_columns[0]->textAt(j); value = m_columns[1]->valueAt(j); if (std::isnan(value)) { n = j; break; } if (classname_to_index[name] == 0) { classname_to_index[name] = partition_number; partition_number++; } class_index = classname_to_index[name]-1; ni[class_index]++; yi_bar[class_index] += value; } for (int i = 0; i < np; i++) { if (ni[i] > 0) yi_bar[i] = yi_bar[i] / ni[i]; } for (int j = 0; j < n; j++) { name = m_columns[0]->textAt(j); value = m_columns[1]->valueAt(j); class_index = classname_to_index[name] - 1; zi_bar[class_index] += abs(value - yi_bar[class_index]); } for (int i = 0; i < np; i++) { zi_bar_bar += zi_bar[i]; zi_bar[i] = zi_bar[i] / ni[i]; } zi_bar_bar = zi_bar_bar / n; double numerator_value = 0; double denominator_value = 0; for (int j = 0; j < n; j++) { name = m_columns[0]->textAt(j); value = m_columns[1]->valueAt(j); class_index = classname_to_index[name] - 1; double zij = abs(value - yi_bar[class_index]); denominator_value += gsl_pow_2( (zij - zi_bar[class_index])); } for (int i = 0; i < np; i++) numerator_value += ni[i]*gsl_pow_2( (zi_bar[i]-zi_bar_bar)); f_value = ((n - np) / (np - 1)) * (numerator_value / denominator_value); // qDebug() << "n is " << n; // qDebug() << "fvalue is " << f_value; // qDebug() << "numerator is " << numerator_value; // qDebug() << "denominator is " << denominator_value; QMapIterator i(classname_to_index); while (i.hasNext()) { i.next(); col_names[i.value()-1] = m_columns[0]->name() + " " + i.key(); } m_columns[0]->setColumnMode(original_col_mode); } df = n - np; int row_count = np+1; int column_count = 4; QVariant* row_major = new QVariant[row_count*column_count]; // header data; row_major[0] = ""; row_major[1] = "Ni"; row_major[2] = "Yi_bar"; row_major[3] = "Zi_bar"; // table data for (int row_i = 1; row_i < row_count; row_i++) { row_major[row_i*column_count] = col_names[row_i-1]; row_major[row_i*column_count + 1] = ni[row_i-1]; row_major[row_i*column_count + 2] = yi_bar[row_i-1]; row_major[row_i*column_count + 3] = zi_bar[row_i-1]; } m_stats_table = getHtmlTable(row_count, column_count, row_major); p_value = nsl_stats_fdist_p(f_value, static_cast(np-1), df); printLine(0, "Null Hypothesis: Variance is equal between all classes", "blue"); printLine(1, "Alternate Hypothesis: Variance is not equal in at-least one pair of classes", "blue"); printLine(2, i18n("Significance level is %1", m_significance_level), "blue"); printLine(4, i18n("F Value is %1 ", f_value), "green"); printLine(5, i18n("P Value is %1 ", p_value), "green"); printLine(6, i18n("Degree of Freedom is %1", df), "green"); if (p_value <= m_significance_level) { q->m_view->setResultLine(5, i18n("We can safely reject Null Hypothesis for significance level %1", m_significance_level), Qt::ToolTipRole); printLine(8, "Requirement for homogeneity is not met", "red"); } else { q->m_view->setResultLine(5, i18n("There is a plausibility for Null Hypothesis to be true"), Qt::ToolTipRole); printLine(8, "Requirement for homogeneity is met", "green"); } emit q->changed(); return; } /***************************************Helper Functions*************************************/ HypothesisTestPrivate::ErrorType HypothesisTestPrivate::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; } if (count < 1) return HypothesisTestPrivate::ErrorEmptyColumn; mean = sum/count; for (int i = 0; i < count; i++) { double row = column->valueAt(i); std += gsl_pow_2( (row - mean)); } if (count > 1) std = std / (count-1); std = qSqrt(std); return HypothesisTestPrivate::NoError; } HypothesisTestPrivate::ErrorType HypothesisTestPrivate::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 HypothesisTestPrivate::ErrorUnqualSize; break; } sum += cell1 - cell2; } if (count < 1) return HypothesisTestPrivate::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 = qSqrt(std); return HypothesisTestPrivate::NoError; } void HypothesisTestPrivate::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); } HypothesisTestPrivate::ErrorType HypothesisTestPrivate::findStatsCategorical(Column *column1, Column *column2, int n[], double sum[], double mean[], double std[], QMap &col_name, const int &np, const int &total_rows) { 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 original_col_mode = columns[0]->columnMode(); columns[0]->setColumnMode(AbstractColumn::Text); int partition_number = 1; for (int i = 0; i < total_rows; i++) { QString name = columns[0]->textAt(i); double value = columns[1]->valueAt(i); if (std::isnan(value)) { columns[0]->setColumnMode(original_col_mode); return HypothesisTestPrivate::ErrorUnqualSize; } if (col_name[name] == 0) { col_name[name] = partition_number; partition_number++; } n[col_name[name]-1]++; sum[col_name[name]-1] += value; } for (int i = 0; i < np; i++) mean[i] = sum[i] / n[i]; for (int i = 0; i < total_rows; i++) { QString name = columns[0]->textAt(i); double value = columns[1]->valueAt(i); std[col_name[name]-1] += gsl_pow_2( (value - mean[col_name[name]-1])); } for (int i = 0; i < np; i++) { if (n[i] > 1) std[i] = std[i] / (n[i] - 1); std[i] = qSqrt(std[i]); } columns[0]->setColumnMode(original_col_mode); if (columns[0]->columnMode() == AbstractColumn::Integer || columns[0]->columnMode() == AbstractColumn::Numeric) { } return HypothesisTestPrivate::NoError; } double HypothesisTestPrivate::getPValue(const HypothesisTestPrivate::TestType &test, double &value, const QString &col1_name, const QString &col2_name, const double mean, const double sp, const int df) { double p_value = 0; //TODO change ("⋖") symbol to ("<"), currently macro UTF8_QSTRING is not working properly if used "<" symbol; switch (test) { case TestT: { switch (tail_type) { case HypothesisTest::TailNegative: p_value = gsl_cdf_tdist_P(value, df); printLine(0, i18n("Null Hypothesis: Population mean of %1 %2 Population mean of %3", col1_name, UTF8_QSTRING("≥"), col2_name), "blue"); printLine(1, i18n("Alternate Hypothesis: Population mean of %1 %2 Population mean of %3", col1_name, UTF8_QSTRING("⋖"), col2_name), "blue"); break; case HypothesisTest::TailPositive: value *= -1; p_value = gsl_cdf_tdist_P(value, df); printLine(0, i18n("Null Hypothesis: Population mean of %1 %2 Population mean of %3", col1_name, UTF8_QSTRING("≤"), col2_name), "blue"); printLine(1, i18n("Alternate Hypothesis: Population mean of %1 %2 Population mean of %3", col1_name, UTF8_QSTRING(">"), col2_name), "blue"); break; case HypothesisTest::TailTwo: + // TODO: check for correctness between: + // p_value = 2*gsl_cdf_tdist_P(value, df) v/s + // p_value = gsl_cdf_tdis_P(value, df) + gsl_cdf_tdis_P(-value, df); p_value = 2.*gsl_cdf_tdist_P(value, df); printLine(0, i18n("Null Hypothesis: Population mean of %1 %2 Population mean of %3", col1_name, UTF8_QSTRING("="), col2_name), "blue"); printLine(1, i18n("Alternate Hypothesis: Population mean of %1 %2 Population mean of %3", col1_name, UTF8_QSTRING("≠"), col2_name), "blue"); break; } break; } case TestZ: { switch (tail_type) { case HypothesisTest::TailNegative: p_value = gsl_cdf_gaussian_P(value - mean, sp); printLine(0, i18n("Null Hypothesis: Population mean of %1 %2 Population mean of %3 ", col1_name, UTF8_QSTRING("≥"), col2_name), "blue"); printLine(1, i18n("Alternate Hypothesis: Population mean of %1 %2 Population mean of %3 ", col1_name, UTF8_QSTRING("⋖"), col2_name), "blue"); break; case HypothesisTest::TailPositive: value *= -1; p_value = nsl_stats_tdist_p(value - mean, sp); printLine(0, i18n("Null Hypothesis: Population mean of %1 %2 Population mean of %3 ", col1_name, UTF8_QSTRING("≤"), col2_name), "blue"); printLine(1, i18n("Alternate Hypothesis: Population mean of %1 %2 Population mean of %3 ", col1_name, UTF8_QSTRING(">"), col2_name), "blue"); break; case HypothesisTest::TailTwo: p_value = 2.*gsl_cdf_gaussian_P(value - mean, sp); printLine(0, i18n("Null Hypothesis: Population mean of %1 %2 Population mean of %3 ", col1_name, UTF8_QSTRING("="), col2_name), "blue"); printLine(1, i18n("Alternate Hypothesis: Population mean of %1 %2 Population mean of %3 ", col1_name, UTF8_QSTRING("≠"), col2_name), "blue"); break; } break; } } if (p_value > 1) return 1; return p_value; } QString HypothesisTestPrivate::getHtmlTable(int row, int column, QVariant *row_major) { 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 = row_major[j].toString(); table += i18n(" ", bg, element); } table += " "; if (pky) bg = "tg-0pky"; else bg = "tg-btxf"; pky = !pky; for (int i = 1; i < row; i++) { table += " "; QString element = row_major[i*column].toString(); table += i18n(" ", bg, element); for (int j = 1; j < column; j++) { QString element = row_major[i*column+j].toString(); table += i18n(" ", bg, element); } table += " "; if (pky) bg = "tg-0pky"; else bg = "tg-btxf"; pky = !pky; } table += "
%2
%2%2
"; return table; } QString HypothesisTestPrivate::getLine(const QString &msg, const QString &color) { return i18n("

%2

", color, msg); } void HypothesisTestPrivate::printLine(const int &index, const QString &msg, const QString &color) { q->m_view->setResultLine(index, getLine(msg, color)); return; } void HypothesisTestPrivate::printError(const QString &error_msg) { printLine(0, error_msg, "red"); emit q->changed(); } -void HypothesisTestPrivate::clearGlobalVariables() { +void HypothesisTestPrivate::clearTestView() { m_stats_table = ""; q->m_view->clearResult(); } /********************************************************************************** * virtual functions implementations * ********************************************************************************/ /*! Saves as XML. */ void HypothesisTest::save(QXmlStreamWriter* writer) const { writer->writeStartElement("hypothesisTest"); writeBasicAttributes(writer); writeCommentElement(writer); //TODO: writer->writeEndElement(); } /*! Loads from XML. */ bool HypothesisTest::load(XmlStreamReader* reader, bool preview) { Q_UNUSED(preview); if (!readBasicAttributes(reader)) return false; //TODO: return !reader->hasError(); } Spreadsheet *HypothesisTest::dataSourceSpreadsheet() const { return d->dataSourceSpreadsheet; } bool HypothesisTest::exportView() const { return true; } bool HypothesisTest::printView() { return true; } bool HypothesisTest::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* HypothesisTest::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* HypothesisTest::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); // Q_ASSERT(menu); // emit requestProjectContextMenu(menu); return menu; } diff --git a/src/backend/hypothesis_test/HypothesisTestPrivate.h b/src/backend/hypothesis_test/HypothesisTestPrivate.h index 6eb84c988..912e15420 100644 --- a/src/backend/hypothesis_test/HypothesisTestPrivate.h +++ b/src/backend/hypothesis_test/HypothesisTestPrivate.h @@ -1,86 +1,86 @@ /*************************************************************************** File : HypothesisTestPrivate.h Project : LabPlot Description : Private members of Hypothesis Test -------------------------------------------------------------------- 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 HYPOTHESISTESTPRIVATE_H #define HYPOTHESISTESTPRIVATE_H #include class QStandardItemModel; class HypothesisTestPrivate { public: explicit HypothesisTestPrivate(HypothesisTest*); virtual ~HypothesisTestPrivate(); enum TestType {TestT, TestZ}; enum ErrorType {ErrorUnqualSize, ErrorEmptyColumn, NoError}; QString name() const; void setDataSourceSpreadsheet(Spreadsheet* spreadsheet); void setColumns(QStringList cols); void performTwoSampleIndependentTest(TestType test, bool categorical_variable = false, bool equal_variance = true); void performTwoSamplePairedTest(TestType test); void performOneSampleTest(TestType test); void performOneWayAnova(); void performLeveneTest(bool categorical_variable); HypothesisTest* const q; HypothesisTest::DataSourceType dataSourceType{HypothesisTest::DataSourceSpreadsheet}; Spreadsheet* dataSourceSpreadsheet{nullptr}; QVector m_columns; QStringList all_columns; bool m_dbCreated{false}; int m_rowCount{0}; int m_columnCount{0}; QString m_currTestName{"Result Table"}; double m_population_mean; double m_significance_level; QString m_stats_table; HypothesisTest::TailType tail_type; private: void countPartitions(Column* column, int &np, int &total_rows); 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 &col_name, const int &np, const int &total_rows); double getPValue(const TestType &test, double &value, const QString &col1_name, const QString &col2_name, const double mean, const double sp, const int df); QString getHtmlTable(int row, int column, QVariant *row_major); QString getLine(const QString &msg, const QString &color = "black"); void printLine(const int &index, const QString &msg, const QString &color = "black"); void printError(const QString &error_msg); - void clearGlobalVariables(); + void clearTestView(); }; #endif diff --git a/src/kdefrontend/dockwidgets/HypothesisTestDock.cpp b/src/kdefrontend/dockwidgets/HypothesisTestDock.cpp index 7d6e377ba..b7614ee48 100644 --- a/src/kdefrontend/dockwidgets/HypothesisTestDock.cpp +++ b/src/kdefrontend/dockwidgets/HypothesisTestDock.cpp @@ -1,783 +1,810 @@ /*************************************************************************** File : HypothesisTestDock.cpp Project : LabPlot Description : widget for hypothesis 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 "HypothesisTestDock.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/pivot/hypothesisTestView.h" #include "kdefrontend/TemplateHandler.h" #include #include #include #include #include #include #include #include #include /*! \class HypothesisTestDock \brief Provides a dock (widget) for hypothesis testing: \ingroup kdefrontend */ //TOOD: Make this dock widget scrollable and automatic resizeable for different screens. HypothesisTestDock::HypothesisTestDock(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"; // adding item to tests and testtype combo box; - ui.cbTest->addItem(i18n("T Test")); - ui.cbTest->addItem(i18n("Z Test")); - ui.cbTest->addItem(i18n("Anova")); - test_type_t_z.append( i18n( "Two Sample Independent")); - test_type_t_z.append( i18n("Two Sample Paired")); - test_type_t_z.append( i18n("One Sample")); - - test_type_anova.append( i18n("One Way")); - test_type_anova.append( i18n("Two Way")); + ui.cbTest->addItem( i18n("T Test"), Test::Type::TTest); + ui.cbTest->addItem( i18n("Z Test"), Test::Type::ZTest); + ui.cbTest->addItem( i18n("ANOVA"), Test::Type::Anova); // making all test blocks invisible at starting. ui.pbLeveneTest->hide(); ui.lCategorical->hide(); ui.chbCategorical->hide(); ui.lCol1->hide(); ui.cbCol1->hide(); ui.lCol2->hide(); ui.cbCol2->hide(); ui.lEqualVariance->hide(); ui.chbEqualVariance->hide(); ui.chbEqualVariance->setChecked(true); ui.pbPerformTest->setEnabled(false); ui.rbH1OneTail2->hide(); ui.rbH1OneTail1->hide(); ui.rbH1TwoTail->hide(); ui.rbH0OneTail1->hide(); ui.rbH0OneTail2->hide(); ui.rbH0TwoTail->hide(); ui.lH0->hide(); ui.lH1->hide(); QString mu = UTF8_QSTRING("μ"); QString mu0 = UTF8_QSTRING("μₒ"); // radio button for null and alternate hypothesis // for alternative hypothesis (h1) // one_tail_1 is mu > mu0; one_tail_2 is mu < mu0; two_tail = mu != mu0; ui.rbH1OneTail1->setText( i18n("%1 %2 %3", mu, UTF8_QSTRING(">"), mu0)); ui.rbH1OneTail2->setText( i18n("%1 %2 %3", mu, UTF8_QSTRING("<"), mu0)); ui.rbH1TwoTail->setText( i18n("%1 %2 %3", mu, UTF8_QSTRING("≠"), mu0)); ui.rbH0OneTail1->setText( i18n("%1 %2 %3",mu, UTF8_QSTRING("≤"), mu0)); ui.rbH0OneTail2->setText( i18n("%1 %2 %3", mu, UTF8_QSTRING("≥"), mu0)); ui.rbH0TwoTail->setText( i18n("%1 %2 %3", mu, UTF8_QSTRING("="), mu0)); ui.rbH0TwoTail->setEnabled(false); ui.rbH0OneTail1->setEnabled(false); ui.rbH0OneTail2->setEnabled(false); // setting muo and alpha buttons ui.lMuo->setText( i18n("%1", mu0)); ui.lAlpha->setText( i18n("%1", UTF8_QSTRING("α"))); ui.leMuo->setText( i18n("%1", population_mean)); ui.leAlpha->setText( i18n("%1", significance_level)); ui.lMuo->hide(); ui.lAlpha->hide(); ui.leMuo->hide(); ui.leAlpha->hide(); ui.pbPerformTest->setIcon(QIcon::fromTheme("run-build")); + ui.leMuo->setText( i18n("%1", population_mean)); + ui.leAlpha->setText( i18n("%1", significance_level)); + // readConnections(); // auto* style = ui.bAddRow->style(); // ui.bAddRow->setIcon(style->standardIcon(QStyle::SP_ArrowRight)); // ui.bAddRow->setToolTip(i18n("Add the selected field to rows")); // ui.bRemoveRow->setIcon(style->standardIcon(QStyle::SP_ArrowLeft)); // ui.bRemoveRow->setToolTip(i18n("Remove the selected field from rows")); // ui.bAddColumn->setIcon(style->standardIcon(QStyle::SP_ArrowRight)); // ui.bAddColumn->setToolTip(i18n("Add the selected field to columns")); // ui.bRemoveColumn->setIcon(style->standardIcon(QStyle::SP_ArrowLeft)); // ui.bRemoveColumn->setToolTip(i18n("Remove the selected field from columns")); // //add/remove buttons only enabled if something was selected // ui.bAddRow->setEnabled(false); // ui.bRemoveRow->setEnabled(false); // ui.bAddColumn->setEnabled(false); // ui.bRemoveColumn->setEnabled(false); // connect(ui.leName, &QLineEdit::textChanged, this, &HypothesisTestDock::nameChanged); // connect(ui.leComment, &QLineEdit::textChanged, this, &HypothesisTestDock::commentChanged); connect(ui.cbDataSourceType, static_cast(&QComboBox::currentIndexChanged), this, &HypothesisTestDock::dataSourceTypeChanged); connect(cbSpreadsheet, &TreeViewComboBox::currentModelIndexChanged, this, &HypothesisTestDock::spreadsheetChanged); // connect(ui.cbConnection, static_cast(&QComboBox::currentIndexChanged), // this, &HypothesisTestDock::connectionChanged); // connect(ui.cbTable, static_cast(&QComboBox::currentIndexChanged), // this, &HypothesisTestDock::tableChanged); // connect(ui.bDatabaseManager, &QPushButton::clicked, this, &HypothesisTestDock::showDatabaseManager); // connect(ui.bAddRow, &QPushButton::clicked, this, &HypothesisTestDock::addRow); // connect(ui.bRemoveRow, &QPushButton::clicked, this,&HypothesisTestDock::removeRow); // connect(ui.bAddColumn, &QPushButton::clicked, this, &HypothesisTestDock::addColumn); // connect(ui.bRemoveColumn, &QPushButton::clicked, this,&HypothesisTestDock::removeColumn); // connect(ui.cbCol1, static_cast(&QComboBox::currentIndexChanged), this, &HypothesisTestDock::doTTest); // connect(ui.cbCol2, static_cast(&QComboBox::currentIndexChanged), this, &HypothesisTestDock::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,&HypothesisTestDock::removeRow); // connect(ui.lwRows, &QListWidget::itemSelectionChanged, this, [=]() { // ui.bRemoveRow->setEnabled(!ui.lwRows->selectedItems().isEmpty()); // }); // connect(ui.lwColumns, &QListWidget::doubleClicked, this,&HypothesisTestDock::removeColumn); // connect(ui.lwColumns, &QListWidget::itemSelectionChanged, this, [=]() { // ui.bRemoveColumn->setEnabled(!ui.lwColumns->selectedItems().isEmpty()); // }); connect(ui.cbTest, static_cast(&QComboBox::activated), this, &HypothesisTestDock::showTestType); connect(ui.cbTestType, static_cast(&QComboBox::activated), this, &HypothesisTestDock::showHypothesisTest); // connect(ui.cbTest, static_cast(&QComboBox::currentIndexChanged), this, &HypothesisTestDock::showHypothesisTest); // connect(ui.cbTestType, static_cast(&QComboBox::currentIndexChanged), this, &HypothesisTestDock::showHypothesisTest); connect(ui.pbPerformTest, &QPushButton::clicked, this, &HypothesisTestDock::doHypothesisTest); connect(ui.pbLeveneTest, &QPushButton::clicked, this, &HypothesisTestDock::performLeveneTest); //connecting null hypothesis and alternate hypothesis radio button connect(ui.rbH1OneTail1, &QRadioButton::toggled, this, &HypothesisTestDock::onRbH1OneTail1Toggled); connect(ui.rbH1OneTail2, &QRadioButton::toggled, this, &HypothesisTestDock::onRbH1OneTail2Toggled); connect(ui.rbH1TwoTail, &QRadioButton::toggled, this, &HypothesisTestDock::onRbH1TwoTailToggled); connect(ui.cbCol1, static_cast(&QComboBox::currentIndexChanged), this, &HypothesisTestDock::col1IndexChanged); connect(ui.chbCategorical, &QCheckBox::stateChanged, this, &HypothesisTestDock::changeCbCol2Label); } void HypothesisTestDock::setHypothesisTest(HypothesisTest* HypothesisTest) { m_initializing = true; m_hypothesisTest = HypothesisTest; m_aspectTreeModel = new AspectTreeModel(m_hypothesisTest->project()); QList list; list << "Folder" << "Workbook" << "Spreadsheet" << "LiveDataSource"; cbSpreadsheet->setTopLevelClasses(list); list.clear(); list << "Spreadsheet" << "LiveDataSource"; m_aspectTreeModel->setSelectableAspects(list); cbSpreadsheet->setModel(m_aspectTreeModel); //show the properties ui.leName->setText(m_hypothesisTest->name()); ui.leComment->setText(m_hypothesisTest->comment()); ui.cbDataSourceType->setCurrentIndex(m_hypothesisTest->dataSourceType()); if (m_hypothesisTest->dataSourceType() == HypothesisTest::DataSourceSpreadsheet) setModelIndexFromAspect(cbSpreadsheet, m_hypothesisTest->dataSourceSpreadsheet()); // else // ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(m_hypothesisTest->dataSourceConnection())); setColumnsComboBoxModel(m_hypothesisTest->dataSourceSpreadsheet()); this->dataSourceTypeChanged(ui.cbDataSourceType->currentIndex()); //setting rows and columns in combo box; //undo functions connect(m_hypothesisTest, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(hypothesisTestDescriptionChanged(const AbstractAspect*))); //TODO: m_initializing = false; } void HypothesisTestDock::showTestType() { - ttest = ui.cbTest->currentText() == "T Test"; - ztest = ui.cbTest->currentText() == "Z Test"; - anova = ui.cbTest->currentText() == "Anova"; + m_test.type = Test::Type(ui.cbTest->currentData().toInt()); ui.cbTestType->clear(); + if (m_test.type & (Test::Type::TTest | Test::Type::ZTest)) { + ui.cbTestType->addItem( i18n("Two Sample Independent"), Test::SubType::TwoSampleIndependent); + ui.cbTestType->addItem( i18n("Two Sample Paired"), Test::SubType::TwoSamplePaired); + ui.cbTestType->addItem( i18n("One Sample"), Test::SubType::OneSample); + } + else if (m_test.type & Test::Type::Anova) { + ui.cbTestType->addItem( i18n("One Way"), Test::SubType::OneWay); + ui.cbTestType->addItem( i18n("Two Way"), Test::SubType::TwoWay); + } - if (ttest || ztest) - ui.cbTestType->addItems(test_type_t_z); - if (anova) - ui.cbTestType->addItems(test_type_anova); showHypothesisTest(); } void HypothesisTestDock::showHypothesisTest() { - one_way = ui.cbTestType->currentText() == "One Way"; - two_way = ui.cbTestType->currentText() == "Two Way"; - - two_sample_independent = ui.cbTestType->currentText() == "Two Sample Independent"; - two_sample_paired = ui.cbTestType->currentText() == "Two Sample Paired"; - one_sample = ui.cbTestType->currentText() == "One Sample"; - - ui.lCol1->setVisible(anova || two_sample_independent || two_sample_paired); - ui.cbCol1->setVisible(anova || two_sample_independent || two_sample_paired); - ui.lCol2->setVisible(anova || two_sample_independent || two_sample_paired || one_sample); - ui.cbCol2->setVisible(anova || two_sample_independent || two_sample_paired || one_sample); - ui.lEqualVariance->setVisible(ttest && two_sample_independent); - ui.chbEqualVariance->setVisible(ttest && two_sample_independent); - ui.lCategorical->setVisible(ttest && two_sample_independent); - ui.chbCategorical->setVisible(ttest && two_sample_independent); - ui.pbLeveneTest->setVisible(anova || (ttest && two_sample_independent)); + m_test.subtype = Test::SubType(ui.cbTestType->currentData().toInt()); + + ui.lCol1->show(); + ui.cbCol1->show(); + + ui.lCol2->setVisible(m_test.subtype & (~Test::SubType::OneSample)); + ui.cbCol2->setVisible(m_test.subtype & (~Test::SubType::OneSample)); + + ui.lEqualVariance->setVisible( (m_test.type & Test::Type::TTest) & + (m_test.subtype & Test::SubType::TwoSampleIndependent)); + ui.chbEqualVariance->setVisible( (m_test.type & Test::Type::TTest) & + (m_test.subtype & Test::SubType::TwoSampleIndependent)); + + ui.lCategorical->setVisible( (m_test.type & Test::Type::TTest) & + (m_test.subtype & Test::SubType::TwoSampleIndependent)); + ui.chbCategorical->setVisible( (m_test.type & Test::Type::TTest) & + (m_test.subtype & Test::SubType::TwoSampleIndependent)); + + ui.pbLeveneTest->setVisible( (m_test.type & Test::Type::Anova) | + ( (m_test.type & Test::Type::TTest) & + (m_test.subtype & Test::SubType::TwoSampleIndependent))); + ui.chbEqualVariance->setChecked(true); - ui.rbH1OneTail2->setVisible(two_sample_independent || two_sample_paired || one_sample); - ui.rbH1OneTail1->setVisible(two_sample_independent || two_sample_paired || one_sample); - ui.rbH1TwoTail->setVisible(two_sample_independent || two_sample_paired || one_sample); - ui.rbH0OneTail1->setVisible(two_sample_independent || two_sample_paired || one_sample); - ui.rbH0OneTail2->setVisible(two_sample_independent || two_sample_paired || one_sample); - ui.rbH0TwoTail->setVisible(two_sample_independent || two_sample_paired || one_sample); - ui.lH0->setVisible(two_sample_independent || two_sample_paired || one_sample); - ui.lH1->setVisible(two_sample_independent || two_sample_paired || one_sample); + ui.lH1->setVisible( (m_test.type & ~Test::Type::Anova)); + ui.rbH1OneTail1->setVisible( (m_test.type & ~Test::Type::Anova)); + ui.rbH1OneTail2->setVisible( (m_test.type & ~Test::Type::Anova)); + ui.rbH1TwoTail->setVisible( (m_test.type & ~Test::Type::Anova)); + + ui.lH0->setVisible( (m_test.type & ~Test::Type::Anova)); + ui.rbH0OneTail1->setVisible( (m_test.type & ~Test::Type::Anova)); + ui.rbH0OneTail2->setVisible( (m_test.type & ~Test::Type::Anova)); + ui.rbH0TwoTail->setVisible( (m_test.type & ~Test::Type::Anova)); ui.rbH1TwoTail->setChecked(true); - ui.lMuo->setVisible(one_sample); - ui.leMuo->setVisible(one_sample); - ui.lAlpha->setVisible(anova || two_sample_independent || two_sample_paired || one_sample); - ui.leAlpha->setVisible(anova || two_sample_independent || two_sample_paired || one_sample); + ui.lMuo->setVisible( (m_test.subtype & Test::SubType::OneSample)); + ui.leMuo->setVisible( (m_test.subtype & Test::SubType::OneSample)); - ui.leMuo->setText( i18n("%1", population_mean)); - ui.leAlpha->setText( i18n("%1", significance_level)); + ui.lAlpha->show(); + ui.leAlpha->show(); setColumnsComboBoxView(); - ui.pbPerformTest->setEnabled(nonEmptySelectedColumns() && - (anova || - two_sample_independent || two_sample_paired || one_sample)); + ui.pbPerformTest->setEnabled(nonEmptySelectedColumns()); + ui.pbLeveneTest->setEnabled(nonEmptySelectedColumns()); } void HypothesisTestDock::doHypothesisTest() { - m_hypothesisTest->setPopulationMean(ui.leMuo->text()); m_hypothesisTest->setSignificanceLevel(ui.leAlpha->text()); QStringList cols; - if(ttest) { - if(two_sample_independent) { - cols << ui.cbCol1->currentText() << ui.cbCol2->currentText(); - m_hypothesisTest->setColumns(cols); + switch (m_test.type) { + case Test::Type::TTest: { + cols << ui.cbCol1->currentText() << ui.cbCol2->currentText(); + m_hypothesisTest->setColumns(cols); + + switch (m_test.subtype) { + case Test::SubType::TwoSampleIndependent: m_hypothesisTest->performTwoSampleIndependentTTest(ui.chbCategorical->isChecked(), ui.chbEqualVariance->isChecked()); - } - else if(two_sample_paired) { - cols << ui.cbCol1->currentText(); - cols << ui.cbCol2->currentText(); - m_hypothesisTest->setColumns(cols); + break; + case Test::SubType::TwoSamplePaired: m_hypothesisTest->performTwoSamplePairedTTest(); - } - else if(one_sample){ + break; + case Test::SubType::OneSample: { cols << ui.cbCol1->currentText(); m_hypothesisTest->setColumns(cols); m_hypothesisTest->performOneSampleTTest(); + break; } + case Test::SubType::OneWay: + break; + case Test::SubType::TwoWay: + break; + } + break; } - else if(ztest) { - if(two_sample_independent) { - cols << ui.cbCol1->currentText(); - cols << ui.cbCol2->currentText(); - m_hypothesisTest->setColumns(cols); + case Test::Type::ZTest: { + cols << ui.cbCol1->currentText() << ui.cbCol2->currentText(); + m_hypothesisTest->setColumns(cols); + + switch (m_test.subtype) { + case Test::SubType::TwoSampleIndependent: m_hypothesisTest->performTwoSampleIndependentZTest(); - } - else if(two_sample_paired) { - cols << ui.cbCol1->currentText(); - cols << ui.cbCol2->currentText(); - m_hypothesisTest->setColumns(cols); + break; + case Test::SubType::TwoSamplePaired: m_hypothesisTest->performTwoSamplePairedZTest(); - } - else if(one_sample){ + break; + case Test::SubType::OneSample: { cols << ui.cbCol1->currentText(); m_hypothesisTest->setColumns(cols); m_hypothesisTest->performOneSampleZTest(); + break; + } + case Test::SubType::OneWay: + break; + case Test::SubType::TwoWay: + break; + case Test::SubType::NoneSubType: + break; } + + break; } - else if(anova) { - QStringList cols; - if(one_way) { - cols << ui.cbCol1->currentText() << ui.cbCol2->currentText(); - m_hypothesisTest->setColumns(cols); + case Test::Type::Anova: { + cols << ui.cbCol1->currentText() << ui.cbCol2->currentText(); + m_hypothesisTest->setColumns(cols); + + switch (m_test.subtype) { + case Test::SubType::OneWay: m_hypothesisTest->performOneWayAnova(); + break; + case Test::SubType::TwoWay: + break; } + + break; + } + case Test::Type::NoneType: + break; } } void HypothesisTestDock::performLeveneTest() { QStringList cols; cols << ui.cbCol1->currentText() << ui.cbCol2->currentText(); m_hypothesisTest->setColumns(cols); m_hypothesisTest->setSignificanceLevel(ui.leAlpha->text()); m_hypothesisTest->performLeveneTest(ui.chbCategorical->isChecked()); } void HypothesisTestDock::setModelIndexFromAspect(TreeViewComboBox* cb, const AbstractAspect* aspect) { if (aspect) cb->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(aspect)); else cb->setCurrentModelIndex(QModelIndex()); } ///*! // shows the database manager where the connections are created and edited. // The selected connection is selected in the connection combo box in this widget. //**/ //void HypothesisTestDock::showDatabaseManager() { // DatabaseManagerDialog* dlg = new DatabaseManagerDialog(this, ui.cbConnection->currentText()); // if (dlg->exec() == QDialog::Accepted) { // //re-read the available connections to be in sync with the changes in DatabaseManager // m_initializing = true; // ui.cbConnection->clear(); // readConnections(); // //select the connection the user has selected in DatabaseManager // const QString& conn = dlg->connection(); // ui.cbConnection->setCurrentIndex(ui.cbConnection->findText(conn)); // m_initializing = false; // connectionChanged(); // } // delete dlg; //} ///*! // loads all available saved connections //*/ //void HypothesisTestDock::readConnections() { // DEBUG("ImportSQLDatabaseWidget: reading available connections"); // KConfig config(m_configPath, KConfig::SimpleConfig); // for (const auto& name : config.groupList()) // ui.cbConnection->addItem(name); //} ///*! // * adds the selected field to the rows // */ //void HypothesisTestDock::addRow() { // QString field = ui.lwFields->currentItem()->text(); // ui.lwRows->addItem(field); // ui.lwFields->takeItem(ui.lwFields->currentRow()); // m_hypothesisTest->addToRows(field); //} ///*! // * removes the selected field from the rows // */ //void HypothesisTestDock::removeRow() { // const QString& field = ui.lwRows->currentItem()->text(); // ui.lwRows->takeItem(ui.lwRows->currentRow()); // m_hypothesisTest->removeFromRows(field); // updateFields(); //} ///*! // * adds the selected field to the columns // */ //void HypothesisTestDock::addColumn() { // QString field = ui.lwFields->currentItem()->text(); // ui.lwColumns->addItem(field); // ui.lwFields->takeItem(ui.lwFields->currentRow()); // m_hypothesisTest->addToColumns(field); //} ///*! // * removes the selected field from the columns // */ //void HypothesisTestDock::removeColumn() { // const QString& field = ui.lwColumns->currentItem()->text(); // ui.lwColumns->takeItem(ui.lwColumns->currentRow()); // m_hypothesisTest->removeFromColumns(field); // updateFields(); //} ///*! // * re-populates the content of the "Fields" list widget by adding the non-selected fields only. // * called when a selected field is removed from rows or columns. // */ //void HypothesisTestDock::updateFields() { // ui.lwFields->clear(); // for (auto dimension : m_hypothesisTest->dimensions()) // if (!fieldSelected(dimension)) // ui.lwFields->addItem(new QListWidgetItem(QIcon::fromTheme("draw-text"), dimension)); // for (auto measure : m_hypothesisTest->measures()) // if (!fieldSelected(measure)) // ui.lwFields->addItem(new QListWidgetItem(measure)); //} ///*! // * return \c true if the field name \c field was selected among rows or columns, // * return \c false otherwise. // * */ //bool HypothesisTestDock::fieldSelected(const QString& field) { // for (int i = 0; icount(); ++i) // if (ui.lwRows->item(i)->text() == field) // return true; // for (int i = 0; icount(); ++i) // if (ui.lwColumns->item(i)->text() == field) // return true; // return false; //} ////************************************************************* ////****** SLOTs for changes triggered in HypothesisTestDock ******* ////************************************************************* //void HypothesisTestDock::nameChanged() { // if (m_initializing) // return; // m_hypothesisTest->setName(ui.leName->text()); //} //void HypothesisTestDock::commentChanged() { // if (m_initializing) // return; // m_hypothesisTest->setComment(ui.leComment->text()); //} void HypothesisTestDock::dataSourceTypeChanged(int index) { HypothesisTest::DataSourceType type = static_cast(index); bool showDatabase = (type == HypothesisTest::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_hypothesisTest->setComment(ui.leComment->text()); } void HypothesisTestDock::spreadsheetChanged(const QModelIndex& index) { auto* aspect = static_cast(index.internalPointer()); Spreadsheet* spreadsheet = dynamic_cast(aspect); setColumnsComboBoxModel(spreadsheet); m_hypothesisTest->setDataSourceSpreadsheet(spreadsheet); } void HypothesisTestDock::changeCbCol2Label() { - if (two_sample_paired) { + if ( (m_test.type & ~Test::Type::Anova) & (m_test.subtype & ~Test::SubType::TwoSampleIndependent)) { ui.lCol2->setText( i18n("Independent Var. 2")); return; } QString selected_text = ui.cbCol1->currentText(); Column* col1 = m_hypothesisTest->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); } } void HypothesisTestDock::col1IndexChanged(int index) { if (index < 0) return; changeCbCol2Label(); } //void HypothesisTestDock::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("HypothesisTestDock: 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_hypothesisTest->setDataSourceConnection(connection); //} //void HypothesisTestDock::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_hypothesisTest->setDataSourceTable(table); //} ////************************************************************* ////******** SLOTs for changes triggered in Matrix ********* ////************************************************************* //void HypothesisTestDock::hypothesisTestDescriptionChanged(const AbstractAspect* aspect) { // if (m_hypothesisTest != 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; //} ////************************************************************* ////******************** SETTINGS ******************************* ////************************************************************* //void HypothesisTestDock::load() { //} //void HypothesisTestDock::loadConfigFromTemplate(KConfig& config) { // Q_UNUSED(config); //} ///*! // loads saved matrix properties from \c config. // */ //void HypothesisTestDock::loadConfig(KConfig& config) { // Q_UNUSED(config); //} ///*! // saves matrix properties to \c config. // */ //void HypothesisTestDock::saveConfigAsTemplate(KConfig& config) { // Q_UNUSED(config); //} //TODO: Rather than inbuilt slots use own decided slots for checked rather than clicked // for alternate hypothesis // one_tail_1 is mu > mu0; one_tail_2 is mu < mu0; two_tail = mu != mu0; void HypothesisTestDock::onRbH1OneTail1Toggled(bool checked) { if (!checked) return; ui.rbH0OneTail1->setChecked(true); m_hypothesisTest->setTailType(HypothesisTest::TailPositive); } void HypothesisTestDock::onRbH1OneTail2Toggled(bool checked) { if (!checked) return; ui.rbH0OneTail2->setChecked(true); m_hypothesisTest->setTailType(HypothesisTest::TailNegative); } void HypothesisTestDock::onRbH1TwoTailToggled(bool checked) { if (!checked) return; ui.rbH0TwoTail->setChecked(true); m_hypothesisTest->setTailType(HypothesisTest::TailTwo); } /**************************************Helper Functions********************************************/ void HypothesisTestDock::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); } void HypothesisTestDock::setColumnsComboBoxModel(Spreadsheet* spreadsheet) { only_values_cols.clear(); two_categorical_cols.clear(); - more_than_two_categorical_cols.clear(); + more_than_two_categorical_cols.clear(); for (auto* col : spreadsheet->children()) { if (col->columnMode() == AbstractColumn::Integer || col->columnMode() == AbstractColumn::Numeric) only_values_cols.append(col->name()); else { int np = 0, n_rows = 0; countPartitions(col, np, n_rows); if (np <= 1) continue; else if (np == 2) two_categorical_cols.append(col->name()); else more_than_two_categorical_cols.append(col->name()); } } setColumnsComboBoxView(); showHypothesisTest(); } void HypothesisTestDock::setColumnsComboBoxView() { ui.cbCol1->clear(); ui.cbCol2->clear(); - if (two_sample_independent) { + if (m_test.subtype & Test::SubType::TwoSampleIndependent) { ui.cbCol1->addItems(only_values_cols); ui.cbCol1->addItems(two_categorical_cols); ui.cbCol2->addItems(only_values_cols); - } else if (two_sample_paired) { + } else if (m_test.subtype & Test::SubType::TwoSamplePaired) { ui.cbCol1->addItems(only_values_cols); ui.cbCol2->addItems(only_values_cols); - } else if (one_sample) + } else if (m_test.subtype & Test::SubType::OneSample) ui.cbCol1->addItems(only_values_cols); - else if (anova) { + else if (m_test.type & Test::Type::Anova) { ui.cbCol1->addItems(two_categorical_cols); ui.cbCol1->addItems(more_than_two_categorical_cols); ui.cbCol2->addItems(only_values_cols); } } bool HypothesisTestDock::nonEmptySelectedColumns() { if (ui.cbCol1->isVisible() && ui.cbCol1->count() < 1) return false; if (ui.cbCol2->isVisible() && ui.cbCol2->count() < 1) return false; return true; } diff --git a/src/kdefrontend/dockwidgets/HypothesisTestDock.h b/src/kdefrontend/dockwidgets/HypothesisTestDock.h index 47b696dca..d5d7ee543 100644 --- a/src/kdefrontend/dockwidgets/HypothesisTestDock.h +++ b/src/kdefrontend/dockwidgets/HypothesisTestDock.h @@ -1,129 +1,138 @@ /*************************************************************************** File : HypothesisTestDock.h Project : LabPlot Description : widget for hypothesis testing 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 * * * ***************************************************************************/ #ifndef HYPOTHESISTESTDOCK_H #define HYPOTHESISTESTDOCK_H #include "backend/hypothesis_test/HypothesisTest.h" #include "ui_hypothesistestdock.h" #include class AbstractAspect; class AspectTreeModel; class HypothesisTest; class TreeViewComboBox; class KConfig; class QScrollArea; class QStandardItemModel; class QStandardItem; class HypothesisTestDock : public QWidget { Q_OBJECT public: + struct Test { + enum Type { + NoneType = 0, + TTest = 1 << 0, + ZTest = 1 << 1, + Anova = 1 << 2 + }; + enum SubType { + NoneSubType = 0, + TwoSampleIndependent = 1 << 0, + TwoSamplePaired = 1 << 1, + OneSample = 1 << 2, + OneWay = 1 << 3, + TwoWay = 1 << 4 + }; + Type type = NoneType; + SubType subtype = NoneSubType; + }; + explicit HypothesisTestDock(QWidget*); void setHypothesisTest(HypothesisTest*); private slots: void onRbH1OneTail1Toggled(bool checked); void onRbH1OneTail2Toggled(bool checked); void onRbH1TwoTailToggled(bool checked); private slots: private: Ui::HypothesisTestDock ui; bool m_initializing{false}; TreeViewComboBox* cbSpreadsheet{nullptr}; HypothesisTest* m_hypothesisTest{nullptr}; AspectTreeModel* m_aspectTreeModel{nullptr}; QSqlDatabase m_db; QString m_configPath; double population_mean{0}; double significance_level{0.05}; // void load(); // void loadConfig(KConfig&); void setModelIndexFromAspect(TreeViewComboBox*, const AbstractAspect*); // void readConnections(); // void updateFields(); // bool fieldSelected(const QString&); - bool ttest{false}; - bool ztest{false}; - bool two_sample_independent{false}; - bool two_sample_paired{false}; - bool one_sample{false}; - bool anova{false}; - bool one_way{false}; - bool two_way{false}; + Test m_test; QScrollArea* scroll_dock; void countPartitions(Column *column, int &np, int &total_rows); void setColumnsComboBoxModel(Spreadsheet* spreadsheet); void setColumnsComboBoxView(); bool nonEmptySelectedColumns(); - QStringList test_type_t_z; - QStringList test_type_anova; - QStringList only_values_cols; QStringList two_categorical_cols; QStringList more_than_two_categorical_cols; private slots: //SLOTs for changes triggered in PivotTableDock // void nameChanged(); // void commentChanged(); void dataSourceTypeChanged(int); void showTestType(); void showHypothesisTest(); void doHypothesisTest(); void performLeveneTest(); void spreadsheetChanged(const QModelIndex&); void changeCbCol2Label(); void col1IndexChanged(int index); // void connectionChanged(); // void tableChanged(); // void showDatabaseManager(); // //SLOTs for changes triggered in PivotTable // void pivotTableDescriptionChanged(const AbstractAspect*); // void addRow(); // void removeRow(); // void addColumn(); // void removeColumn(); // //save/load template // void loadConfigFromTemplate(KConfig&); // void saveConfigAsTemplate(KConfig&); signals: // void info(const QString&); }; #endif // PIVOTTABLEDOCK_H diff --git a/ttest.diff b/ttest.diff deleted file mode 100644 index 1e2e92624..000000000 --- a/ttest.diff +++ /dev/null @@ -1,260 +0,0 @@ -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 1c39d438f..0d00a555b 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -236,6 +236,7 @@ set(BACKEND_SOURCES - ${BACKEND_DIR}/matrix/matrixcommands.cpp - ${BACKEND_DIR}/matrix/MatrixModel.cpp - ${BACKEND_DIR}/pivot/PivotTable.cpp -+ ${BACKEND_DIR}/t_test/TTest.cpp - ${BACKEND_DIR}/spreadsheet/Spreadsheet.cpp - ${BACKEND_DIR}/spreadsheet/SpreadsheetModel.cpp - ${BACKEND_DIR}/lib/XmlStreamReader.cpp -diff --git a/src/backend/t_test/TTest.cpp b/src/backend/t_test/TTest.cpp -new file mode 100644 -index 000000000..9bfb0cf25 ---- /dev/null -+++ b/src/backend/t_test/TTest.cpp -@@ -0,0 +1,120 @@ -+#include "TTest.h" -+#include "backend/spreadsheet/Spreadsheet.h" -+#include "backend/core/column/Column.h" -+//#include "commonfrontend/spreadsheet/SpreadsheetView.h" -+ -+#include -+#include -+#include -+#include -+ -+ -+TTest::TTest(const QString &name){ -+ Q_UNUSED(name); -+} -+ -+void TTest::setDataSourceSpreadsheet(Spreadsheet *spreadsheet){ -+ dataSourceSpreadsheet = spreadsheet; -+ -+ m_rowCount = dataSourceSpreadsheet->rowCount(); -+ m_columnCount = dataSourceSpreadsheet->columnCount(); -+ QDEBUG("in ttest::setDataSourceSpreadsheet"); -+ -+ // now finding the number of columns and rows; -+ QDEBUG("row count is " << m_rowCount); -+ QDEBUG("row count is " << m_columnCount); -+ QDEBUG("exiting ttest::setDataSourceSpreadsheet"); -+} -+ -+void TTest::setColumns(QVector cols){ -+ m_columns = cols; -+ return; -+} -+ -+void TTest::performTwoSampleTest(){ -+ QMessageBox* msg_box = new QMessageBox(); -+ // checking for cols; -+ if(m_columns.size() != 2){ -+ msg_box->setText(i18n("Inappropriate number of columns selected")); -+ msg_box->exec(); -+ return; -+ } -+ -+ bool modeOk = true; -+ for (int i = 0; i < 2; i++){ -+ if(m_columns[0]->columnMode() == AbstractColumn::Numeric || m_columns[i]->columnMode() == AbstractColumn::Integer) -+ continue; -+ modeOk = false; -+ } -+ -+ if(!modeOk){ -+ msg_box->setText(i18n("select only columns with numbers")); -+ msg_box->exec(); -+ return; -+ } -+ -+ // use of three than two for human readiblity of code; -+ int n[2]; -+ double sum[2], mean[2], std[2]; -+ -+ for (int i = 0; i < 2; i++) { -+ findStats(m_columns[i], n[i], sum[i], mean[i], std[i]); -+ QDEBUG("for " << i); -+ QDEBUG("n is "<setText(i18n("atleast one of selected column empty")); -+ msg_box->exec(); -+ return; -+ } -+ } -+ int df = n[0] + n[1] - 2; -+ -+ //Assuming equal variance -+ double sp = qSqrt( ((n[0]-1)*qPow(std[0],2) + (n[1]-1)*qPow(std[1],2))/df); -+ -+ QDEBUG("sp is " << sp); -+ -+ double t = (mean[0] - mean[1])/(sp*qSqrt(1.0/n[0] + 1.0/n[1])); -+ QString text = i18n("T value for test is %1",t); -+ msg_box->setText(text); -+ msg_box->exec(); -+ return; -+ -+// double t_value = -+ -+} -+ -+void TTest::findStats(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; -+ } -+ -+ if (count < 1) return; -+ mean = sum/count; -+ -+ for (int i = 0; i < count; i++) { -+ double row = column->valueAt(i); -+ std += qPow((row - mean),2); -+ } -+ -+ if (count > 1) -+ std = std / (count-1); -+ std = qSqrt(std); -+ return; -+} -+ -+ -+ -diff --git a/src/backend/t_test/TTest.h b/src/backend/t_test/TTest.h -new file mode 100644 -index 000000000..7e888a27b ---- /dev/null -+++ b/src/backend/t_test/TTest.h -@@ -0,0 +1,27 @@ -+#ifndef TTEST_H -+#define TTEST_H -+#include -+ -+ -+class Spreadsheet; -+class QString; -+class Column; -+ -+class TTest{ -+public: -+ explicit TTest(const QString& name); -+ void setDataSourceSpreadsheet(Spreadsheet* spreadsheet); -+ void setColumns(QVector cols); -+ void performTwoSampleTest(); -+private: -+// double findMean(Column* col); -+// double findStandardDeviation(Column* col, double mean); -+ void findStats(Column* column, int &count, double &sum, double &mean, double &std); -+ -+ Spreadsheet* dataSourceSpreadsheet{nullptr}; -+ int m_rowCount{0}; -+ int m_columnCount{0}; -+ QVector m_columns; -+}; -+ -+#endif // TTEST_H -diff --git a/src/commonfrontend/spreadsheet/SpreadsheetView.cpp b/src/commonfrontend/spreadsheet/SpreadsheetView.cpp -index ef0717c15..7a658653b 100644 ---- a/src/commonfrontend/spreadsheet/SpreadsheetView.cpp -+++ b/src/commonfrontend/spreadsheet/SpreadsheetView.cpp -@@ -43,6 +43,8 @@ - #include "backend/core/datatypes/DateTime2StringFilter.h" - #include "backend/core/datatypes/String2DateTimeFilter.h" - #include "backend/pivot/PivotTable.h" -+#include "backend/t_test/TTest.h" -+ - - #include - #include -@@ -220,6 +222,7 @@ void SpreadsheetView::initActions() { - action_go_to_cell = new QAction(QIcon::fromTheme("go-jump"), i18n("&Go to Cell"), this); - action_statistics_all_columns = new QAction(QIcon::fromTheme("view-statistics"), i18n("Statisti&cs"), this ); - action_pivot_table = new QAction(QIcon::fromTheme("table"), i18n("Pivot Table"), this); -+ action_do_ttest = new QAction(i18n("T Test"), this); - - // column related actions - action_insert_column_left = new QAction(QIcon::fromTheme("edit-table-insert-column-left"), i18n("Insert Column Left"), this); -@@ -471,6 +474,12 @@ void SpreadsheetView::initMenus() { - m_columnMenu->addMenu(m_columnManipulateDataMenu); - m_columnMenu->addSeparator(); - -+ //for ttest statistics; -+ m_columnHypothesisTestingMenu = new QMenu("Hypothesis Testing",this); -+ m_columnHypothesisTestingMenu->addAction(action_do_ttest); -+ m_columnMenu->addMenu(m_columnHypothesisTestingMenu); -+ m_columnMenu->addSeparator(); -+ - m_columnSortMenu = new QMenu(i18n("Sort"), this); - m_columnSortMenu->setIcon(QIcon::fromTheme("view-sort-ascending")); - m_columnSortMenu->addAction(action_sort_asc_column); -@@ -558,6 +567,7 @@ void SpreadsheetView::connectActions() { - connect(action_go_to_cell, &QAction::triggered, this, - static_cast(&SpreadsheetView::goToCell)); - connect(action_pivot_table, &QAction::triggered, this, &SpreadsheetView::createPivotTable); -+ connect(action_do_ttest, &QAction::triggered, this, &SpreadsheetView::doTTest); - - connect(action_insert_column_left, &QAction::triggered, this, &SpreadsheetView::insertColumnLeft); - connect(action_insert_column_right, &QAction::triggered, this, &SpreadsheetView::insertColumnRight); -@@ -764,9 +774,17 @@ void SpreadsheetView::createPivotTable() { - PivotTable* pivot = new PivotTable(i18n("Pivot Table for %1", m_spreadsheet->name())); - pivot->setDataSourceType(PivotTable::DataSourceSpreadsheet); - pivot->setDataSourceSpreadsheet(m_spreadsheet); -- m_spreadsheet->parentAspect()->addChild(pivot); -+ m_spreadsheet->parentAspect()->addChild(pivot); - } - -+void SpreadsheetView::doTTest() -+{ -+ TTest* ttest = new TTest(i18n("doing T Test for %1", m_spreadsheet->name())); -+ ttest->setColumns(this->selectedColumns()); -+ ttest->performTwoSampleTest(); -+} -+ -+ - void SpreadsheetView::handleHorizontalSectionMoved(int index, int from, int to) { - Q_UNUSED(index); - -diff --git a/src/commonfrontend/spreadsheet/SpreadsheetView.h b/src/commonfrontend/spreadsheet/SpreadsheetView.h -index b67df1252..eceb1c556 100644 ---- a/src/commonfrontend/spreadsheet/SpreadsheetView.h -+++ b/src/commonfrontend/spreadsheet/SpreadsheetView.h -@@ -130,6 +130,7 @@ private: - QAction* action_go_to_cell; - QAction* action_statistics_all_columns; - QAction* action_pivot_table; -+ QAction* action_do_ttest; - - //column related actions - QAction* action_insert_column_left; -@@ -192,6 +193,7 @@ private: - QMenu* m_spreadsheetMenu; - QMenu* m_plotDataMenu; - QMenu* m_analyzePlotMenu; -+ QMenu* m_columnHypothesisTestingMenu{nullptr}; - - public slots: - void createContextMenu(QMenu*); -@@ -206,6 +208,7 @@ private slots: - void goToPreviousColumn(); - void goToCell(); - void createPivotTable(); -+ void doTTest(); - void sortSpreadsheet(); - void sortDialog(QVector); - diff --git a/verticalHeaderCompleted.diff b/verticalHeaderCompleted.diff deleted file mode 100644 index f48cee771..000000000 --- a/verticalHeaderCompleted.diff +++ /dev/null @@ -1,693 +0,0 @@ -diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt -index 1c39d438f..0d00a555b 100644 ---- a/src/CMakeLists.txt -+++ b/src/CMakeLists.txt -@@ -236,6 +236,7 @@ set(BACKEND_SOURCES - ${BACKEND_DIR}/matrix/matrixcommands.cpp - ${BACKEND_DIR}/matrix/MatrixModel.cpp - ${BACKEND_DIR}/pivot/PivotTable.cpp -+ ${BACKEND_DIR}/t_test/TTest.cpp - ${BACKEND_DIR}/spreadsheet/Spreadsheet.cpp - ${BACKEND_DIR}/spreadsheet/SpreadsheetModel.cpp - ${BACKEND_DIR}/lib/XmlStreamReader.cpp -diff --git a/src/backend/pivot/PivotTable.cpp b/src/backend/pivot/PivotTable.cpp -index b9c66d8dd..c166da97a 100644 ---- a/src/backend/pivot/PivotTable.cpp -+++ b/src/backend/pivot/PivotTable.cpp -@@ -45,6 +45,8 @@ - #include - #include - -+#include -+ - /*! - \class PivotTable - \brief Aspect providing a pivot table. -@@ -257,7 +259,7 @@ void PivotTablePrivate::recalculate() { - - if (rows.isEmpty() && columns.isEmpty() && !showTotals) { - //notify about the new result -- emit q->changed(); -+ emit q->changed(); - return; - } - -@@ -309,7 +311,9 @@ void PivotTablePrivate::recalculate() { - - } - -- QDEBUG(query); -+ //QDEBUG(query); -+ -+ qDebug()<<"query is " << query; - - //execute the query - QSqlQuery sqlQuery; -@@ -326,10 +330,15 @@ void PivotTablePrivate::recalculate() { - int firstValueIndex = rows.size() + columns.size(); - int valuesCount = columnsCount - firstValueIndex; - -- DEBUG("nubmer of columns " << columnsCount); -- DEBUG("number rows: " << rowsCount); -- DEBUG("number values: " << valuesCount); -- DEBUG("index of the first value column: " << firstValueIndex); -+// DEBUG("nubmer of columns " << columnsCount); -+// DEBUG("number rows: " << rowsCount); -+// DEBUG("number values: " << valuesCount); -+// DEBUG("index of the first value column: " << firstValueIndex); -+ -+ qDebug() << "number of columns " << columnsCount; -+ qDebug() << "number of rows" << rowsCount; -+ qDebug() << "number of values" << valuesCount; -+ qDebug() << "index of first value column " << firstValueIndex; - - qDebug()<<"model in recalculate " << horizontalHeaderModel; - if (!horizontalHeaderModel) { -@@ -360,7 +369,7 @@ void PivotTablePrivate::recalculate() { - verticalHeaderModel->setColumnCount(rows.count()); - - //horizontal header -- horizontalHeaderModel->setColumnCount(valuesCount); -+ horizontalHeaderModel->setColumnCount(valuesCount); - horizontalHeaderModel->setRowCount(1); - - //TODO: only "Totals" value at the moment, needs to be extended later when we allow to add other values -@@ -396,16 +405,52 @@ void PivotTablePrivate::recalculate() { - - } else if (columns.isEmpty()) { - qDebug()<<"everything on rows"; -+ int* start_span = new int[firstValueIndex]; -+ int* end_span = new int[firstValueIndex]; -+ QString* last_value= new QString[firstValueIndex]; -+ -+ verticalHeaderModel->setRowCount(row+1); -+ for (int i = 0; i < firstValueIndex; ++i) { -+ start_span[i] = 1; -+ end_span[i] = 1; -+ last_value[i] = ""; -+ verticalHeaderModel->setData(verticalHeaderModel->index(row, i), rows.at(i), Qt::DisplayRole); -+ } -+ row++; -+ -+ - while (sqlQuery.next()) { - qDebug()<<"row: " << row; -- horizontalHeaderModel->setRowCount(row+1); -- for (int i = 0; i < firstValueIndex; ++i) { -- qDebug()<<"adding to the horizontal header " << sqlQuery.value(i); -- horizontalHeaderModel->setData(horizontalHeaderModel->index(row, i), sqlQuery.value(i), Qt::DisplayRole); -+ verticalHeaderModel->setRowCount(row+1); -+ -+// if(sqlQuery.value(0).toString() != last_value) -+// { -+// if(end_span > start_span) -+// verticalHeaderModel->setSpan(start_span,0,end_span-start_span,0); -+// start_span = end_span; -+// last_value = sqlQuery.value(0).toString(); -+// } -+// end_span = end_span + 1; -+ bool parent_header_changed = false; -+ for (int i = 0; i < firstValueIndex; ++i) { -+ QString queryVal = sqlQuery.value(i).toString(); -+ qDebug()<<"adding to the horizontal header " << query; -+ -+ if(queryVal != last_value[i] || parent_header_changed) -+ { -+ verticalHeaderModel->setData(verticalHeaderModel->index(row, i), queryVal, Qt::DisplayRole); -+ -+ if(end_span[i] > start_span[i]+1) -+ verticalHeaderModel->setSpan(start_span[i],i,end_span[i]-start_span[i],0); -+ start_span[i] = end_span[i]; -+ parent_header_changed = true; -+ } -+ last_value[i] = queryVal; -+ end_span[i] = end_span[i] + 1; - } - - //values -- for (int i = firstValueIndex; i < columnsCount; ++i) { -+ for (int i = firstValueIndex; i < columnsCount; ++i) { - QString value = sqlQuery.value(i).toString(); - qDebug()<<"adding value " << value; - if (rowsCount == -1) -@@ -415,6 +460,13 @@ void PivotTablePrivate::recalculate() { - - ++row; - } -+ for(int i = 0; i < firstValueIndex; ++i){ -+ if(end_span[i] > start_span[i]){ -+ verticalHeaderModel->setSpan(start_span[i],i,end_span[i]-start_span[i],0); -+ } -+ } -+ verticalHeaderModel->setSpan(1,0,0,rows.count()); -+ - } else if (rows.isEmpty()) { - qDebug()<<"everything on columns"; - // for (int i = firstValueIndex; i < columnsCount; ++i) { -diff --git a/src/backend/t_test/TTest.cpp b/src/backend/t_test/TTest.cpp -new file mode 100644 -index 000000000..9bfb0cf25 ---- /dev/null -+++ b/src/backend/t_test/TTest.cpp -@@ -0,0 +1,120 @@ -+#include "TTest.h" -+#include "backend/spreadsheet/Spreadsheet.h" -+#include "backend/core/column/Column.h" -+//#include "commonfrontend/spreadsheet/SpreadsheetView.h" -+ -+#include -+#include -+#include -+#include -+ -+ -+TTest::TTest(const QString &name){ -+ Q_UNUSED(name); -+} -+ -+void TTest::setDataSourceSpreadsheet(Spreadsheet *spreadsheet){ -+ dataSourceSpreadsheet = spreadsheet; -+ -+ m_rowCount = dataSourceSpreadsheet->rowCount(); -+ m_columnCount = dataSourceSpreadsheet->columnCount(); -+ QDEBUG("in ttest::setDataSourceSpreadsheet"); -+ -+ // now finding the number of columns and rows; -+ QDEBUG("row count is " << m_rowCount); -+ QDEBUG("row count is " << m_columnCount); -+ QDEBUG("exiting ttest::setDataSourceSpreadsheet"); -+} -+ -+void TTest::setColumns(QVector cols){ -+ m_columns = cols; -+ return; -+} -+ -+void TTest::performTwoSampleTest(){ -+ QMessageBox* msg_box = new QMessageBox(); -+ // checking for cols; -+ if(m_columns.size() != 2){ -+ msg_box->setText(i18n("Inappropriate number of columns selected")); -+ msg_box->exec(); -+ return; -+ } -+ -+ bool modeOk = true; -+ for (int i = 0; i < 2; i++){ -+ if(m_columns[0]->columnMode() == AbstractColumn::Numeric || m_columns[i]->columnMode() == AbstractColumn::Integer) -+ continue; -+ modeOk = false; -+ } -+ -+ if(!modeOk){ -+ msg_box->setText(i18n("select only columns with numbers")); -+ msg_box->exec(); -+ return; -+ } -+ -+ // use of three than two for human readiblity of code; -+ int n[2]; -+ double sum[2], mean[2], std[2]; -+ -+ for (int i = 0; i < 2; i++) { -+ findStats(m_columns[i], n[i], sum[i], mean[i], std[i]); -+ QDEBUG("for " << i); -+ QDEBUG("n is "<setText(i18n("atleast one of selected column empty")); -+ msg_box->exec(); -+ return; -+ } -+ } -+ int df = n[0] + n[1] - 2; -+ -+ //Assuming equal variance -+ double sp = qSqrt( ((n[0]-1)*qPow(std[0],2) + (n[1]-1)*qPow(std[1],2))/df); -+ -+ QDEBUG("sp is " << sp); -+ -+ double t = (mean[0] - mean[1])/(sp*qSqrt(1.0/n[0] + 1.0/n[1])); -+ QString text = i18n("T value for test is %1",t); -+ msg_box->setText(text); -+ msg_box->exec(); -+ return; -+ -+// double t_value = -+ -+} -+ -+void TTest::findStats(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; -+ } -+ -+ if (count < 1) return; -+ mean = sum/count; -+ -+ for (int i = 0; i < count; i++) { -+ double row = column->valueAt(i); -+ std += qPow((row - mean),2); -+ } -+ -+ if (count > 1) -+ std = std / (count-1); -+ std = qSqrt(std); -+ return; -+} -+ -+ -+ -diff --git a/src/backend/t_test/TTest.h b/src/backend/t_test/TTest.h -new file mode 100644 -index 000000000..7e888a27b ---- /dev/null -+++ b/src/backend/t_test/TTest.h -@@ -0,0 +1,27 @@ -+#ifndef TTEST_H -+#define TTEST_H -+#include -+ -+ -+class Spreadsheet; -+class QString; -+class Column; -+ -+class TTest{ -+public: -+ explicit TTest(const QString& name); -+ void setDataSourceSpreadsheet(Spreadsheet* spreadsheet); -+ void setColumns(QVector cols); -+ void performTwoSampleTest(); -+private: -+// double findMean(Column* col); -+// double findStandardDeviation(Column* col, double mean); -+ void findStats(Column* column, int &count, double &sum, double &mean, double &std); -+ -+ Spreadsheet* dataSourceSpreadsheet{nullptr}; -+ int m_rowCount{0}; -+ int m_columnCount{0}; -+ QVector m_columns; -+}; -+ -+#endif // TTEST_H -diff --git a/src/commonfrontend/spreadsheet/SpreadsheetView.cpp b/src/commonfrontend/spreadsheet/SpreadsheetView.cpp -index ef0717c15..7a658653b 100644 ---- a/src/commonfrontend/spreadsheet/SpreadsheetView.cpp -+++ b/src/commonfrontend/spreadsheet/SpreadsheetView.cpp -@@ -43,6 +43,8 @@ - #include "backend/core/datatypes/DateTime2StringFilter.h" - #include "backend/core/datatypes/String2DateTimeFilter.h" - #include "backend/pivot/PivotTable.h" -+#include "backend/t_test/TTest.h" -+ - - #include - #include -@@ -220,6 +222,7 @@ void SpreadsheetView::initActions() { - action_go_to_cell = new QAction(QIcon::fromTheme("go-jump"), i18n("&Go to Cell"), this); - action_statistics_all_columns = new QAction(QIcon::fromTheme("view-statistics"), i18n("Statisti&cs"), this ); - action_pivot_table = new QAction(QIcon::fromTheme("table"), i18n("Pivot Table"), this); -+ action_do_ttest = new QAction(i18n("T Test"), this); - - // column related actions - action_insert_column_left = new QAction(QIcon::fromTheme("edit-table-insert-column-left"), i18n("Insert Column Left"), this); -@@ -471,6 +474,12 @@ void SpreadsheetView::initMenus() { - m_columnMenu->addMenu(m_columnManipulateDataMenu); - m_columnMenu->addSeparator(); - -+ //for ttest statistics; -+ m_columnHypothesisTestingMenu = new QMenu("Hypothesis Testing",this); -+ m_columnHypothesisTestingMenu->addAction(action_do_ttest); -+ m_columnMenu->addMenu(m_columnHypothesisTestingMenu); -+ m_columnMenu->addSeparator(); -+ - m_columnSortMenu = new QMenu(i18n("Sort"), this); - m_columnSortMenu->setIcon(QIcon::fromTheme("view-sort-ascending")); - m_columnSortMenu->addAction(action_sort_asc_column); -@@ -558,6 +567,7 @@ void SpreadsheetView::connectActions() { - connect(action_go_to_cell, &QAction::triggered, this, - static_cast(&SpreadsheetView::goToCell)); - connect(action_pivot_table, &QAction::triggered, this, &SpreadsheetView::createPivotTable); -+ connect(action_do_ttest, &QAction::triggered, this, &SpreadsheetView::doTTest); - - connect(action_insert_column_left, &QAction::triggered, this, &SpreadsheetView::insertColumnLeft); - connect(action_insert_column_right, &QAction::triggered, this, &SpreadsheetView::insertColumnRight); -@@ -764,9 +774,17 @@ void SpreadsheetView::createPivotTable() { - PivotTable* pivot = new PivotTable(i18n("Pivot Table for %1", m_spreadsheet->name())); - pivot->setDataSourceType(PivotTable::DataSourceSpreadsheet); - pivot->setDataSourceSpreadsheet(m_spreadsheet); -- m_spreadsheet->parentAspect()->addChild(pivot); -+ m_spreadsheet->parentAspect()->addChild(pivot); - } - -+void SpreadsheetView::doTTest() -+{ -+ TTest* ttest = new TTest(i18n("doing T Test for %1", m_spreadsheet->name())); -+ ttest->setColumns(this->selectedColumns()); -+ ttest->performTwoSampleTest(); -+} -+ -+ - void SpreadsheetView::handleHorizontalSectionMoved(int index, int from, int to) { - Q_UNUSED(index); - -diff --git a/src/commonfrontend/spreadsheet/SpreadsheetView.h b/src/commonfrontend/spreadsheet/SpreadsheetView.h -index b67df1252..eceb1c556 100644 ---- a/src/commonfrontend/spreadsheet/SpreadsheetView.h -+++ b/src/commonfrontend/spreadsheet/SpreadsheetView.h -@@ -130,6 +130,7 @@ private: - QAction* action_go_to_cell; - QAction* action_statistics_all_columns; - QAction* action_pivot_table; -+ QAction* action_do_ttest; - - //column related actions - QAction* action_insert_column_left; -@@ -192,6 +193,7 @@ private: - QMenu* m_spreadsheetMenu; - QMenu* m_plotDataMenu; - QMenu* m_analyzePlotMenu; -+ QMenu* m_columnHypothesisTestingMenu{nullptr}; - - public slots: - void createContextMenu(QMenu*); -@@ -206,6 +208,7 @@ private slots: - void goToPreviousColumn(); - void goToCell(); - void createPivotTable(); -+ void doTTest(); - void sortSpreadsheet(); - void sortDialog(QVector); - -diff --git a/src/kdefrontend/pivot/HierarchicalHeaderView.cpp b/src/kdefrontend/pivot/HierarchicalHeaderView.cpp -index 09236b144..bc05ffcb1 100755 ---- a/src/kdefrontend/pivot/HierarchicalHeaderView.cpp -+++ b/src/kdefrontend/pivot/HierarchicalHeaderView.cpp -@@ -29,6 +29,8 @@ - #include - #include - -+#include -+ - HierarchicalHeaderItem::HierarchicalHeaderItem(HierarchicalHeaderItem* parent): - row_prop(0),column_prop(0),parent_item(parent) { - } -@@ -48,13 +50,13 @@ HierarchicalHeaderItem* HierarchicalHeaderItem::insertChild(int row, int col) { - const HierarchicalHeaderItem* HierarchicalHeaderItem::child(int row,int col) const { - QHash,HierarchicalHeaderItem*>::const_iterator itr = child_items.find(QPair(row,col)); - if (itr != child_items.end()) return itr.value(); -- return 0; -+ return nullptr; - } - - HierarchicalHeaderItem* HierarchicalHeaderItem::child(int row,int col) { - QHash,HierarchicalHeaderItem*>::iterator itr = child_items.find(QPair(row,col)); - if (itr != child_items.end()) return itr.value(); -- return 0; -+ return nullptr; - } - - void HierarchicalHeaderItem::setText(const QString& text) { -@@ -86,22 +88,58 @@ HierarchicalHeaderItem* HierarchicalHeaderItem::parent() { - void HierarchicalHeaderItem::clear() { - QList items = child_items.values(); - foreach (HierarchicalHeaderItem* item, child_items) { -- if (item) delete item; -+ if (item) { -+ delete item; -+ } - } - child_items.clear(); - } - --//########### -+/********************************************************************************************** -+ * MODEL IMPLEMENTATION -+ * -+ * ********************************************************************************************/ - HierarchicalHeaderModel::HierarchicalHeaderModel(QObject* parent) : QAbstractTableModel(parent), - m_rootItem(new HierarchicalHeaderItem()) { - } - HierarchicalHeaderModel::HierarchicalHeaderModel(int rows, int cols, QObject* parent) : - QAbstractTableModel(parent),m_rowCount(rows),m_columnCount(cols),m_rootItem(new HierarchicalHeaderItem()) { -+ maxWidthArr = new int[m_columnCount]; -+ for(int col=0; colclear(); - delete m_rootItem; -+ delete maxWidthArr; -+} -+ -+void HierarchicalHeaderModel::setBaseSectionSize(QSize size) -+{ -+ -+ baseSectionSize = size; -+ -+ if(orientation == Qt::Vertical){ -+ for (int row=0;rowsetData(this->index(row,col),baseSectionSize,Qt::SizeHintRole); -+ } -+ return; -+ } -+ -+ for (int row=0;rowsetData(this->index(row,col),baseSectionSize,Qt::SizeHintRole); -+ } -+} -+ -+void HierarchicalHeaderModel::setOrientation(Qt::Orientation orient) -+{ -+ orientation = orient; - } - - QModelIndex HierarchicalHeaderModel::index(int row, int column, const QModelIndex & parent) const { -@@ -115,7 +153,6 @@ QModelIndex HierarchicalHeaderModel::index(int row, int column, const QModelInde - if (!childItem) childItem = parentItem->insertChild(row,column); - return createIndex(row,column,childItem); - -- return QModelIndex(); - } - - void HierarchicalHeaderModel::setRowCount(int count) { -@@ -129,6 +166,11 @@ void HierarchicalHeaderModel::setRowCount(int count) { - - void HierarchicalHeaderModel::setColumnCount(int count) { - m_columnCount = count; -+ -+ if(m_columnCount == 0) return; -+ maxWidthArr = new int[m_columnCount]; -+ for(int col=0; colsetData(span,ROW_SPAN_ROLE); - } -- } else -+ } -+ else if (role == Qt::DisplayRole || role == Qt::EditRole){ -+ item->setData(value, role); -+ if(orientation == Qt::Vertical) -+ { -+ int width = value.toString().length()*10; -+ int col = index.column(); -+ if(width > maxWidthArr[col]) -+ maxWidthArr[col] = width; -+ } -+ } -+ else - item->setData(value,role); - - return true; -@@ -195,9 +248,15 @@ Qt::ItemFlags HierarchicalHeaderModel::flags(const QModelIndex &index) const { - } - - void HierarchicalHeaderModel::clear() { -+// setRowCount(0); -+// setColumnCount(0); -+// m_rootItem->clear(); - } - --//######################### -+/********************************************************************************************** -+ * VIEW IMPLEMENTATION -+ * -+ * ********************************************************************************************/ - HierarchicalHeaderView::HierarchicalHeaderView(Qt::Orientation orientation, int rows, int columns, QWidget* parent): - QHeaderView(orientation,parent) { - QSize baseSectionSize; -@@ -224,7 +283,6 @@ HierarchicalHeaderView::HierarchicalHeaderView(Qt::Orientation orientation, int - - HierarchicalHeaderView::HierarchicalHeaderView(Qt::Orientation orientation, QWidget* parent): - QHeaderView(orientation,parent) { -- QSize baseSectionSize; - if (orientation == Qt::Horizontal) { - baseSectionSize.setWidth(defaultSectionSize()); - baseSectionSize.setHeight(20); -@@ -241,10 +299,24 @@ HierarchicalHeaderView::HierarchicalHeaderView(Qt::Orientation orientation, QWid - - HierarchicalHeaderView::~HierarchicalHeaderView() = default; - -+ -+QSize HierarchicalHeaderView::getBaseSectionSize() const -+{ -+ return baseSectionSize; -+} -+ -+void HierarchicalHeaderView::setNewModel(HierarchicalHeaderModel* model) -+{ -+ m_model = model; -+ setModel(m_model); -+} -+ - HierarchicalHeaderModel* HierarchicalHeaderView::hierarchicalModel() const { - return m_model; - } - -+ -+ - void HierarchicalHeaderView::setRowHeight(int row, int rowHeight) { - const int cols = m_model->columnCount(); - for (int col = 0; col < cols; ++col) { -@@ -584,7 +656,9 @@ int HierarchicalHeaderView::getSectionRange(QModelIndex& index, int* beginSectio - } - - void HierarchicalHeaderView::onSectionResized(int logicalIndex,int oldSize,int newSize) { -- if (!m_model) -+ Q_UNUSED(oldSize); -+ -+ if (!m_model) - return; - - const int OTN = orientation(); -diff --git a/src/kdefrontend/pivot/HierarchicalHeaderView.h b/src/kdefrontend/pivot/HierarchicalHeaderView.h -index 398c80805..abd18e06b 100755 ---- a/src/kdefrontend/pivot/HierarchicalHeaderView.h -+++ b/src/kdefrontend/pivot/HierarchicalHeaderView.h -@@ -32,10 +32,10 @@ - #include - - enum eRbHeaderRole { -- COLUMN_SPAN_ROLE = Qt::UserRole+1, -- ROW_SPAN_ROLE, -- COLUMN_SIZE_ROLE, -- ROW_SIZE_ROLE, -+ COLUMN_SPAN_ROLE = Qt::UserRole+1, -+ ROW_SPAN_ROLE, -+ COLUMN_SIZE_ROLE, -+ ROW_SIZE_ROLE, - }; - - class HierarchicalHeaderItem { -@@ -65,6 +65,10 @@ private: - QHash role_datas; - }; - -+/**************************************************************************************************** -+ * -+ * MODEL DECLARATIONS -+ * *************************************************************************************************/ - - class HierarchicalHeaderModel: public QAbstractTableModel { - Q_OBJECT -@@ -85,12 +89,23 @@ public: - Qt::ItemFlags flags(const QModelIndex &index) const override; - void clear(); - -+ void setBaseSectionSize(QSize size); -+ void setOrientation(Qt::Orientation orient); - private: - int m_rowCount{0}; - int m_columnCount{0}; - HierarchicalHeaderItem* m_rootItem{nullptr}; -+ -+ int *maxWidthArr; -+ QSize baseSectionSize; -+ Qt::Orientation orientation; - }; - -+/**************************************************************************************************** -+ * -+ * VIEW DECLARATIONS -+ * *************************************************************************************************/ -+ - class HierarchicalHeaderView : public QHeaderView { - Q_OBJECT - public: -@@ -104,6 +119,9 @@ public: - void setCellBackgroundColor(const QModelIndex&, const QColor&); - void setCellForegroundColor(const QModelIndex&, const QColor&); - -+ QSize getBaseSectionSize() const; -+ void setNewModel(HierarchicalHeaderModel* model); -+ - protected: - void mousePressEvent(QMouseEvent*) override; - QModelIndex indexAt(const QPoint&) const override; -@@ -124,6 +142,7 @@ signals: - - private: - HierarchicalHeaderModel* m_model{nullptr}; -+ QSize baseSectionSize; - }; - - #endif -diff --git a/src/kdefrontend/pivot/PivotTableView.cpp b/src/kdefrontend/pivot/PivotTableView.cpp -index 38cb37ce1..c6493c8ce 100644 ---- a/src/kdefrontend/pivot/PivotTableView.cpp -+++ b/src/kdefrontend/pivot/PivotTableView.cpp -@@ -46,6 +46,8 @@ - #include - #include - -+#include -+ - /*! - \class PivotTableView - \brief View class for PivotTable -@@ -177,7 +179,21 @@ void PivotTableView::print(QPrinter* printer) const { - - void PivotTableView::changed() { - --} -+ qDebug() << "in PivotTableView::changed()"; -+ HierarchicalHeaderModel* horizontalHeaderModel = static_cast(m_horizontalHeaderView->hierarchicalModel()); -+ HierarchicalHeaderModel* verticalHeaderModel = static_cast(m_verticalHeaderView->hierarchicalModel()); -+ -+ horizontalHeaderModel->setOrientation(Qt::Horizontal); -+ verticalHeaderModel->setOrientation(Qt::Vertical); -+ -+// qDebug() << " setting size for horizontal header"; -+// qDebug() << " rows, cols = " << horizontalHeaderModel->rowCount() << ", " << horizontalHeaderModel->columnCount(); -+ horizontalHeaderModel->setBaseSectionSize(m_horizontalHeaderView->getBaseSectionSize()); -+ -+// qDebug() << "settign size for vertical header"; -+// qDebug() << " rows, cols = " << verticalHeaderModel->rowCount() << ", " << verticalHeaderModel->columnCount(); -+ verticalHeaderModel->setBaseSectionSize(m_verticalHeaderView->getBaseSectionSize()); -+ } - - void PivotTableView::exportToFile(const QString& path, const bool exportHeader, const QString& separator, QLocale::Language language) const { - Q_UNUSED(exportHeader);