[{"data":1,"prerenderedAt":1899},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fcalculate-raster-statistics-pyqgis":3},{"id":4,"title":5,"body":6,"description":1892,"extension":1893,"meta":1894,"navigation":116,"path":1895,"seo":1896,"stem":1897,"__hash__":1898},"docs\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fcalculate-raster-statistics-pyqgis\u002Findex.md","Calculate Raster Statistics in PyQGIS",{"type":7,"value":8,"toc":1878},"minimark",[9,13,36,39,44,64,72,76,83,367,405,408,481,494,502,506,509,667,683,687,694,929,948,952,958,1122,1171,1182,1186,1196,1326,1354,1358,1361,1631,1640,1644,1651,1713,1727,1731,1791,1795,1807,1811,1824,1838,1850,1856,1860,1874],[10,11,5],"h1",{"id":12},"calculate-raster-statistics-in-pyqgis",[14,15,16,17,21,22,25,26,29,30,35],"p",{},"Summarizing the values in a raster — its minimum, maximum, mean, and standard deviation — is the starting point for choosing a color ramp, normalizing data, or quality-checking a DEM. PyQGIS exposes this directly through ",[18,19,20],"code",{},"layer.dataProvider().bandStatistics(...)",", which returns a ",[18,23,24],{},"QgsRasterBandStatistics"," object, and through the ",[18,27,28],{},"native:zonalstatisticsfb"," algorithm for per-polygon summaries. Both are essential tools in ",[31,32,34],"a",{"href":33},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002F","Raster Analysis Workflows",".",[14,37,38],{},"This page covers single-band statistics, building a histogram of value frequencies, and computing zonal statistics that summarize a raster within each polygon of a vector layer.",[40,41,43],"h2",{"id":42},"prerequisites","Prerequisites",[45,46,47,51,54,57],"ul",{},[48,49,50],"li",{},"QGIS 3.34 LTR (bundled Python 3.12) with Processing available.",[48,52,53],{},"A single- or multi-band raster (a GeoTIFF DEM, NDVI grid, or similar).",[48,55,56],{},"For zonal statistics, a polygon vector layer overlapping the raster.",[48,58,59,60,63],{},"The QGIS Python Console (",[18,61,62],{},"Plugins > Python Console",").",[14,65,66,67,71],{},"Band indices in the data-provider API are ",[68,69,70],"strong",{},"1-based",": band 1 is the first band, not band 0.",[40,73,75],{"id":74},"read-band-statistics","Read Band Statistics",[14,77,78,79,82],{},"Load the raster and ask its data provider for statistics on a band. The ",[18,80,81],{},"bandStatistics"," method accepts a flag for which statistics to compute and returns an object with the results.",[84,85,90],"pre",{"className":86,"code":87,"language":88,"meta":89,"style":89},"language-python shiki shiki-themes github-dark","from qgis.core import QgsRasterLayer, QgsRasterBandStats\n\nraster = QgsRasterLayer(\"\u002Fdata\u002Fdem.tif\", \"dem\")\nif not raster.isValid():\n    raise RuntimeError(\"Raster failed to load\")\n\nprovider = raster.dataProvider()\nstats = provider.bandStatistics(1, QgsRasterBandStats.All)\n\nprint(f\"Min:    {stats.minimumValue:.3f}\")\nprint(f\"Max:    {stats.maximumValue:.3f}\")\nprint(f\"Mean:   {stats.mean:.3f}\")\nprint(f\"StdDev: {stats.stdDev:.3f}\")\nprint(f\"Range:  {stats.range:.3f}\")\nprint(f\"Sum:    {stats.sum:.3f}\")\n","python","",[18,91,92,111,118,143,155,173,178,189,206,211,242,267,292,317,342],{"__ignoreMap":89},[93,94,97,101,105,108],"span",{"class":95,"line":96},"line",1,[93,98,100],{"class":99},"snl16","from",[93,102,104],{"class":103},"s95oV"," qgis.core ",[93,106,107],{"class":99},"import",[93,109,110],{"class":103}," QgsRasterLayer, QgsRasterBandStats\n",[93,112,114],{"class":95,"line":113},2,[93,115,117],{"emptyLinePlaceholder":116},true,"\n",[93,119,121,124,127,130,134,137,140],{"class":95,"line":120},3,[93,122,123],{"class":103},"raster ",[93,125,126],{"class":99},"=",[93,128,129],{"class":103}," QgsRasterLayer(",[93,131,133],{"class":132},"sU2Wk","\"\u002Fdata\u002Fdem.tif\"",[93,135,136],{"class":103},", ",[93,138,139],{"class":132},"\"dem\"",[93,141,142],{"class":103},")\n",[93,144,146,149,152],{"class":95,"line":145},4,[93,147,148],{"class":99},"if",[93,150,151],{"class":99}," not",[93,153,154],{"class":103}," raster.isValid():\n",[93,156,158,161,165,168,171],{"class":95,"line":157},5,[93,159,160],{"class":99},"    raise",[93,162,164],{"class":163},"sDLfK"," RuntimeError",[93,166,167],{"class":103},"(",[93,169,170],{"class":132},"\"Raster failed to load\"",[93,172,142],{"class":103},[93,174,176],{"class":95,"line":175},6,[93,177,117],{"emptyLinePlaceholder":116},[93,179,181,184,186],{"class":95,"line":180},7,[93,182,183],{"class":103},"provider ",[93,185,126],{"class":99},[93,187,188],{"class":103}," raster.dataProvider()\n",[93,190,192,195,197,200,203],{"class":95,"line":191},8,[93,193,194],{"class":103},"stats ",[93,196,126],{"class":99},[93,198,199],{"class":103}," provider.bandStatistics(",[93,201,202],{"class":163},"1",[93,204,205],{"class":103},", QgsRasterBandStats.All)\n",[93,207,209],{"class":95,"line":208},9,[93,210,117],{"emptyLinePlaceholder":116},[93,212,214,217,219,222,225,228,231,234,237,240],{"class":95,"line":213},10,[93,215,216],{"class":163},"print",[93,218,167],{"class":103},[93,220,221],{"class":99},"f",[93,223,224],{"class":132},"\"Min:    ",[93,226,227],{"class":163},"{",[93,229,230],{"class":103},"stats.minimumValue",[93,232,233],{"class":99},":.3f",[93,235,236],{"class":163},"}",[93,238,239],{"class":132},"\"",[93,241,142],{"class":103},[93,243,245,247,249,251,254,256,259,261,263,265],{"class":95,"line":244},11,[93,246,216],{"class":163},[93,248,167],{"class":103},[93,250,221],{"class":99},[93,252,253],{"class":132},"\"Max:    ",[93,255,227],{"class":163},[93,257,258],{"class":103},"stats.maximumValue",[93,260,233],{"class":99},[93,262,236],{"class":163},[93,264,239],{"class":132},[93,266,142],{"class":103},[93,268,270,272,274,276,279,281,284,286,288,290],{"class":95,"line":269},12,[93,271,216],{"class":163},[93,273,167],{"class":103},[93,275,221],{"class":99},[93,277,278],{"class":132},"\"Mean:   ",[93,280,227],{"class":163},[93,282,283],{"class":103},"stats.mean",[93,285,233],{"class":99},[93,287,236],{"class":163},[93,289,239],{"class":132},[93,291,142],{"class":103},[93,293,295,297,299,301,304,306,309,311,313,315],{"class":95,"line":294},13,[93,296,216],{"class":163},[93,298,167],{"class":103},[93,300,221],{"class":99},[93,302,303],{"class":132},"\"StdDev: ",[93,305,227],{"class":163},[93,307,308],{"class":103},"stats.stdDev",[93,310,233],{"class":99},[93,312,236],{"class":163},[93,314,239],{"class":132},[93,316,142],{"class":103},[93,318,320,322,324,326,329,331,334,336,338,340],{"class":95,"line":319},14,[93,321,216],{"class":163},[93,323,167],{"class":103},[93,325,221],{"class":99},[93,327,328],{"class":132},"\"Range:  ",[93,330,227],{"class":163},[93,332,333],{"class":103},"stats.range",[93,335,233],{"class":99},[93,337,236],{"class":163},[93,339,239],{"class":132},[93,341,142],{"class":103},[93,343,345,347,349,351,354,356,359,361,363,365],{"class":95,"line":344},15,[93,346,216],{"class":163},[93,348,167],{"class":103},[93,350,221],{"class":99},[93,352,353],{"class":132},"\"Sum:    ",[93,355,227],{"class":163},[93,357,358],{"class":103},"stats.sum",[93,360,233],{"class":99},[93,362,236],{"class":163},[93,364,239],{"class":132},[93,366,142],{"class":103},[14,368,369,372,373,376,377,380,381,384,385,136,388,136,391,136,394,136,397,400,401,404],{},[68,370,371],{},"Breakdown:"," ",[18,374,375],{},"bandStatistics(1, QgsRasterBandStats.All)"," computes the full set for band 1. ",[18,378,379],{},"QgsRasterBandStats.All"," forces a complete scan; passing a narrower flag such as ",[18,382,383],{},"QgsRasterBandStats.Min | QgsRasterBandStats.Max"," is faster when you only need extremes. The returned object exposes ",[18,386,387],{},"minimumValue",[18,389,390],{},"maximumValue",[18,392,393],{},"mean",[18,395,396],{},"stdDev",[18,398,399],{},"range",", and ",[18,402,403],{},"sum"," as plain attributes — no method calls needed.",[14,406,407],{},"For large rasters where an exact scan is slow, request statistics over a sample extent and resolution to get a fast approximation:",[84,409,411],{"className":86,"code":410,"language":88,"meta":89,"style":89},"extent = raster.extent()\nsample_size = 250000\napprox = provider.bandStatistics(1, QgsRasterBandStats.All, extent, sample_size)\nprint(f\"Approx mean over {sample_size} px: {approx.mean:.3f}\")\n",[18,412,413,423,433,447],{"__ignoreMap":89},[93,414,415,418,420],{"class":95,"line":96},[93,416,417],{"class":103},"extent ",[93,419,126],{"class":99},[93,421,422],{"class":103}," raster.extent()\n",[93,424,425,428,430],{"class":95,"line":113},[93,426,427],{"class":103},"sample_size ",[93,429,126],{"class":99},[93,431,432],{"class":163}," 250000\n",[93,434,435,438,440,442,444],{"class":95,"line":120},[93,436,437],{"class":103},"approx ",[93,439,126],{"class":99},[93,441,199],{"class":103},[93,443,202],{"class":163},[93,445,446],{"class":103},", QgsRasterBandStats.All, extent, sample_size)\n",[93,448,449,451,453,455,458,460,463,465,468,470,473,475,477,479],{"class":95,"line":145},[93,450,216],{"class":163},[93,452,167],{"class":103},[93,454,221],{"class":99},[93,456,457],{"class":132},"\"Approx mean over ",[93,459,227],{"class":163},[93,461,462],{"class":103},"sample_size",[93,464,236],{"class":163},[93,466,467],{"class":132}," px: ",[93,469,227],{"class":163},[93,471,472],{"class":103},"approx.mean",[93,474,233],{"class":99},[93,476,236],{"class":163},[93,478,239],{"class":132},[93,480,142],{"class":103},[14,482,483,485,486,489,490,493],{},[68,484,371],{}," Supplying an ",[18,487,488],{},"extent"," and a ",[18,491,492],{},"sampleSize"," caps how many pixels are read, trading a little accuracy for speed on multi-gigabyte rasters. With no sample size the provider reads every pixel, which is exact but slow.",[14,495,496,497,501],{},"The min and max you get here are what you would feed into a color ramp — see ",[31,498,500],{"href":499},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fapply-color-ramp-to-raster-pyqgis\u002F","Apply a Color Ramp to a Raster in PyQGIS"," for styling with these values.",[40,503,505],{"id":504},"loop-over-multiple-bands","Loop Over Multiple Bands",[14,507,508],{},"For multi-band imagery (e.g. RGB or multispectral), iterate the band count and collect per-band statistics:",[84,510,512],{"className":86,"code":511,"language":88,"meta":89,"style":89},"from qgis.core import QgsRasterLayer, QgsRasterBandStats\n\nraster = QgsRasterLayer(\"\u002Fdata\u002Flandsat.tif\", \"scene\")\nprovider = raster.dataProvider()\n\nfor band in range(1, raster.bandCount() + 1):\n    s = provider.bandStatistics(band, QgsRasterBandStats.All)\n    name = raster.bandName(band)\n    print(f\"{name}: mean={s.mean:.2f} min={s.minimumValue:.2f} max={s.maximumValue:.2f}\")\n",[18,513,514,524,528,546,554,558,588,598,608],{"__ignoreMap":89},[93,515,516,518,520,522],{"class":95,"line":96},[93,517,100],{"class":99},[93,519,104],{"class":103},[93,521,107],{"class":99},[93,523,110],{"class":103},[93,525,526],{"class":95,"line":113},[93,527,117],{"emptyLinePlaceholder":116},[93,529,530,532,534,536,539,541,544],{"class":95,"line":120},[93,531,123],{"class":103},[93,533,126],{"class":99},[93,535,129],{"class":103},[93,537,538],{"class":132},"\"\u002Fdata\u002Flandsat.tif\"",[93,540,136],{"class":103},[93,542,543],{"class":132},"\"scene\"",[93,545,142],{"class":103},[93,547,548,550,552],{"class":95,"line":145},[93,549,183],{"class":103},[93,551,126],{"class":99},[93,553,188],{"class":103},[93,555,556],{"class":95,"line":157},[93,557,117],{"emptyLinePlaceholder":116},[93,559,560,563,566,569,572,574,576,579,582,585],{"class":95,"line":175},[93,561,562],{"class":99},"for",[93,564,565],{"class":103}," band ",[93,567,568],{"class":99},"in",[93,570,571],{"class":163}," range",[93,573,167],{"class":103},[93,575,202],{"class":163},[93,577,578],{"class":103},", raster.bandCount() ",[93,580,581],{"class":99},"+",[93,583,584],{"class":163}," 1",[93,586,587],{"class":103},"):\n",[93,589,590,593,595],{"class":95,"line":180},[93,591,592],{"class":103},"    s ",[93,594,126],{"class":99},[93,596,597],{"class":103}," provider.bandStatistics(band, QgsRasterBandStats.All)\n",[93,599,600,603,605],{"class":95,"line":191},[93,601,602],{"class":103},"    name ",[93,604,126],{"class":99},[93,606,607],{"class":103}," raster.bandName(band)\n",[93,609,610,613,615,617,619,621,624,626,629,631,634,637,639,642,644,647,649,651,654,656,659,661,663,665],{"class":95,"line":208},[93,611,612],{"class":163},"    print",[93,614,167],{"class":103},[93,616,221],{"class":99},[93,618,239],{"class":132},[93,620,227],{"class":163},[93,622,623],{"class":103},"name",[93,625,236],{"class":163},[93,627,628],{"class":132},": mean=",[93,630,227],{"class":163},[93,632,633],{"class":103},"s.mean",[93,635,636],{"class":99},":.2f",[93,638,236],{"class":163},[93,640,641],{"class":132}," min=",[93,643,227],{"class":163},[93,645,646],{"class":103},"s.minimumValue",[93,648,636],{"class":99},[93,650,236],{"class":163},[93,652,653],{"class":132}," max=",[93,655,227],{"class":163},[93,657,658],{"class":103},"s.maximumValue",[93,660,636],{"class":99},[93,662,236],{"class":163},[93,664,239],{"class":132},[93,666,142],{"class":103},[14,668,669,372,671,674,675,678,679,682],{},[68,670,371],{},[18,672,673],{},"bandCount()"," gives the number of bands and ",[18,676,677],{},"range(1, bandCount() + 1)"," produces 1-based indices. ",[18,680,681],{},"bandName(band)"," returns a human-readable label. This pattern is the basis for per-band normalization or building a summary report across a scene.",[40,684,686],{"id":685},"build-a-histogram","Build a Histogram",[14,688,689,690,693],{},"A histogram shows how many pixels fall into each value bin — useful for spotting bimodal distributions, saturation, or nodata spikes. The provider's ",[18,691,692],{},"histogram"," method returns the bin counts:",[84,695,697],{"className":86,"code":696,"language":88,"meta":89,"style":89},"from qgis.core import QgsRasterLayer, QgsRasterBandStats\n\nraster = QgsRasterLayer(\"\u002Fdata\u002Fdem.tif\", \"dem\")\nprovider = raster.dataProvider()\nstats = provider.bandStatistics(1, QgsRasterBandStats.All)\n\nbin_count = 20\nhist = provider.histogram(\n    1,                       # band\n    bin_count,               # number of bins\n    stats.minimumValue,      # range minimum\n    stats.maximumValue,      # range maximum\n)\n\nwidth = (stats.maximumValue - stats.minimumValue) \u002F bin_count\nfor i, count in enumerate(hist.histogramVector):\n    lo = stats.minimumValue + i * width\n    print(f\"[{lo:8.2f}, {lo + width:8.2f}): {count}\")\n",[18,698,699,709,713,729,737,749,753,763,773,785,793,801,809,813,817,839,855,877],{"__ignoreMap":89},[93,700,701,703,705,707],{"class":95,"line":96},[93,702,100],{"class":99},[93,704,104],{"class":103},[93,706,107],{"class":99},[93,708,110],{"class":103},[93,710,711],{"class":95,"line":113},[93,712,117],{"emptyLinePlaceholder":116},[93,714,715,717,719,721,723,725,727],{"class":95,"line":120},[93,716,123],{"class":103},[93,718,126],{"class":99},[93,720,129],{"class":103},[93,722,133],{"class":132},[93,724,136],{"class":103},[93,726,139],{"class":132},[93,728,142],{"class":103},[93,730,731,733,735],{"class":95,"line":145},[93,732,183],{"class":103},[93,734,126],{"class":99},[93,736,188],{"class":103},[93,738,739,741,743,745,747],{"class":95,"line":157},[93,740,194],{"class":103},[93,742,126],{"class":99},[93,744,199],{"class":103},[93,746,202],{"class":163},[93,748,205],{"class":103},[93,750,751],{"class":95,"line":175},[93,752,117],{"emptyLinePlaceholder":116},[93,754,755,758,760],{"class":95,"line":180},[93,756,757],{"class":103},"bin_count ",[93,759,126],{"class":99},[93,761,762],{"class":163}," 20\n",[93,764,765,768,770],{"class":95,"line":191},[93,766,767],{"class":103},"hist ",[93,769,126],{"class":99},[93,771,772],{"class":103}," provider.histogram(\n",[93,774,775,778,781],{"class":95,"line":208},[93,776,777],{"class":163},"    1",[93,779,780],{"class":103},",                       ",[93,782,784],{"class":783},"sAwPA","# band\n",[93,786,787,790],{"class":95,"line":213},[93,788,789],{"class":103},"    bin_count,               ",[93,791,792],{"class":783},"# number of bins\n",[93,794,795,798],{"class":95,"line":244},[93,796,797],{"class":103},"    stats.minimumValue,      ",[93,799,800],{"class":783},"# range minimum\n",[93,802,803,806],{"class":95,"line":269},[93,804,805],{"class":103},"    stats.maximumValue,      ",[93,807,808],{"class":783},"# range maximum\n",[93,810,811],{"class":95,"line":294},[93,812,142],{"class":103},[93,814,815],{"class":95,"line":319},[93,816,117],{"emptyLinePlaceholder":116},[93,818,819,822,824,827,830,833,836],{"class":95,"line":344},[93,820,821],{"class":103},"width ",[93,823,126],{"class":99},[93,825,826],{"class":103}," (stats.maximumValue ",[93,828,829],{"class":99},"-",[93,831,832],{"class":103}," stats.minimumValue) ",[93,834,835],{"class":99},"\u002F",[93,837,838],{"class":103}," bin_count\n",[93,840,842,844,847,849,852],{"class":95,"line":841},16,[93,843,562],{"class":99},[93,845,846],{"class":103}," i, count ",[93,848,568],{"class":99},[93,850,851],{"class":163}," enumerate",[93,853,854],{"class":103},"(hist.histogramVector):\n",[93,856,858,861,863,866,868,871,874],{"class":95,"line":857},17,[93,859,860],{"class":103},"    lo ",[93,862,126],{"class":99},[93,864,865],{"class":103}," stats.minimumValue ",[93,867,581],{"class":99},[93,869,870],{"class":103}," i ",[93,872,873],{"class":99},"*",[93,875,876],{"class":103}," width\n",[93,878,880,882,884,886,889,891,894,897,899,901,903,906,908,911,913,915,918,920,923,925,927],{"class":95,"line":879},18,[93,881,612],{"class":163},[93,883,167],{"class":103},[93,885,221],{"class":99},[93,887,888],{"class":132},"\"[",[93,890,227],{"class":163},[93,892,893],{"class":103},"lo",[93,895,896],{"class":99},":8.2f",[93,898,236],{"class":163},[93,900,136],{"class":132},[93,902,227],{"class":163},[93,904,905],{"class":103},"lo ",[93,907,581],{"class":99},[93,909,910],{"class":103}," width",[93,912,896],{"class":99},[93,914,236],{"class":163},[93,916,917],{"class":132},"): ",[93,919,227],{"class":163},[93,921,922],{"class":103},"count",[93,924,236],{"class":163},[93,926,239],{"class":132},[93,928,142],{"class":103},[14,930,931,372,933,936,937,940,941,944,945,947],{},[68,932,371],{},[18,934,935],{},"histogram(band, binCount, min, max)"," returns a ",[18,938,939],{},"QgsRasterHistogram","; its ",[18,942,943],{},"histogramVector"," is a list of pixel counts per bin. Bounding the range with the actual min\u002Fmax from ",[18,946,81],{}," keeps the bins meaningful. The loop reconstructs each bin's interval from the bin width so the output is readable.",[40,949,951],{"id":950},"compute-zonal-statistics","Compute Zonal Statistics",[14,953,954,955,957],{},"Zonal statistics summarize raster values inside each polygon of a vector layer — average elevation per catchment, mean NDVI per field. Use ",[18,956,28],{},", which writes the summary columns into a new vector layer (the \"fb\" variant returns a fresh feature-based output rather than editing in place):",[84,959,961],{"className":86,"code":960,"language":88,"meta":89,"style":89},"import processing\n\nresult = processing.run(\"native:zonalstatisticsfb\", {\n    \"INPUT\": \"\u002Fdata\u002Fparcels.gpkg\",\n    \"RASTER\": \"\u002Fdata\u002Fdem.tif\",\n    \"RASTER_BAND\": 1,\n    \"COLUMN_PREFIX\": \"elev_\",\n    \"STATISTICS\": [0, 1, 2],   # 0=Count, 1=Sum, 2=Mean\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nfor feat in result.getFeatures():\n    print(feat[\"id\"], feat[\"elev_mean\"])\n",[18,962,963,970,974,990,1004,1015,1026,1038,1064,1076,1087,1091,1103],{"__ignoreMap":89},[93,964,965,967],{"class":95,"line":96},[93,966,107],{"class":99},[93,968,969],{"class":103}," processing\n",[93,971,972],{"class":95,"line":113},[93,973,117],{"emptyLinePlaceholder":116},[93,975,976,979,981,984,987],{"class":95,"line":120},[93,977,978],{"class":103},"result ",[93,980,126],{"class":99},[93,982,983],{"class":103}," processing.run(",[93,985,986],{"class":132},"\"native:zonalstatisticsfb\"",[93,988,989],{"class":103},", {\n",[93,991,992,995,998,1001],{"class":95,"line":145},[93,993,994],{"class":132},"    \"INPUT\"",[93,996,997],{"class":103},": ",[93,999,1000],{"class":132},"\"\u002Fdata\u002Fparcels.gpkg\"",[93,1002,1003],{"class":103},",\n",[93,1005,1006,1009,1011,1013],{"class":95,"line":157},[93,1007,1008],{"class":132},"    \"RASTER\"",[93,1010,997],{"class":103},[93,1012,133],{"class":132},[93,1014,1003],{"class":103},[93,1016,1017,1020,1022,1024],{"class":95,"line":175},[93,1018,1019],{"class":132},"    \"RASTER_BAND\"",[93,1021,997],{"class":103},[93,1023,202],{"class":163},[93,1025,1003],{"class":103},[93,1027,1028,1031,1033,1036],{"class":95,"line":180},[93,1029,1030],{"class":132},"    \"COLUMN_PREFIX\"",[93,1032,997],{"class":103},[93,1034,1035],{"class":132},"\"elev_\"",[93,1037,1003],{"class":103},[93,1039,1040,1043,1046,1049,1051,1053,1055,1058,1061],{"class":95,"line":191},[93,1041,1042],{"class":132},"    \"STATISTICS\"",[93,1044,1045],{"class":103},": [",[93,1047,1048],{"class":163},"0",[93,1050,136],{"class":103},[93,1052,202],{"class":163},[93,1054,136],{"class":103},[93,1056,1057],{"class":163},"2",[93,1059,1060],{"class":103},"],   ",[93,1062,1063],{"class":783},"# 0=Count, 1=Sum, 2=Mean\n",[93,1065,1066,1069,1071,1074],{"class":95,"line":208},[93,1067,1068],{"class":132},"    \"OUTPUT\"",[93,1070,997],{"class":103},[93,1072,1073],{"class":132},"\"TEMPORARY_OUTPUT\"",[93,1075,1003],{"class":103},[93,1077,1078,1081,1084],{"class":95,"line":213},[93,1079,1080],{"class":103},"})[",[93,1082,1083],{"class":132},"\"OUTPUT\"",[93,1085,1086],{"class":103},"]\n",[93,1088,1089],{"class":95,"line":244},[93,1090,117],{"emptyLinePlaceholder":116},[93,1092,1093,1095,1098,1100],{"class":95,"line":269},[93,1094,562],{"class":99},[93,1096,1097],{"class":103}," feat ",[93,1099,568],{"class":99},[93,1101,1102],{"class":103}," result.getFeatures():\n",[93,1104,1105,1107,1110,1113,1116,1119],{"class":95,"line":294},[93,1106,612],{"class":163},[93,1108,1109],{"class":103},"(feat[",[93,1111,1112],{"class":132},"\"id\"",[93,1114,1115],{"class":103},"], feat[",[93,1117,1118],{"class":132},"\"elev_mean\"",[93,1120,1121],{"class":103},"])\n",[14,1123,1124,372,1126,1129,1130,1132,1133,1135,1136,1138,1139,1142,1143,1146,1147,1150,1151,1154,1155,1158,1159,1162,1163,1166,1167,35],{},[68,1125,371],{},[18,1127,1128],{},"STATISTICS"," is a list of integer codes — ",[18,1131,1048],{}," count, ",[18,1134,202],{}," sum, ",[18,1137,1057],{}," mean, ",[18,1140,1141],{},"3"," median, ",[18,1144,1145],{},"4"," stddev, ",[18,1148,1149],{},"5"," min, ",[18,1152,1153],{},"6"," max, and so on. Each requested statistic becomes a field named ",[18,1156,1157],{},"COLUMN_PREFIX"," plus the statistic (e.g. ",[18,1160,1161],{},"elev_mean","). The ",[18,1164,1165],{},"RASTER_BAND"," is 1-based, matching the provider API. Because the input and raster must overlap in the same CRS, reproject first if they differ — see ",[31,1168,1170],{"href":1169},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002F","Coordinate Reference Systems",[14,1172,1173,1174,1176,1177,1181],{},"To pull the global mean directly into your zonal calc, you can pre-read it with ",[18,1175,81],{}," and compare per-zone means against it for anomaly detection. Once you have the raster trimmed to a region with ",[31,1178,1180],{"href":1179},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fclip-raster-by-mask-layer-pyqgis\u002F","Clip a Raster by a Mask Layer in PyQGIS",", the same statistics calls report on just that area.",[40,1183,1185],{"id":1184},"exclude-nodata-from-the-result","Exclude Nodata from the Result",[14,1187,1188,1189,1192,1193,1195],{},"Statistics are only trustworthy if nodata pixels are excluded. The data-provider statistics respect the band's declared nodata value automatically, but if a raster carries an undeclared fill (a stray ",[18,1190,1191],{},"-9999"," or ",[18,1194,1048],{},"), you must register it first so it does not skew the mean:",[84,1197,1199],{"className":86,"code":1198,"language":88,"meta":89,"style":89},"from qgis.core import QgsRasterLayer, QgsRasterBandStats, QgsRasterRange\n\nraster = QgsRasterLayer(\"\u002Fdata\u002Fdem.tif\", \"dem\")\nprovider = raster.dataProvider()\n\n# Declare the fill value as nodata for band 1, then recompute\nprovider.setUserNoDataValue(1, [QgsRasterRange(-9999, -9999)])\nprovider.setUseSourceNoDataValue(1, True)\n\nstats = provider.bandStatistics(1, QgsRasterBandStats.All)\nprint(f\"Mean excluding fill: {stats.mean:.3f}\")\n",[18,1200,1201,1212,1216,1232,1240,1244,1249,1273,1287,1291,1303],{"__ignoreMap":89},[93,1202,1203,1205,1207,1209],{"class":95,"line":96},[93,1204,100],{"class":99},[93,1206,104],{"class":103},[93,1208,107],{"class":99},[93,1210,1211],{"class":103}," QgsRasterLayer, QgsRasterBandStats, QgsRasterRange\n",[93,1213,1214],{"class":95,"line":113},[93,1215,117],{"emptyLinePlaceholder":116},[93,1217,1218,1220,1222,1224,1226,1228,1230],{"class":95,"line":120},[93,1219,123],{"class":103},[93,1221,126],{"class":99},[93,1223,129],{"class":103},[93,1225,133],{"class":132},[93,1227,136],{"class":103},[93,1229,139],{"class":132},[93,1231,142],{"class":103},[93,1233,1234,1236,1238],{"class":95,"line":145},[93,1235,183],{"class":103},[93,1237,126],{"class":99},[93,1239,188],{"class":103},[93,1241,1242],{"class":95,"line":157},[93,1243,117],{"emptyLinePlaceholder":116},[93,1245,1246],{"class":95,"line":175},[93,1247,1248],{"class":783},"# Declare the fill value as nodata for band 1, then recompute\n",[93,1250,1251,1254,1256,1259,1261,1264,1266,1268,1270],{"class":95,"line":180},[93,1252,1253],{"class":103},"provider.setUserNoDataValue(",[93,1255,202],{"class":163},[93,1257,1258],{"class":103},", [QgsRasterRange(",[93,1260,829],{"class":99},[93,1262,1263],{"class":163},"9999",[93,1265,136],{"class":103},[93,1267,829],{"class":99},[93,1269,1263],{"class":163},[93,1271,1272],{"class":103},")])\n",[93,1274,1275,1278,1280,1282,1285],{"class":95,"line":191},[93,1276,1277],{"class":103},"provider.setUseSourceNoDataValue(",[93,1279,202],{"class":163},[93,1281,136],{"class":103},[93,1283,1284],{"class":163},"True",[93,1286,142],{"class":103},[93,1288,1289],{"class":95,"line":208},[93,1290,117],{"emptyLinePlaceholder":116},[93,1292,1293,1295,1297,1299,1301],{"class":95,"line":213},[93,1294,194],{"class":103},[93,1296,126],{"class":99},[93,1298,199],{"class":103},[93,1300,202],{"class":163},[93,1302,205],{"class":103},[93,1304,1305,1307,1309,1311,1314,1316,1318,1320,1322,1324],{"class":95,"line":244},[93,1306,216],{"class":163},[93,1308,167],{"class":103},[93,1310,221],{"class":99},[93,1312,1313],{"class":132},"\"Mean excluding fill: ",[93,1315,227],{"class":163},[93,1317,283],{"class":103},[93,1319,233],{"class":99},[93,1321,236],{"class":163},[93,1323,239],{"class":132},[93,1325,142],{"class":103},[14,1327,1328,372,1330,1333,1334,1336,1337,400,1340,1343,1344,1346,1347,1349,1350,1353],{},[68,1329,371],{},[18,1331,1332],{},"setUserNoDataValue"," registers ",[18,1335,1191],{}," as nodata for band 1 using a ",[18,1338,1339],{},"QgsRasterRange",[18,1341,1342],{},"setUseSourceNoDataValue"," ensures any nodata already in the file is honored too. After this, ",[18,1345,81],{}," ignores those pixels, so the mean and standard deviation reflect real data only. Import ",[18,1348,1339],{}," from ",[18,1351,1352],{},"qgis.core"," alongside the other classes.",[40,1355,1357],{"id":1356},"write-statistics-to-a-report","Write Statistics to a Report",[14,1359,1360],{},"For documentation or QA it is useful to dump the figures to a small text or CSV report rather than just printing them. Reuse the per-band loop and write rows:",[84,1362,1364],{"className":86,"code":1363,"language":88,"meta":89,"style":89},"import csv\nfrom qgis.core import QgsRasterLayer, QgsRasterBandStats\n\nraster = QgsRasterLayer(\"\u002Fdata\u002Flandsat.tif\", \"scene\")\nprovider = raster.dataProvider()\n\nwith open(\"\u002Fdata\u002Foutput\u002Fband_stats.csv\", \"w\", newline=\"\") as fh:\n    writer = csv.writer(fh)\n    writer.writerow([\"band\", \"name\", \"min\", \"max\", \"mean\", \"stddev\"])\n    for band in range(1, raster.bandCount() + 1):\n        s = provider.bandStatistics(band, QgsRasterBandStats.All)\n        writer.writerow([\n            band, raster.bandName(band),\n            f\"{s.minimumValue:.4f}\", f\"{s.maximumValue:.4f}\",\n            f\"{s.mean:.4f}\", f\"{s.stdDev:.4f}\",\n        ])\n\nprint(\"Wrote band_stats.csv\")\n",[18,1365,1366,1373,1383,1387,1403,1411,1415,1453,1463,1498,1521,1530,1535,1540,1576,1611,1616,1620],{"__ignoreMap":89},[93,1367,1368,1370],{"class":95,"line":96},[93,1369,107],{"class":99},[93,1371,1372],{"class":103}," csv\n",[93,1374,1375,1377,1379,1381],{"class":95,"line":113},[93,1376,100],{"class":99},[93,1378,104],{"class":103},[93,1380,107],{"class":99},[93,1382,110],{"class":103},[93,1384,1385],{"class":95,"line":120},[93,1386,117],{"emptyLinePlaceholder":116},[93,1388,1389,1391,1393,1395,1397,1399,1401],{"class":95,"line":145},[93,1390,123],{"class":103},[93,1392,126],{"class":99},[93,1394,129],{"class":103},[93,1396,538],{"class":132},[93,1398,136],{"class":103},[93,1400,543],{"class":132},[93,1402,142],{"class":103},[93,1404,1405,1407,1409],{"class":95,"line":157},[93,1406,183],{"class":103},[93,1408,126],{"class":99},[93,1410,188],{"class":103},[93,1412,1413],{"class":95,"line":175},[93,1414,117],{"emptyLinePlaceholder":116},[93,1416,1417,1420,1423,1425,1428,1430,1433,1435,1439,1441,1444,1447,1450],{"class":95,"line":180},[93,1418,1419],{"class":99},"with",[93,1421,1422],{"class":163}," open",[93,1424,167],{"class":103},[93,1426,1427],{"class":132},"\"\u002Fdata\u002Foutput\u002Fband_stats.csv\"",[93,1429,136],{"class":103},[93,1431,1432],{"class":132},"\"w\"",[93,1434,136],{"class":103},[93,1436,1438],{"class":1437},"s9osk","newline",[93,1440,126],{"class":99},[93,1442,1443],{"class":132},"\"\"",[93,1445,1446],{"class":103},") ",[93,1448,1449],{"class":99},"as",[93,1451,1452],{"class":103}," fh:\n",[93,1454,1455,1458,1460],{"class":95,"line":191},[93,1456,1457],{"class":103},"    writer ",[93,1459,126],{"class":99},[93,1461,1462],{"class":103}," csv.writer(fh)\n",[93,1464,1465,1468,1471,1473,1476,1478,1481,1483,1486,1488,1491,1493,1496],{"class":95,"line":208},[93,1466,1467],{"class":103},"    writer.writerow([",[93,1469,1470],{"class":132},"\"band\"",[93,1472,136],{"class":103},[93,1474,1475],{"class":132},"\"name\"",[93,1477,136],{"class":103},[93,1479,1480],{"class":132},"\"min\"",[93,1482,136],{"class":103},[93,1484,1485],{"class":132},"\"max\"",[93,1487,136],{"class":103},[93,1489,1490],{"class":132},"\"mean\"",[93,1492,136],{"class":103},[93,1494,1495],{"class":132},"\"stddev\"",[93,1497,1121],{"class":103},[93,1499,1500,1503,1505,1507,1509,1511,1513,1515,1517,1519],{"class":95,"line":213},[93,1501,1502],{"class":99},"    for",[93,1504,565],{"class":103},[93,1506,568],{"class":99},[93,1508,571],{"class":163},[93,1510,167],{"class":103},[93,1512,202],{"class":163},[93,1514,578],{"class":103},[93,1516,581],{"class":99},[93,1518,584],{"class":163},[93,1520,587],{"class":103},[93,1522,1523,1526,1528],{"class":95,"line":244},[93,1524,1525],{"class":103},"        s ",[93,1527,126],{"class":99},[93,1529,597],{"class":103},[93,1531,1532],{"class":95,"line":269},[93,1533,1534],{"class":103},"        writer.writerow([\n",[93,1536,1537],{"class":95,"line":294},[93,1538,1539],{"class":103},"            band, raster.bandName(band),\n",[93,1541,1542,1545,1547,1549,1551,1554,1556,1558,1560,1562,1564,1566,1568,1570,1572,1574],{"class":95,"line":319},[93,1543,1544],{"class":99},"            f",[93,1546,239],{"class":132},[93,1548,227],{"class":163},[93,1550,646],{"class":103},[93,1552,1553],{"class":99},":.4f",[93,1555,236],{"class":163},[93,1557,239],{"class":132},[93,1559,136],{"class":103},[93,1561,221],{"class":99},[93,1563,239],{"class":132},[93,1565,227],{"class":163},[93,1567,658],{"class":103},[93,1569,1553],{"class":99},[93,1571,236],{"class":163},[93,1573,239],{"class":132},[93,1575,1003],{"class":103},[93,1577,1578,1580,1582,1584,1586,1588,1590,1592,1594,1596,1598,1600,1603,1605,1607,1609],{"class":95,"line":344},[93,1579,1544],{"class":99},[93,1581,239],{"class":132},[93,1583,227],{"class":163},[93,1585,633],{"class":103},[93,1587,1553],{"class":99},[93,1589,236],{"class":163},[93,1591,239],{"class":132},[93,1593,136],{"class":103},[93,1595,221],{"class":99},[93,1597,239],{"class":132},[93,1599,227],{"class":163},[93,1601,1602],{"class":103},"s.stdDev",[93,1604,1553],{"class":99},[93,1606,236],{"class":163},[93,1608,239],{"class":132},[93,1610,1003],{"class":103},[93,1612,1613],{"class":95,"line":841},[93,1614,1615],{"class":103},"        ])\n",[93,1617,1618],{"class":95,"line":857},[93,1619,117],{"emptyLinePlaceholder":116},[93,1621,1622,1624,1626,1629],{"class":95,"line":879},[93,1623,216],{"class":163},[93,1625,167],{"class":103},[93,1627,1628],{"class":132},"\"Wrote band_stats.csv\"",[93,1630,142],{"class":103},[14,1632,1633,1635,1636,1639],{},[68,1634,371],{}," The standard-library ",[18,1637,1638],{},"csv"," module writes a header then one row per band, formatting each statistic to four decimals. This produces a portable summary you can attach to a data delivery or diff between processing runs to catch regressions.",[40,1641,1643],{"id":1642},"qgis-version-compatibility","QGIS Version Compatibility",[14,1645,1646,1647,1650],{},"The code targets ",[68,1648,1649],{},"QGIS 3.34 LTR"," (Python 3.12).",[1652,1653,1654,1670],"table",{},[1655,1656,1657],"thead",{},[1658,1659,1660,1664,1667],"tr",{},[1661,1662,1663],"th",{},"QGIS version",[1661,1665,1666],{},"Python",[1661,1668,1669],{},"Notes",[1671,1672,1673,1692,1703],"tbody",{},[1658,1674,1675,1679,1682],{},[1676,1677,1678],"td",{},"3.28 LTR",[1676,1680,1681],{},"3.9",[1676,1683,1684,835,1686,1688,1689,1691],{},[18,1685,81],{},[18,1687,692],{}," identical; ",[18,1690,28],{}," available.",[1658,1693,1694,1697,1700],{},[1676,1695,1696],{},"3.34 LTR",[1676,1698,1699],{},"3.12",[1676,1701,1702],{},"Baseline for this page.",[1658,1704,1705,1708,1710],{},[1676,1706,1707],{},"3.40 \u002F 3.44",[1676,1709,1699],{},[1676,1711,1712],{},"Same APIs; minor speedups on large-raster scans.",[14,1714,1715,1716,1719,1720,1722,1723,1726],{},"The older ",[18,1717,1718],{},"native:zonalstatistics"," algorithm edited the input layer in place and is deprecated in favor of ",[18,1721,28],{},". Prefer the ",[18,1724,1725],{},"fb"," variant on all current versions; it leaves the source untouched and returns a clean output layer.",[40,1728,1730],{"id":1729},"troubleshooting","Troubleshooting",[45,1732,1733,1745,1754,1760,1779],{},[48,1734,1735,1738,1739,1741,1742,35],{},[68,1736,1737],{},"All statistics return 0 or nan."," The band has nodata where you expect data, or you used a 0-based band index. Use band ",[18,1740,202],{}," for the first band and confirm the nodata value with ",[18,1743,1744],{},"provider.sourceNoDataValue(1)",[48,1746,1747,1750,1751,1753],{},[68,1748,1749],{},"Statistics seem wrong on a huge raster."," You may have passed a ",[18,1752,492],{},", giving an approximation. Drop the sample arguments for an exact full-scan result.",[48,1755,1756,1759],{},[68,1757,1758],{},"Zonal output has empty stat columns."," The polygons do not overlap the raster, or their CRS differs. Reproject the vector layer to the raster's CRS and confirm extents intersect.",[48,1761,1762,1768,1769,1771,1772,1775,1776,1778],{},[68,1763,1764,1767],{},[18,1765,1766],{},"AttributeError"," on stats object."," Use attributes (",[18,1770,283],{},") not method calls (",[18,1773,1774],{},"stats.mean()","). ",[18,1777,24],{}," exposes plain fields.",[48,1780,1781,1784,1785,1787,1788,1790],{},[68,1782,1783],{},"Histogram bins look skewed."," You did not bound the range; pass ",[18,1786,230],{}," and ",[18,1789,258],{}," so bins span the real data range.",[40,1792,1794],{"id":1793},"conclusion","Conclusion",[14,1796,1797,1798,1800,1801,1803,1804,1806],{},"PyQGIS gives you raster statistics at two scales. ",[18,1799,81],{}," reads whole-band min, max, mean, and standard deviation in one call, and ",[18,1802,692],{}," reveals the value distribution behind those numbers — both exact or sampled for speed. For per-feature summaries, ",[18,1805,28],{}," writes count, mean, and other statistics into each polygon. Remembering that band indices are 1-based and that vector and raster must share a CRS is what keeps these results trustworthy across DEMs, imagery, and analysis pipelines.",[40,1808,1810],{"id":1809},"frequently-asked-questions","Frequently Asked Questions",[14,1812,1813,1816,1817,1820,1821,1823],{},[68,1814,1815],{},"Are raster band indices 0-based or 1-based?","\n1-based. Band 1 is the first band in both ",[18,1818,1819],{},"bandStatistics(1, ...)"," and the ",[18,1822,1165],{}," parameter of zonal statistics. Using 0 raises an error or returns empty results.",[14,1825,1826,1829,1830,489,1832,1834,1835,1837],{},[68,1827,1828],{},"How do I compute statistics faster on a large raster?","\nPass an ",[18,1831,488],{},[18,1833,492],{}," to ",[18,1836,81],{},". This reads a capped number of pixels and returns an approximation, which is far quicker than an exact full scan and usually accurate enough for ramp limits.",[14,1839,1840,1843,1844,1846,1847,1849],{},[68,1841,1842],{},"What is the difference between zonalstatistics and zonalstatisticsfb?","\nThe legacy ",[18,1845,1718],{}," modifies the input vector layer in place. ",[18,1848,28],{}," returns a new feature-based output and leaves the source untouched — the recommended choice on current QGIS.",[14,1851,1852,1855],{},[68,1853,1854],{},"Why are my zonal statistics columns empty?","\nThe polygons and the raster do not overlap, or they are in different coordinate reference systems. Reproject the vector layer to the raster's CRS and verify their extents intersect before running.",[40,1857,1859],{"id":1858},"related","Related",[45,1861,1862,1866,1870],{},[48,1863,1864],{},[31,1865,34],{"href":33},[48,1867,1868],{},[31,1869,1180],{"href":1179},[48,1871,1872],{},[31,1873,500],{"href":499},[1875,1876,1877],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":89,"searchDepth":113,"depth":113,"links":1879},[1880,1881,1882,1883,1884,1885,1886,1887,1888,1889,1890,1891],{"id":42,"depth":113,"text":43},{"id":74,"depth":113,"text":75},{"id":504,"depth":113,"text":505},{"id":685,"depth":113,"text":686},{"id":950,"depth":113,"text":951},{"id":1184,"depth":113,"text":1185},{"id":1356,"depth":113,"text":1357},{"id":1642,"depth":113,"text":1643},{"id":1729,"depth":113,"text":1730},{"id":1793,"depth":113,"text":1794},{"id":1809,"depth":113,"text":1810},{"id":1858,"depth":113,"text":1859},"Calculate raster statistics in PyQGIS. Read band min, max, mean and stddev with QgsRasterBandStats, run zonal statistics, and build a histogram.","md",{},"\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fcalculate-raster-statistics-pyqgis",{"title":5,"description":1892},"spatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fcalculate-raster-statistics-pyqgis\u002Findex","YGI9yqn8x_GO_kn_3a_uGJDraXsjTr1DCnU3ZJBQkPY",1781792483477]