From 5793af177810b272733723c8aa2058e0afdffc61 Mon Sep 17 00:00:00 2001 From: anubhav1004 Date: Tue, 24 Mar 2026 05:04:00 +0000 Subject: [PATCH 1/2] Add ShareClaw logistic regression label projection variants --- .../shareclaw_logreg_balanced/config.vsh.yaml | 37 +++++++++++ .../shareclaw_logreg_balanced/script.py | 62 +++++++++++++++++++ .../shareclaw_logreg_scaled/config.vsh.yaml | 37 +++++++++++ src/methods/shareclaw_logreg_scaled/script.py | 54 ++++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 src/methods/shareclaw_logreg_balanced/config.vsh.yaml create mode 100644 src/methods/shareclaw_logreg_balanced/script.py create mode 100644 src/methods/shareclaw_logreg_scaled/config.vsh.yaml create mode 100644 src/methods/shareclaw_logreg_scaled/script.py diff --git a/src/methods/shareclaw_logreg_balanced/config.vsh.yaml b/src/methods/shareclaw_logreg_balanced/config.vsh.yaml new file mode 100644 index 00000000..bd455ad0 --- /dev/null +++ b/src/methods/shareclaw_logreg_balanced/config.vsh.yaml @@ -0,0 +1,37 @@ +__merge__: /src/api/comp_method.yaml +name: "shareclaw_logreg_balanced" +label: ShareClaw Logistic Regression Balanced +summary: "Standardize PCA coordinates and fit a class-balanced logistic regression classifier for macro-F1-oriented label projection." +description: | + ShareClaw Logistic Regression Balanced applies a StandardScaler to the PCA + embedding and fits a logistic regression classifier with balanced class + weights. The goal is to preserve a simple, reproducible linear baseline while + improving performance on rarer classes that matter for macro F1. +references: + bibtex: | + @book{hosmer2013applied, + title = {Applied logistic regression}, + author = {Hosmer Jr, D.W. and Lemeshow, S. and Sturdivant, R.X.}, + year = {2013}, + publisher = {John Wiley \& Sons}, + volume = {398} + } +links: + repository: https://github.com/anubhav1004/shareclaw + documentation: https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html +info: + preferred_normalization: log_cp10k +resources: + - type: python_script + path: script.py +engines: + - type: docker + image: openproblems/base_python:1 + setup: + - type: python + packages: scikit-learn +runners: + - type: executable + - type: nextflow + directives: + label: [midtime, midmem, midcpu] diff --git a/src/methods/shareclaw_logreg_balanced/script.py b/src/methods/shareclaw_logreg_balanced/script.py new file mode 100644 index 00000000..74a817ed --- /dev/null +++ b/src/methods/shareclaw_logreg_balanced/script.py @@ -0,0 +1,62 @@ +import anndata as ad +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + +## VIASH START +par = { + "input_train": "resources_test/task_label_projection/cxg_immune_cell_atlas/train.h5ad", + "input_test": "resources_test/task_label_projection/cxg_immune_cell_atlas/test.h5ad", + "output": "output.h5ad", +} +meta = { + "name": "foo", +} +## VIASH END + + +def require_pca(adata_obj, label): + if "X_pca" not in adata_obj.obsm: + raise KeyError(f"{label} is missing obsm['X_pca']") + + +print("Load input data", flush=True) +input_train = ad.read_h5ad(par["input_train"]) +input_test = ad.read_h5ad(par["input_test"]) + +require_pca(input_train, "input_train") +require_pca(input_test, "input_test") + +print("Fit to train data", flush=True) +classifier = Pipeline( + steps=[ + ("scale", StandardScaler()), + ( + "model", + LogisticRegression( + max_iter=500, + solver="lbfgs", + random_state=7, + class_weight="balanced", + ), + ), + ] +) +classifier.fit(input_train.obsm["X_pca"], input_train.obs["label"].astype(str)) + +print("Predict on test data", flush=True) +label_pred = classifier.predict(input_test.obsm["X_pca"]) + +print("Create output data", flush=True) +output = ad.AnnData( + obs=pd.DataFrame({"label_pred": label_pred}, index=input_test.obs.index), + uns={ + "method_id": meta["name"], + "dataset_id": input_test.uns["dataset_id"], + "normalization_id": input_test.uns["normalization_id"], + }, +) + +print("Write output data", flush=True) +output.write_h5ad(par["output"], compression="gzip") diff --git a/src/methods/shareclaw_logreg_scaled/config.vsh.yaml b/src/methods/shareclaw_logreg_scaled/config.vsh.yaml new file mode 100644 index 00000000..592f4859 --- /dev/null +++ b/src/methods/shareclaw_logreg_scaled/config.vsh.yaml @@ -0,0 +1,37 @@ +__merge__: /src/api/comp_method.yaml +name: "shareclaw_logreg_scaled" +label: ShareClaw Logistic Regression Scaled +summary: "Standardize PCA coordinates before fitting a higher-iteration logistic regression classifier for label projection." +description: | + ShareClaw Logistic Regression Scaled applies a StandardScaler to the PCA + embedding provided in the task inputs before fitting a multinomial logistic + regression classifier. The goal is to preserve the simplicity of a linear + baseline while using a more stable optimization setup for the task. +references: + bibtex: | + @book{hosmer2013applied, + title = {Applied logistic regression}, + author = {Hosmer Jr, D.W. and Lemeshow, S. and Sturdivant, R.X.}, + year = {2013}, + publisher = {John Wiley \& Sons}, + volume = {398} + } +links: + repository: https://github.com/anubhav1004/shareclaw + documentation: https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html +info: + preferred_normalization: log_cp10k +resources: + - type: python_script + path: script.py +engines: + - type: docker + image: openproblems/base_python:1 + setup: + - type: python + packages: scikit-learn +runners: + - type: executable + - type: nextflow + directives: + label: [midtime, midmem, midcpu] diff --git a/src/methods/shareclaw_logreg_scaled/script.py b/src/methods/shareclaw_logreg_scaled/script.py new file mode 100644 index 00000000..d6699531 --- /dev/null +++ b/src/methods/shareclaw_logreg_scaled/script.py @@ -0,0 +1,54 @@ +import anndata as ad +import pandas as pd +from sklearn.linear_model import LogisticRegression +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + +## VIASH START +par = { + "input_train": "resources_test/task_label_projection/cxg_immune_cell_atlas/train.h5ad", + "input_test": "resources_test/task_label_projection/cxg_immune_cell_atlas/test.h5ad", + "output": "output.h5ad", +} +meta = { + "name": "foo", +} +## VIASH END + + +def require_pca(adata_obj, label): + if "X_pca" not in adata_obj.obsm: + raise KeyError(f"{label} is missing obsm['X_pca']") + + +print("Load input data", flush=True) +input_train = ad.read_h5ad(par["input_train"]) +input_test = ad.read_h5ad(par["input_test"]) + +require_pca(input_train, "input_train") +require_pca(input_test, "input_test") + +print("Fit to train data", flush=True) +classifier = Pipeline( + steps=[ + ("scale", StandardScaler()), + ("model", LogisticRegression(max_iter=500, solver="lbfgs", random_state=7)), + ] +) +classifier.fit(input_train.obsm["X_pca"], input_train.obs["label"].astype(str)) + +print("Predict on test data", flush=True) +label_pred = classifier.predict(input_test.obsm["X_pca"]) + +print("Create output data", flush=True) +output = ad.AnnData( + obs=pd.DataFrame({"label_pred": label_pred}, index=input_test.obs.index), + uns={ + "method_id": meta["name"], + "dataset_id": input_test.uns["dataset_id"], + "normalization_id": input_test.uns["normalization_id"], + }, +) + +print("Write output data", flush=True) +output.write_h5ad(par["output"], compression="gzip") From 5f12a1f801892087b6f520adf08a05ecee6e5036 Mon Sep 17 00:00:00 2001 From: anubhav1004 Date: Tue, 24 Mar 2026 15:12:42 +0000 Subject: [PATCH 2/2] Add ShareClaw LDA label projection methods --- .../shareclaw_lda_lsqr_auto/config.vsh.yaml | 40 ++++++++++++++ src/methods/shareclaw_lda_lsqr_auto/script.py | 54 +++++++++++++++++++ src/methods/shareclaw_lda_svd/config.vsh.yaml | 39 ++++++++++++++ src/methods/shareclaw_lda_svd/script.py | 54 +++++++++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 src/methods/shareclaw_lda_lsqr_auto/config.vsh.yaml create mode 100644 src/methods/shareclaw_lda_lsqr_auto/script.py create mode 100644 src/methods/shareclaw_lda_svd/config.vsh.yaml create mode 100644 src/methods/shareclaw_lda_svd/script.py diff --git a/src/methods/shareclaw_lda_lsqr_auto/config.vsh.yaml b/src/methods/shareclaw_lda_lsqr_auto/config.vsh.yaml new file mode 100644 index 00000000..2e6fdf90 --- /dev/null +++ b/src/methods/shareclaw_lda_lsqr_auto/config.vsh.yaml @@ -0,0 +1,40 @@ +__merge__: /src/api/comp_method.yaml +name: "shareclaw_lda_lsqr_auto" +label: ShareClaw LDA LSQR Auto +summary: "Standardize PCA coordinates and fit a shrinkage-aware linear discriminant analysis classifier." +description: | + ShareClaw LDA LSQR Auto applies a StandardScaler to the PCA embedding and + then fits a LinearDiscriminantAnalysis classifier using the LSQR solver with + automatic shrinkage. In our ShareClaw-driven search, this variant delivered + the strongest Zebrafish macro-F1 among the lightweight linear baselines we + evaluated. +references: + bibtex: | + @article{fisher1936use, + title = {The use of multiple measurements in taxonomic problems}, + author = {Fisher, Ronald Aylmer}, + journal = {Annals of Eugenics}, + volume = {7}, + number = {2}, + pages = {179--188}, + year = {1936} + } +links: + repository: https://github.com/anubhav1004/shareclaw + documentation: https://scikit-learn.org/stable/modules/generated/sklearn.discriminant_analysis.LinearDiscriminantAnalysis.html +info: + preferred_normalization: log_cp10k +resources: + - type: python_script + path: script.py +engines: + - type: docker + image: openproblems/base_python:1 + setup: + - type: python + packages: scikit-learn +runners: + - type: executable + - type: nextflow + directives: + label: [midtime, midmem, midcpu] diff --git a/src/methods/shareclaw_lda_lsqr_auto/script.py b/src/methods/shareclaw_lda_lsqr_auto/script.py new file mode 100644 index 00000000..279f8195 --- /dev/null +++ b/src/methods/shareclaw_lda_lsqr_auto/script.py @@ -0,0 +1,54 @@ +import anndata as ad +import pandas as pd +from sklearn.discriminant_analysis import LinearDiscriminantAnalysis +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + +## VIASH START +par = { + "input_train": "resources_test/task_label_projection/cxg_immune_cell_atlas/train.h5ad", + "input_test": "resources_test/task_label_projection/cxg_immune_cell_atlas/test.h5ad", + "output": "output.h5ad", +} +meta = { + "name": "foo", +} +## VIASH END + + +def require_pca(adata_obj, label): + if "X_pca" not in adata_obj.obsm: + raise KeyError(f"{label} is missing obsm['X_pca']") + + +print("Load input data", flush=True) +input_train = ad.read_h5ad(par["input_train"]) +input_test = ad.read_h5ad(par["input_test"]) + +require_pca(input_train, "input_train") +require_pca(input_test, "input_test") + +print("Fit to train data", flush=True) +classifier = Pipeline( + steps=[ + ("scale", StandardScaler()), + ("model", LinearDiscriminantAnalysis(solver="lsqr", shrinkage="auto")), + ] +) +classifier.fit(input_train.obsm["X_pca"], input_train.obs["label"].astype(str)) + +print("Predict on test data", flush=True) +label_pred = classifier.predict(input_test.obsm["X_pca"]) + +print("Create output data", flush=True) +output = ad.AnnData( + obs=pd.DataFrame({"label_pred": label_pred}, index=input_test.obs.index), + uns={ + "method_id": meta["name"], + "dataset_id": input_test.uns["dataset_id"], + "normalization_id": input_test.uns["normalization_id"], + }, +) + +print("Write output data", flush=True) +output.write_h5ad(par["output"], compression="gzip") diff --git a/src/methods/shareclaw_lda_svd/config.vsh.yaml b/src/methods/shareclaw_lda_svd/config.vsh.yaml new file mode 100644 index 00000000..61960246 --- /dev/null +++ b/src/methods/shareclaw_lda_svd/config.vsh.yaml @@ -0,0 +1,39 @@ +__merge__: /src/api/comp_method.yaml +name: "shareclaw_lda_svd" +label: ShareClaw LDA SVD +summary: "Standardize PCA coordinates and fit a linear discriminant analysis classifier with the SVD solver." +description: | + ShareClaw LDA SVD applies a StandardScaler to the PCA embedding and then fits + a LinearDiscriminantAnalysis classifier with the SVD solver. In our ShareClaw + search loop, this was the strongest macro-F1 performer on the official small + task resources while also materially improving the local Zebrafish baseline. +references: + bibtex: | + @article{fisher1936use, + title = {The use of multiple measurements in taxonomic problems}, + author = {Fisher, Ronald Aylmer}, + journal = {Annals of Eugenics}, + volume = {7}, + number = {2}, + pages = {179--188}, + year = {1936} + } +links: + repository: https://github.com/anubhav1004/shareclaw + documentation: https://scikit-learn.org/stable/modules/generated/sklearn.discriminant_analysis.LinearDiscriminantAnalysis.html +info: + preferred_normalization: log_cp10k +resources: + - type: python_script + path: script.py +engines: + - type: docker + image: openproblems/base_python:1 + setup: + - type: python + packages: scikit-learn +runners: + - type: executable + - type: nextflow + directives: + label: [midtime, midmem, midcpu] diff --git a/src/methods/shareclaw_lda_svd/script.py b/src/methods/shareclaw_lda_svd/script.py new file mode 100644 index 00000000..1b3c9cab --- /dev/null +++ b/src/methods/shareclaw_lda_svd/script.py @@ -0,0 +1,54 @@ +import anndata as ad +import pandas as pd +from sklearn.discriminant_analysis import LinearDiscriminantAnalysis +from sklearn.pipeline import Pipeline +from sklearn.preprocessing import StandardScaler + +## VIASH START +par = { + "input_train": "resources_test/task_label_projection/cxg_immune_cell_atlas/train.h5ad", + "input_test": "resources_test/task_label_projection/cxg_immune_cell_atlas/test.h5ad", + "output": "output.h5ad", +} +meta = { + "name": "foo", +} +## VIASH END + + +def require_pca(adata_obj, label): + if "X_pca" not in adata_obj.obsm: + raise KeyError(f"{label} is missing obsm['X_pca']") + + +print("Load input data", flush=True) +input_train = ad.read_h5ad(par["input_train"]) +input_test = ad.read_h5ad(par["input_test"]) + +require_pca(input_train, "input_train") +require_pca(input_test, "input_test") + +print("Fit to train data", flush=True) +classifier = Pipeline( + steps=[ + ("scale", StandardScaler()), + ("model", LinearDiscriminantAnalysis(solver="svd")), + ] +) +classifier.fit(input_train.obsm["X_pca"], input_train.obs["label"].astype(str)) + +print("Predict on test data", flush=True) +label_pred = classifier.predict(input_test.obsm["X_pca"]) + +print("Create output data", flush=True) +output = ad.AnnData( + obs=pd.DataFrame({"label_pred": label_pred}, index=input_test.obs.index), + uns={ + "method_id": meta["name"], + "dataset_id": input_test.uns["dataset_id"], + "normalization_id": input_test.uns["normalization_id"], + }, +) + +print("Write output data", flush=True) +output.write_h5ad(par["output"], compression="gzip")