|
4 | 4 | from numba.typed import List |
5 | 5 | from scipy.spatial.distance import canberra |
6 | 6 | from sklearn.preprocessing import MinMaxScaler |
| 7 | +from numba.core import types |
| 8 | +import numpy.typing as npt |
| 9 | +from numba.typed import Dict, List |
7 | 10 |
|
8 | 11 | from .base import get_lrs_scores |
9 | 12 |
|
@@ -264,23 +267,54 @@ def get_similar_genes_fast( |
264 | 267 | return similar_genes |
265 | 268 |
|
266 | 269 |
|
267 | | -@njit |
268 | | -def gen_rand_pairs(genes1: np.ndarray, genes2: np.ndarray, n_pairs: int, seed: int): |
269 | | - """Generates random pairs of genes.""" |
| 270 | +def gen_rand_pairs( |
| 271 | + genes1: npt.NDArray[np.str_], |
| 272 | + genes2: npt.NDArray[np.str_], |
| 273 | + n_pairs: int, |
| 274 | + seed: int, |
| 275 | +) -> npt.NDArray[np.str_]: |
| 276 | + """Generate unique random gene pairs for building background LR scores. |
| 277 | +
|
| 278 | + Each pair is formed by drawing one gene from genes1 and one from genes2, |
| 279 | + formatted as 'gene1_gene2'. Self-pairs (the same gene on both sides) and |
| 280 | + duplicate pairs are rejected, so every returned pair is unique and its two |
| 281 | + genes are distinct. |
| 282 | +
|
| 283 | + Parameters |
| 284 | + ---------- |
| 285 | + genes1: npt.NDArray[np.str_] |
| 286 | + Candidate genes for the first (ligand) position of each pair. |
| 287 | + genes2: npt.NDArray[np.str_] |
| 288 | + Candidate genes for the second (receptor) position of each pair. |
| 289 | + n_pairs: int |
| 290 | + Number of unique pairs to generate. |
| 291 | + seed: int |
| 292 | + Seed for the random generator, for reproducible pair selection. |
| 293 | + """ |
| 294 | + n_possible = len(genes1) * len(genes2) - np.intersect1d(genes1, genes2).size |
| 295 | + if n_pairs > n_possible: |
| 296 | + raise ValueError( |
| 297 | + f"Requested {n_pairs} unique pairs but only {n_possible} are " |
| 298 | + f"possible from {len(genes1)}×{len(genes2)} genes." |
| 299 | + ) |
| 300 | + return np.array(list(_gen_rand_pairs(genes1, genes2, n_pairs, seed))) |
| 301 | + |
270 | 302 |
|
| 303 | +@njit |
| 304 | +def _gen_rand_pairs(genes1, genes2, n_pairs, seed): |
271 | 305 | np.random.seed(seed) # noqa: NPY002 (numba requires legacy API) |
272 | 306 | rand_pairs = List() |
| 307 | + seen = Dict.empty(types.unicode_type, types.boolean) # O(1) membership |
273 | 308 | for _j in range(0, n_pairs): |
274 | 309 | l_rand = np.random.choice(genes1, 1)[0] # noqa: NPY002 |
275 | 310 | r_rand = np.random.choice(genes2, 1)[0] # noqa: NPY002 |
276 | 311 | rand_pair = "_".join([l_rand, r_rand]) |
277 | | - while rand_pair in rand_pairs or l_rand == r_rand: |
| 312 | + while rand_pair in seen or l_rand == r_rand: # was: in rand_pairs |
278 | 313 | l_rand = np.random.choice(genes1, 1)[0] # noqa: NPY002 |
279 | 314 | r_rand = np.random.choice(genes2, 1)[0] # noqa: NPY002 |
280 | 315 | rand_pair = "_".join([l_rand, r_rand]) |
281 | | - |
282 | 316 | rand_pairs.append(rand_pair) |
283 | | - |
| 317 | + seen[rand_pair] = True |
284 | 318 | return rand_pairs |
285 | 319 |
|
286 | 320 |
|
|
0 commit comments