Skip to content

Commit 2c92c41

Browse files
committed
docs(colouring_by_expression): render gene_symbols/table_name demos from throwaway SpatialData (non-destructive), define region keys, table_layer gene-only
1 parent cf609a1 commit 2c92c41

1 file changed

Lines changed: 54 additions & 35 deletions

File tree

examples/colouring_by_expression.ipynb

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@
3636
"id": "fcc86e1b",
3737
"metadata": {
3838
"execution": {
39-
"iopub.execute_input": "2026-08-19T02:29:17.361016Z",
40-
"iopub.status.busy": "2026-08-19T02:29:17.360908Z",
41-
"iopub.status.idle": "2026-08-19T02:29:26.826290Z",
42-
"shell.execute_reply": "2026-08-19T02:29:26.825597Z"
39+
"iopub.execute_input": "2026-08-19T03:40:35.735050Z",
40+
"iopub.status.busy": "2026-08-19T03:40:35.734947Z",
41+
"iopub.status.idle": "2026-08-19T03:40:41.639318Z",
42+
"shell.execute_reply": "2026-08-19T03:40:41.638786Z"
4343
}
4444
},
4545
"outputs": [
@@ -101,6 +101,7 @@
101101
"import scipy.sparse as sp\n",
102102
"import anndata as ad\n",
103103
"import squidpy as sq\n",
104+
"from spatialdata import SpatialData\n",
104105
"from spatialdata.models import TableModel\n",
105106
"\n",
106107
"import spatialdata_plot # noqa: F401 # registers the .pl accessor\n",
@@ -128,10 +129,10 @@
128129
"id": "bafdb4b8",
129130
"metadata": {
130131
"execution": {
131-
"iopub.execute_input": "2026-08-19T02:29:26.827939Z",
132-
"iopub.status.busy": "2026-08-19T02:29:26.827804Z",
133-
"iopub.status.idle": "2026-08-19T02:29:27.274938Z",
134-
"shell.execute_reply": "2026-08-19T02:29:27.274322Z"
132+
"iopub.execute_input": "2026-08-19T03:40:41.641157Z",
133+
"iopub.status.busy": "2026-08-19T03:40:41.641016Z",
134+
"iopub.status.idle": "2026-08-19T03:40:41.978141Z",
135+
"shell.execute_reply": "2026-08-19T03:40:41.977468Z"
135136
}
136137
},
137138
"outputs": [
@@ -159,7 +160,10 @@
159160
"\n",
160161
"A table can hold several matrices in `.layers` (raw counts, normalised, scaled). `table_layer=` picks\n",
161162
"which one to colour from. Here we add a log-normalised layer and compare it with the raw `X` above —\n",
162-
"the log scale compresses the bright outliers and reveals more structure."
163+
"the log scale compresses the bright outliers and reveals more structure.\n",
164+
"\n",
165+
"(Setup — real data often already carries such a layer.) Note `table_layer` only affects\n",
166+
"gene-expression colouring, i.e. a `var_names` lookup; colouring by an `obs` column ignores it."
163167
]
164168
},
165169
{
@@ -168,10 +172,10 @@
168172
"id": "6e0ed124",
169173
"metadata": {
170174
"execution": {
171-
"iopub.execute_input": "2026-08-19T02:29:27.276470Z",
172-
"iopub.status.busy": "2026-08-19T02:29:27.276360Z",
173-
"iopub.status.idle": "2026-08-19T02:29:27.941572Z",
174-
"shell.execute_reply": "2026-08-19T02:29:27.940894Z"
175+
"iopub.execute_input": "2026-08-19T03:40:41.979694Z",
176+
"iopub.status.busy": "2026-08-19T03:40:41.979585Z",
177+
"iopub.status.idle": "2026-08-19T03:40:42.627545Z",
178+
"shell.execute_reply": "2026-08-19T03:40:42.626972Z"
175179
}
176180
},
177181
"outputs": [
@@ -187,6 +191,7 @@
187191
}
188192
],
189193
"source": [
194+
"# setup: add a normalised layer (your data may already have one)\n",
190195
"raw = table.X.toarray() if sp.issparse(table.X) else table.X\n",
191196
"table.layers[\"lognorm\"] = np.log1p(raw)\n",
192197
"\n",
@@ -202,7 +207,8 @@
202207
"\n",
203208
"Many datasets index `var` by a stable ID (Ensembl) and keep the human-readable symbol in a column.\n",
204209
"`color=` then can't find a symbol directly. `gene_symbols=` names the column to map through. We\n",
205-
"simulate that layout by moving the current symbols into a column and indexing `var` by `gene_ids`."
210+
"simulate that layout on a **copy** of the table and render it through a throwaway `SpatialData`, so the\n",
211+
"original table stays symbol-indexed and every earlier cell keeps working."
206212
]
207213
},
208214
{
@@ -211,10 +217,10 @@
211217
"id": "9adeae30",
212218
"metadata": {
213219
"execution": {
214-
"iopub.execute_input": "2026-08-19T02:29:27.943747Z",
215-
"iopub.status.busy": "2026-08-19T02:29:27.943616Z",
216-
"iopub.status.idle": "2026-08-19T02:29:28.258279Z",
217-
"shell.execute_reply": "2026-08-19T02:29:28.257738Z"
220+
"iopub.execute_input": "2026-08-19T03:40:42.629356Z",
221+
"iopub.status.busy": "2026-08-19T03:40:42.629247Z",
222+
"iopub.status.idle": "2026-08-19T03:40:43.040137Z",
223+
"shell.execute_reply": "2026-08-19T03:40:43.039522Z"
218224
}
219225
},
220226
"outputs": [
@@ -230,11 +236,15 @@
230236
}
231237
],
232238
"source": [
233-
"table.var[\"symbol\"] = table.var_names\n",
234-
"table.var.index = list(table.var[\"gene_ids\"].values) # now var is indexed by Ensembl IDs\n",
239+
"# setup — skip on your own data: simulate an ID-indexed table on a copy so the original is untouched\n",
240+
"table_ids = table.copy()\n",
241+
"table_ids.var[\"symbol\"] = table_ids.var_names\n",
242+
"table_ids.var.index = list(table_ids.var[\"gene_ids\"].values) # var now indexed by Ensembl IDs\n",
235243
"\n",
236-
"# color= still takes the symbol; gene_symbols tells the renderer where to find it\n",
237-
"sdata.pl.render_shapes(\"spots\", color=gene, gene_symbols=\"symbol\").pl.show()"
244+
"# render from a lightweight SpatialData that reuses the spots and the copied, ID-indexed table\n",
245+
"# color= still takes the symbol; gene_symbols tells the renderer which var column to map through\n",
246+
"demo = SpatialData(shapes={\"spots\": sdata.shapes[\"spots\"]}, tables={\"adata\": table_ids})\n",
247+
"demo.pl.render_shapes(\"spots\", color=gene, gene_symbols=\"symbol\").pl.show()"
238248
]
239249
},
240250
{
@@ -246,7 +256,12 @@
246256
"\n",
247257
"An element can be annotated by more than one table. When there is only one, it is used automatically;\n",
248258
"with several you disambiguate with `table_name=`. We add a second table of per-spot QC flags and\n",
249-
"colour by it."
259+
"colour by it.\n",
260+
"\n",
261+
"A table links to its element through three keys in `table.uns['spatialdata_attrs']`: `region` (the\n",
262+
"annotated element), `region_key` (the obs column naming that element for each row), and `instance_key`\n",
263+
"(the obs column giving each row's id within it). We assemble the two tables into a throwaway\n",
264+
"`SpatialData` so the original object is left untouched."
250265
]
251266
},
252267
{
@@ -255,10 +270,10 @@
255270
"id": "bf5138a9",
256271
"metadata": {
257272
"execution": {
258-
"iopub.execute_input": "2026-08-19T02:29:28.260048Z",
259-
"iopub.status.busy": "2026-08-19T02:29:28.259937Z",
260-
"iopub.status.idle": "2026-08-19T02:29:28.500584Z",
261-
"shell.execute_reply": "2026-08-19T02:29:28.499938Z"
273+
"iopub.execute_input": "2026-08-19T03:40:43.041675Z",
274+
"iopub.status.busy": "2026-08-19T03:40:43.041570Z",
275+
"iopub.status.idle": "2026-08-19T03:40:43.267498Z",
276+
"shell.execute_reply": "2026-08-19T03:40:43.266879Z"
262277
}
263278
},
264279
"outputs": [
@@ -283,16 +298,19 @@
283298
],
284299
"source": [
285300
"attrs = table.uns[\"spatialdata_attrs\"]\n",
286-
"region_key, instance_key, region = attrs[\"region_key\"], attrs[\"instance_key\"], attrs[\"region\"]\n",
301+
"region_key = attrs[\"region_key\"] # obs column naming which element each row annotates\n",
302+
"instance_key = attrs[\"instance_key\"] # obs column giving the row id within that element\n",
303+
"region = attrs[\"region\"] # the annotated element (\"spots\")\n",
287304
"\n",
288305
"qc_obs = table.obs[[region_key, instance_key]].copy()\n",
289306
"qc_obs[\"qc\"] = np.where(table.obs[\"total_counts\"] > table.obs[\"total_counts\"].median(), \"high\", \"low\")\n",
290307
"qc_table = TableModel.parse(\n",
291308
" ad.AnnData(obs=qc_obs), region=region, region_key=region_key, instance_key=instance_key\n",
292309
")\n",
293-
"sdata.tables[\"qc\"] = qc_table\n",
294310
"\n",
295-
"sdata.pl.render_shapes(\"spots\", color=\"qc\", table_name=\"qc\").pl.show()"
311+
"# two tables annotate the same spots; table_name picks which one to colour from\n",
312+
"demo = SpatialData(shapes={\"spots\": sdata.shapes[\"spots\"]}, tables={\"adata\": table, \"qc\": qc_table})\n",
313+
"demo.pl.render_shapes(\"spots\", color=\"qc\", table_name=\"qc\").pl.show()"
296314
]
297315
},
298316
{
@@ -303,7 +321,8 @@
303321
"## Summary\n",
304322
"\n",
305323
"- Colouring by a gene reads from the **annotating table** of the element.\n",
306-
"- `table_layer=` selects the matrix within that table (e.g. raw `X` vs a `lognorm` layer).\n",
324+
"- `table_layer=` selects the matrix within that table (e.g. raw `X` vs a `lognorm` layer), for\n",
325+
" gene-expression colouring only — an `obs`-column colour ignores it.\n",
307326
"- `gene_symbols=` names the `var` column of symbols to look `color=` up in, for ID-indexed tables.\n",
308327
"- `table_name=` picks the table when an element has more than one; with a single table it is\n",
309328
" automatic.\n",
@@ -326,10 +345,10 @@
326345
"id": "5519326c",
327346
"metadata": {
328347
"execution": {
329-
"iopub.execute_input": "2026-08-19T02:29:28.502662Z",
330-
"iopub.status.busy": "2026-08-19T02:29:28.502516Z",
331-
"iopub.status.idle": "2026-08-19T02:29:28.585357Z",
332-
"shell.execute_reply": "2026-08-19T02:29:28.584701Z"
348+
"iopub.execute_input": "2026-08-19T03:40:43.269197Z",
349+
"iopub.status.busy": "2026-08-19T03:40:43.269067Z",
350+
"iopub.status.idle": "2026-08-19T03:40:43.301023Z",
351+
"shell.execute_reply": "2026-08-19T03:40:43.300478Z"
333352
}
334353
},
335354
"outputs": [

0 commit comments

Comments
 (0)