uchrom.spatial

Cells in tissue: neighbour graphs from cell positions (one frame per section / field of view), spatial autocorrelation of per-cell values (Moran’s I, Geary’s C) and tests of spatial variation (SPARK-X) — for 3-D genome features of spatial Hi-C spots or traced cells — with covariates such as the cells’ transcriptomes, to ask what varies in space beyond the cell types.

Neighbour graphs of cells (or spatial Hi-C spots) from their positions in the tissue.

Positions are read with ChromData.cell_positions() (key: the position set, e.g. "default"). Cells in different regions — sections of a spatial Hi-C study, fields of view of a tracing experiment (the record’s region_col) — are never neighbours: each region is its own coordinate frame. The graph is cheap to build (a k-d tree per region), so it is computed when needed and not stored on the object.

Methods: "knn" (each cell to its n_neighbors nearest, made symmetric), "radius" (every pair closer than radius; on a barcode grid in spot units radius=1 gives the 4 and radius=1.5 the 8 adjacent spots), "delaunay" (the edges of the Delaunay triangulation, 2-D positions).

class uchrom.spatial.graph.SpatialGraph(cell_ids: ndarray, adjacency: scipy.sparse.csr_matrix, positions: DataFrame, params: dict = <factory>)[source]

Bases: object

A neighbour graph over cells.

Variables:
  • cell_ids (ndarray of str) – The cells, in the order of the rows and columns of adjacency.

  • adjacency (scipy.sparse.csr_matrix (n, n)) – 1 for neighbours (symmetric, zero diagonal).

  • positions (DataFrame) – x, y[, z] of the cells (index cell_id) and region when the position set has regions.

  • params (dict) – How the graph was built.

adjacency: scipy.sparse.csr_matrix
cell_ids: ndarray
property n: int
neighbour_counts() → ndarray[source]
params: dict
positions: DataFrame
property regions: ndarray | None
subset(mask) → SpatialGraph[source]

The graph restricted to the cells of a boolean mask (edges to removed cells are dropped).

weights(transform: str = 'r') → scipy.sparse.csr_matrix[source]

The spatial weights: "b" binary (the adjacency), "r" row-standardised (each row sums to 1; cells without neighbours keep an empty row).

uchrom.spatial.graph.cell_positions_table(cd, key: str = 'default', *, cells: Iterable | None = None, physical: bool = False) → DataFrame[source]

The positions of cells (all cells by default) with finite coordinates: x, y[, z][, region].

uchrom.spatial.graph.spatial_neighbors(cd, *, key: str = 'default', method: str = 'knn', n_neighbors: int = 6, radius: float | None = None, cells: Iterable | None = None, physical: bool = False) → SpatialGraph[source]

The neighbour graph of the cells of cd in tissue space.

Parameters:
  • cd – A ChromData with cell positions (set_cell_positions()).

  • key – The position set.

  • method – "knn", "radius" or "delaunay" (module docstring). On a regular barcode grid prefer "radius": the k nearest of a grid are tied.

  • n_neighbors – Of "knn" and "radius" (in the units of the positions).

  • radius – Of "knn" and "radius" (in the units of the positions).

  • cells – Only these cells (default: every cell with a position).

  • physical – Positions in physical units for positions stored in voxels.

Returns:

Symmetric, no self-loops, no edges between regions.

Return type:

SpatialGraph

Global spatial autocorrelation of per-cell values over a neighbour graph: Moran’s I and Geary’s C, with their moments under normality and under randomisation (Cliff & Ord 1981) and permutation p-values, many features at once.

For a feature x on n cells with weights w (transform="r": rows standardised to sum 1), z = x - mean(x), S0 = sum w_ij, S1 = 1/2 sum (w_ij + w_ji)^2, S2 = sum_i (w_i. + w_.i)^2:

  • Moran’s I = n / S0 * z’Wz / z’z, E[I] = -1 / (n - 1);

  • Geary’s C = (n - 1) sum w_ij (x_i - x_j)^2 / (2 S0 z’z), E[C] = 1.

The variances are those of PySAL’s esda (VI_norm / VI_rand, VC_norm / VC_rand); positive autocorrelation — a spatially variable feature — is I > E[I], C < 1. Cells where a feature is missing (NaN) are left out of that feature: its weights are those of the graph restricted to the cells with a value, transformed again.

References

Moran, P. A. P. (1950) Biometrika 37:17-23. Geary, R. C. (1954) The Incorporated Statistician 5:115-145. Cliff, A. D. & Ord, J. K. (1981) Spatial Processes: Models and Applications. Pion.

uchrom.spatial.autocorr.bh_adjust(p: ndarray) → ndarray[source]

Benjamini-Hochberg adjusted p-values (NaN kept).

uchrom.spatial.autocorr.cell_values(cd, features: None | str | Sequence[str] = None, *, values: None | DataFrame | Series = None) → DataFrame[source]

Per-cell values as a DataFrame indexed by cell id (str), one column per feature.

values is used as given (a Series becomes one column); else features names columns of cd.cells, or one key of cd.cellm (a 2-D array: columns "<key>[j]").

uchrom.spatial.autocorr.spatial_autocorr(cd=None, features=None, *, graph: SpatialGraph, values=None, method: str = 'moran', transform: str = 'r', alternative: str = 'greater', n_perms: int = 0, seed: int = 0, min_cells: int = 10) → DataFrame[source]

Moran’s I or Geary’s C of every feature over graph.

Parameters:
  • cd – The per-cell values (cell_values()); cd may be None with values=.

  • features – The per-cell values (cell_values()); cd may be None with values=.

  • values – The per-cell values (cell_values()); cd may be None with values=.

  • graph – spatial_neighbors() (its cells; values of other cells are ignored).

  • method – "moran" or "geary".

  • transform – "r" row-standardised (default, as esda) or "b" binary weights.

  • alternative – "greater" (positive autocorrelation; default), "less" or "two-sided".

  • n_perms – Permutations for p_perm (0: none); the cells’ values are shuffled over the graph.

  • min_cells – Features with fewer cells with a value get NaN.

Returns:

One row per feature: n (cells with a value), I or C, expected, var_norm, z_norm, p_norm, var_rand, z_rand, p_rand [, p_perm], pval (p_perm with permutations, else p_rand) and padj (Benjamini-Hochberg over the features).

Return type:

DataFrame

SPARK-X, the non-parametric covariance test of spatial variation of Zhu, Sun & Zhou (2021), for many features at once and with covariates (an implementation of the published method, checked against the authors’ R package SPARK::sparkx; uchrom-reproducibility extended_data_fig2).

For a feature y on n cells, the covariates X (an intercept first) and a coordinate kernel S (n x d), with S_r = (I - H) S the coordinates residualised on X (H the projection on X):

T = n * y’ S_r (S_r’ S_r)^-1 S_r’ y / y’y, lambda = y’(I - H) y / y’y,

and under no spatial pattern T ~ lambda * chi2_d (the R package evaluates the same mixture, whose kernel eigenvalues are all 1, with Davies’ method at accuracy 1e-4; here the chi-square tail is exact). Eleven kernels are tested: the coordinates themselves (projection), and, per axis after centring, five Gaussian (gaus1-gaus5: exp(-c^2 / 2 l^2)) and five cosine (cos1-cos5: cos(2 pi c / l)) transforms with l the 20 / 40 / 60 / 80 / 100 % quantiles of |c|. The kernel p-values are combined by the Cauchy combination (ACAT, Liu & Xie 2020), adjusted over the features by Benjamini-Yekutieli as the package does.

Values may be any per-cell number (counts, scores, ratios); a feature’s missing cells (NaN) are left out of its test (the kernels stay those of all cells, residualised on the cells with a value).

References

Zhu, J., Sun, S. & Zhou, X. (2021) SPARK-X: non-parametric modeling enables scalable and robust detection of spatial expression patterns for large spatial transcriptomic studies. Genome Biol. 22:184. Liu, Y. & Xie, J. (2020) Cauchy combination test. J. Am. Stat. Assoc. 115:393-402.

uchrom.spatial.sparkx.acat(p: ndarray, weights: ndarray | None = None) → ndarray[source]

Cauchy combination of the p-values in each row of p (n_features x n_tests), as SPARK::ACAT: a row with a 0 gives 0, else a row with a 1 gives 1; p < 1e-16 use the approximation tan((0.5-p) pi) ~ 1 / (p pi); a statistic above 1e15 gives 1 / (stat pi). Rows with NaN give NaN.

uchrom.spatial.sparkx.by_adjust(p: ndarray) → ndarray[source]

Benjamini-Yekutieli adjusted p-values, as R’s p.adjust(method="BY") (NaN kept, not counted).

uchrom.spatial.sparkx.coordinate_kernels(coords: ndarray, kernels: str | Sequence[str] = 'mixture') → List[Tuple[str, ndarray]][source]

The coordinate kernels of SPARK-X: "mixture" (all eleven), "single" (projection only) or a list of names from KERNELS.

uchrom.spatial.sparkx.sparkx(values, coords: ndarray, *, covariates: ndarray | None = None, intercept: bool = True, kernels: str | Sequence[str] = 'mixture', min_cells: int = 10) → DataFrame[source]

SPARK-X on arrays.

Parameters:
  • values – n_cells x n_features (DataFrame: its columns name the features; NaN = missing).

  • coords – n_cells x d positions.

  • covariates – n_cells x c covariates the test is adjusted for (cell-type composition, depth, …).

  • intercept – Add an intercept to the covariates (the package’s test without covariates is the test with an intercept only; give covariates an intercept column and intercept=False to reproduce the package’s covariate path exactly).

  • kernels – "mixture", "single" or kernel names (coordinate_kernels()).

Returns:

One row per feature: n, stat_<kernel>, pval_<kernel>, pval (combined) and padj (Benjamini-Yekutieli); .attrs["df"] holds the degrees of freedom of each kernel.

Return type:

DataFrame

Which per-cell features vary in space: SPARK-X (default) or Moran’s I on the cells of a ChromData, per region (section / field of view), optionally adjusted for covariates such as cell-type composition or depth.

A 3-D genome feature that follows the cell types will vary in space wherever the cell types do. Testing with the composition (or the cell type) as covariates asks the second question — does the feature vary beyond what the cells’ identities explain? Both answers are worth reporting; the second is the one that needs space.

uchrom.spatial.variable.covariate_matrix(cd, covariates, cell_ids: Sequence[str]) → DataFrame | None[source]

The covariates of cell_ids as a numeric DataFrame: covariates is a DataFrame (index = cell ids), or names of cd.cells columns (and cd.cellm keys: their columns); categorical columns become indicators (the first level dropped).

uchrom.spatial.variable.spatially_variable(cd, features: None | str | Sequence[str] = None, *, values=None, key: str = 'default', method: str = 'sparkx', covariates=None, cells: Iterable | None = None, regions: Sequence[str] | None = None, graph: SpatialGraph | None = None, graph_kwargs: dict | None = None, kernels='mixture', n_perms: int = 0, physical: bool = False, key_added=UNSET, copy: bool = False)[source]

Test every feature for spatial variation, region by region.

Parameters:
  • cd – A ChromData with cell positions.

  • features – The per-cell values (cell_values(): cd.cells columns, a cd.cellm key, or a DataFrame indexed by cell id).

  • values – The per-cell values (cell_values(): cd.cells columns, a cd.cellm key, or a DataFrame indexed by cell id).

  • key – The position set (its region_col splits the cells into regions).

  • method – "sparkx" (sparkx()) or "moran" (spatial_autocorr() on graph / spatial_neighbors(**graph_kwargs); with covariates, Moran’s I of the residuals of a least-squares fit on them, permutation p-values recommended).

  • covariates – Adjust for these (covariate_matrix()).

  • cells – Only these cells / regions.

  • regions – Only these cells / regions.

  • kernels – Of SPARK-X / Moran’s I.

  • n_perms – Of SPARK-X / Moran’s I.

Returns:

region, feature then the test’s columns (pval, padj per region); stored as cd.results[key_added] (default "spatially_variable.<method>"; None: not stored) per the calling convention.

Return type:

DataFrame