diff --git a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/META-INF/MANIFEST.MF b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/META-INF/MANIFEST.MF
index ef419afa9d..3016614695 100644
--- a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/META-INF/MANIFEST.MF
+++ b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/META-INF/MANIFEST.MF
@@ -47,7 +47,8 @@ Require-Bundle: org.eclipse.ui,
org.eclipse.help
Bundle-RequiredExecutionEnvironment: JavaSE-21
Import-Package: jakarta.annotation;version="2.1.1",
- jakarta.inject;version="2.0.1"
+ jakarta.inject;version="2.0.1",
+ org.apache.commons.math3.stat.descriptive;version="[3.6.0,4.0.0)"
Bundle-ActivationPolicy: lazy
Service-Component: OSGI-INF/org.eclipse.chemclipse.xxd.process.supplier.pca.ui.quickstart.FileTileDefinition.xml,
OSGI-INF/org.eclipse.chemclipse.xxd.process.supplier.pca.ui.quickstart.FilesLongFormatTileDefinition.xml,
diff --git a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/fragment.e4xmi b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/fragment.e4xmi
index 247f1de47b..6907274fab 100644
--- a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/fragment.e4xmi
+++ b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/fragment.e4xmi
@@ -7,6 +7,7 @@
+
@@ -19,6 +20,7 @@
+
diff --git a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/internal/provider/FeatureStatComparator.java b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/internal/provider/FeatureStatComparator.java
new file mode 100644
index 0000000000..621754c77a
--- /dev/null
+++ b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/internal/provider/FeatureStatComparator.java
@@ -0,0 +1,135 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Lablicate GmbH.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Lorenz Gerber - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.chemclipse.xxd.process.supplier.pca.ui.internal.provider;
+
+import java.util.List;
+
+import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
+import org.eclipse.chemclipse.model.statistics.ISampleData;
+import org.eclipse.chemclipse.model.statistics.IVariable;
+import org.eclipse.chemclipse.support.ui.swt.AbstractRecordTableComparator;
+import org.eclipse.chemclipse.support.ui.swt.IRecordTableComparator;
+import org.eclipse.chemclipse.xxd.process.supplier.pca.model.Feature;
+import org.eclipse.jface.viewers.Viewer;
+
+public class FeatureStatComparator extends AbstractRecordTableComparator implements IRecordTableComparator {
+
+ @Override
+ public int compare(Viewer viewer, Object e1, Object e2) {
+
+ int sortOrder = 0;
+ if(e1 instanceof Feature feature1 && e2 instanceof Feature feature2) {
+ IVariable variable1 = feature1.getVariable();
+ IVariable variable2 = feature2.getVariable();
+ DescriptiveStatistics stats = new DescriptiveStatistics();
+ List> sampleData1 = feature1.getSampleData();
+ List> sampleData2 = feature2.getSampleData();
+ //
+ int columnIndex = getPropertyIndex();
+ switch(columnIndex) {
+ case 0:
+ try {
+ double value1 = Double.parseDouble(variable1.getValue());
+ double value2 = Double.parseDouble(variable2.getValue());
+ sortOrder = Double.compare(value2, value1);
+ } catch(Exception e) {
+ sortOrder = variable2.getValue().compareTo(variable1.getValue());
+ }
+ break;
+ case 1:
+ sortOrder = variable2.getDescription().compareTo(variable1.getDescription());
+ break;
+ case 2:
+ for(int i = 0; i < sampleData1.size(); i++) {
+ stats.addValue(sampleData1.get(i).getData());
+ }
+ Double mean1 = stats.getMean();
+ stats = new DescriptiveStatistics();
+ for(int i = 0; i < sampleData2.size(); i++) {
+ stats.addValue(sampleData2.get(i).getData());
+ }
+ Double mean2 = stats.getMean();
+ sortOrder = Double.compare(mean2, mean1);
+ break;
+ case 3:
+ for(int i = 0; i < sampleData1.size(); i++) {
+ stats.addValue(sampleData1.get(i).getData());
+ }
+ Double min1 = stats.getMin();
+ stats = new DescriptiveStatistics();
+ for(int i = 0; i < sampleData2.size(); i++) {
+ stats.addValue(sampleData2.get(i).getData());
+ }
+ Double min2 = stats.getMin();
+ sortOrder = Double.compare(min2, min1);
+ break;
+ case 4:
+ for(int i = 0; i < sampleData1.size(); i++) {
+ stats.addValue(sampleData1.get(i).getData());
+ }
+ Double max1 = stats.getMax();
+ stats = new DescriptiveStatistics();
+ for(int i = 0; i < sampleData2.size(); i++) {
+ stats.addValue(sampleData2.get(i).getData());
+ }
+ Double max2 = stats.getMax();
+ sortOrder = Double.compare(max2, max1);
+ break;
+ case 5:
+ for(int i = 0; i < sampleData1.size(); i++) {
+ stats.addValue(sampleData1.get(i).getData());
+ }
+ Double rsd1 = stats.getStandardDeviation();
+ rsd1 = 100.0 / stats.getSum() * rsd1;
+ stats = new DescriptiveStatistics();
+ for(int i = 0; i < sampleData2.size(); i++) {
+ stats.addValue(sampleData2.get(i).getData());
+ }
+ Double rsd2 = stats.getStandardDeviation();
+ rsd2 = 100.0 / stats.getSum() * rsd2;
+ sortOrder = Double.compare(rsd2, rsd1);
+ break;
+ case 6:
+ for(int i = 0; i < sampleData1.size(); i++) {
+ stats.addValue(sampleData1.get(i).getData());
+ }
+ Double skew1 = stats.getSkewness();
+ stats = new DescriptiveStatistics();
+ for(int i = 0; i < sampleData2.size(); i++) {
+ stats.addValue(sampleData2.get(i).getData());
+ }
+ Double skew2 = stats.getSkewness();
+ sortOrder = Double.compare(skew2, skew1);
+ break;
+ case 7:
+ for(int i = 0; i < sampleData1.size(); i++) {
+ stats.addValue(sampleData1.get(i).getData());
+ }
+ Double kurt1 = stats.getKurtosis();
+ stats = new DescriptiveStatistics();
+ for(int i = 0; i < sampleData2.size(); i++) {
+ stats.addValue(sampleData2.get(i).getData());
+ }
+ Double kurt2 = stats.getKurtosis();
+ sortOrder = Double.compare(kurt2, kurt1);
+ break;
+ default:
+ break;
+ }
+ }
+ if(getDirection() == ASCENDING) {
+ sortOrder = -sortOrder;
+ }
+ return sortOrder;
+ }
+}
diff --git a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/internal/provider/FeatureStatLabelProvider.java b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/internal/provider/FeatureStatLabelProvider.java
new file mode 100644
index 0000000000..5936deabbb
--- /dev/null
+++ b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/internal/provider/FeatureStatLabelProvider.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Lablicate GmbH.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Lorenz Gerber - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.chemclipse.xxd.process.supplier.pca.ui.internal.provider;
+
+import java.text.DecimalFormat;
+import java.util.List;
+
+import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
+import org.eclipse.chemclipse.model.statistics.ISampleData;
+import org.eclipse.chemclipse.model.statistics.IVariable;
+import org.eclipse.chemclipse.support.ui.provider.AbstractChemClipseLabelProvider;
+import org.eclipse.chemclipse.xxd.process.supplier.pca.model.Feature;
+import org.eclipse.swt.graphics.Image;
+
+public class FeatureStatLabelProvider extends AbstractChemClipseLabelProvider {
+
+ public static final String VARIABLE = "Variable";
+ public static final String NAME = "Name";
+ public static final String MEAN = "Mean";
+ public static final String MIN = "Min";
+ public static final String MAX = "Max";
+ public static final String RELATIVESTDDEV = "RSD";
+ public static final String SKEWNESS = "Skewness";
+ public static final String KURTOSIS = "Excess Kurtosis";
+ //
+ private DecimalFormat decimalFormat = getDecimalFormat();
+ //
+ public static String[] TITLES = {//
+ VARIABLE, //
+ NAME, //
+ MEAN, //
+ MIN, //
+ MAX, //
+ RELATIVESTDDEV, //
+ SKEWNESS, //
+ KURTOSIS, //
+ };
+ public static int[] BOUNDS = {//
+ 50, //
+ 280, //
+ 140, //
+ 140, //
+ 140, //
+ 140, //
+ 140, //
+ 140 //
+ };
+
+ public FeatureStatLabelProvider(String pattern) {
+
+ super(pattern);
+ }
+
+ @Override
+ public String getColumnText(Object element, int columnIndex) {
+
+ String text = "";
+ if(element instanceof Feature feature) {
+ IVariable variable = feature.getVariable();
+ List> sampleData = feature.getSampleData();
+ DescriptiveStatistics stats = new DescriptiveStatistics();
+ for(int i = 0; i < sampleData.size(); i++) {
+ stats.addValue(sampleData.get(i).getData());
+ }
+ double value = 0.0;
+ //
+ switch(columnIndex) {
+ case 0:
+ text = variable.getValue();
+ break;
+ case 1:
+ text = variable.getDescription();
+ break;
+ case 2:
+ value = 0.0;
+ value = stats.getMean();
+ text = Double.isNaN(value) ? "NaN" : decimalFormat.format(value);
+ break;
+ case 3:
+ value = 0.0;
+ value = stats.getMin();
+ text = Double.isNaN(value) ? "NaN" : decimalFormat.format(value);
+ break;
+ case 4:
+ value = 0.0;
+ value = stats.getMax();
+ text = Double.isNaN(value) ? "NaN" : decimalFormat.format(value);
+ break;
+ case 5:
+ value = 0.0;
+ value = 100.0 / stats.getSum() * stats.getStandardDeviation();
+ text = Double.isNaN(value) ? "NaN" : decimalFormat.format(value);
+ break;
+ case 6:
+ value = 0.0;
+ value = stats.getSkewness();
+ text = Double.isNaN(value) ? "NaN" : decimalFormat.format(value);
+ break;
+ case 7:
+ value = 0.0;
+ value = stats.getKurtosis() - 3.0;
+ text = Double.isNaN(value) ? "NaN" : decimalFormat.format(value);
+ default:
+ break;
+ }
+ }
+ return text;
+ }
+
+ @Override
+ public Image getColumnImage(Object element, int columnIndex) {
+
+ return null;
+ }
+}
diff --git a/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/parts/FeatureStatTablePart.java b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/parts/FeatureStatTablePart.java
new file mode 100644
index 0000000000..609fadea61
--- /dev/null
+++ b/chemclipse/plugins/org.eclipse.chemclipse.xxd.process.supplier.pca.ui/src/org/eclipse/chemclipse/xxd/process/supplier/pca/ui/parts/FeatureStatTablePart.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Lablicate GmbH.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Lorenz Gerber - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.chemclipse.xxd.process.supplier.pca.ui.parts;
+
+import java.util.List;
+
+import org.eclipse.chemclipse.xxd.process.supplier.pca.model.IEvaluation;
+import org.eclipse.chemclipse.xxd.process.supplier.pca.ui.swt.ExtendedFeatureStatListUI;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+
+import jakarta.inject.Inject;
+
+public class FeatureStatTablePart extends AbstractPartPCA {
+
+ @Inject
+ public FeatureStatTablePart(Composite parent) {
+
+ super(parent);
+ }
+
+ @Override
+ protected ExtendedFeatureStatListUI createControl(Composite parent) {
+
+ return new ExtendedFeatureStatListUI(parent, SWT.NONE);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected boolean updateData(List