You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add rescale and standardize normalization utilities (#1028)
* Add rescale and standardize normalization utilities (#1027)
New module xrspatial/normalize.py with two functions:
- rescale: min-max normalization to a target range
- standardize: z-score normalization (mean 0, std 1)
Both support all four backends via ArrayTypeFunctionMapping.
* Add tests for rescale and standardize (#1027)
29 tests covering NumPy, Dask, and CuPy backends. Tests include
known-value checks, NaN/inf passthrough, constant rasters, single
cells, coordinate preservation, and cross-backend agreement.
* Add rescale/standardize to API reference docs (#1027)
* Add user guide notebook for rescale and standardize (#1027)
* Add rescale/standardize to README feature matrix (#1027)
* Rename normalization notebook to 34 (#1027)
"source": "# Normalization: rescale and standardize\n\nTwo common preprocessing steps before combining rasters or feeding them into models:\n\n- **rescale** maps values to a target range (default [0, 1]) using min-max normalization.\n- **standardize** centers values at zero with unit variance (z-score normalization).\n\nBoth functions handle NaN and infinite values (they pass through unchanged) and work on all four xarray-spatial backends: NumPy, CuPy, Dask+NumPy, and Dask+CuPy.",
7
+
"metadata": {}
8
+
},
9
+
{
10
+
"cell_type": "code",
11
+
"id": "wzy5yzkbde",
12
+
"source": "%matplotlib inline\nimport numpy as np\nimport xarray as xr\nimport matplotlib.pyplot as plt\n\nfrom xrspatial.normalize import rescale, standardize\nfrom xrspatial import generate_terrain",
13
+
"metadata": {},
14
+
"execution_count": null,
15
+
"outputs": []
16
+
},
17
+
{
18
+
"cell_type": "markdown",
19
+
"id": "5zey0oy2cji",
20
+
"source": "## Synthetic terrain\n\nGenerate a 500x500 elevation raster with values roughly in the 0-1200 range. We'll sprinkle in a few NaN cells to show how they're preserved.",
"source": "## rescale: min-max normalization\n\nBy default, `rescale()` maps finite values to [0, 1]. You can supply a custom range with `new_min` and `new_max`.",
"source": "## standardize: z-score normalization\n\n`standardize()` subtracts the mean and divides by the standard deviation of finite values. The result has mean ~0 and std ~1. Use `ddof=1` for sample standard deviation.",
"source": "## Practical use case: combining layers with different scales\n\nWhen combining elevation and slope into a composite index, the raw values live on different scales. Rescaling both to [0, 1] puts them on equal footing.",
63
+
"metadata": {}
64
+
},
65
+
{
66
+
"cell_type": "code",
67
+
"id": "9hhldlalk7f",
68
+
"source": "from xrspatial import slope\n\nslp = slope(terrain)\n\n# Raw values are on very different scales\nprint(f\"Elevation range: {float(np.nanmin(terrain)):.0f} to {float(np.nanmax(terrain)):.0f}\")\nprint(f\"Slope range: {float(np.nanmin(slp)):.1f} to {float(np.nanmax(slp)):.1f}\")\n\n# Rescale both to [0, 1] and combine\nelev_norm = rescale(terrain)\nslope_norm = rescale(slp)\n\n# Simple composite: high elevation + steep slope = high risk\ncomposite = 0.6 * elev_norm + 0.4 * slope_norm\n\nfig, axes = plt.subplots(1, 3, figsize=(18, 5))\n\nelev_norm.plot.imshow(ax=axes[0], cmap='terrain', add_colorbar=True)\naxes[0].set_title('Elevation [0, 1]')\naxes[0].set_axis_off()\n\nslope_norm.plot.imshow(ax=axes[1], cmap='YlOrRd', add_colorbar=True)\naxes[1].set_title('Slope [0, 1]')\naxes[1].set_axis_off()\n\ncomposite.plot.imshow(ax=axes[2], cmap='inferno', add_colorbar=True)\naxes[2].set_title('Weighted composite (0.6 elev + 0.4 slope)')\naxes[2].set_axis_off()\n\nplt.tight_layout()",
69
+
"metadata": {},
70
+
"execution_count": null,
71
+
"outputs": []
72
+
},
73
+
{
74
+
"cell_type": "markdown",
75
+
"id": "c6w0w124c4c",
76
+
"source": "## Accessor syntax\n\nBoth functions are available through the `.xrs` accessor on DataArrays.\n\n```python\nimport xrspatial\n\nterrain.xrs.rescale()\nterrain.xrs.standardize(ddof=1)\n```",
0 commit comments