// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-FileCopyrightText: Copyright 2008 Sandia Corporation
// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-Sandia-USGov
// .SECTION Thanks
// Thanks to Philippe Pebay and David Thompson from Sandia National Laboratories
// for implementing this test.

#include "vtkDataAssembly.h"
#include "vtkDataObjectCollection.h"
#include "vtkDataSetAttributes.h"
#include "vtkDescriptiveStatistics.h"
#include "vtkDoubleArray.h"
#include "vtkLogger.h"
#include "vtkMath.h"
#include "vtkNew.h"
#include "vtkStatisticalModel.h"
#include "vtkStringArray.h"
#include "vtkTable.h"
#include "vtkTimerLog.h"
#include "vtkUnsignedCharArray.h"

#include <iostream>

//=============================================================================
int TestDescriptiveStatistics(int, char*[])
{
  int testStatus = 0;

  // ************** Test with 3 columns of input data **************

  // Input data
  double mingledData[] = {
    46,
    45,
    47,
    49,
    46,
    47,
    46,
    46,
    47,
    46,
    47,
    49,
    49,
    49,
    47,
    45,
    50,
    50,
    46,
    46,
    51,
    50,
    48,
    48,
    52,
    54,
    48,
    47,
    52,
    52,
    49,
    49,
    53,
    54,
    50,
    50,
    53,
    54,
    50,
    52,
    53,
    53,
    50,
    51,
    54,
    54,
    49,
    49,
    52,
    52,
    50,
    51,
    52,
    52,
    49,
    47,
    48,
    48,
    48,
    50,
    46,
    48,
    47,
    47,
  };

  // Test with entire data set
  int nVals1 = 32;

  vtkDoubleArray* dataset1Arr = vtkDoubleArray::New();
  dataset1Arr->SetNumberOfComponents(1);
  dataset1Arr->SetName("Metric 0");

  vtkDoubleArray* dataset2Arr = vtkDoubleArray::New();
  dataset2Arr->SetNumberOfComponents(1);
  dataset2Arr->SetName("Metric 1");

  vtkDoubleArray* dataset3Arr = vtkDoubleArray::New();
  dataset3Arr->SetNumberOfComponents(1);
  dataset3Arr->SetName("Metric 2");

  for (int i = 0; i < nVals1; ++i)
  {
    int ti = i << 1;
    dataset1Arr->InsertNextValue(mingledData[ti]);
    dataset2Arr->InsertNextValue(mingledData[ti + 1]);
    dataset3Arr->InsertNextValue(-1.);
  }

  vtkTable* datasetTable1 = vtkTable::New();
  datasetTable1->AddColumn(dataset1Arr);
  dataset1Arr->Delete();
  datasetTable1->AddColumn(dataset2Arr);
  dataset2Arr->Delete();
  datasetTable1->AddColumn(dataset3Arr);
  dataset3Arr->Delete();

  // Pairs of interest
  int nMetrics = 3;
  std::string columns[] = { "Metric 1", "Metric 2", "Metric 0" };

  // Reference values
  // Means for metrics 0, 1, and 2, respectively
  double means1[] = { 49.21875, 49.5, -1. };

  // Standard deviations for metrics 0, 1, and 2, respectively
  double stdevs1[] = { sqrt(5.9828629), sqrt(7.548397), 0. };

  // Set descriptive statistics algorithm and its input data port
  vtkDescriptiveStatistics* ds1 = vtkDescriptiveStatistics::New();

  // First verify that absence of input does not cause trouble
  std::cout << "\n## Verifying that absence of input does not cause trouble... ";
  ds1->Update();
  std::cout << "done.\n";

  // Prepare first test with data
  ds1->SetInputData(vtkStatisticsAlgorithm::INPUT_DATA, datasetTable1);
  datasetTable1->Delete();

  // Select Columns of Interest
  for (int i = 0; i < nMetrics; ++i)
  {
    ds1->AddColumn(columns[i].c_str());
  }

  // Test Learn, Derive, Test, and Assess options
  ds1->SetLearnOption(true);
  ds1->SetDeriveOption(true);
  ds1->SetAssessOption(true);
  ds1->SetTestOption(true);
  ds1->SignedDeviationsOff();
  ds1->Update();

  // Get output data and meta tables
  vtkTable* outputData1 = ds1->GetOutput(vtkStatisticsAlgorithm::OUTPUT_DATA);
  auto* outputMetaDS1 = vtkStatisticalModel::SafeDownCast(
    ds1->GetOutputDataObject(vtkStatisticsAlgorithm::OUTPUT_MODEL));
  vtkTable* outputPrimary1 = outputMetaDS1->GetTable(vtkStatisticalModel::Learned, 0);
  vtkTable* outputDerived1 = outputMetaDS1->GetTable(vtkStatisticalModel::Derived, 0);
  vtkTable* outputTest1 = ds1->GetOutput(vtkStatisticsAlgorithm::OUTPUT_TEST);

  std::cout << "\n## Calculated the following primary statistics for first data set:\n";
  for (vtkIdType r = 0; r < outputPrimary1->GetNumberOfRows(); ++r)
  {
    std::cout << "   ";
    for (int i = 0; i < outputPrimary1->GetNumberOfColumns(); ++i)
    {
      double val = outputPrimary1->GetValue(r, i).ToDouble();
      std::cout << outputPrimary1->GetColumnName(i) << "=" << val << "  ";
    }

    // Verify some of the calculated primary statistics
    if (fabs(outputPrimary1->GetValueByName(r, "Mean").ToDouble() - means1[r]) > 1.e-6)
    {
      vtkGenericWarningMacro("Incorrect mean");
      testStatus = 1;
    }
    std::cout << "\n";
  }

  std::cout << "\n## Calculated the following derived statistics for first data set:\n";
  for (vtkIdType r = 0; r < outputDerived1->GetNumberOfRows(); ++r)
  {
    double error = false;
    std::cout << "   ";
    for (int i = 0; i < outputDerived1->GetNumberOfColumns(); ++i)
    {
      double val = outputDerived1->GetValue(r, i).ToDouble();
      std::cout << outputDerived1->GetColumnName(i) << "=" << val << "  ";
      // In this case, there is a zero variance, so skew and kurt should be NaN
      if (r == 2 && (i == 2 || i == 3) && val == val)
      {
        error = true;
      }
    }
    if (error)
    {
      vtkLog(ERROR, "When variance is zero, skew and kurt should be NaN");
    }

    // Verify some of the calculated derived statistics
    if (fabs(outputDerived1->GetValueByName(r, "Standard Deviation").ToDouble() - stdevs1[r]) >
      1.e-5)
    {
      vtkGenericWarningMacro("Incorrect standard deviation");
      testStatus = 1;
    }
    std::cout << "\n";
  }

  // Check some results of the Test option
  std::cout << "\n## Calculated the following Jarque-Bera statistics:\n";
  for (vtkIdType r = 0; r < outputTest1->GetNumberOfRows(); ++r)
  {
    std::cout << "   ";
    for (int i = 0; i < outputTest1->GetNumberOfColumns(); ++i)
    {
      std::cout << outputTest1->GetColumnName(i) << "=" << outputTest1->GetValue(r, i).ToString()
                << "  ";
    }

    std::cout << "\n";
  }

  // Search for outliers to check results of Assess option
  double maxdev = 1.5;
  std::cout << "\n## Searching for outliers from mean with relative deviation > " << maxdev
            << " for metric 1:\n";

  vtkDoubleArray* vals0 =
    vtkArrayDownCast<vtkDoubleArray>(outputData1->GetColumnByName("Metric 0"));
  vtkDoubleArray* vals1 =
    vtkArrayDownCast<vtkDoubleArray>(outputData1->GetColumnByName("Metric 1"));
  vtkDoubleArray* devs0 =
    vtkArrayDownCast<vtkDoubleArray>(outputData1->GetColumnByName("d(Metric 0)"));
  vtkDoubleArray* devs1 =
    vtkArrayDownCast<vtkDoubleArray>(outputData1->GetColumnByName("d(Metric 1)"));

  if (!devs0 || !devs1 || !vals0 || !vals1)
  {
    vtkGenericWarningMacro("Empty output column(s).\n");
    testStatus = 1;

    return testStatus;
  }

  double dev;
  int m0outliers = 0;
  int m1outliers = 0;
  for (vtkIdType r = 0; r < outputData1->GetNumberOfRows(); ++r)
  {
    dev = devs0->GetValue(r);
    if (dev > maxdev)
    {
      ++m0outliers;
      std::cout << "   "
                << " row " << r << ", " << devs0->GetName() << " = " << dev << " > " << maxdev
                << " (value: " << vals0->GetValue(r) << ")\n";
    }
  }
  for (vtkIdType r = 0; r < outputData1->GetNumberOfRows(); ++r)
  {
    dev = devs1->GetValue(r);
    if (dev > maxdev)
    {
      ++m1outliers;
      std::cout << "   "
                << " row " << r << ", " << devs1->GetName() << " = " << dev << " > " << maxdev
                << " (value: " << vals1->GetValue(r) << ")\n";
    }
  }

  std::cout << "  Found " << m0outliers << " outliers for Metric 0"
            << " and " << m1outliers << " outliers for Metric 1.\n";

  if (m0outliers != 4 || m1outliers != 6)
  {
    vtkGenericWarningMacro("Expected 4 outliers for Metric 0 and 6 outliers for Metric 1.");
    testStatus = 1;
  }

  // Now, used modified output 1 as input 1 to test 0-deviation
  std::cout << "\n## Searching for values not equal to 50 for metric 1:\n";

  vtkTable* modifiedPrimary = vtkTable::New();
  modifiedPrimary->ShallowCopy(outputPrimary1);
  modifiedPrimary->SetValueByName(1, "Mean", 50.);

  vtkTable* modifiedDerived = vtkTable::New();
  modifiedDerived->ShallowCopy(outputDerived1);
  modifiedDerived->SetValueByName(1, "Standard Deviation", 0.);

  auto* modifiedModel = vtkStatisticalModel::New();
  modifiedModel->SetNumberOfTables(vtkStatisticalModel::Learned, 1);
  modifiedModel->SetNumberOfTables(vtkStatisticalModel::Derived, 1);
  modifiedModel->SetTable(vtkStatisticalModel::Learned, 0, modifiedPrimary,
    outputMetaDS1->GetTableName(vtkStatisticalModel::Learned, 0));
  modifiedModel->SetTable(vtkStatisticalModel::Derived, 0, modifiedDerived,
    outputMetaDS1->GetTableName(vtkStatisticalModel::Derived, 0));

  // Run with Assess option only (do not recalculate nor rederive a model)
  ds1->SetInputData(vtkStatisticsAlgorithm::INPUT_MODEL, modifiedModel);
  ds1->SetLearnOption(false);
  ds1->SetDeriveOption(false);
  ds1->SetTestOption(true);
  ds1->SetAssessOption(true);
  ds1->Update();

  vals1 = vtkArrayDownCast<vtkDoubleArray>(outputData1->GetColumnByName("Metric 1"));
  devs1 = vtkArrayDownCast<vtkDoubleArray>(outputData1->GetColumnByName("d(Metric 1)"));

  if (!devs1 || !vals1)
  {
    vtkGenericWarningMacro("Empty output column(s).\n");
    testStatus = 1;

    return testStatus;
  }

  m1outliers = 0;
  for (vtkIdType r = 0; r < outputData1->GetNumberOfRows(); ++r)
  {
    dev = devs1->GetValue(r);
    if (dev)
    {
      ++m1outliers;
    }
  }

  std::cout << "  Found " << m1outliers << " outliers for Metric 1.\n";

  if (m1outliers != 28)
  {
    vtkGenericWarningMacro("Expected 28 outliers for Metric 1, found " << m1outliers << ".");
    testStatus = 1;
  }

  // Clean up (which implies resetting input model to first algorithm parameters table values which
  // were modified to their initial values)
  modifiedPrimary->SetValueByName(1, "Mean", means1[1]);
  modifiedPrimary->Delete();
  modifiedDerived->SetValueByName(1, "Standard Deviation", stdevs1[1]);
  modifiedDerived->Delete();
  modifiedModel->Delete();

  // Test with a slight variation of initial data set (to test model aggregation)
  int nVals2 = 32;

  vtkDoubleArray* dataset4Arr = vtkDoubleArray::New();
  dataset4Arr->SetNumberOfComponents(1);
  dataset4Arr->SetName("Metric 0");

  vtkDoubleArray* dataset5Arr = vtkDoubleArray::New();
  dataset5Arr->SetNumberOfComponents(1);
  dataset5Arr->SetName("Metric 1");

  vtkDoubleArray* dataset6Arr = vtkDoubleArray::New();
  dataset6Arr->SetNumberOfComponents(1);
  dataset6Arr->SetName("Metric 2");

  for (int i = 0; i < nVals2; ++i)
  {
    int ti = i << 1;
    dataset4Arr->InsertNextValue(mingledData[ti] + 1.);
    dataset5Arr->InsertNextValue(mingledData[ti + 1]);
    dataset6Arr->InsertNextValue(1.);
  }

  vtkTable* datasetTable2 = vtkTable::New();
  datasetTable2->AddColumn(dataset4Arr);
  dataset4Arr->Delete();
  datasetTable2->AddColumn(dataset5Arr);
  dataset5Arr->Delete();
  datasetTable2->AddColumn(dataset6Arr);
  dataset6Arr->Delete();

  // Set descriptive statistics algorithm and its input data port
  vtkDescriptiveStatistics* ds2 = vtkDescriptiveStatistics::New();
  ds2->SetInputData(vtkStatisticsAlgorithm::INPUT_DATA, datasetTable2);

  // Select Columns of Interest (all of them)
  for (int i = 0; i < nMetrics; ++i)
  {
    ds2->AddColumn(columns[i].c_str());
  }

  // Update with Learn option only
  ds2->SetLearnOption(true);
  ds2->SetDeriveOption(false);
  ds2->SetTestOption(false);
  ds2->SetAssessOption(false);
  ds2->Update();

  // Get output meta tables
  auto* outputMetaDS2 = vtkStatisticalModel::SafeDownCast(
    ds2->GetOutputDataObject(vtkStatisticsAlgorithm::OUTPUT_MODEL));
  vtkTable* outputPrimary2 = outputMetaDS2->GetTable(vtkStatisticalModel::Learned, 0);

  std::cout << "\n## Calculated the following primary statistics for second data set:\n";
  for (vtkIdType r = 0; r < outputPrimary2->GetNumberOfRows(); ++r)
  {
    std::cout << "   ";
    for (int i = 0; i < outputPrimary2->GetNumberOfColumns(); ++i)
    {
      std::cout << outputPrimary2->GetColumnName(i) << "="
                << outputPrimary2->GetValue(r, i).ToString() << "  ";
    }
    std::cout << "\n";
  }

  // Test model aggregation by adding new data to engine which already has a model
  ds1->SetInputData(vtkStatisticsAlgorithm::INPUT_DATA, datasetTable2);
  auto* model = vtkStatisticalModel::New();
  model->ShallowCopy(outputMetaDS1);
  ds1->SetInputData(vtkStatisticsAlgorithm::INPUT_MODEL, model);

  // Clean up
  model->Delete();
  datasetTable2->Delete();
  ds2->Delete();

  // Update with Learn and Derive options only
  ds1->SetLearnOption(true);
  ds1->SetDeriveOption(true);
  ds1->SetTestOption(false);
  ds1->SetAssessOption(false);
  ds1->Update();

  // Updated reference values
  // Means deviations for metrics 0, 1, and 2, respectively
  double means0[] = { 49.71875, 49.5, 0. };

  // Standard deviations for metrics 0, 1, and 2, respectively
  double stdevs0[] = { sqrt(6.1418651), sqrt(7.548397 * 62. / 63.), sqrt(64. / 63.) };

  // Get output data and meta tables
  outputMetaDS1 = vtkStatisticalModel::SafeDownCast(
    ds1->GetOutputDataObject(vtkStatisticsAlgorithm::OUTPUT_MODEL));
  outputPrimary1 = outputMetaDS1->GetTable(vtkStatisticalModel::Learned, 0);
  outputDerived1 = outputMetaDS1->GetTable(vtkStatisticalModel::Derived, 0);

  std::cout
    << "\n## Calculated the following primary statistics for updated (first + second) data set:\n";
  for (vtkIdType r = 0; r < outputPrimary1->GetNumberOfRows(); ++r)
  {
    std::cout << "   ";
    for (int i = 0; i < outputPrimary1->GetNumberOfColumns(); ++i)
    {
      std::cout << outputPrimary1->GetColumnName(i) << "="
                << outputPrimary1->GetValue(r, i).ToString() << "  ";
    }

    // Verify some of the calculated primary statistics
    if (fabs(outputPrimary1->GetValueByName(r, "Mean").ToDouble() - means0[r]) > 1.e-6)
    {
      vtkGenericWarningMacro("Incorrect mean");
      testStatus = 1;
    }
    std::cout << "\n";
  }

  std::cout
    << "\n## Calculated the following derived statistics for updated (first + second) data set:\n";
  for (vtkIdType r = 0; r < outputDerived1->GetNumberOfRows(); ++r)
  {
    std::cout << "   ";
    for (int i = 0; i < outputDerived1->GetNumberOfColumns(); ++i)
    {
      std::cout << outputDerived1->GetColumnName(i) << "="
                << outputDerived1->GetValue(r, i).ToString() << "  ";
    }

    // Verify some of the calculated derived statistics
    if (fabs(outputDerived1->GetValueByName(r, "Standard Deviation").ToDouble() - stdevs0[r]) >
      1.e-5)
    {
      vtkGenericWarningMacro("Incorrect standard deviation");
      testStatus = 1;
    }
    std::cout << "\n";
  }

  // Clean up
  ds1->Delete();

  // ************** Very simple example, for baseline comparison vs. R *********
  double simpleData[] = {
    0,
    1,
    999, // ghost
    -1,
    -1,
    4,
    999, // ghost
    5,
    6,
    7,
    8,
    999, // ghost
    9,
  };
  unsigned char simpleGhosts[] = {
    // Only ghosts in for binary form xxxx xxx1 should be skipped
    64,
    128,
    5, // to be skipped
    2,
    0,
    0,
    9, // to be skipped
    4,
    0,
    32,
    16,
    1, // to be skipped
    8,
  };
  int nSimpleVals = 13;

  vtkNew<vtkUnsignedCharArray> ghosts;
  ghosts->SetName(vtkDataSetAttributes::GhostArrayName());

  vtkDoubleArray* datasetArr = vtkDoubleArray::New();
  datasetArr->SetNumberOfComponents(1);
  datasetArr->SetName("Digits");

  for (int i = 0; i < nSimpleVals; ++i)
  {
    datasetArr->InsertNextValue(simpleData[i]);
    ghosts->InsertNextValue(simpleGhosts[i]);
  }

  vtkTable* simpleTable = vtkTable::New();
  simpleTable->AddColumn(datasetArr);
  simpleTable->AddColumn(ghosts);
  datasetArr->Delete();

  double mean = 3.8;
  double variance = 14.4;
  double standardDeviation = 3.7947332;
  double skewness = -0.0969911;
  double kurtosis = -1.7033638;

  // Set descriptive statistics algorithm and its input data port
  vtkDescriptiveStatistics* ds3 = vtkDescriptiveStatistics::New();
  ds3->SetInputData(vtkStatisticsAlgorithm::INPUT_DATA, simpleTable);
  ds3->SetGhostsToSkip(1);
  simpleTable->Delete();

  // Select column of interest
  ds3->AddColumn("Digits");

  // Add non existing column
  ds3->AddColumn("Bogus");

  // Warning for non existing column will mess up output
  std::cout << "\n";

  // Test Learn and Derive options only
  ds3->SetLearnOption(true);
  ds3->SetDeriveOption(true);
  ds3->SetTestOption(false);
  ds3->SetAssessOption(false);
  ds3->SampleEstimateOn();
  ds3->Update();

  vtkLog(INFO, "### Testing Sample Statistics ###");

  // Get output data and meta tables
  auto* outputMetaDS3 = vtkStatisticalModel::SafeDownCast(
    ds3->GetOutputDataObject(vtkStatisticsAlgorithm::OUTPUT_MODEL));
  vtkTable* outputPrimary3 = outputMetaDS3->GetTable(vtkStatisticalModel::Learned, 0);
  vtkTable* outputDerived3 = outputMetaDS3->GetTable(vtkStatisticalModel::Derived, 0);

  std::cout << "\n## Calculated the following primary statistics for {0,...9} sequence:\n";
  std::cout << "   ";
  for (int i = 0; i < outputPrimary3->GetNumberOfColumns(); ++i)
  {
    std::cout << outputPrimary3->GetColumnName(i) << "="
              << outputPrimary3->GetValue(0, i).ToString() << "  ";
  }

  // Verify some of the calculated primary statistics
  if (fabs(outputPrimary3->GetValueByName(0, "Mean").ToDouble() - mean) > 1.e-6)
  {
    vtkGenericWarningMacro("Incorrect mean");
    testStatus = 1;
  }
  std::cout << "\n";

  std::cout << "\n## Calculated the following derived statistics for {0,...9} sequence:\n";
  std::cout << "   ";
  for (int i = 0; i < outputDerived3->GetNumberOfColumns(); ++i)
  {
    std::cout << outputDerived3->GetColumnName(i) << "="
              << outputDerived3->GetValue(0, i).ToString() << "  ";
  }

  // Verify some of the calculated derived statistics
  if (fabs(outputDerived3->GetValueByName(0, "Variance").ToDouble() - variance) > 1.e-6)
  {
    vtkGenericWarningMacro("Incorrect variance");
    testStatus = 1;
  }

  if (fabs(outputDerived3->GetValueByName(0, "Standard Deviation").ToDouble() - standardDeviation) >
    1.e-6)
  {
    vtkGenericWarningMacro("Incorrect standard deviation");
    testStatus = 1;
  }

  if (fabs(outputDerived3->GetValueByName(0, "Skewness").ToDouble() - skewness) > 1.e-6)
  {
    vtkGenericWarningMacro("Incorrect skewness");
    testStatus = 1;
  }

  if (fabs(outputDerived3->GetValueByName(0, "Kurtosis").ToDouble() - kurtosis) > 1.e-6)
  {
    vtkGenericWarningMacro("Incorrect kurtosis");
    testStatus = 1;
  }
  std::cout << "\n";

  vtkLog(INFO, "### Testing Population Statistics ###");

  // Testing population estimate
  ds3->SampleEstimateOff();
  ds3->SetGhostsToSkip(1);
  ds3->Update();
  outputMetaDS3 = vtkStatisticalModel::SafeDownCast(
    ds3->GetOutputDataObject(vtkStatisticsAlgorithm::OUTPUT_MODEL));
  outputDerived3 = outputMetaDS3->GetTable(vtkStatisticalModel::Derived, 0);

  variance = 12.96;
  standardDeviation = 3.6;
  skewness = -0.0817901;
  kurtosis = -1.5089735;

  // Verify some of the calculated derived statistics
  if (fabs(outputDerived3->GetValueByName(0, "Variance").ToDouble() - variance) > 1.e-6)
  {
    vtkGenericWarningMacro("Incorrect variance");
    testStatus = 1;
  }

  if (fabs(outputDerived3->GetValueByName(0, "Standard Deviation").ToDouble() - standardDeviation) >
    1.e-6)
  {
    vtkGenericWarningMacro("Incorrect standard deviation");
    testStatus = 1;
  }

  if (fabs(outputDerived3->GetValueByName(0, "Skewness").ToDouble() - skewness) > 1.e-6)

    if (fabs(outputDerived3->GetValueByName(0, "Skewness").ToDouble() - skewness) > 1.e-6)
    {
      vtkGenericWarningMacro("Incorrect skewness");
      testStatus = 1;
    }

  if (fabs(outputDerived3->GetValueByName(0, "Kurtosis").ToDouble() - kurtosis) > 1.e-6)
  {
    vtkGenericWarningMacro("Incorrect kurtosis");
    testStatus = 1;
  }
  std::cout << "\n";

  // Clean up
  ds3->Delete();

  // ************** Pseudo-random sample to exercise Jarque-Bera test *********
  int nVals = 10000;

  vtkDoubleArray* datasetNormal = vtkDoubleArray::New();
  datasetNormal->SetNumberOfComponents(1);
  datasetNormal->SetName("Standard Normal");

  vtkDoubleArray* datasetUniform = vtkDoubleArray::New();
  datasetUniform->SetNumberOfComponents(1);
  datasetUniform->SetName("Standard Uniform");

  vtkDoubleArray* datasetLogNormal = vtkDoubleArray::New();
  datasetLogNormal->SetNumberOfComponents(1);
  datasetLogNormal->SetName("Standard Log-Normal");

  vtkDoubleArray* datasetExponential = vtkDoubleArray::New();
  datasetExponential->SetNumberOfComponents(1);
  datasetExponential->SetName("Standard Exponential");

  vtkDoubleArray* datasetLaplace = vtkDoubleArray::New();
  datasetLaplace->SetNumberOfComponents(1);
  datasetLaplace->SetName("Standard Laplace");

  // Seed random number generator
  vtkMath::RandomSeed(static_cast<int>(vtkTimerLog::GetUniversalTime()));

  for (int i = 0; i < nVals; ++i)
  {
    datasetNormal->InsertNextValue(vtkMath::Gaussian());
    datasetUniform->InsertNextValue(vtkMath::Random());
    datasetLogNormal->InsertNextValue(exp(vtkMath::Gaussian()));
    datasetExponential->InsertNextValue(-log(vtkMath::Random()));
    double u = vtkMath::Random() - .5;
    datasetLaplace->InsertNextValue((u < 0. ? 1. : -1.) * log(1. - 2. * fabs(u)));
  }

  vtkTable* gaussianTable = vtkTable::New();
  gaussianTable->AddColumn(datasetNormal);
  datasetNormal->Delete();
  gaussianTable->AddColumn(datasetUniform);
  datasetUniform->Delete();
  gaussianTable->AddColumn(datasetLogNormal);
  datasetLogNormal->Delete();
  gaussianTable->AddColumn(datasetExponential);
  datasetExponential->Delete();
  gaussianTable->AddColumn(datasetLaplace);
  datasetLaplace->Delete();

  // Set descriptive statistics algorithm and its input data port
  vtkDescriptiveStatistics* ds4 = vtkDescriptiveStatistics::New();
  ds4->SetInputData(vtkStatisticsAlgorithm::INPUT_DATA, gaussianTable);
  gaussianTable->Delete();

  // Select Column of Interest
  ds4->AddColumn("Standard Normal");
  ds4->AddColumn("Standard Uniform");
  ds4->AddColumn("Standard Log-Normal");
  ds4->AddColumn("Standard Exponential");
  ds4->AddColumn("Standard Laplace");

  // Test Learn, Derive, and Test options only
  ds4->SetLearnOption(true);
  ds4->SetDeriveOption(true);
  ds4->SetTestOption(true);
  ds4->SetAssessOption(false);
  ds4->Update();

  // Get output data and meta tables
  auto* outputMetaDS4 = vtkStatisticalModel::SafeDownCast(
    ds4->GetOutputDataObject(vtkStatisticsAlgorithm::OUTPUT_MODEL));
  vtkTable* outputPrimary4 = outputMetaDS4->GetTable(vtkStatisticalModel::Learned, 0);
  vtkTable* outputDerived4 = outputMetaDS4->GetTable(vtkStatisticalModel::Derived, 0);
  vtkTable* outputTest4 = ds4->GetOutput(vtkStatisticsAlgorithm::OUTPUT_TEST);

  std::cout << "\n## Calculated the following primary statistics for pseudo-random variables (n="
            << nVals << "):\n";
  for (vtkIdType r = 0; r < outputPrimary4->GetNumberOfRows(); ++r)
  {
    std::cout << "   ";
    for (int i = 0; i < outputPrimary4->GetNumberOfColumns(); ++i)
    {
      std::cout << outputPrimary4->GetColumnName(i) << "="
                << outputPrimary4->GetValue(r, i).ToString() << "  ";
    }

    std::cout << "\n";
  }

  std::cout << "\n## Calculated the following derived statistics for pseudo-random variables (n="
            << nVals << "):\n";
  for (vtkIdType r = 0; r < outputDerived4->GetNumberOfRows(); ++r)
  {
    std::cout << "   ";
    for (int i = 0; i < outputDerived4->GetNumberOfColumns(); ++i)
    {
      std::cout << outputDerived4->GetColumnName(i) << "="
                << outputDerived4->GetValue(r, i).ToString() << "  ";
    }

    std::cout << "\n";
  }

  // Check some results of the Test option
  std::cout
    << "\n## Calculated the following Jarque-Bera statistics for pseudo-random variables (n="
    << nVals;

#ifdef USE_GNU_R
  int nNonGaussian = 3;
  int nRejected = 0;
  double alpha = .01;

  std::cout << ", null hypothesis: normality, significance level=" << alpha;
#endif // USE_GNU_R

  std::cout << "):\n";

  // Loop over Test table
  for (vtkIdType r = 0; r < outputTest4->GetNumberOfRows(); ++r)
  {
    std::cout << "   ";
    for (int c = 0; c < outputTest4->GetNumberOfColumns(); ++c)
    {
      std::cout << outputTest4->GetColumnName(c) << "=" << outputTest4->GetValue(r, c).ToString()
                << "  ";
    }

#ifdef USE_GNU_R
    // Check if null hypothesis is rejected at specified significance level
    double p = outputTest4->GetValueByName(r, "P").ToDouble();
    // Must verify that p value is valid (it is set to -1 if R has failed)
    if (p > -1 && p < alpha)
    {
      std::cout << "N.H. rejected";

      ++nRejected;
    }
#endif // USE_GNU_R

    std::cout << "\n";
  }

#ifdef USE_GNU_R
  if (nRejected < nNonGaussian)
  {
    vtkGenericWarningMacro("Rejected only " << nRejected << " null hypotheses of normality whereas "
                                            << nNonGaussian << " variables are not Gaussian");
    testStatus = 1;
  }
#endif // USE_GNU_R

  // Clean up
  ds4->Delete();

  return testStatus;
}
