|
36 | 36 | "id": "fcc86e1b", |
37 | 37 | "metadata": { |
38 | 38 | "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" |
43 | 43 | } |
44 | 44 | }, |
45 | 45 | "outputs": [ |
|
101 | 101 | "import scipy.sparse as sp\n", |
102 | 102 | "import anndata as ad\n", |
103 | 103 | "import squidpy as sq\n", |
| 104 | + "from spatialdata import SpatialData\n", |
104 | 105 | "from spatialdata.models import TableModel\n", |
105 | 106 | "\n", |
106 | 107 | "import spatialdata_plot # noqa: F401 # registers the .pl accessor\n", |
|
128 | 129 | "id": "bafdb4b8", |
129 | 130 | "metadata": { |
130 | 131 | "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" |
135 | 136 | } |
136 | 137 | }, |
137 | 138 | "outputs": [ |
|
159 | 160 | "\n", |
160 | 161 | "A table can hold several matrices in `.layers` (raw counts, normalised, scaled). `table_layer=` picks\n", |
161 | 162 | "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." |
163 | 167 | ] |
164 | 168 | }, |
165 | 169 | { |
|
168 | 172 | "id": "6e0ed124", |
169 | 173 | "metadata": { |
170 | 174 | "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" |
175 | 179 | } |
176 | 180 | }, |
177 | 181 | "outputs": [ |
|
187 | 191 | } |
188 | 192 | ], |
189 | 193 | "source": [ |
| 194 | + "# setup: add a normalised layer (your data may already have one)\n", |
190 | 195 | "raw = table.X.toarray() if sp.issparse(table.X) else table.X\n", |
191 | 196 | "table.layers[\"lognorm\"] = np.log1p(raw)\n", |
192 | 197 | "\n", |
|
202 | 207 | "\n", |
203 | 208 | "Many datasets index `var` by a stable ID (Ensembl) and keep the human-readable symbol in a column.\n", |
204 | 209 | "`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." |
206 | 212 | ] |
207 | 213 | }, |
208 | 214 | { |
|
211 | 217 | "id": "9adeae30", |
212 | 218 | "metadata": { |
213 | 219 | "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" |
218 | 224 | } |
219 | 225 | }, |
220 | 226 | "outputs": [ |
|
230 | 236 | } |
231 | 237 | ], |
232 | 238 | "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", |
235 | 243 | "\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()" |
238 | 248 | ] |
239 | 249 | }, |
240 | 250 | { |
|
246 | 256 | "\n", |
247 | 257 | "An element can be annotated by more than one table. When there is only one, it is used automatically;\n", |
248 | 258 | "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." |
250 | 265 | ] |
251 | 266 | }, |
252 | 267 | { |
|
255 | 270 | "id": "bf5138a9", |
256 | 271 | "metadata": { |
257 | 272 | "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" |
262 | 277 | } |
263 | 278 | }, |
264 | 279 | "outputs": [ |
|
283 | 298 | ], |
284 | 299 | "source": [ |
285 | 300 | "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", |
287 | 304 | "\n", |
288 | 305 | "qc_obs = table.obs[[region_key, instance_key]].copy()\n", |
289 | 306 | "qc_obs[\"qc\"] = np.where(table.obs[\"total_counts\"] > table.obs[\"total_counts\"].median(), \"high\", \"low\")\n", |
290 | 307 | "qc_table = TableModel.parse(\n", |
291 | 308 | " ad.AnnData(obs=qc_obs), region=region, region_key=region_key, instance_key=instance_key\n", |
292 | 309 | ")\n", |
293 | | - "sdata.tables[\"qc\"] = qc_table\n", |
294 | 310 | "\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()" |
296 | 314 | ] |
297 | 315 | }, |
298 | 316 | { |
|
303 | 321 | "## Summary\n", |
304 | 322 | "\n", |
305 | 323 | "- 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", |
307 | 326 | "- `gene_symbols=` names the `var` column of symbols to look `color=` up in, for ID-indexed tables.\n", |
308 | 327 | "- `table_name=` picks the table when an element has more than one; with a single table it is\n", |
309 | 328 | " automatic.\n", |
|
326 | 345 | "id": "5519326c", |
327 | 346 | "metadata": { |
328 | 347 | "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" |
333 | 352 | } |
334 | 353 | }, |
335 | 354 | "outputs": [ |
|
0 commit comments