Skills › Scientific Agent Skills › scanpy › inspect_data.py
inspect_data.py
86 lines · skills/scanpy/scripts/inspect_data.py at 5f2bc03
1#!/usr/bin/env python3
2"""
3Inspect an AnnData / single-cell file and print a structured summary.
5Reports shape, obs/var columns (with dtypes and category counts), layers,
6obsm/varm/uns keys, X dtype and value range, and whether the data looks like
7raw counts or normalized values. Use this before analysis to understand an
8unfamiliar dataset and decide which pipeline steps still need to run.
10Examples:
11 python inspect_data.py data.h5ad
12 python inspect_data.py 10x_dir/
13"""
15import argparse
17import numpy as np
19from _common import configure_scanpy, info, load_anndata
22def _is_integer_matrix(X, n=10000):
23 from scipy import sparse
24 if X is None:
25 return False
26 arr = X.data[:n] if sparse.issparse(X) else np.asarray(X[:50]).ravel()[:n]
27 return bool(arr.size and np.isfinite(arr).all() and (arr >= 0).all()
28 and np.equal(arr, np.floor(arr)).all())
31def main():
32 p = argparse.ArgumentParser(description=__doc__,
33 formatter_class=argparse.RawDescriptionHelpFormatter)
34 p.add_argument("input", help="Input file (.h5ad, .h5, .csv, .loom, or 10x mtx dir)")
35 p.add_argument("--max-cols", type=int, default=40, help="Max obs/var columns to list")
36 args = p.parse_args()
38 configure_scanpy()
39 adata = load_anndata(args.input)
41 print("=" * 70)
42 print(f"AnnData: {adata.n_obs} cells x {adata.n_vars} genes")
43 print("=" * 70)
45 X = adata.X
46 if X is None:
47 print("\nX is absent; inspect named layers and their provenance")
48 else:
49 looks_int = _is_integer_matrix(X)
50 xmax = float(X.max()) if min(X.shape) else float("nan")
51 print(f"\nX dtype={X.dtype} max={xmax:.2f}; sampled values are "
52 f"{'nonnegative integers' if looks_int else 'not count-like'}. "
53 "This does not establish raw-count provenance.")
55 print(f"\nobs columns ({len(adata.obs.columns)}):")
56 for c in adata.obs.columns[:args.max_cols]:
57 col = adata.obs[c]
58 from pandas.api.types import is_numeric_dtype
59 if not is_numeric_dtype(col.dtype):
60 nuniq = col.nunique()
61 extra = f" {nuniq} categories" + (f": {list(col.unique()[:8])}" if nuniq <= 8 else "")
62 else:
63 extra = f" range=[{col.min():.2f}, {col.max():.2f}]"
64 print(f" - {c} ({col.dtype}){extra}")
66 print(f"\nvar columns ({len(adata.var.columns)}): {list(adata.var.columns[:args.max_cols])}")
67 print(f"\nlayers: {list(adata.layers.keys())}")
68 print(f"obsm: {list(adata.obsm.keys())}")
69 print(f"varm: {list(adata.varm.keys())}")
70 print(f"uns: {list(adata.uns.keys())}")
71 print(f"raw: {'present' if adata.raw is not None else 'none'}")
73 done = []
74 if any(k in adata.obsm for k in ("X_pca",)):
75 done.append("PCA")
76 if "neighbors" in adata.uns:
77 done.append("neighbors")
78 if "X_umap" in adata.obsm:
79 done.append("UMAP")
80 if any(k in adata.obs.columns for k in ("leiden", "louvain")):
81 done.append("clustering")
82 print("\nPipeline steps already present: " + (", ".join(done) if done else "none"))
85if __name__ == "__main__":
86 main()