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.
| # requirements = versionGE(split(Target.CondorVersion)[1], "24.8.0") && (isenforcingdiskusage =!= true) | ||
| enable_profiling: false | ||
|
|
||
| # Override the default container image for specific algorithms. |
There was a problem hiding this comment.
We need to keep these deleted comments
| # evaluation per algorithm will not run unless ml include and ml aggregate_per_algorithm are set to true | ||
| aggregate_per_algorithm: true | ||
| lpca: | ||
| include: false |
There was a problem hiding this comment.
We should document what true/false does. If true, will SPRAS run PCA and LPCA? That behavior would make sense to me. I initially view LPCA as an additional analysis, not a replacement for PCA. Running them in parallel will help us assess the similarity of the two. If we later find that LPCA is "better" than PCA by some criteria, we could change that behavior.
| m: 6 | ||
| # if true, choose m by cross-validation; if false, use the fixed m above | ||
| cv: false |
There was a problem hiding this comment.
It could be possible to collapse these into a single parameter m that takes cv as a valid value. I don't think it is necessary but am sharing the idea in case anyone else prefers that. It would complicate parsing and the config schema as a downside.
Somewhere we should document the analysis in your other repo that led to 6 as a default. It could be in the pull request comments.
| # LPCA (Logistic PCA) wrapper | ||
|
|
||
| This wrapper runs [logisticPCA](https://github.com/andland/logisticPCA) | ||
| (Landgraf & Lee, 2020) as a SPRAS analysis step. It reduces the binary |
There was a problem hiding this comment.
| (Landgraf & Lee, 2020) as a SPRAS analysis step. It reduces the binary | |
| ([Landgraf & Lee, 2020](https://doi.org/10.1016/j.jmva.2020.104668)) as a SPRAS analysis step. It reduces the binary |
There was a problem hiding this comment.
All of the other Docker wrappers use caps in the subdirectory (for some reason). Should we rename the directory to "LPCA"?
| matrix = matrix.T | ||
| print(f'LPCA: Matrix shape: {matrix.shape}') | ||
|
|
||
| # Step 2: write the matrix next to the outputs, namespaced by algorithm |
There was a problem hiding this comment.
Is this the same directory where PCA outputs are written? We should follow that convention.
It looks like it is.
| output_dir = Path(output_scores).parent | ||
| output_dir.mkdir(parents=True, exist_ok=True) | ||
| algo_name = Path(output_scores).name.replace('-lpca-scores.csv', '') | ||
| matrix_path = str(output_dir / f'{algo_name}-lpca_binary_matrix.csv') |
There was a problem hiding this comment.
This is the full binary data matrix? I don't think we need to save that here. We eventually can move that to a rule in the Snakefile so it can be reused across algorithms.
| run_container_and_log('LPCA-CV', LPCA_CONTAINER_SUFFIX, command_cv, volumes, | ||
| LPCA_WORK_DIR, None, container_settings) | ||
|
|
||
| m_used = pd.read_csv(cv_output_path)['best_m'][0] |
There was a problem hiding this comment.
We could check this file exists before reading it to throw a friendly error.
|
|
||
| print(f'LPCA: Done! Scores saved to {output_scores}') | ||
|
|
||
| def plot_lpca(scores_file: str, output_png: str, output_coord: str, labels: bool = True) -> None: |
There was a problem hiding this comment.
Somewhere we should document that the KDE parameter selection always uses PCA even if LPCA is run. That may change in the future.
| X = scores.values | ||
| fig, ax = plt.subplots(figsize=(10, 8)) | ||
|
|
||
| sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=column_names, s=70, ax=ax) |
There was a problem hiding this comment.
PCA use create_palette to set consistent colors.
agitter
left a comment
There was a problem hiding this comment.
Additional comments because I accidentally submitted the first batch before finishing.
| # Initialize a Docker client using environment variables | ||
| try: | ||
| client = docker.from_env() | ||
| client = docker.from_env(timeout=600) |
There was a problem hiding this comment.
Is this still needed? Was it a workaround for the memory issues?
This modifies Docker but not Singularity.
| except Exception: | ||
| return False | ||
|
|
||
| skip_if_no_lpca_image = pytest.mark.skipif( |
There was a problem hiding this comment.
Do we want to silently skip LPCA tests if the image is not available? Shouldn't it always been on DockerHub now that I pushed it?
| OUT_DIR.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| @skip_if_no_lpca_image | ||
| def test_lpca_output_exists(self): |
There was a problem hiding this comment.
A stronger test would be that the expected output is created when we use known inputs. These are okay first steps, but if there is time we would add the stronger test.
There is some degeneracy with these existing pathways though that cause the PCA tests to fail at random. It may be okay here because we don't do KDE testing.
Will we need to account for sign non-identifiability in LPCA?
| ml.hac_horizontal(summary_df, output.hac_image_horizontal, output.hac_clusters_horizontal, **hac_params) | ||
| ml.pca(summary_df, output.pca_image, output.pca_variance, output.pca_coordinates, **pca_params) | ||
|
|
||
|
|
|
|
||
| rule lpca_analysis: | ||
| input: | ||
| pathways = collect_pathways_per_algo |
There was a problem hiding this comment.
Can we also support running LPCA on all pathways across algorithms.
| ml.hac_horizontal(summary_df, output.hac_image_horizontal, output.hac_clusters_horizontal, **hac_params) | ||
| ml.pca(summary_df, output.pca_image, output.pca_variance, output.pca_coordinates, **pca_params) | ||
|
|
||
| rule lpca_analysis: |
There was a problem hiding this comment.
Should this be a new rule or a conditional extension of ml_analysis and ml_analysis_aggregate_algo?
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