diff --git a/src/backend/hypothesisTest/HypothesisTest.cpp b/src/backend/hypothesisTest/HypothesisTest.cpp --- a/src/backend/hypothesisTest/HypothesisTest.cpp +++ b/src/backend/hypothesisTest/HypothesisTest.cpp @@ -39,13 +39,16 @@ #include #include #include -#include #include +#include + #include #include #include +#include + extern "C" { #include "backend/nsl/nsl_stats.h" } @@ -105,26 +108,29 @@ d->tailType = test.tail; switch (test.subtype) { case HypothesisTest::Test::SubType::TwoSampleIndependent: { - d->currTestName = i18n( "

Two Sample Independent Test

"); + d->currTestName = "

" + i18n("Two Sample Independent Test") + "

"; d->performTwoSampleIndependentTest(test.type, categoricalVariable, equalVariance); break; } case HypothesisTest::Test::SubType::TwoSamplePaired: - d->currTestName = i18n( "

Two Sample Paired Test

"); + d->currTestName = "

" + i18n("Two Sample Paired Test") + "

"; d->performTwoSamplePairedTest(test.type); break; case HypothesisTest::Test::SubType::OneSample: { - d->currTestName = i18n( "

One Sample Test

"); + d->currTestName = "

" + i18n("One Sample Test") + "

"; d->performOneSampleTest(test.type); break; } case HypothesisTest::Test::SubType::OneWay: { - d->currTestName = i18n( "

One Way Anova

"); + d->currTestName = "

" + i18n("One Way Anova") + "

"; d->performOneWayAnova(); break; } - case HypothesisTest::Test::SubType::TwoWay: - break; + case HypothesisTest::Test::SubType::TwoWay: { + d->currTestName = "

" + i18n("Two Way Anova") + "

"; + d->performTwoWayAnova(); + break; + } case HypothesisTest::Test::SubType::NoneSubType: break; } @@ -133,7 +139,7 @@ } void HypothesisTest::performLeveneTest(bool categoricalVariable) { - d->currTestName = i18n( "

Levene Test for Equality of Variance

"); + d->currTestName = "

" + i18n("Levene Test for Equality of Variance") + "

"; d->performLeveneTest(categoricalVariable); emit changed(); @@ -154,10 +160,8 @@ /****************************************************************************** * Private Implementations * ****************************************************************************/ - -//TODO: round off numbers while printing //TODO: backend of z test; - +//TODO: add tooltip to tables. (currently it is not possible to use with QTextDocument); HypothesisTestPrivate::HypothesisTestPrivate(HypothesisTest* owner) : q(owner), summaryLayout(new QVBoxLayout()) { @@ -175,8 +179,8 @@ dataSourceSpreadsheet = spreadsheet; //setting rows and columns count; - rowCount = dataSourceSpreadsheet->rowCount(); - columnCount = dataSourceSpreadsheet->columnCount(); +// rowCount = dataSourceSpreadsheet->rowCount(); +// columnCount = dataSourceSpreadsheet->columnCount(); for (auto* col : dataSourceSpreadsheet->children()) allColumns << col->name(); @@ -215,12 +219,14 @@ if (!categoricalVariable && isNumericOrInteger(columns[0])) { for (int i = 0; i < 2; i++) { findStats(columns[i], n[i], sum[i], mean[i], std[i]); - - if (n[i] < 1) { - printError("At least one of selected column is empty"); - - return; - } + if (n[i] == 0) { + printError("Atleast two values should be there in every column"); + return; + } + if (std[i] == 0) { + printError(i18n("Standard Deviation of atleast one column is equal to 0: last column is: %1", columns[i]->name())); + return; + } } } else { QMap colName; @@ -270,6 +276,17 @@ statsTable = getHtmlTable(3, 5, rowMajor); + for (int i = 0; i < 2; i++) { + if (n[i] == 0) { + printError("Atleast two values should be there in every column"); + return; + } + if (std[i] == 0) { + printError( i18n("Standard Deviation of atleast one column is equal to 0: last column is: %1", columns[i]->name())); + return; + } + } + QString testName; int df = 0; double sp = 0; @@ -280,6 +297,7 @@ if (equalVariance) { 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 ); statisticValue = (mean[0] - mean[1]) / (sp * qSqrt(1.0/n[0] + 1.0/n[1])); @@ -307,17 +325,16 @@ break; } case HypothesisTest::Test::Type::Anova: - break; case HypothesisTest::Test::Type::NoneType: break; } - currTestName = i18n( "

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

", testName, col1Name, col2Name); + currTestName = "

" + i18n("Two Sample Independent %1 Test for %2 vs %3", testName, col1Name, col2Name) + "

"; pValue = getPValue(test, statisticValue, col1Name, col2Name, (mean[0] - mean[1]), sp, df); - printLine(2, i18n("Significance level is %1", significanceLevel), "blue"); + printLine(2, i18n("Significance level is %1", round(significanceLevel)), "blue"); - printLine(4, i18n("%1 Value is %2 ", testName, statisticValue), "green"); + printLine(4, i18n("%1 Value is %2 ", testName, round(statisticValue)), "green"); printTooltip(4, i18n("More is the |%1-value|, more safely we can reject the null hypothesis", testName)); printLine(5, i18n("P Value is %1 ", pValue), "green"); @@ -326,7 +343,7 @@ printTooltip(6, i18n("Number of independent Pieces of information that went into calculating the estimate")); if (pValue <= significanceLevel) - printTooltip(5, i18n("We can safely reject Null Hypothesis for significance level %1", significanceLevel)); + printTooltip(5, i18n("We can safely reject Null Hypothesis for significance level %1", round(significanceLevel))); else printTooltip(5, i18n("There is a plausibility for Null Hypothesis to be true")); return; @@ -362,24 +379,12 @@ } case ErrorEmptyColumn: { printError("columns are empty"); - return; } case NoError: break; } - if (n == -1) { - printError("both columns are having different sizes"); - - return; - } - - if (n < 1) { - printError("columns are empty"); - - return; - } QVariant rowMajor[] = {"", "N", "Sum", "Mean", "Std", "difference", n, sum, mean, std @@ -387,6 +392,12 @@ statsTable = getHtmlTable(2, 5, rowMajor); + if (std == 0) { + printError("Standard deviation of the difference is 0"); + return; + } + + QString testName; int df = 0; @@ -412,10 +423,10 @@ } pValue = getPValue(test, statisticValue, columns[0]->name(), i18n("%1", populationMean), mean, std, df); - currTestName = i18n( "

One Sample %1 Test for %2 vs %3

", testName, columns[0]->name(), columns[1]->name()); + currTestName = "

" + i18n("One Sample %1 Test for %2 vs %3", testName, columns[0]->name(), columns[1]->name()) + "

"; - printLine(2, i18n("Significance level is %1 ", significanceLevel), "blue"); - printLine(4, i18n("%1 Value is %2 ", testName, statisticValue), "green"); + printLine(2, i18n("Significance level is %1 ", round(significanceLevel)), "blue"); + printLine(4, i18n("%1 Value is %2 ", testName, round(statisticValue)), "green"); printLine(5, i18n("P Value is %1 ", pValue), "green"); if (pValue <= significanceLevel) @@ -448,15 +459,13 @@ ErrorType errorCode = findStats(columns[0], n, sum, mean, std); switch (errorCode) { - case ErrorUnqualSize: { + case ErrorEmptyColumn: { printError("column is empty"); - return; } case NoError: break; - case ErrorEmptyColumn: { - + case ErrorUnqualSize: { return; } } @@ -467,6 +476,12 @@ statsTable = getHtmlTable(2, 5, rowMajor); + if (std == 0) { + printError("Standard deviation is 0"); + return; + } + + QString testName; int df = 0; @@ -485,16 +500,15 @@ break; } case HypothesisTest::Test::Type::Anova: - break; case HypothesisTest::Test::Type::NoneType: break; } pValue = getPValue(test, statisticValue, columns[0]->name(), i18n("%1",populationMean), mean - populationMean, std, df); - currTestName = i18n( "

One Sample %1 Test for %2

", testName, columns[0]->name()); + currTestName = "

" + i18n("One Sample %1 Test for %2", testName, columns[0]->name()) + "

"; - printLine(2, i18n("Significance level is %1", significanceLevel), "blue"); - printLine(4, i18n("%1 Value is %2", testName, statisticValue), "green"); + printLine(2, i18n("Significance level is %1", round(significanceLevel)), "blue"); + printLine(4, i18n("%1 Value is %2", testName, round(statisticValue)), "green"); printLine(5, i18n("P Value is %1", pValue), "green"); if (pValue <= significanceLevel) @@ -585,19 +599,19 @@ rowMajor[row_i*columnCount] = colNames[row_i - 1]; rowMajor[row_i*columnCount + 1] = ni[row_i - 1]; rowMajor[row_i*columnCount + 2] = sum[row_i - 1]; - rowMajor[row_i*columnCount + 3] = QString::number( mean[row_i - 1], 'f', 3); - rowMajor[row_i*columnCount + 4] = QString::number( std[row_i - 1], 'f', 3); + rowMajor[row_i*columnCount + 3] = mean[row_i - 1]; + rowMajor[row_i*columnCount + 4] = std[row_i - 1]; } - statsTable = i18n( "

Group Summary Statistics

"); + statsTable = "

" + i18n("Group Summary Statistics") + "

"; statsTable += getHtmlTable(rowCount, columnCount, rowMajor); statsTable += getLine(""); statsTable += getLine(""); - statsTable += i18n( "

Grand Summary Statistics

"); + statsTable += "

" + i18n("Grand Summary Statistics") + "

"; statsTable += getLine(""); - statsTable += getLine(i18n("Overall Mean is %1", yBar)); + statsTable += getLine(i18n("Overall Mean is %1", round(yBar))); rowCount = 4; columnCount = 3; @@ -629,7 +643,7 @@ delete[] std; delete[] colNames; - printLine(1, i18n("F Value is %1", fValue), "blue"); + printLine(1, i18n("F Value is %1", round(fValue)), "green"); printLine(2, i18n("P Value is %1 ", pValue), "green"); if (pValue <= significanceLevel) @@ -637,12 +651,168 @@ else printTooltip(2, i18n("There is a plausibility for Null Hypothesis to be true")); - return; + return; } -/**************************************Levene Test****************************************/ -// TODO: Fix: Program crashes when n = np; +/*************************************Two Way Anova***************************************/ + +// all formulas and symbols are taken from: http://statweb.stanford.edu/~susan/courses/s141/exanova.pdf + +//TODO: suppress warning of variable length array are a C99 feature. +//TODO: changed int mean to double mean; +void HypothesisTestPrivate::performTwoWayAnova() { + clearTestView(); + int np_a, totalRows_a; + int np_b, totalRows_b; + countPartitions(columns[0], np_a, totalRows_a); + countPartitions(columns[1], np_b, totalRows_b); + + double groupMean[np_a][np_b]; + int replicates[np_a][np_b]; + + for (int i = 0; i < np_a; i++) + for (int j = 0; j < np_b; j++) { + groupMean[i][j] = 0; + replicates[i][j] = 0; + } + + if (totalRows_a != totalRows_b) { + printError("There is missing data in atleast one of the rows"); + return; + } + + QMap catToNumber_a; + QMap catToNumber_b; + + int partitionNumber_a = 1; + int partitionNumber_b = 1; + for (int i = 0; i < totalRows_a; i++) { + QString name_a = columns[0]->textAt(i); + QString name_b = columns[1]->textAt(i); + double value = columns[2]->valueAt(i); + + if (catToNumber_a[name_a] == 0) { + catToNumber_a[name_a] = partitionNumber_a; + partitionNumber_a++; + } + + if (catToNumber_b[name_b] == 0) { + catToNumber_b[name_b] = partitionNumber_b; + partitionNumber_b++; + } + + groupMean[catToNumber_a[name_a] - 1][catToNumber_b[name_b] - 1] += value; + replicates[catToNumber_a[name_a] - 1][catToNumber_b[name_b] - 1] += 1; + } + + int replicate = replicates[0][0]; + for (int i = 0; i < np_a; i++) + for (int j = 0; j < np_b; j++) { + if (replicates[i][j] == 0) { + printError("Dataset should have atleast one data value corresponding to each feature combination"); + return; + } + if (replicates[i][j] != replicate) { + printError("Number of experiments perfomed for each combination of levels
" + "between Independet Var.1 and Independent Var.2 must be equal"); + return; + } + groupMean[i][j] /= replicates[i][j]; + } + + for (int i = 0; i < np_a; i++) + for (int j = 0; j < np_b; j++) + groupMean[i][j] = int(groupMean[i][j]); + + double ss_within = 0; + for (int i = 0; i < totalRows_a; i++) { + QString name_a = columns[0]->textAt(i); + QString name_b = columns[1]->textAt(i); + double value = columns[2]->valueAt(i); + + ss_within += gsl_pow_2(value - groupMean[catToNumber_a[name_a] - 1][catToNumber_b[name_b] - 1]); + } + + int df_within = (replicate - 1) * np_a * np_b; + double ms_within = ss_within / df_within; + + double mean_a[np_a]; + double mean_b[np_b]; + for (int i = 0; i < np_a; i++) { + for (int j = 0; j < np_b; j++) { + mean_a[i] += groupMean[i][j] / np_b; + mean_b[j] += groupMean[i][j] / np_a; + } + } + + double mean = 0; + for (int i = 0; i < np_a; i++) + mean += mean_a[i] / np_a; + + QDEBUG("ss_within is " << ss_within); + QDEBUG("df_within is " << df_within); + QDEBUG("ms_within is " << ms_within); + + for (int i = 0; i < np_a; i++) + QDEBUG("mean_a is " << mean_a[i]); + for (int i = 0; i < np_b; i++) + QDEBUG("mean_b is " << mean_b[i]); + + + QString partitionNames_a[np_a]; + QString partitionNames_b[np_b]; + + QMapIterator itr_a(catToNumber_a); + while (itr_a.hasNext()) { + itr_a.next(); + partitionNames_a[itr_a.value()-1] = itr_a.key(); + } + + QMapIterator itr_b(catToNumber_b); + while (itr_b.hasNext()) { + itr_b.next(); + partitionNames_b[itr_b.value()-1] = itr_b.key(); + } + + // printing table; + // cell constructor structure; data, level, rowSpanCount, columnSpanCount, isHeader; + QList rowMajor; + rowMajor.append(new Cell("", 0, true, 2, 1)); + for (int i = 0; i < np_b; i++) + rowMajor.append(new Cell(partitionNames_b[i], 0, true, 1, 2)); + rowMajor.append(new Cell("Mean", 0, true, 2)); + + for (int i = 0; i < np_b; i++) { + rowMajor.append(new Cell("Mean", 1, true)); + rowMajor.append(new Cell("Replicate", 1, true)); + } + + int level = 2; + for (int i = 0; i < np_a; i++) { + rowMajor.append(new Cell(partitionNames_a[i], level, true)); + for (int j = 0; j < np_b; j++) { + rowMajor.append(new Cell(groupMean[i][j], level)); + rowMajor.append(new Cell(replicates[i][j], level)); + } + rowMajor.append(new Cell(mean_a[i], level)); + level++; + } + + rowMajor.append(new Cell("Mean", level, true)); + for (int i = 0; i < np_b; i++) + rowMajor.append(new Cell(mean_b[i], level, false, 1, 2)); + rowMajor.append(new Cell(mean, level)); + + statsTable = "

" + i18n("Contingency Table") + "

"; + statsTable += getHtmlTable3(rowMajor); + +// QDEBUG(""); +// QDEBUG(""); +// QDEBUG(statsTable); + return; +} +/**************************************Levene Test****************************************/ // Some reference to local variables. // np = number of partitions // df = degree of fredom @@ -671,7 +841,7 @@ countPartitions(columns[0], np, n); if (np < 2) { - printError("select atleast two columns / classes"); + printError("Select atleast two columns / classes"); return; } @@ -717,6 +887,10 @@ for (int i = 0; i < np; i++) { if (ni[i] > 0) yiBar[i] = yiBar[i] / ni[i]; + else { + printError("One of the selected columns is empty"); + return; + } } for (int j = 0; j < totalRows; j++) { @@ -748,6 +922,12 @@ } } + + if (denominatorValue <= 0) { + printError( i18n("Denominator value is %1", denominatorValue)); + return; + } + for (int i = 0; i < np; i++) { colNames[i] = columns[i]->name(); numberatorValue += ni[i]*gsl_pow_2( (ziBar[i]-ziBarBar)); @@ -787,6 +967,10 @@ for (int i = 0; i < np; i++) { if (ni[i] > 0) yiBar[i] = yiBar[i] / ni[i]; + else { + printError("One of the selected columns is empty"); + return; + } } for (int j = 0; j < n; j++) { @@ -865,8 +1049,8 @@ 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", significanceLevel), "blue"); - printLine(4, i18n("F Value is %1 ", fValue), "green"); + printLine(2, i18n("Significance level is %1", round(significanceLevel)), "blue"); + printLine(4, i18n("F Value is %1 ", round(fValue)), "green"); printLine(5, i18n("P Value is %1 ", pValue), "green"); printLine(6, i18n("Degree of Freedom is %1", df), "green"); @@ -883,6 +1067,20 @@ /***************************************Helper Functions*************************************/ +QString HypothesisTestPrivate::round(QVariant number, int precision) { + if (number.userType() == QMetaType::Double || number.userType() == QMetaType::Float) { + double multiplierPrecision = qPow(10, precision); + int tempNum = int(number.toDouble()*multiplierPrecision*10); + + if (tempNum % 10 < 5) + return QString::number((tempNum/10) / multiplierPrecision); + else + return QString::number((tempNum/10 + 1) / multiplierPrecision); + } + return i18n("%1", number.toString()); +} + + bool HypothesisTestPrivate::isNumericOrInteger(Column* column) { return (column->columnMode() == AbstractColumn::Numeric || column->columnMode() == AbstractColumn::Integer); } @@ -1104,72 +1302,210 @@ break; } case HypothesisTest::Test::Type::Anova: - break; case HypothesisTest::Test::Type::NoneType: break; } if (pValue > 1) return 1; - return pValue; + return pValue; } -QString HypothesisTestPrivate::getHtmlTable(int row, int column, QVariant* rowMajor) { - if (row < 1 || column < 1) - return QString(); +int HypothesisTestPrivate::setSpanValues(HypothesisTestPrivate::Node* root, int& totalLevels) { + if (root == nullptr) { + totalLevels = 0; + return 0; + } - QString table; - table = "" - "" - " "; - - QString bg = "tg-0pky"; - bool pky = true; - - QString element; - table += " "; - for (int j = 0; j < column; j++) { - element = rowMajor[j].toString(); - table += i18n(" ", bg, element); - } - table += " "; + int val = 0; + int level = 0; + int maxLevel = 0; + for (int i = 0; i < root->children.size(); i++) { + val += setSpanValues(root->children[i], level); + maxLevel = std::max(level, maxLevel); + } - if (pky) - bg = "tg-0pky"; - else - bg = "tg-btxf"; - pky = !pky; + totalLevels = maxLevel + 1; + if (val == 0) + root->spanCount = 1; + else + root->spanCount = val; - for (int i = 1; i < row; i++) { - table += " "; + return root->spanCount; - QString element = rowMajor[i*column].toString(); - table += i18n(" ", bg, element); - for (int j = 1; j < column; j++) { - QString element = rowMajor[i*column+j].toString(); - table += i18n(" ", bg, element); - } +} - table += " "; - if (pky) - bg = "tg-0pky"; - else - bg = "tg-btxf"; - pky = !pky; - } - table += "
%2
%2%2
"; +QString HypothesisTestPrivate::getHtmlHeader(HypothesisTestPrivate::Node *root) { + if (root == nullptr) + return QString(); + + QString header; + + int totalLevels = 0; - return table; + setSpanValues(root, totalLevels); + totalLevels -= 1; + + root->level = 0; + QQueue nodeQueue; + + for (int i = 0; i < root->children.size(); i++) { + Node* child = root->children[i]; + child->level = 1; + nodeQueue.enqueue(child); + } + + int prevLevel = 1; + header = " "; + header += " "; + + while(!nodeQueue.isEmpty()) { + Node* node = nodeQueue.dequeue(); + int nodeLevel = node->level; + + for (int i = 0; i < node->children.size(); i++) { + Node* child = node->children[i]; + child->level = nodeLevel + 1; + nodeQueue.enqueue(child); + } + + if (nodeLevel != prevLevel) { + prevLevel = nodeLevel; + header += " "; + header += " "; + } + + header += " " + node->data + ""; + } + header += " "; + return header; +} + +QString HypothesisTestPrivate::getHtmlTable2(int rowCount, int columnCount, Node* columnHeaderRoot, QVariant* rowMajor) { + if (rowCount < 1 || columnCount < 1) + return QString(); + + QString table; + + table = "" + ""; + table += getHtmlHeader(columnHeaderRoot); + + for (int i = 0; i < rowCount; i++) { + table += " "; + table += " "; + for (int j = 1; j < columnCount; j++) + table += " "; + table += " "; + } + + table += "
" + round(rowMajor[i*columnCount]) + "" + round(rowMajor[i*columnCount + j]) + "
"; + return table; +} + + +QString HypothesisTestPrivate::getHtmlTable(int row, int column, QVariant* rowMajor) { + if (row < 1 || column < 1) + return QString(); + + QString table; + table = "" + "" + " "; + + QString bg = "tg-0pky"; + bool pky = true; + + QString element; + table += " "; + for (int j = 0; j < column; j++) { + element = rowMajor[j].toString(); + table += " "; + } + table += " "; + + if (pky) + bg = "tg-0pky"; + else + bg = "tg-btxf"; + pky = !pky; + + for (int i = 1; i < row; i++) { + table += " "; + + QString element = round(rowMajor[i*column]); + table += " "; + for (int j = 1; j < column; j++) { + element = round(rowMajor[i*column+j]); + table += " "; + } + + table += " "; + if (pky) + bg = "tg-0pky"; + else + bg = "tg-btxf"; + pky = !pky; + } + table += "
" + i18n("%1", element) + "
" + i18n("%1", element) + "" + i18n("%1", element) + "
"; + + return table; +} + +QString HypothesisTestPrivate::getHtmlTable3(const QList& rowMajor) { + int rowMajorSize = rowMajor.size(); + + if (rowMajorSize == 0) + return QString(); + + QString table; + table = ""; + + table += " "; + int prevLevel = 0; + for (int i = 0; i < rowMajorSize; i++) { + Cell* currCell = rowMajor[i]; + if (currCell->level != prevLevel) { + table += " "; + table += " "; + prevLevel = currCell->level; + } + QString cellStartTag = ""; + return table; } QString HypothesisTestPrivate::getLine(const QString& msg, const QString& color) { - return i18n("

%2

", color, msg); + return "

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

"; } void HypothesisTestPrivate::printLine(const int& index, const QString& msg, const QString& color) { @@ -1213,7 +1549,6 @@ writer->writeStartElement("hypothesisTest"); writeBasicAttributes(writer); writeCommentElement(writer); - //TODO: writer->writeEndElement(); } @@ -1226,8 +1561,6 @@ if (!readBasicAttributes(reader)) return false; - //TODO: - return !reader->hasError(); } diff --git a/src/backend/hypothesisTest/HypothesisTestPrivate.h b/src/backend/hypothesisTest/HypothesisTestPrivate.h --- a/src/backend/hypothesisTest/HypothesisTestPrivate.h +++ b/src/backend/hypothesisTest/HypothesisTestPrivate.h @@ -38,6 +38,32 @@ explicit HypothesisTestPrivate(HypothesisTest*); virtual ~HypothesisTestPrivate(); + struct Node { + QString data; + int spanCount; + int level; + + QVector children; + void addChild(Node* child) { + children.push_back(child); + } + }; + + struct Cell { + QString data; + int level; + int rowSpanCount; + int columnSpanCount; + bool isHeader; + Cell(QVariant data = "", int level = 0, bool isHeader = false, int rowSpanCount = 1, int columnSpanCount = 1) { + this->data = data.toString(); + this->level = level; + this->isHeader = isHeader; + this->rowSpanCount = rowSpanCount; + this->columnSpanCount = columnSpanCount; + } + }; + enum ErrorType {ErrorUnqualSize, ErrorEmptyColumn, NoError}; QString name() const; @@ -47,6 +73,7 @@ void performTwoSamplePairedTest(HypothesisTest::Test::Type test); void performOneSampleTest(HypothesisTest::Test::Type test); void performOneWayAnova(); + void performTwoWayAnova(); void performLeveneTest(bool categoricalVariable); @@ -56,8 +83,8 @@ QVector columns; QStringList allColumns; - int rowCount{0}; - int columnCount{0}; +// int rowCount{0}; +// int columnCount{0}; QString currTestName{"Result Table"}; double populationMean; double significanceLevel; @@ -72,13 +99,20 @@ private: bool isNumericOrInteger(Column* column); - void countPartitions(Column* column, int& np, int& totalRows); + QString round(QVariant number, int precision = 3); + + void countPartitions(Column* column, int& np, int& totalRows); ErrorType findStats(const Column* column,int& count, double& sum, double& mean, double& std); ErrorType findStatsPaired(const Column* column1, const Column* column2, int& count, double& sum, double& mean, double& std); ErrorType findStatsCategorical(Column* column1, Column* column2, int n[], double sum[], double mean[], double std[], QMap& colName, const int& np, const int& totalRows); double getPValue(const HypothesisTest::Test::Type& test, double& value, const QString& col1Name, const QString& col2name, const double mean, const double sp, const int df); - QString getHtmlTable(int row, int column, QVariant* rowMajor); + int setSpanValues(Node* root, int& totalLevels); + QString getHtmlHeader(Node* root); + QString getHtmlTable2(int rowCount, int columnCount, Node* columnHeaderRoot, QVariant* rowMajor); + QString getHtmlTable(int row, int column, QVariant* rowMajor); + QString getHtmlTable3(const QList& rowMajor); + QString getLine(const QString& msg, const QString& color = "black"); void printLine(const int& index, const QString& msg, const QString& color = "black"); diff --git a/src/kdefrontend/dockwidgets/HypothesisTestDock.h b/src/kdefrontend/dockwidgets/HypothesisTestDock.h --- a/src/kdefrontend/dockwidgets/HypothesisTestDock.h +++ b/src/kdefrontend/dockwidgets/HypothesisTestDock.h @@ -50,13 +50,6 @@ 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}; @@ -99,12 +92,16 @@ void changeCbCol2Label(); void chbPopulationSigmaStateChanged(); void col1IndexChanged(int index); + + void onRbH1OneTail1Toggled(bool checked); + void onRbH1OneTail2Toggled(bool checked); + void onRbH1TwoTailToggled(bool checked); // void connectionChanged(); // void tableChanged(); // void showDatabaseManager(); // //SLOTs for changes triggered in PivotTable - // void pivotTableDescriptionChanged(const AbstractAspect*); + void hypothesisTestDescriptionChanged(const AbstractAspect*); // void addRow(); // void removeRow(); diff --git a/src/kdefrontend/dockwidgets/HypothesisTestDock.cpp b/src/kdefrontend/dockwidgets/HypothesisTestDock.cpp --- a/src/kdefrontend/dockwidgets/HypothesisTestDock.cpp +++ b/src/kdefrontend/dockwidgets/HypothesisTestDock.cpp @@ -34,7 +34,6 @@ #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 @@ -44,7 +43,6 @@ #include #include #include -#include #include #include @@ -54,11 +52,11 @@ \ingroup kdefrontend */ -//TOOD: Make this dock widget scrollable and automatic resizeable for different screens. -//TODO: Better initalization: All widgets needs to be clicked in whole session atleast once. //TODO: To add tooltips in docks for non obvious widgets. +//TODO: Add functionality for database along with spreadsheet. HypothesisTestDock::HypothesisTestDock(QWidget* parent) : QWidget(parent) { + //QDEBUG("in hypothesis test constructor "); ui.setupUi(this); ui.cbDataSourceType->addItem(i18n("Spreadsheet")); @@ -73,7 +71,7 @@ // adding item to tests and testtype combo box; - ui.cbTest->addItem( i18n("T Test"), HypothesisTest::Test::Type::TTest); + ui.cbTest->addItem( i18n("T Test"), HypothesisTest::Test::Type::TTest); ui.cbTest->addItem( i18n("Z Test"), HypothesisTest::Test::Type::ZTest); ui.cbTest->addItem( i18n("ANOVA"), HypothesisTest::Test::Type::Anova); @@ -143,7 +141,6 @@ ui.leMuo->setText( i18n("%1", m_populationMean)); ui.leAlpha->setText( i18n("%1", m_significanceLevel)); - showTestType(); // readConnections(); @@ -200,8 +197,8 @@ // 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::showTestType); + connect(ui.cbTestType, static_cast(&QComboBox::currentIndexChanged), 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); @@ -217,10 +214,16 @@ connect(ui.chbCategorical, &QCheckBox::stateChanged, this, &HypothesisTestDock::changeCbCol2Label); connect(ui.chbPopulationSigma, &QCheckBox::stateChanged, this, &HypothesisTestDock::chbPopulationSigmaStateChanged); + + ui.cbTest->setCurrentIndex(0); + emit ui.cbTest->currentIndexChanged(0); + ui.cbTestType->setCurrentIndex(0); + emit ui.cbTestType->currentIndexChanged(0); } void HypothesisTestDock::setHypothesisTest(HypothesisTest* HypothesisTest) { - m_initializing = true; + //QDEBUG("in set hypothesis test"); + m_initializing = true; m_hypothesisTest = HypothesisTest; m_aspectTreeModel = new AspectTreeModel(m_hypothesisTest->project()); @@ -251,14 +254,14 @@ //setting rows and columns in combo box; //undo functions - connect(m_hypothesisTest, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(hypothesisTestDescriptionChanged(const AbstractAspect*))); - //TODO: +// connect(m_hypothesisTest, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(hypothesisTestDescriptionChanged(const AbstractAspect*))); m_initializing = false; - showTestType(); + } void HypothesisTestDock::showTestType() { + //QDEBUG("in show test type"); m_test.type = HypothesisTest::Test::Type(ui.cbTest->currentData().toInt()); ui.cbTestType->clear(); @@ -270,58 +273,65 @@ ui.cbTestType->addItem( i18n("One Way"), HypothesisTest::Test::SubType::OneWay); ui.cbTestType->addItem( i18n("Two Way"), HypothesisTest::Test::SubType::TwoWay); } - - showHypothesisTest(); } void HypothesisTestDock::showHypothesisTest() { - m_test.subtype = HypothesisTest::Test::SubType(ui.cbTestType->currentData().toInt()); + //QDEBUG("in showHypothesisTest"); + + if (ui.cbTestType->count() == 0) + return; + + m_test.subtype = HypothesisTest::Test::SubType(ui.cbTestType->currentData().toInt()); ui.lCol1->show(); ui.cbCol1->show(); - if ((m_test.type & HypothesisTest::Test::Type::Anova) & - (m_test.subtype & HypothesisTest::Test::SubType::OneWay)) - ui.lCol1->setToolTip("Can only select with Data type: text"); + ui.lCol2->setVisible(bool(m_test.subtype & (~HypothesisTest::Test::SubType::OneSample))); + ui.cbCol2->setVisible(bool(m_test.subtype & (~HypothesisTest::Test::SubType::OneSample))); + ui.lCol3->setVisible(bool(m_test.type & (HypothesisTest::Test::Anova) & + setAllBits(m_test.subtype & HypothesisTest::Test::SubType::TwoWay))); + ui.cbCol3->setVisible(bool(m_test.type & (HypothesisTest::Test::Anova) & + setAllBits(m_test.subtype & HypothesisTest::Test::SubType::TwoWay))); - ui.lCol2->setVisible(m_test.subtype & (~HypothesisTest::Test::SubType::OneSample)); - ui.cbCol2->setVisible(ui.lCol2->isVisible()); + ui.lEqualVariance->setVisible(bool( (m_test.type & HypothesisTest::Test::Type::TTest) & + (m_test.subtype & HypothesisTest::Test::SubType::TwoSampleIndependent))); + ui.chbEqualVariance->setVisible(bool( (m_test.type & HypothesisTest::Test::Type::TTest) & + (m_test.subtype & HypothesisTest::Test::SubType::TwoSampleIndependent))); - ui.lEqualVariance->setVisible( (m_test.type & HypothesisTest::Test::Type::TTest) & - (m_test.subtype & HypothesisTest::Test::SubType::TwoSampleIndependent)); - ui.chbEqualVariance->setVisible( ui.lEqualVariance->isVisible()); - - ui.lCategorical->setVisible( (m_test.type & HypothesisTest::Test::Type::TTest) & - (m_test.subtype & HypothesisTest::Test::SubType::TwoSampleIndependent)); - ui.chbCategorical->setVisible( ui.lCategorical->isVisible()); + ui.lCategorical->setVisible(bool((m_test.type & HypothesisTest::Test::Type::TTest) & + (m_test.subtype & HypothesisTest::Test::SubType::TwoSampleIndependent))); + ui.chbCategorical->setVisible(bool((m_test.type & HypothesisTest::Test::Type::TTest) & + (m_test.subtype & HypothesisTest::Test::SubType::TwoSampleIndependent))); ui.chbEqualVariance->setChecked(true); - ui.lPopulationSigma->setVisible((m_test.type & (HypothesisTest::Test::Type::TTest | + ui.lPopulationSigma->setVisible(bool((m_test.type & (HypothesisTest::Test::Type::TTest | HypothesisTest::Test::Type::ZTest)) & - ~(setAllBits(m_test.subtype & HypothesisTest::Test::SubType::OneSample))); + ~(setAllBits(m_test.subtype & HypothesisTest::Test::SubType::OneSample)))); - ui.chbPopulationSigma->setVisible( ui.lPopulationSigma->isVisible()); - ui.chbPopulationSigma->setChecked(false); + ui.chbPopulationSigma->setVisible(bool((m_test.type & (HypothesisTest::Test::Type::TTest | + HypothesisTest::Test::Type::ZTest)) & + ~(setAllBits(m_test.subtype & HypothesisTest::Test::SubType::OneSample)))); ui.chbPopulationSigma->setChecked(false); - ui.pbLeveneTest->setVisible( (m_test.type & (HypothesisTest::Test::Type::Anova | - HypothesisTest::Test::Type::TTest)) & - setAllBits(m_test.subtype & HypothesisTest::Test::SubType::TwoSampleIndependent)); + ui.pbLeveneTest->setVisible(bool((m_test.type & HypothesisTest::Test::Type::Anova & + setAllBits(m_test.subtype & HypothesisTest::Test::SubType::OneWay)) | + (HypothesisTest::Test::Type::TTest & + setAllBits(m_test.subtype & HypothesisTest::Test::SubType::TwoSampleIndependent)))); - ui.lH1->setVisible(m_test.type & ~HypothesisTest::Test::Type::Anova); - ui.rbH1OneTail1->setVisible(m_test.type & ~HypothesisTest::Test::Type::Anova); - ui.rbH1OneTail2->setVisible(m_test.type & ~HypothesisTest::Test::Type::Anova); - ui.rbH1TwoTail->setVisible(m_test.type & ~HypothesisTest::Test::Type::Anova); + ui.lH1->setVisible(bool(m_test.type & ~HypothesisTest::Test::Type::Anova)); + ui.rbH1OneTail1->setVisible(bool(m_test.type & ~HypothesisTest::Test::Type::Anova)); + ui.rbH1OneTail2->setVisible(bool(m_test.type & ~HypothesisTest::Test::Type::Anova)); + ui.rbH1TwoTail->setVisible(bool(m_test.type & ~HypothesisTest::Test::Type::Anova)); - ui.lH0->setVisible(m_test.type & ~HypothesisTest::Test::Type::Anova); - ui.rbH0OneTail1->setVisible(m_test.type & ~HypothesisTest::Test::Type::Anova); - ui.rbH0OneTail2->setVisible(m_test.type & ~HypothesisTest::Test::Type::Anova); - ui.rbH0TwoTail->setVisible(m_test.type & ~HypothesisTest::Test::Type::Anova); + ui.lH0->setVisible(bool(m_test.type & ~HypothesisTest::Test::Type::Anova)); + ui.rbH0OneTail1->setVisible(bool(m_test.type & ~HypothesisTest::Test::Type::Anova)); + ui.rbH0OneTail2->setVisible(bool(m_test.type & ~HypothesisTest::Test::Type::Anova)); + ui.rbH0TwoTail->setVisible(bool(m_test.type & ~HypothesisTest::Test::Type::Anova)); ui.rbH1TwoTail->setChecked(true); - ui.lMuo->setVisible(m_test.subtype & HypothesisTest::Test::SubType::OneSample); - ui.leMuo->setVisible(ui.lMuo->isVisible()); + ui.lMuo->setVisible(bool(m_test.subtype & HypothesisTest::Test::SubType::OneSample)); + ui.leMuo->setVisible(bool(ui.lMuo->isVisible())); ui.lAlpha->show(); ui.leAlpha->show(); @@ -333,13 +343,24 @@ } void HypothesisTestDock::doHypothesisTest() { + //QDEBUG("in doHypothesisTest"); m_hypothesisTest->setPopulationMean(ui.leMuo->text()); m_hypothesisTest->setSignificanceLevel(ui.leAlpha->text()); QVector cols; + + if (ui.cbCol1->count() == 0) + return; + cols << reinterpret_cast(ui.cbCol1->currentData().toLongLong()); - if (m_test.subtype & (~HypothesisTest::Test::SubType::OneSample)) - cols << reinterpret_cast(ui.cbCol2->currentData().toLongLong()); + + if (m_test.subtype & HypothesisTest::Test::SubType::TwoWay) + cols << reinterpret_cast(ui.cbCol3->currentData().toLongLong()); + + if (m_test.subtype & (~HypothesisTest::Test::SubType::OneSample)) + if (ui.cbCol2->count() > 0) + cols << reinterpret_cast(ui.cbCol2->currentData().toLongLong()); + m_hypothesisTest->setColumns(cols); m_hypothesisTest->performTest(m_test, ui.chbCategorical->isChecked(), ui.chbEqualVariance->isChecked()); @@ -347,6 +368,10 @@ void HypothesisTestDock::performLeveneTest() { QVector cols; + + if (ui.cbCol1->count() == 0 || ui.cbCol2->count() == 0) + return; + cols << reinterpret_cast(ui.cbCol1->currentData().toLongLong()); cols << reinterpret_cast(ui.cbCol2->currentData().toLongLong()); m_hypothesisTest->setColumns(cols); @@ -485,6 +510,7 @@ //} void HypothesisTestDock::dataSourceTypeChanged(int index) { + //QDEBUG("in dataSourceTypeChanged"); HypothesisTest::DataSourceType type = static_cast(index); bool showDatabase = (type == HypothesisTest::DataSourceType::DataSourceDatabase); ui.lSpreadsheet->setVisible(!showDatabase); @@ -499,21 +525,26 @@ return; m_hypothesisTest->setComment(ui.leComment->text()); + } void HypothesisTestDock::spreadsheetChanged(const QModelIndex& index) { - auto* aspect = static_cast(index.internalPointer()); + //QDEBUG("in spreadsheetChanged"); + auto* aspect = static_cast(index.internalPointer()); Spreadsheet* spreadsheet = dynamic_cast(aspect); setColumnsComboBoxModel(spreadsheet); m_hypothesisTest->setDataSourceSpreadsheet(spreadsheet); } void HypothesisTestDock::changeCbCol2Label() { + //QDEBUG("in changeCbCol2Label"); if ( (m_test.type & ~HypothesisTest::Test::Type::Anova) & (m_test.subtype & ~HypothesisTest::Test::SubType::TwoSampleIndependent)) { ui.lCol2->setText( i18n("Independent Var. 2")); return; } + if (ui.cbCol1->count() == 0) return; + QString selected_text = ui.cbCol1->currentText(); Column* col1 = m_hypothesisTest->dataSourceSpreadsheet()->column(selected_text); @@ -569,7 +600,7 @@ // } // //open the selected connection -// QDEBUG("HypothesisTestDock: connecting to " + connection); +// //QDEBUG("HypothesisTestDock: connecting to " + connection); // const QString& driver = group.readEntry("Driver"); // m_db = QSqlDatabase::addDatabase(driver); @@ -634,20 +665,22 @@ //} ////************************************************************* -////******** SLOTs for changes triggered in Matrix ********* +////******** SLOTs for changes triggered in Spreadsheet ********* ////************************************************************* -//void HypothesisTestDock::hypothesisTestDescriptionChanged(const AbstractAspect* aspect) { -// if (m_hypothesisTest != aspect) -// return; +void HypothesisTestDock::hypothesisTestDescriptionChanged(const AbstractAspect* aspect) { + //QDEBUG("in hypothesisTestDescriptionChanged"); -// 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()); + if (m_hypothesisTest != aspect) + return; -// m_initializing = false; -//} + 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 ******************************* @@ -699,7 +732,6 @@ /**************************************Helper Functions********************************************/ - void HypothesisTestDock::countPartitions(Column *column, int &np, int &total_rows) { total_rows = column->rowCount(); np = 0; @@ -755,32 +787,78 @@ ui.cbCol1->clear(); ui.cbCol2->clear(); + ui.cbCol3->clear(); QList::iterator i; - if (m_test.subtype & HypothesisTest::Test::SubType::TwoSampleIndependent) { - for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) { - ui.cbCol1->addItem( (*i)->name(), qint64(*i)); - ui.cbCol2->addItem( (*i)->name(), qint64(*i)); - } - for (i = m_twoCategoricalCols.begin(); i != m_twoCategoricalCols.end(); i++) - ui.cbCol1->addItem( (*i)->name(), qint64(*i)); - } else if (m_test.subtype & HypothesisTest::Test::SubType::TwoSamplePaired) { - for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) { - ui.cbCol1->addItem( (*i)->name(), qint64(*i)); - ui.cbCol2->addItem( (*i)->name(), qint64(*i)); - } - } else if (m_test.subtype & HypothesisTest::Test::SubType::OneSample) { - for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) - ui.cbCol1->addItem( (*i)->name(), qint64(*i)); - } else if (m_test.type & HypothesisTest::Test::Type::Anova) { - for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) - ui.cbCol2->addItem( (*i)->name(), qint64(*i)); - for (i = m_twoCategoricalCols.begin(); i != m_twoCategoricalCols.end(); i++) - ui.cbCol1->addItem( (*i)->name(), qint64(*i)); - for (i = m_multiCategoricalCols.begin(); i != m_multiCategoricalCols.end(); i++) - ui.cbCol1->addItem( (*i)->name(), qint64(*i)); - } + switch (m_test.type) { + case (HypothesisTest::Test::Type::ZTest): + case (HypothesisTest::Test::Type::TTest): { + switch (m_test.subtype) { + case (HypothesisTest::Test::SubType::TwoSampleIndependent): { + for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) { + ui.cbCol1->addItem( (*i)->name(), qint64(*i)); + ui.cbCol2->addItem( (*i)->name(), qint64(*i)); + } + for (i = m_twoCategoricalCols.begin(); i != m_twoCategoricalCols.end(); i++) + ui.cbCol1->addItem( (*i)->name(), qint64(*i)); + break; + } + case (HypothesisTest::Test::SubType::TwoSamplePaired): { + for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) { + ui.cbCol1->addItem( (*i)->name(), qint64(*i)); + ui.cbCol2->addItem( (*i)->name(), qint64(*i)); + } + break; + } + case (HypothesisTest::Test::SubType::OneSample): { + for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) + ui.cbCol1->addItem( (*i)->name(), qint64(*i)); + break; + } + case HypothesisTest::Test::SubType::OneWay: + case HypothesisTest::Test::SubType::TwoWay: + case HypothesisTest::Test::SubType::NoneSubType: + break; + } + break; + } + case HypothesisTest::Test::Type::Anova: { + switch (m_test.subtype) { + case HypothesisTest::Test::SubType::OneWay: { + for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) + ui.cbCol2->addItem( (*i)->name(), qint64(*i)); + for (i = m_twoCategoricalCols.begin(); i != m_twoCategoricalCols.end(); i++) + ui.cbCol1->addItem( (*i)->name(), qint64(*i)); + for (i = m_multiCategoricalCols.begin(); i != m_multiCategoricalCols.end(); i++) + ui.cbCol1->addItem( (*i)->name(), qint64(*i)); + break; + } + case HypothesisTest::Test::SubType::TwoWay: { + for (i = m_onlyValuesCols.begin(); i != m_onlyValuesCols.end(); i++) + ui.cbCol2->addItem( (*i)->name(), qint64(*i)); + + for (i = m_twoCategoricalCols.begin(); i != m_twoCategoricalCols.end(); i++) { + ui.cbCol1->addItem( (*i)->name(), qint64(*i)); + ui.cbCol3->addItem( (*i)->name(), qint64(*i)); + } + for (i = m_multiCategoricalCols.begin(); i != m_multiCategoricalCols.end(); i++) { + ui.cbCol1->addItem( (*i)->name(), qint64(*i)); + ui.cbCol3->addItem( (*i)->name(), qint64(*i)); + } + break; + } + case HypothesisTest::Test::SubType::TwoSampleIndependent: + case HypothesisTest::Test::SubType::TwoSamplePaired: + case HypothesisTest::Test::SubType::OneSample: + case HypothesisTest::Test::SubType::NoneSubType: + break; + } + break; + } + case HypothesisTest::Test::Type::NoneType: + break; + } } bool HypothesisTestDock::nonEmptySelectedColumns() { diff --git a/src/kdefrontend/hypothesisTest/HypothesisTestView.cpp b/src/kdefrontend/hypothesisTest/HypothesisTestView.cpp --- a/src/kdefrontend/hypothesisTest/HypothesisTestView.cpp +++ b/src/kdefrontend/hypothesisTest/HypothesisTestView.cpp @@ -48,8 +48,6 @@ #include #include -#include - /*! \class HypothesisTestView \brief View class for Hypothesis Test diff --git a/src/kdefrontend/ui/dockwidgets/hypothesistestdock.ui b/src/kdefrontend/ui/dockwidgets/hypothesistestdock.ui --- a/src/kdefrontend/ui/dockwidgets/hypothesistestdock.ui +++ b/src/kdefrontend/ui/dockwidgets/hypothesistestdock.ui @@ -507,7 +507,7 @@ - TextLabel + Independent Var. 2
isHeader) { + cellStartTag = "" + + i18n("%1", currCell->data) + + cellEndTag; + } + table += "