Nearest-Neighbor Identification¶
Identifies the top-N nearest neighbors per individual in normalized depth space using Euclidean distance (scikit-learn). Neighbors serve as an ancestry-matched reference panel for diploid copy number normalization.
- grid.utils.find_neighbors.filter_regions_by_variance(sigma2ratios, frac_r=1.0, sigma2_max=1000.0, console=None)¶
Select region indices whose sigma2 ratio passes the [sigma2min, sigma2max] filter, exactly mirroring the C++ logic.
- Parameters:
sigma2ratios (ndarray) – per-region variance ratios from the file header (length R)
frac_r (float) – fraction of regions to retain by lower bound (C++ = 1.0)
sigma2_max (float) – upper bound on variance ratio (C++ = 1000)
console – Rich console for logging
- Returns:
np.ndarray of int indices that pass the filter R_use : number of retained regions
- Return type:
valid_indices
- grid.utils.find_neighbors.find_neighbors(config, console)¶
Find nearest neighbors from normalized coverage data for all samples.
- Mirrors the C++ find_neighbors.cpp logic:
Read N, R, per-region means (header row 0) — means unused here but read to advance the file pointer.
Read per-region sigma2 ratios (header row 1) — used to filter regions.
Clip z-scores to [-zmax, zmax] and replace NaN → 0.
Filter regions: keep those with sigma2_min <= ratio <= sigma2_max.
Compute pairwise Euclidean distances on the filtered z-score matrix.
For each individual output the top N_OUTPUT nearest neighbors (excluding self), with normalised distance = raw_dist / (2 * R_use).
- Parameters:
config – configuration dictionary
console – Rich console for logging
- grid.utils.find_neighbors.find_neighbors_sklearn(data_matrix, individuals, n_neighbors=500)¶
Find nearest neighbors using scikit-learn’s NearestNeighbors.
The C++ computes raw squared Euclidean distances then writes dist / (2 * R_use). sklearn returns Euclidean (not squared), so we square the distances before normalising to match — the normalisation itself happens in save_neighbors.
Clipping and NaN-filling must be done BEFORE calling this function (handled in find_neighbors).
- Parameters:
data_matrix (ndarray) – np.ndarray [N x R_use], already clipped and NaN-filled
individuals (list[str]) – list of N sample IDs
n_neighbors (int) – number of neighbors to return per individual (C++ = 500)
- Returns:
[(neighbor_id, squared_euclidean_distance), …]} Length of each list = min(n_neighbors, N-1).
- Return type:
dict {individual_id
- grid.utils.find_neighbors.read_normalized_data(input_file)¶
Parse the gzipped normalized depth file produced by normalize_mosdepth.
- File layout (matches write_normalized_output):
Line 0 : N <tab> Rwant <tab> mu_1 … mu_Rwant Line 1 : N <tab> Rwant <tab> varRatio_1 … varRatio_Rwant Line 2+: ID <tab> scale <tab> z_1 … z_Rwant
- Parameters:
input_file (Path) – Path to the .tsv.gz output of normalize_mosdepth
- Returns:
list of sample IDs (length N) sigma2ratios : np.ndarray of per-region variance ratios (length Rwant) data_matrix : np.ndarray shape [N x Rwant] of z-scores scales : dict {sample_id: scale_value}
- Return type:
individuals
- grid.utils.find_neighbors.save_neighbors(neighbors_dict, scales, output_file, zmax, R_use)¶
Write neighbors to a gzipped text file.
- Output format per line (matches C++):
ID scale nb1_ID nb1_scale nb1_norm_dist nb2_ID …
where norm_dist = squared_euclidean_distance / (2 * R_use), exactly as in C++: fout << dists[n*Nbatch+i] / (2*Ruse)
NOTE: the original Python constructed the output filename here again, causing a double-suffix bug (.zMax2.0.txt.gz.zMax2.0.txt.gz). The caller now passes the fully-resolved Path directly.
- Parameters:
neighbors_dict (dict[str, list[tuple[str, float]]]) – {id: [(neighbor_id, squared_dist), …]}
scales (dict[str, float]) – {id: scale_value}
output_file (Path) – fully-resolved output Path (no filename construction here)
zmax (float) – z-score clip value (unused in output, kept for logging)
R_use (int) – number of regions used, for distance normalisation
- Return type:
None