Skip to content

Commit

Permalink
review #1
Browse files Browse the repository at this point in the history
  • Loading branch information
SteffenHeu committed Mar 8, 2024
1 parent c239fe2 commit 6a54205
Show file tree
Hide file tree
Showing 17 changed files with 130 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,21 @@

import javafx.beans.property.Property;
import javafx.collections.ObservableList;
import javafx.geometry.Pos;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;

public class FxComponentFactory {

public static <T> HBox createLabelledComboBox(String label, ObservableList<T> values,
public static <T> HBox createLabeledComboBox(String label, ObservableList<T> values,
Property<T> modelProperty) {
final Label lab = new Label(label);
final ComboBox<T> comboBox = new ComboBox<>(values);
comboBox.valueProperty().bindBidirectional(modelProperty);
lab.setLabelFor(comboBox);
return new HBox(5, lab, comboBox);
final HBox hBox = new HBox(5, lab, comboBox);
hBox.setAlignment(Pos.CENTER_LEFT);
return hBox;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,11 @@ public Float get(@Nullable ModularDataModel featureOrRow) {
return featureOrRow.get(type);
}

public Float getOrNaN(@Nullable ModularDataModel featureOrRow) {
if (featureOrRow == null) {
return Float.NaN;
}
return featureOrRow.get(type);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public class EStandardChartTheme extends StandardChartTheme {
private static final boolean DEFAULT_CROSS_HAIR_VISIBLE = true;
private static final Stroke DEFAULT_CROSS_HAIR_STROKE = new BasicStroke(1.0F,
BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f, new float[]{5.0F, 3.0F}, 0.0F);

public static final BasicStroke DEFAULT_STROKE = new BasicStroke(2f);
// master font
protected Font masterFont;
protected Color masterFontColor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,8 @@ public class ColoredXYShapeRenderer extends XYShapeRenderer {

private static final int defaultSize = 7;
private final Shape dataPointsShape;

private final boolean drawOutlinesOnly;
private BasicStroke outlineStroke = new BasicStroke(2.0f);
private final BasicStroke outlineStroke = EStandardChartTheme.DEFAULT_STROKE;

public ColoredXYShapeRenderer(boolean drawOutlinesOnly, Shape shape) {
super();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@ public sealed interface FxControllerBinding permits SelectedAbundanceMeasureCont
SelectedMetadataColumnController, SelectedRowsController, SelectedFeaturesController,
SelectedFilesController, SelectedFeatureListsController {

public static void bindExposedProperties(Object master, Object child) {
if (master instanceof FxControllerBinding && child instanceof FxControllerBinding) {
bindExposedProperties(master, child);
}
}

public static void bindExposedProperties(FxControllerBinding master, FxControllerBinding child) {
if(master == null ||child == null) {
if (master == null || child == null) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ public ObjectProperty<List<FeatureListRow>> selectedRowsProperty() {
return model.selectedRowsProperty();
}

public ObjectProperty<List<FeatureList>> featureListsProperty() {
return model.flistsProperty();
}

private void initListeners() {
model.flistsProperty().addListener(_ -> update());
model.domainPcProperty().addListener(_ -> update());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@

package io.github.mzmine.modules.dataanalysis.pca_new;

import static io.github.mzmine.util.annotations.CompoundAnnotationUtils.getAnnotationTypesByPriority;
import static io.github.mzmine.util.annotations.CompoundAnnotationUtils.rankUniqueAnnotationTypes;

import io.github.mzmine.datamodel.features.FeatureListRow;
import io.github.mzmine.datamodel.features.types.DataType;
import io.github.mzmine.datamodel.features.types.DataTypes;
Expand All @@ -38,6 +35,7 @@
import io.github.mzmine.gui.chartbasics.simplechart.providers.ZCategoryProvider;
import io.github.mzmine.main.MZmineCore;
import io.github.mzmine.taskcontrol.TaskStatus;
import io.github.mzmine.util.annotations.CompoundAnnotationUtils;
import io.github.mzmine.util.color.SimpleColorPalette;
import java.awt.Color;
import java.util.Map;
Expand All @@ -47,33 +45,33 @@
import org.jfree.chart.renderer.LookupPaintScale;
import org.jfree.chart.renderer.PaintScale;

public class LoadingsProvider extends SimpleXYProvider implements PlotXYZDataProvider,
public class PCALoadingsProvider extends SimpleXYProvider implements PlotXYZDataProvider,
ZCategoryProvider, XYItemObjectProvider<FeatureListRow> {

private final PCARowsResult result;
private final int loadingsY;
private final int loadingsX;
private final int loadingsIndexY;
private final int loadingsIndexX;

private int[] zCategories;
private int numberOfCategories;
private LookupPaintScale paintScale;
private String[] legendNames;

/**
* @param loadingsX index of the principal component used for domain axis, subtract 1 from the
* number since the pc matrix starts at 0.
* @param loadingsY index of the principal component used for range axis, subtract 1 from the
* number since the pc matrix starts at 0.
* @param loadingsIndexX index of the principal component used for domain axis, subtract 1 from
* the number since the pc matrix starts at 0.
* @param loadingsIndexY index of the principal component used for range axis, subtract 1 from the
* number since the pc matrix starts at 0.
*/
public LoadingsProvider(PCARowsResult result, String seriesKey, Color awt, int loadingsX,
int loadingsY) {
public PCALoadingsProvider(PCARowsResult result, String seriesKey, Color awt, int loadingsIndexX,
int loadingsIndexY) {
super(seriesKey, awt);
this.result = result;
this.loadingsX = loadingsX;
this.loadingsY = loadingsY;
this.loadingsIndexX = loadingsIndexX;
this.loadingsIndexY = loadingsIndexY;
}

public LoadingsProvider(PCARowsResult result, String seriesKey, Color awt) {
public PCALoadingsProvider(PCARowsResult result, String seriesKey, Color awt) {
this(result, seriesKey, awt, 0, 1);
}

Expand All @@ -83,11 +81,12 @@ public void computeValues(Property<TaskStatus> status) {

final RealMatrix loadingsMatrix = pcaResult.getLoadingsMatrix();

final Map<FeatureListRow, DataType<?>> bestRowAnnotationType = getAnnotationTypesByPriority(
final Map<FeatureListRow, DataType<?>> bestRowAnnotationType = CompoundAnnotationUtils.mapBestAnnotationTypesByPriority(
result.rows(), true);

// only create order of actually existing annotaiton types
Map<DataType<?>, Integer> typesInOrder = rankUniqueAnnotationTypes(bestRowAnnotationType.values());
Map<DataType<?>, Integer> typesInOrder = CompoundAnnotationUtils.rankUniqueAnnotationTypes(
bestRowAnnotationType.values());
numberOfCategories = typesInOrder.size();

double[] domainData = new double[loadingsMatrix.getColumnDimension()];
Expand All @@ -97,8 +96,8 @@ public void computeValues(Property<TaskStatus> status) {

final MissingValueType missing = DataTypes.get(MissingValueType.class);
for (int i = 0; i < loadingsMatrix.getColumnDimension(); i++) {
domainData[i] = loadingsMatrix.getEntry(loadingsX, i);
rangeData[i] = loadingsMatrix.getEntry(loadingsY, i);
domainData[i] = loadingsMatrix.getEntry(loadingsIndexX, i);
rangeData[i] = loadingsMatrix.getEntry(loadingsIndexY, i);
// find annotation type or missing type
FeatureListRow row = result.rows().get(i);
final DataType<?> bestTypeWithValue = bestRowAnnotationType.get(row);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,36 @@
import org.apache.commons.math3.linear.SingularValueDecomposition;
import org.jetbrains.annotations.NotNull;

/**
* A pca based on singular value decomposition. The data matrix X is decomposed into X = U*S*V.
* Columns of U contrain the principal components, S are the singular values, which can be projected
* into the PC space using U and a submatrix of S, which creates the scores plot. Loadings are the
* transpose of V.
*
* https://stats.stackexchange.com/questions/134282/relationship-between-svd-and-pca-how-to-use-svd-to-perform-pca
*/
public record PCAResult(RealMatrix data, RealMatrix dataMeanCentered,
SingularValueDecomposition svd) {

/**
* @param numComponents
* @return Returns a sub-matrix the first n principal components of the decomposition.
*/
public RealMatrix firstNComponents(int numComponents) {
return svd.getU().getSubMatrix(0, svd.getU().getRowDimension() - 1, 0,
numComponents - 1);
return svd.getU().getSubMatrix(0, svd.getU().getRowDimension() - 1, 0, numComponents - 1);
}

public RealMatrix principalComponentsMatrix() {
// the u matrix of an svd contains the principal components.
return svd.getU();
}

/**
* Projects the data matrix onto the principal components. The result is n dimensional and is
* called "scores" matrix and is used for the scores plot.
*
* @param numComponents the number of components n.
*/
public RealMatrix projectDataToScores(int numComponents) {
final RealMatrix firstNComponents = firstNComponents(numComponents);
final RealMatrix subMatrixS = svd.getS()
Expand All @@ -51,15 +69,25 @@ public RealMatrix projectDataToScores(int numComponents) {
return projectedData;
}

/**
* Projects the data matrix onto the selected principal components. The result is 2 dimensional
* and is called "scores" matrix and is used for the scores plot.
*
* @see #projectDataToScores(int)
*/
public RealMatrix projectDataToScores(int domainColIndex, int rangeColIndex) {
final RealMatrix pcMatrix = pcMatrix(domainColIndex, rangeColIndex);
final RealMatrix projected = pcMatrix.multiply(svd.getS().getSubMatrix(0, 1, 0, 1));
return projected;
}

/**
* Retrieves two specific PCs from the PC matrix.
*/
@NotNull
private RealMatrix pcMatrix(int domainColIndex, int rangeColIndex) {
final RealMatrix pcs = svd.getU();
// the vectors are the respective components.
final RealVector domainVector = pcs.getColumnVector(domainColIndex);
final RealVector rangeVector = pcs.getColumnVector(rangeColIndex);
RealMatrix pcMatrix = new Array2DRowRealMatrix(pcs.getRowDimension(), 2);
Expand All @@ -69,8 +97,16 @@ private RealMatrix pcMatrix(int domainColIndex, int rangeColIndex) {
return pcMatrix;
}

/**
* Retrieves the loadings (importance of each observed feature) from the pca. After svd the
* loadings are the transpose of the v matrix.
*/
public RealMatrix getLoadingsMatrix() {
final RealMatrix transpose = svd.getV().transpose();
return transpose;
}

public int componentCount() {
return principalComponentsMatrix().getRowDimension();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
import org.jfree.chart.renderer.LookupPaintScale;
import org.jfree.chart.renderer.PaintScale;

public class ScoresProvider extends SimpleXYProvider implements PlotXYZDataProvider,
public class PCAScoresProvider extends SimpleXYProvider implements PlotXYZDataProvider,
ZCategoryProvider, XYItemObjectProvider<RawDataFile> {

private final PCARowsResult result;
Expand All @@ -66,7 +66,7 @@ public class ScoresProvider extends SimpleXYProvider implements PlotXYZDataProvi
* @param pcY index of the principal component used for range axis, subtract 1 from the number
* since the pc matrix starts at 0.
*/
public ScoresProvider(PCARowsResult result, String seriesKey, Color awt, int pcX, int pcY,
public PCAScoresProvider(PCARowsResult result, String seriesKey, Color awt, int pcX, int pcY,
MetadataColumn<?> groupingColumn) {
super(seriesKey, awt);
this.result = result;
Expand All @@ -75,7 +75,7 @@ public ScoresProvider(PCARowsResult result, String seriesKey, Color awt, int pcX
this.groupingColumn = groupingColumn;
}

public ScoresProvider(PCARowsResult result, String seriesKey, Color awt) {
public PCAScoresProvider(PCARowsResult result, String seriesKey, Color awt) {
this(result, seriesKey, awt, 0, 1, null);
}

Expand All @@ -86,9 +86,8 @@ public void computeValues(Property<TaskStatus> status) {
final RealMatrix scores = pcaResult.projectDataToScores(pcX, pcY);

final List<RawDataFile> files = result.files();
final Map<?, List<RawDataFile>> groupedFiles =
groupingColumn != null ? MZmineCore.getProjectMetadata().groupFilesByColumn(groupingColumn)
: Map.of();
final Map<?, List<RawDataFile>> groupedFiles = MZmineCore.getProjectMetadata()
.groupFilesByColumn(groupingColumn);

numberOfCategories = Math.max(groupedFiles.size(), 1);
groupNames = new String[Math.max(groupedFiles.size(), 1)];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,13 @@ protected void process() {
pcaRowsResult = PCAUtils.performPCAOnRows(flists.get(0).getRows(), abundance);
progressProvider.getAndIncrement();

final ScoresProvider scores = new ScoresProvider(pcaRowsResult, "Scores", Color.RED,
final PCAScoresProvider scores = new PCAScoresProvider(pcaRowsResult, "Scores", Color.RED,
domainPcIndex, rangePcIndex,
MZmineCore.getProjectMetadata().getColumnByName(metadataColumn));
final ColoredXYZDataset scoresDS = new ColoredXYZDataset(scores, RunOption.THIS_THREAD);
progressProvider.getAndIncrement();

final LoadingsProvider loadings = new LoadingsProvider(pcaRowsResult, "Loadings", Color.RED,
final PCALoadingsProvider loadings = new PCALoadingsProvider(pcaRowsResult, "Loadings", Color.RED,
domainPcIndex, rangePcIndex);
final ColoredXYZDataset loadingsDS = new ColoredXYZDataset(loadings, RunOption.THIS_THREAD);
progressProvider.getAndIncrement();
Expand All @@ -123,12 +123,12 @@ protected void updateGuiModel() {
if (rangePcIndex < components.size()) {
model.setRangePc(rangePcIndex + 1);
} else {
model.setRangePc(components.getLast() + 1);
model.setRangePc(components.getLast());
}
if (domainPcIndex < components.size()) {
model.setDomainPc(domainPcIndex + 1);
} else {
model.setDomainPc(components.getLast() + 1);
model.setDomainPc(components.getLast());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ public class PCAUtils {

private static final Logger logger = Logger.getLogger(PCAUtils.class.getName());

/**
* Calculates the PCA of a matrix by singular value decomposition (svd).
* https://stats.stackexchange.com/questions/134282/relationship-between-svd-and-pca-how-to-use-svd-to-perform-pca
*
* @param data the data
* @return A pca result.
*/
public static PCAResult calculatePCA(RealMatrix data) {

logger.finest(() -> "Performing scaling mean centering");
Expand All @@ -49,6 +56,13 @@ public static PCAResult calculatePCA(RealMatrix data) {
return new PCAResult(data, centeredMatrix, svd);
}

/**
* Performs a PCA on a list of feature list rows. Imputes missing values as 0s.
*
* @param rows The rows.
* @param measure The abundance to use.
* @return A pca result that can be mapped to the used rows.
*/
public static PCARowsResult performPCAOnRows(List<FeatureListRow> rows,
AbundanceMeasure measure) {
final List<RawDataFile> files = rows.stream().flatMap(row -> row.getRawDataFiles().stream())
Expand Down
Loading

0 comments on commit 6a54205

Please sign in to comment.