Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.11", "3.12", "3.13", "3.14"]

steps:
- name: Checkout
Expand Down
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ repos:

# Python syntax upgrades (should run before linting/formatting)
- repo: https://github.com/asottile/pyupgrade
rev: v3.21.2
rev: v3.22.0
hooks:
- id: pyupgrade
args: ["--py311-plus"]
Expand All @@ -36,7 +36,7 @@ repos:

# Ruff for linting and formatting Python files
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.9
rev: v0.17.1
hooks:
- id: ruff-check
args: ["--fix"]
Expand Down Expand Up @@ -66,7 +66,7 @@ repos:

# Keep pre-commit itself up to date (run last)
- repo: https://gitlab.com/vojko.pribudic.foss/pre-commit-update
rev: v0.9.0
rev: v0.10.0
hooks:
- id: pre-commit-update
args: ["--verbose", "--exclude", "cffconvert"]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
![Buscar Logo](https://raw.githubusercontent.com/WayScience/buscar/main/logo/with-text-for-light-bg.svg)

[![DOI](https://img.shields.io/badge/DOI-10.64898%2F2026.04.15.718737-blue)](https://doi.org/10.64898/2026.04.15.718737)
[![Python](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)](https://github.com/axiomcura/buscar/blob/main/pyproject.toml)
[![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue)](https://github.com/axiomcura/buscar/blob/main/pyproject.toml)

## About

Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ description = "A Python framework for prioritizing compounds in high-content ima
authors = [{ name = "Erik Serrano", email = "erik.serrano@cuanschutz.edu" }]
readme = "README.md"
license = "BSD-3-Clause"
requires-python = ">=3.10,<3.15"
requires-python = ">=3.11,<3.15"
dynamic = ["version"]
keywords = ["single-cell", "drug-screening", "image-profiling", "phenotypic-screening"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Science/Research",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
Expand Down Expand Up @@ -62,7 +61,7 @@ addopts = "-ra"
testpaths = ["tests"]

[tool.ruff]
target-version = "py310"
target-version = "py311"
line-length = 88

[tool.ruff.lint]
Expand Down
52 changes: 52 additions & 0 deletions tests/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,58 @@ def test_emd_direct(synthetic_profiles):
assert emd > 0.0


@pytest.mark.parametrize(
("target", "treated", "expected"),
[
pytest.param(
[[0], [1], [3]],
[[3], [0], [1]],
0.0,
id="1d-identical-reordered",
),
pytest.param(
[[0], [0], [2]],
[[0], [2], [2], [2]],
# 5/12 of the mass must move between 0 and 2 at a squared cost of 4
5 / 3,
id="1d-unequal-sample-sizes",
),
pytest.param(
[[0, 0], [0, 1], [0, 2]],
[[3, 4], [3, 5], [3, 6]],
# every sample moves by (3, 4): squared distance 9 + 16 = 25
25.0,
id="2d-translation",
),
pytest.param(
[[0, 0], [2, 2], [0, 0], [2, 2]],
[[0, 2], [2, 0], [0, 2], [2, 0]],
# same marginals, but every pairing costs a squared distance of 4
4.0,
id="2d-same-marginals-different-joint",
),
],
)
def test_compute_earth_movers_distance_matches_analytic_solution(
target, treated, expected
):
"""Check EMD against analytical costs for empirical distributions.

The ground metric is squared Euclidean distance (the ``ot.dist`` default),
so expected values are minimal *squared*-distance transport costs. All
samples are used (no subsampling) so every case has an exact solution.
"""
features = [f"Feature_{i}" for i in range(len(target[0]))]
schema = {feature: pl.Float64 for feature in features}

target_df = pl.DataFrame(target, schema=schema, orient="row")
treated_df = pl.DataFrame(treated, schema=schema, orient="row")

actual = compute_earth_movers_distance(target_df, treated_df)

assert actual == pytest.approx(expected, rel=1e-9, abs=1e-12)


def test_calculate_score_small_group_returns_nan_not_zero():
"""A too-small treated group must not silently report a 0.0 off-score.

Expand Down
Loading
Loading