Add Logistic PCA (LPCA) analysis - #500
Conversation
Adds an analysis.lpca config block (include, k, m, cv, transpose) with a matching LpcaAnalysis schema, an lpca_analysis Snakemake rule restricted to algorithms with multiple parameter combinations, and an LPCA analysis module that builds the binary edge-by-run matrix via summarize_networks and runs the logisticPCA container through run_container_and_log. m is fixed by default; cross-validation and matrix transposition are opt-in.
Adds docker-wrappers/lpca with a pinned rocker/r-base Dockerfile that installs logisticPCA and its ggplot2 dependencies, the run_lpca.R and run_cv.R scripts under /app, and a README documenting the config options, script contracts, and how to build and publish reedcompbio/lpca:v1.
agitter
left a comment
There was a problem hiding this comment.
Note that this will close #271 when merged.
I pushed the image to DockerHub: https://hub.docker.com/r/reedcompbio/lpca
We should add this to our Docker image building test: https://github.com/Reed-CompBio/spras/blob/main/.github/workflows/build-containers.yml
This comment should be modified to note the alternative LPCA
Line 159 in 07707bf
I didn't comment on every detail yet, but in general there are some small things we can to do make this more like the existing PCA.
agitter
left a comment
There was a problem hiding this comment.
Additional comments because I accidentally submitted the first batch before finishing.
…o LPCA, Snakefile cleanup
agitter
left a comment
There was a problem hiding this comment.
I am testing locally, and my workflow is stuck at the Cytoscape step. I wonder if that is related to the longer Docker timeout and it would have quickly failed otherwise? I eventually killed Snakemake.
After disabling Cytoscape, I ran into errors at the ml_analysis step. I didn't get a specific error message though, and I don't see what changes in this pull request would have affected that.
As a reminder for myself, I should rebuild and the Docker image when I merge this.
agitter
left a comment
There was a problem hiding this comment.
Now that I can run everything locally, I have more observations about the output. Anything we can't finish today we can log as a new issue so we can merge this as-is.
I am running into errors when aggregating by algorithm. The error message indicates this is because the input binary matrix is too small:
RuleException:
ContainerError in file "spras\Snakefile", line 456:
(Command formatted as list: `['Rscript', '/app/run_lpca.R', '/app/YZQWZK5/rwr_72021389-lpca-binary-matrix.csv', '/app/VBUONQG/rwr_72021389-lpca-scores.csv', '2', '6.0']`)
An unexpected non-zero exit status (1) inside the docker image docker.io/reedcompbio/lpca:v1 occurred:
Error in fun(A, k, nu, nv, opts, mattype = "matrix") :
nrow(A) and ncol(A) should be at least 3
Calls: logisticPCA -> <Anonymous> -> <Anonymous> -> svds.matrix -> fun
Execution halted
| uses a truncated (rARPACK-based) decomposition instead of a full one. This is | ||
| hardcoded rather than exposed as a parameter: | ||
|
|
||
| - On small datasets it has no practical effect on the result. |
There was a problem hiding this comment.
Noting that the package documentations suggests that it can slow down decomposition for small datasets. I think it is still fair to say that is harmless, so I don't recommend a change. I'm noting it in case we have future problems.
Since logistic PCA requires iteratively computing the eigendecomposition, it can be slow for large data. When
kis small relative to the number of columns, however, the eigendecomposition can be sped up by only solving for the firstkeigenvectors. This can be done for all three formulations using thepartial_decompargument. We have implemented this using theRSpectrapackage. Be careful when using this argument, because it can substantially slow down computation is the number of columns is small (e.g. ~20) or ifkis of comparable size to the number of columns. An example of the speed-up is below.
| @@ -4,7 +4,6 @@ Docker image: https://hub.docker.com/r/reedcompbio/lpca | |||
|
|
|||
| This wrapper runs [logisticPCA](https://github.com/andland/logisticPCA) | |||
|
|
|||
| write.csv(scores, output_file, row.names = TRUE) | ||
|
|
||
| # Save deviance explained | ||
| deviance_file = sub("\\.csv$", "_deviance.txt", output_file) |
There was a problem hiding this comment.
I'm now able to inspect the outputs of LPCA. Does it only write a scalar deviance explained unlike PCA, which provides variance per dimension? Does this account for the k requested or is it the full dimensionality?
LPCA deviance file:
0.996362653717224
PCA variance file:
PC1: 86.36363636
PC2: 13.63636364
| # Save coordinates | ||
| coord_df = pd.DataFrame(X, columns=['PC1', 'PC2'], index=scores.index) | ||
| coord_df.to_csv(output_coord) | ||
| print(f'LPCA: Plot saved to {output_png}') |
There was a problem hiding this comment.
What information does the coordinates file provide that is not already in the scores file? It looks redundant, but I may be missing something.
|
|
||
| cat("LPCA done! Scores saved to", output_file, "\n") | ||
| cat("Score dimensions:", nrow(scores), "x", ncol(scores), "\n") | ||
| cat("Proportion of deviance explained:", model$prop_deviance_expl, "\n") No newline at end of file |
There was a problem hiding this comment.
This can be Inf, such as data0 pathlinker_72021389-lpca-scores_deviance.txt in the example config. PCA gives nan PCs on that input.
We may want to create an issue noting this to fix both of them later.
| dataframe: pd.DataFrame, | ||
| output_scores: str, | ||
| output_matrix: str, | ||
| k: int = 2, |
There was a problem hiding this comment.
We should probably do some value checking for these arguments like PCA does
if components < 2:
raise ValueError(f"components={components} must be greater than or equal to 2 in the config file.")
|
|
||
| if labels: | ||
| for i, label in enumerate(scores.index): | ||
| ax.annotate(label, (X[i, 0], X[i, 1]), fontsize=6, alpha=0.7) |


Summary
Adds Logistic PCA (LPCA) as a new SPRAS analysis method. LPCA is the binary-data counterpart to the existing PCA analysis. It is designed for binary matrices (0/1) like the edge-by-run matrix built from pathway reconstruction outputs, whereas standard PCA assumes continuous data.
Changes
New config block
analysis.lpcawith five options:include: enable/disable LPCA (defaultfalse)k: number of principal components (default2)m: logisticPCA tuning parameter, used whencvisfalse(default6)cv: iftrue, determine m by cross-validation; iffalse, use fixed m (defaultfalse)transpose: iffalse, run LPCA on edges x runs matrix (one embedding per edge);if
true, run on runs x edges matrix (one embedding per pathway run,mirrors the classic PCA analysis) (default
false)New Snakemake rule
lpca_analysis{algorithm}-lpca-scores.csv,{algorithm}-lpca.png,{algorithm}-lpca-coordinates.txtNew module
spras/analysis/lpca.pysummarize_networksrun_container_and_logNew Docker wrapper
docker-wrappers/lpca/rocker/r-base:4.4.2Dockerfile installinglogisticPCAandggplot2run_lpca.R: runs LPCA and saves scores + deviance explainedrun_cv.R: cross-validation to find optimal mUnit tests
test/analysis/test_lpca.pyDocker timeout
containers.pyto handle larger binary matricesTesting locally
Build the image and run the pipeline:
Enable
analysis.lpca.include: truein your config and run as usual.Tested end-to-end with OmicsIntegrator2 (216 combos) and MincostFlow (14 combos).
Before / at merge
The image must be published to
reedcompbio/lpca:v1on Docker Hub by a maintainer:cc @agitter @ntalluri