[{"data":1,"prerenderedAt":2495},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fcalculate-raster-statistics-pyqgis":3},{"id":4,"title":5,"body":6,"description":2483,"extension":2484,"meta":2485,"navigation":382,"path":2491,"seo":2492,"stem":2493,"__hash__":2494},"docs\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fcalculate-raster-statistics-pyqgis\u002Findex.md","Calculate Raster Statistics in PyQGIS",{"type":7,"value":8,"toc":2467},"minimark",[9,13,36,39,303,306,311,331,339,343,350,633,671,674,747,760,768,772,775,933,949,953,960,1195,1214,1218,1224,1387,1433,1444,1448,1458,1588,1616,1620,1623,1893,1902,1906,1909,2096,2099,2103,2106,2226,2229,2233,2240,2302,2316,2320,2380,2384,2396,2400,2413,2427,2439,2445,2449,2463],[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",", the parent guide this page sits under.",[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. Raster statistics come at two scales — the whole band in one call, or per-feature summaries that break the surface down by an overlaid vector layer — and choosing the right one is the crux of most workflows.",[14,40,41],{},[42,43,48,52,56,63,80,92,101,106,138,143,148,155,159,195,199,201,223,228,233,236,240,243,245,249,299],"svg",{"viewBox":44,"role":45,"ariaLabel":46,"xmlns":47},"0 0 820 430","img","Two scales of raster statistics: whole-band bandStatistics returns one summary for the entire band, while zonalstatisticsfb returns one row per overlaid polygon","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[49,50,51],"title",{},"Whole-band statistics versus per-polygon zonal statistics",[53,54,55],"desc",{},"On the left, bandStatistics reads every pixel of a raster band and returns a single QgsRasterBandStatistics object with one minimum, maximum, mean and standard deviation. On the right, native:zonalstatisticsfb overlays polygons on the same raster and returns a new layer with one row of count and mean per polygon feature. Same raster, two scales of summary.",[57,58],"rect",{"x":59,"y":59,"width":60,"height":61,"fill":62},"0","820","430","#f6f3ea",[64,65,66],"defs",{},[67,68,75],"marker",{"id":69,"viewBox":70,"refX":71,"refY":72,"markerWidth":73,"markerHeight":73,"orient":74},"rs-arrow","0 0 10 10","8","5","7","auto-start-reverse",[76,77],"path",{"d":78,"fill":79},"M0 0 L10 5 L0 10 z","#2f3b35",[81,82],"line",{"x1":83,"y1":84,"x2":83,"y2":85,"stroke":86,"style":87,"strokeDashArray":88,"opacity":91},"410","34","400","currentColor","stroke-width:1.5",[89,90],"4","6","0.4",[93,94,100],"text",{"x":95,"y":96,"style":97,"fill":98,"textAnchor":99},"210","26","text-anchor:middle;font-size:16px;font-weight:bold;font-family:sans-serif","#17211d","middle","Whole-band statistics",[93,102,105],{"x":95,"y":103,"style":104,"fill":79,"textAnchor":99},"48","text-anchor:middle;font-size:13px;font-family:monospace;opacity:0.72","bandStatistics(1, All)",[107,108,111,118,122,125,128,131,135],"g",{"stroke":109,"style":110},"#3b82f6","stroke-width:2",[57,112],{"x":113,"y":114,"width":115,"height":116,"rx":89,"fill":117},"110","70","200","108","#fffdf7",[81,119],{"x1":120,"y1":114,"x2":120,"y2":121},"150","178",[81,123],{"x1":124,"y1":114,"x2":124,"y2":121},"190",[81,126],{"x1":127,"y1":114,"x2":127,"y2":121},"230",[81,129],{"x1":130,"y1":114,"x2":130,"y2":121},"270",[81,132],{"x1":113,"y1":133,"x2":134,"y2":133},"106","310",[81,136],{"x1":113,"y1":137,"x2":134,"y2":137},"142",[93,139,142],{"x":95,"y":140,"style":141,"fill":79,"textAnchor":99},"196","text-anchor:middle;font-size:12px;font-family:sans-serif;opacity:0.72","every pixel of band 1",[81,144],{"x1":95,"y1":145,"x2":95,"y2":146,"stroke":86,"style":147},"204","242","stroke-width:2.5;marker-end:url(#rs-arrow)",[57,149],{"x":150,"y":151,"width":127,"height":120,"rx":152,"fill":117,"stroke":153,"style":154},"95","248","10","#0d9488","stroke-width:2.5",[93,156,24],{"x":95,"y":157,"style":158,"fill":98,"textAnchor":99},"272","text-anchor:middle;font-size:12px;font-weight:bold;font-family:monospace",[107,160,162,168,174,178,181,185,188,192],{"style":161,"fill":79},"font-size:13px;font-family:sans-serif",[93,163,167],{"x":164,"y":165,"style":166,"fill":79},"120","302","opacity:0.72","Min",[93,169,173],{"x":170,"y":165,"style":171,"textAnchor":172,"fill":79},"300","text-anchor:end;font-family:monospace","end","402.1",[93,175,177],{"x":164,"y":176,"style":166,"fill":79},"328","Max",[93,179,180],{"x":170,"y":176,"style":171,"textAnchor":172,"fill":79},"1180.6",[93,182,184],{"x":164,"y":183,"style":166,"fill":79},"354","Mean",[93,186,187],{"x":170,"y":183,"style":171,"textAnchor":172,"fill":79},"731.4",[93,189,191],{"x":164,"y":190,"style":166,"fill":79},"380","StdDev",[93,193,194],{"x":170,"y":190,"style":171,"textAnchor":172,"fill":79},"142.8",[93,196,198],{"x":197,"y":96,"style":97,"fill":98,"textAnchor":99},"615","Per-polygon statistics",[93,200,28],{"x":197,"y":103,"style":104,"fill":79,"textAnchor":99},[107,202,203,206,209,212,215,218,221],{"stroke":109,"style":110},[57,204],{"x":205,"y":114,"width":115,"height":116,"rx":89,"fill":117},"515",[81,207],{"x1":208,"y1":114,"x2":208,"y2":121},"555",[81,210],{"x1":211,"y1":114,"x2":211,"y2":121},"595",[81,213],{"x1":214,"y1":114,"x2":214,"y2":121},"635",[81,216],{"x1":217,"y1":114,"x2":217,"y2":121},"675",[81,219],{"x1":205,"y1":133,"x2":220,"y2":133},"715",[81,222],{"x1":205,"y1":137,"x2":220,"y2":137},[76,224],{"d":225,"fill":226,"fillOpacity":227,"stroke":226,"style":154},"M528 82 L580 78 L596 116 L546 132 Z","#d97706",0.18,[93,229,232],{"x":230,"y":116,"style":231,"fill":98,"textAnchor":99},"558","text-anchor:middle;font-size:13px;font-weight:bold;font-family:sans-serif","A",[76,234],{"d":235,"fill":226,"fillOpacity":227,"stroke":226,"style":154},"M612 118 L700 108 L706 168 L626 172 Z",[93,237,239],{"x":238,"y":120,"style":231,"fill":98,"textAnchor":99},"662","B",[93,241,242],{"x":197,"y":140,"style":141,"fill":79,"textAnchor":99},"polygons overlaid on band 1",[81,244],{"x1":197,"y1":145,"x2":197,"y2":146,"stroke":86,"style":147},[57,246],{"x":247,"y":151,"width":248,"height":120,"rx":152,"fill":117,"stroke":226,"style":154},"470","290",[107,250,252,258,261,265,271,275,277,280,283,286,289,293,296],{"style":251,"fill":79},"font-size:13px;font-family:monospace",[93,253,257],{"x":254,"y":255,"style":256,"textAnchor":99,"fill":98},"505","276","text-anchor:middle;font-weight:bold;opacity:0.72","id",[93,259,260],{"x":197,"y":255,"style":256,"textAnchor":99,"fill":98},"elev_count",[93,262,264],{"x":263,"y":255,"style":256,"textAnchor":99,"fill":98},"714","elev_mean",[81,266],{"x1":267,"y1":268,"x2":269,"y2":268,"stroke":86,"style":87,"opacity":270},"490","286","740","0.35",[93,272,232],{"x":254,"y":273,"style":274,"textAnchor":99,"fill":79},"312","text-anchor:middle",[93,276,95],{"x":197,"y":273,"style":274,"textAnchor":99,"fill":79},[93,278,279],{"x":263,"y":273,"style":274,"textAnchor":99,"fill":79},"690.2",[93,281,239],{"x":254,"y":282,"style":274,"textAnchor":99,"fill":79},"340",[93,284,285],{"x":197,"y":282,"style":274,"textAnchor":99,"fill":79},"158",[93,287,288],{"x":263,"y":282,"style":274,"textAnchor":99,"fill":79},"774.5",[93,290,292],{"x":254,"y":291,"style":274,"textAnchor":99,"fill":79},"368","C",[93,294,295],{"x":197,"y":291,"style":274,"textAnchor":99,"fill":79},"305",[93,297,298],{"x":263,"y":291,"style":274,"textAnchor":99,"fill":79},"712.0",[93,300,302],{"x":83,"y":301,"style":141,"fill":79,"textAnchor":99},"422","Same raster, two scales — one summary for the whole band, or one row per overlaid polygon feature.",[14,304,305],{},"Because a statistic is only as honest as the pixels behind it, the sections below also cover excluding nodata and pinning results to a defined value range so numbers stay comparable between runs.",[307,308,310],"h2",{"id":309},"prerequisites","Prerequisites",[312,313,314,318,321,324],"ul",{},[315,316,317],"li",{},"QGIS 3.34 LTR (bundled Python 3.12) with Processing available.",[315,319,320],{},"A single- or multi-band raster (a GeoTIFF DEM, NDVI grid, or similar).",[315,322,323],{},"For zonal statistics, a polygon vector layer overlapping the raster.",[315,325,326,327,330],{},"The QGIS Python Console (",[18,328,329],{},"Plugins > Python Console",").",[14,332,333,334,338],{},"Band indices in the data-provider API are ",[335,336,337],"strong",{},"1-based",": band 1 is the first band, not band 0.",[307,340,342],{"id":341},"read-band-statistics","Read Band Statistics",[14,344,345,346,349],{},"Load the raster and ask its data provider for statistics on a band. The ",[18,347,348],{},"bandStatistics"," method accepts a flag for which statistics to compute and returns an object with the results.",[351,352,357],"pre",{"className":353,"code":354,"language":355,"meta":356,"style":356},"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,358,359,377,384,409,421,439,444,455,472,477,508,533,558,583,608],{"__ignoreMap":356},[360,361,363,367,371,374],"span",{"class":81,"line":362},1,[360,364,366],{"class":365},"snl16","from",[360,368,370],{"class":369},"s95oV"," qgis.core ",[360,372,373],{"class":365},"import",[360,375,376],{"class":369}," QgsRasterLayer, QgsRasterBandStats\n",[360,378,380],{"class":81,"line":379},2,[360,381,383],{"emptyLinePlaceholder":382},true,"\n",[360,385,387,390,393,396,400,403,406],{"class":81,"line":386},3,[360,388,389],{"class":369},"raster ",[360,391,392],{"class":365},"=",[360,394,395],{"class":369}," QgsRasterLayer(",[360,397,399],{"class":398},"sU2Wk","\"\u002Fdata\u002Fdem.tif\"",[360,401,402],{"class":369},", ",[360,404,405],{"class":398},"\"dem\"",[360,407,408],{"class":369},")\n",[360,410,412,415,418],{"class":81,"line":411},4,[360,413,414],{"class":365},"if",[360,416,417],{"class":365}," not",[360,419,420],{"class":369}," raster.isValid():\n",[360,422,424,427,431,434,437],{"class":81,"line":423},5,[360,425,426],{"class":365},"    raise",[360,428,430],{"class":429},"sDLfK"," RuntimeError",[360,432,433],{"class":369},"(",[360,435,436],{"class":398},"\"Raster failed to load\"",[360,438,408],{"class":369},[360,440,442],{"class":81,"line":441},6,[360,443,383],{"emptyLinePlaceholder":382},[360,445,447,450,452],{"class":81,"line":446},7,[360,448,449],{"class":369},"provider ",[360,451,392],{"class":365},[360,453,454],{"class":369}," raster.dataProvider()\n",[360,456,458,461,463,466,469],{"class":81,"line":457},8,[360,459,460],{"class":369},"stats ",[360,462,392],{"class":365},[360,464,465],{"class":369}," provider.bandStatistics(",[360,467,468],{"class":429},"1",[360,470,471],{"class":369},", QgsRasterBandStats.All)\n",[360,473,475],{"class":81,"line":474},9,[360,476,383],{"emptyLinePlaceholder":382},[360,478,480,483,485,488,491,494,497,500,503,506],{"class":81,"line":479},10,[360,481,482],{"class":429},"print",[360,484,433],{"class":369},[360,486,487],{"class":365},"f",[360,489,490],{"class":398},"\"Min:    ",[360,492,493],{"class":429},"{",[360,495,496],{"class":369},"stats.minimumValue",[360,498,499],{"class":365},":.3f",[360,501,502],{"class":429},"}",[360,504,505],{"class":398},"\"",[360,507,408],{"class":369},[360,509,511,513,515,517,520,522,525,527,529,531],{"class":81,"line":510},11,[360,512,482],{"class":429},[360,514,433],{"class":369},[360,516,487],{"class":365},[360,518,519],{"class":398},"\"Max:    ",[360,521,493],{"class":429},[360,523,524],{"class":369},"stats.maximumValue",[360,526,499],{"class":365},[360,528,502],{"class":429},[360,530,505],{"class":398},[360,532,408],{"class":369},[360,534,536,538,540,542,545,547,550,552,554,556],{"class":81,"line":535},12,[360,537,482],{"class":429},[360,539,433],{"class":369},[360,541,487],{"class":365},[360,543,544],{"class":398},"\"Mean:   ",[360,546,493],{"class":429},[360,548,549],{"class":369},"stats.mean",[360,551,499],{"class":365},[360,553,502],{"class":429},[360,555,505],{"class":398},[360,557,408],{"class":369},[360,559,561,563,565,567,570,572,575,577,579,581],{"class":81,"line":560},13,[360,562,482],{"class":429},[360,564,433],{"class":369},[360,566,487],{"class":365},[360,568,569],{"class":398},"\"StdDev: ",[360,571,493],{"class":429},[360,573,574],{"class":369},"stats.stdDev",[360,576,499],{"class":365},[360,578,502],{"class":429},[360,580,505],{"class":398},[360,582,408],{"class":369},[360,584,586,588,590,592,595,597,600,602,604,606],{"class":81,"line":585},14,[360,587,482],{"class":429},[360,589,433],{"class":369},[360,591,487],{"class":365},[360,593,594],{"class":398},"\"Range:  ",[360,596,493],{"class":429},[360,598,599],{"class":369},"stats.range",[360,601,499],{"class":365},[360,603,502],{"class":429},[360,605,505],{"class":398},[360,607,408],{"class":369},[360,609,611,613,615,617,620,622,625,627,629,631],{"class":81,"line":610},15,[360,612,482],{"class":429},[360,614,433],{"class":369},[360,616,487],{"class":365},[360,618,619],{"class":398},"\"Sum:    ",[360,621,493],{"class":429},[360,623,624],{"class":369},"stats.sum",[360,626,499],{"class":365},[360,628,502],{"class":429},[360,630,505],{"class":398},[360,632,408],{"class":369},[14,634,635,638,639,642,643,646,647,650,651,402,654,402,657,402,660,402,663,666,667,670],{},[335,636,637],{},"Breakdown:"," ",[18,640,641],{},"bandStatistics(1, QgsRasterBandStats.All)"," computes the full set for band 1. ",[18,644,645],{},"QgsRasterBandStats.All"," forces a complete scan; passing a narrower flag such as ",[18,648,649],{},"QgsRasterBandStats.Min | QgsRasterBandStats.Max"," is faster when you only need extremes. The returned object exposes ",[18,652,653],{},"minimumValue",[18,655,656],{},"maximumValue",[18,658,659],{},"mean",[18,661,662],{},"stdDev",[18,664,665],{},"range",", and ",[18,668,669],{},"sum"," as plain attributes — no method calls needed.",[14,672,673],{},"For large rasters where an exact scan is slow, request statistics over a sample extent and resolution to get a fast approximation:",[351,675,677],{"className":353,"code":676,"language":355,"meta":356,"style":356},"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,678,679,689,699,713],{"__ignoreMap":356},[360,680,681,684,686],{"class":81,"line":362},[360,682,683],{"class":369},"extent ",[360,685,392],{"class":365},[360,687,688],{"class":369}," raster.extent()\n",[360,690,691,694,696],{"class":81,"line":379},[360,692,693],{"class":369},"sample_size ",[360,695,392],{"class":365},[360,697,698],{"class":429}," 250000\n",[360,700,701,704,706,708,710],{"class":81,"line":386},[360,702,703],{"class":369},"approx ",[360,705,392],{"class":365},[360,707,465],{"class":369},[360,709,468],{"class":429},[360,711,712],{"class":369},", QgsRasterBandStats.All, extent, sample_size)\n",[360,714,715,717,719,721,724,726,729,731,734,736,739,741,743,745],{"class":81,"line":411},[360,716,482],{"class":429},[360,718,433],{"class":369},[360,720,487],{"class":365},[360,722,723],{"class":398},"\"Approx mean over ",[360,725,493],{"class":429},[360,727,728],{"class":369},"sample_size",[360,730,502],{"class":429},[360,732,733],{"class":398}," px: ",[360,735,493],{"class":429},[360,737,738],{"class":369},"approx.mean",[360,740,499],{"class":365},[360,742,502],{"class":429},[360,744,505],{"class":398},[360,746,408],{"class":369},[14,748,749,751,752,755,756,759],{},[335,750,637],{}," Supplying an ",[18,753,754],{},"extent"," and a ",[18,757,758],{},"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,761,762,763,767],{},"The min and max you get here are what you would feed into a color ramp — see ",[31,764,766],{"href":765},"\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.",[307,769,771],{"id":770},"loop-over-multiple-bands","Loop Over Multiple Bands",[14,773,774],{},"For multi-band imagery (e.g. RGB or multispectral), iterate the band count and collect per-band statistics:",[351,776,778],{"className":353,"code":777,"language":355,"meta":356,"style":356},"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,779,780,790,794,812,820,824,854,864,874],{"__ignoreMap":356},[360,781,782,784,786,788],{"class":81,"line":362},[360,783,366],{"class":365},[360,785,370],{"class":369},[360,787,373],{"class":365},[360,789,376],{"class":369},[360,791,792],{"class":81,"line":379},[360,793,383],{"emptyLinePlaceholder":382},[360,795,796,798,800,802,805,807,810],{"class":81,"line":386},[360,797,389],{"class":369},[360,799,392],{"class":365},[360,801,395],{"class":369},[360,803,804],{"class":398},"\"\u002Fdata\u002Flandsat.tif\"",[360,806,402],{"class":369},[360,808,809],{"class":398},"\"scene\"",[360,811,408],{"class":369},[360,813,814,816,818],{"class":81,"line":411},[360,815,449],{"class":369},[360,817,392],{"class":365},[360,819,454],{"class":369},[360,821,822],{"class":81,"line":423},[360,823,383],{"emptyLinePlaceholder":382},[360,825,826,829,832,835,838,840,842,845,848,851],{"class":81,"line":441},[360,827,828],{"class":365},"for",[360,830,831],{"class":369}," band ",[360,833,834],{"class":365},"in",[360,836,837],{"class":429}," range",[360,839,433],{"class":369},[360,841,468],{"class":429},[360,843,844],{"class":369},", raster.bandCount() ",[360,846,847],{"class":365},"+",[360,849,850],{"class":429}," 1",[360,852,853],{"class":369},"):\n",[360,855,856,859,861],{"class":81,"line":446},[360,857,858],{"class":369},"    s ",[360,860,392],{"class":365},[360,862,863],{"class":369}," provider.bandStatistics(band, QgsRasterBandStats.All)\n",[360,865,866,869,871],{"class":81,"line":457},[360,867,868],{"class":369},"    name ",[360,870,392],{"class":365},[360,872,873],{"class":369}," raster.bandName(band)\n",[360,875,876,879,881,883,885,887,890,892,895,897,900,903,905,908,910,913,915,917,920,922,925,927,929,931],{"class":81,"line":474},[360,877,878],{"class":429},"    print",[360,880,433],{"class":369},[360,882,487],{"class":365},[360,884,505],{"class":398},[360,886,493],{"class":429},[360,888,889],{"class":369},"name",[360,891,502],{"class":429},[360,893,894],{"class":398},": mean=",[360,896,493],{"class":429},[360,898,899],{"class":369},"s.mean",[360,901,902],{"class":365},":.2f",[360,904,502],{"class":429},[360,906,907],{"class":398}," min=",[360,909,493],{"class":429},[360,911,912],{"class":369},"s.minimumValue",[360,914,902],{"class":365},[360,916,502],{"class":429},[360,918,919],{"class":398}," max=",[360,921,493],{"class":429},[360,923,924],{"class":369},"s.maximumValue",[360,926,902],{"class":365},[360,928,502],{"class":429},[360,930,505],{"class":398},[360,932,408],{"class":369},[14,934,935,638,937,940,941,944,945,948],{},[335,936,637],{},[18,938,939],{},"bandCount()"," gives the number of bands and ",[18,942,943],{},"range(1, bandCount() + 1)"," produces 1-based indices. ",[18,946,947],{},"bandName(band)"," returns a human-readable label. This pattern is the basis for per-band normalization or building a summary report across a scene.",[307,950,952],{"id":951},"build-a-histogram","Build a Histogram",[14,954,955,956,959],{},"A histogram shows how many pixels fall into each value bin — useful for spotting bimodal distributions, saturation, or nodata spikes. The provider's ",[18,957,958],{},"histogram"," method returns the bin counts:",[351,961,963],{"className":353,"code":962,"language":355,"meta":356,"style":356},"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,964,965,975,979,995,1003,1015,1019,1029,1039,1051,1059,1067,1075,1079,1083,1105,1121,1143],{"__ignoreMap":356},[360,966,967,969,971,973],{"class":81,"line":362},[360,968,366],{"class":365},[360,970,370],{"class":369},[360,972,373],{"class":365},[360,974,376],{"class":369},[360,976,977],{"class":81,"line":379},[360,978,383],{"emptyLinePlaceholder":382},[360,980,981,983,985,987,989,991,993],{"class":81,"line":386},[360,982,389],{"class":369},[360,984,392],{"class":365},[360,986,395],{"class":369},[360,988,399],{"class":398},[360,990,402],{"class":369},[360,992,405],{"class":398},[360,994,408],{"class":369},[360,996,997,999,1001],{"class":81,"line":411},[360,998,449],{"class":369},[360,1000,392],{"class":365},[360,1002,454],{"class":369},[360,1004,1005,1007,1009,1011,1013],{"class":81,"line":423},[360,1006,460],{"class":369},[360,1008,392],{"class":365},[360,1010,465],{"class":369},[360,1012,468],{"class":429},[360,1014,471],{"class":369},[360,1016,1017],{"class":81,"line":441},[360,1018,383],{"emptyLinePlaceholder":382},[360,1020,1021,1024,1026],{"class":81,"line":446},[360,1022,1023],{"class":369},"bin_count ",[360,1025,392],{"class":365},[360,1027,1028],{"class":429}," 20\n",[360,1030,1031,1034,1036],{"class":81,"line":457},[360,1032,1033],{"class":369},"hist ",[360,1035,392],{"class":365},[360,1037,1038],{"class":369}," provider.histogram(\n",[360,1040,1041,1044,1047],{"class":81,"line":474},[360,1042,1043],{"class":429},"    1",[360,1045,1046],{"class":369},",                       ",[360,1048,1050],{"class":1049},"sjoCn","# band\n",[360,1052,1053,1056],{"class":81,"line":479},[360,1054,1055],{"class":369},"    bin_count,               ",[360,1057,1058],{"class":1049},"# number of bins\n",[360,1060,1061,1064],{"class":81,"line":510},[360,1062,1063],{"class":369},"    stats.minimumValue,      ",[360,1065,1066],{"class":1049},"# range minimum\n",[360,1068,1069,1072],{"class":81,"line":535},[360,1070,1071],{"class":369},"    stats.maximumValue,      ",[360,1073,1074],{"class":1049},"# range maximum\n",[360,1076,1077],{"class":81,"line":560},[360,1078,408],{"class":369},[360,1080,1081],{"class":81,"line":585},[360,1082,383],{"emptyLinePlaceholder":382},[360,1084,1085,1088,1090,1093,1096,1099,1102],{"class":81,"line":610},[360,1086,1087],{"class":369},"width ",[360,1089,392],{"class":365},[360,1091,1092],{"class":369}," (stats.maximumValue ",[360,1094,1095],{"class":365},"-",[360,1097,1098],{"class":369}," stats.minimumValue) ",[360,1100,1101],{"class":365},"\u002F",[360,1103,1104],{"class":369}," bin_count\n",[360,1106,1108,1110,1113,1115,1118],{"class":81,"line":1107},16,[360,1109,828],{"class":365},[360,1111,1112],{"class":369}," i, count ",[360,1114,834],{"class":365},[360,1116,1117],{"class":429}," enumerate",[360,1119,1120],{"class":369},"(hist.histogramVector):\n",[360,1122,1124,1127,1129,1132,1134,1137,1140],{"class":81,"line":1123},17,[360,1125,1126],{"class":369},"    lo ",[360,1128,392],{"class":365},[360,1130,1131],{"class":369}," stats.minimumValue ",[360,1133,847],{"class":365},[360,1135,1136],{"class":369}," i ",[360,1138,1139],{"class":365},"*",[360,1141,1142],{"class":369}," width\n",[360,1144,1146,1148,1150,1152,1155,1157,1160,1163,1165,1167,1169,1172,1174,1177,1179,1181,1184,1186,1189,1191,1193],{"class":81,"line":1145},18,[360,1147,878],{"class":429},[360,1149,433],{"class":369},[360,1151,487],{"class":365},[360,1153,1154],{"class":398},"\"[",[360,1156,493],{"class":429},[360,1158,1159],{"class":369},"lo",[360,1161,1162],{"class":365},":8.2f",[360,1164,502],{"class":429},[360,1166,402],{"class":398},[360,1168,493],{"class":429},[360,1170,1171],{"class":369},"lo ",[360,1173,847],{"class":365},[360,1175,1176],{"class":369}," width",[360,1178,1162],{"class":365},[360,1180,502],{"class":429},[360,1182,1183],{"class":398},"): ",[360,1185,493],{"class":429},[360,1187,1188],{"class":369},"count",[360,1190,502],{"class":429},[360,1192,505],{"class":398},[360,1194,408],{"class":369},[14,1196,1197,638,1199,1202,1203,1206,1207,1210,1211,1213],{},[335,1198,637],{},[18,1200,1201],{},"histogram(band, binCount, min, max)"," returns a ",[18,1204,1205],{},"QgsRasterHistogram","; its ",[18,1208,1209],{},"histogramVector"," is a list of pixel counts per bin. Bounding the range with the actual min\u002Fmax from ",[18,1212,348],{}," keeps the bins meaningful. The loop reconstructs each bin's interval from the bin width so the output is readable.",[307,1215,1217],{"id":1216},"compute-zonal-statistics","Compute Zonal Statistics",[14,1219,1220,1221,1223],{},"Zonal statistics summarize raster values inside each polygon of a vector layer — average elevation per catchment, mean NDVI per field. Use ",[18,1222,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):",[351,1225,1227],{"className":353,"code":1226,"language":355,"meta":356,"style":356},"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,1228,1229,1236,1240,1256,1270,1281,1292,1304,1329,1341,1352,1356,1368],{"__ignoreMap":356},[360,1230,1231,1233],{"class":81,"line":362},[360,1232,373],{"class":365},[360,1234,1235],{"class":369}," processing\n",[360,1237,1238],{"class":81,"line":379},[360,1239,383],{"emptyLinePlaceholder":382},[360,1241,1242,1245,1247,1250,1253],{"class":81,"line":386},[360,1243,1244],{"class":369},"result ",[360,1246,392],{"class":365},[360,1248,1249],{"class":369}," processing.run(",[360,1251,1252],{"class":398},"\"native:zonalstatisticsfb\"",[360,1254,1255],{"class":369},", {\n",[360,1257,1258,1261,1264,1267],{"class":81,"line":411},[360,1259,1260],{"class":398},"    \"INPUT\"",[360,1262,1263],{"class":369},": ",[360,1265,1266],{"class":398},"\"\u002Fdata\u002Fparcels.gpkg\"",[360,1268,1269],{"class":369},",\n",[360,1271,1272,1275,1277,1279],{"class":81,"line":423},[360,1273,1274],{"class":398},"    \"RASTER\"",[360,1276,1263],{"class":369},[360,1278,399],{"class":398},[360,1280,1269],{"class":369},[360,1282,1283,1286,1288,1290],{"class":81,"line":441},[360,1284,1285],{"class":398},"    \"RASTER_BAND\"",[360,1287,1263],{"class":369},[360,1289,468],{"class":429},[360,1291,1269],{"class":369},[360,1293,1294,1297,1299,1302],{"class":81,"line":446},[360,1295,1296],{"class":398},"    \"COLUMN_PREFIX\"",[360,1298,1263],{"class":369},[360,1300,1301],{"class":398},"\"elev_\"",[360,1303,1269],{"class":369},[360,1305,1306,1309,1312,1314,1316,1318,1320,1323,1326],{"class":81,"line":457},[360,1307,1308],{"class":398},"    \"STATISTICS\"",[360,1310,1311],{"class":369},": [",[360,1313,59],{"class":429},[360,1315,402],{"class":369},[360,1317,468],{"class":429},[360,1319,402],{"class":369},[360,1321,1322],{"class":429},"2",[360,1324,1325],{"class":369},"],   ",[360,1327,1328],{"class":1049},"# 0=Count, 1=Sum, 2=Mean\n",[360,1330,1331,1334,1336,1339],{"class":81,"line":474},[360,1332,1333],{"class":398},"    \"OUTPUT\"",[360,1335,1263],{"class":369},[360,1337,1338],{"class":398},"\"TEMPORARY_OUTPUT\"",[360,1340,1269],{"class":369},[360,1342,1343,1346,1349],{"class":81,"line":479},[360,1344,1345],{"class":369},"})[",[360,1347,1348],{"class":398},"\"OUTPUT\"",[360,1350,1351],{"class":369},"]\n",[360,1353,1354],{"class":81,"line":510},[360,1355,383],{"emptyLinePlaceholder":382},[360,1357,1358,1360,1363,1365],{"class":81,"line":535},[360,1359,828],{"class":365},[360,1361,1362],{"class":369}," feat ",[360,1364,834],{"class":365},[360,1366,1367],{"class":369}," result.getFeatures():\n",[360,1369,1370,1372,1375,1378,1381,1384],{"class":81,"line":560},[360,1371,878],{"class":429},[360,1373,1374],{"class":369},"(feat[",[360,1376,1377],{"class":398},"\"id\"",[360,1379,1380],{"class":369},"], feat[",[360,1382,1383],{"class":398},"\"elev_mean\"",[360,1385,1386],{"class":369},"])\n",[14,1388,1389,638,1391,1394,1395,1397,1398,1400,1401,1403,1404,1407,1408,1410,1411,1413,1414,1416,1417,1420,1421,1423,1424,1427,1428,1432],{},[335,1390,637],{},[18,1392,1393],{},"STATISTICS"," is a list of integer codes — ",[18,1396,59],{}," count, ",[18,1399,468],{}," sum, ",[18,1402,1322],{}," mean, ",[18,1405,1406],{},"3"," median, ",[18,1409,89],{}," stddev, ",[18,1412,72],{}," min, ",[18,1415,90],{}," max, and so on. Each requested statistic becomes a field named ",[18,1418,1419],{},"COLUMN_PREFIX"," plus the statistic (e.g. ",[18,1422,264],{},"). The ",[18,1425,1426],{},"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,1429,1431],{"href":1430},"\u002Fspatial-data-processing-automation\u002Fcoordinate-reference-systems\u002F","Coordinate Reference Systems",".",[14,1434,1435,1436,1438,1439,1443],{},"To pull the global mean directly into your zonal calc, you can pre-read it with ",[18,1437,348],{}," and compare per-zone means against it for anomaly detection. Once you have the raster trimmed to a region with ",[31,1440,1442],{"href":1441},"\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.",[307,1445,1447],{"id":1446},"exclude-nodata-from-the-result","Exclude Nodata from the Result",[14,1449,1450,1451,1454,1455,1457],{},"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,1452,1453],{},"-9999"," or ",[18,1456,59],{},"), you must register it first so it does not skew the mean:",[351,1459,1461],{"className":353,"code":1460,"language":355,"meta":356,"style":356},"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,1462,1463,1474,1478,1494,1502,1506,1511,1535,1549,1553,1565],{"__ignoreMap":356},[360,1464,1465,1467,1469,1471],{"class":81,"line":362},[360,1466,366],{"class":365},[360,1468,370],{"class":369},[360,1470,373],{"class":365},[360,1472,1473],{"class":369}," QgsRasterLayer, QgsRasterBandStats, QgsRasterRange\n",[360,1475,1476],{"class":81,"line":379},[360,1477,383],{"emptyLinePlaceholder":382},[360,1479,1480,1482,1484,1486,1488,1490,1492],{"class":81,"line":386},[360,1481,389],{"class":369},[360,1483,392],{"class":365},[360,1485,395],{"class":369},[360,1487,399],{"class":398},[360,1489,402],{"class":369},[360,1491,405],{"class":398},[360,1493,408],{"class":369},[360,1495,1496,1498,1500],{"class":81,"line":411},[360,1497,449],{"class":369},[360,1499,392],{"class":365},[360,1501,454],{"class":369},[360,1503,1504],{"class":81,"line":423},[360,1505,383],{"emptyLinePlaceholder":382},[360,1507,1508],{"class":81,"line":441},[360,1509,1510],{"class":1049},"# Declare the fill value as nodata for band 1, then recompute\n",[360,1512,1513,1516,1518,1521,1523,1526,1528,1530,1532],{"class":81,"line":446},[360,1514,1515],{"class":369},"provider.setUserNoDataValue(",[360,1517,468],{"class":429},[360,1519,1520],{"class":369},", [QgsRasterRange(",[360,1522,1095],{"class":365},[360,1524,1525],{"class":429},"9999",[360,1527,402],{"class":369},[360,1529,1095],{"class":365},[360,1531,1525],{"class":429},[360,1533,1534],{"class":369},")])\n",[360,1536,1537,1540,1542,1544,1547],{"class":81,"line":457},[360,1538,1539],{"class":369},"provider.setUseSourceNoDataValue(",[360,1541,468],{"class":429},[360,1543,402],{"class":369},[360,1545,1546],{"class":429},"True",[360,1548,408],{"class":369},[360,1550,1551],{"class":81,"line":474},[360,1552,383],{"emptyLinePlaceholder":382},[360,1554,1555,1557,1559,1561,1563],{"class":81,"line":479},[360,1556,460],{"class":369},[360,1558,392],{"class":365},[360,1560,465],{"class":369},[360,1562,468],{"class":429},[360,1564,471],{"class":369},[360,1566,1567,1569,1571,1573,1576,1578,1580,1582,1584,1586],{"class":81,"line":510},[360,1568,482],{"class":429},[360,1570,433],{"class":369},[360,1572,487],{"class":365},[360,1574,1575],{"class":398},"\"Mean excluding fill: ",[360,1577,493],{"class":429},[360,1579,549],{"class":369},[360,1581,499],{"class":365},[360,1583,502],{"class":429},[360,1585,505],{"class":398},[360,1587,408],{"class":369},[14,1589,1590,638,1592,1595,1596,1598,1599,666,1602,1605,1606,1608,1609,1611,1612,1615],{},[335,1591,637],{},[18,1593,1594],{},"setUserNoDataValue"," registers ",[18,1597,1453],{}," as nodata for band 1 using a ",[18,1600,1601],{},"QgsRasterRange",[18,1603,1604],{},"setUseSourceNoDataValue"," ensures any nodata already in the file is honored too. After this, ",[18,1607,348],{}," ignores those pixels, so the mean and standard deviation reflect real data only. Import ",[18,1610,1601],{}," from ",[18,1613,1614],{},"qgis.core"," alongside the other classes.",[307,1617,1619],{"id":1618},"write-statistics-to-a-report","Write Statistics to a Report",[14,1621,1622],{},"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:",[351,1624,1626],{"className":353,"code":1625,"language":355,"meta":356,"style":356},"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,1627,1628,1635,1645,1649,1665,1673,1677,1715,1725,1760,1783,1792,1797,1802,1838,1873,1878,1882],{"__ignoreMap":356},[360,1629,1630,1632],{"class":81,"line":362},[360,1631,373],{"class":365},[360,1633,1634],{"class":369}," csv\n",[360,1636,1637,1639,1641,1643],{"class":81,"line":379},[360,1638,366],{"class":365},[360,1640,370],{"class":369},[360,1642,373],{"class":365},[360,1644,376],{"class":369},[360,1646,1647],{"class":81,"line":386},[360,1648,383],{"emptyLinePlaceholder":382},[360,1650,1651,1653,1655,1657,1659,1661,1663],{"class":81,"line":411},[360,1652,389],{"class":369},[360,1654,392],{"class":365},[360,1656,395],{"class":369},[360,1658,804],{"class":398},[360,1660,402],{"class":369},[360,1662,809],{"class":398},[360,1664,408],{"class":369},[360,1666,1667,1669,1671],{"class":81,"line":423},[360,1668,449],{"class":369},[360,1670,392],{"class":365},[360,1672,454],{"class":369},[360,1674,1675],{"class":81,"line":441},[360,1676,383],{"emptyLinePlaceholder":382},[360,1678,1679,1682,1685,1687,1690,1692,1695,1697,1701,1703,1706,1709,1712],{"class":81,"line":446},[360,1680,1681],{"class":365},"with",[360,1683,1684],{"class":429}," open",[360,1686,433],{"class":369},[360,1688,1689],{"class":398},"\"\u002Fdata\u002Foutput\u002Fband_stats.csv\"",[360,1691,402],{"class":369},[360,1693,1694],{"class":398},"\"w\"",[360,1696,402],{"class":369},[360,1698,1700],{"class":1699},"s9osk","newline",[360,1702,392],{"class":365},[360,1704,1705],{"class":398},"\"\"",[360,1707,1708],{"class":369},") ",[360,1710,1711],{"class":365},"as",[360,1713,1714],{"class":369}," fh:\n",[360,1716,1717,1720,1722],{"class":81,"line":457},[360,1718,1719],{"class":369},"    writer ",[360,1721,392],{"class":365},[360,1723,1724],{"class":369}," csv.writer(fh)\n",[360,1726,1727,1730,1733,1735,1738,1740,1743,1745,1748,1750,1753,1755,1758],{"class":81,"line":474},[360,1728,1729],{"class":369},"    writer.writerow([",[360,1731,1732],{"class":398},"\"band\"",[360,1734,402],{"class":369},[360,1736,1737],{"class":398},"\"name\"",[360,1739,402],{"class":369},[360,1741,1742],{"class":398},"\"min\"",[360,1744,402],{"class":369},[360,1746,1747],{"class":398},"\"max\"",[360,1749,402],{"class":369},[360,1751,1752],{"class":398},"\"mean\"",[360,1754,402],{"class":369},[360,1756,1757],{"class":398},"\"stddev\"",[360,1759,1386],{"class":369},[360,1761,1762,1765,1767,1769,1771,1773,1775,1777,1779,1781],{"class":81,"line":479},[360,1763,1764],{"class":365},"    for",[360,1766,831],{"class":369},[360,1768,834],{"class":365},[360,1770,837],{"class":429},[360,1772,433],{"class":369},[360,1774,468],{"class":429},[360,1776,844],{"class":369},[360,1778,847],{"class":365},[360,1780,850],{"class":429},[360,1782,853],{"class":369},[360,1784,1785,1788,1790],{"class":81,"line":510},[360,1786,1787],{"class":369},"        s ",[360,1789,392],{"class":365},[360,1791,863],{"class":369},[360,1793,1794],{"class":81,"line":535},[360,1795,1796],{"class":369},"        writer.writerow([\n",[360,1798,1799],{"class":81,"line":560},[360,1800,1801],{"class":369},"            band, raster.bandName(band),\n",[360,1803,1804,1807,1809,1811,1813,1816,1818,1820,1822,1824,1826,1828,1830,1832,1834,1836],{"class":81,"line":585},[360,1805,1806],{"class":365},"            f",[360,1808,505],{"class":398},[360,1810,493],{"class":429},[360,1812,912],{"class":369},[360,1814,1815],{"class":365},":.4f",[360,1817,502],{"class":429},[360,1819,505],{"class":398},[360,1821,402],{"class":369},[360,1823,487],{"class":365},[360,1825,505],{"class":398},[360,1827,493],{"class":429},[360,1829,924],{"class":369},[360,1831,1815],{"class":365},[360,1833,502],{"class":429},[360,1835,505],{"class":398},[360,1837,1269],{"class":369},[360,1839,1840,1842,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862,1865,1867,1869,1871],{"class":81,"line":610},[360,1841,1806],{"class":365},[360,1843,505],{"class":398},[360,1845,493],{"class":429},[360,1847,899],{"class":369},[360,1849,1815],{"class":365},[360,1851,502],{"class":429},[360,1853,505],{"class":398},[360,1855,402],{"class":369},[360,1857,487],{"class":365},[360,1859,505],{"class":398},[360,1861,493],{"class":429},[360,1863,1864],{"class":369},"s.stdDev",[360,1866,1815],{"class":365},[360,1868,502],{"class":429},[360,1870,505],{"class":398},[360,1872,1269],{"class":369},[360,1874,1875],{"class":81,"line":1107},[360,1876,1877],{"class":369},"        ])\n",[360,1879,1880],{"class":81,"line":1123},[360,1881,383],{"emptyLinePlaceholder":382},[360,1883,1884,1886,1888,1891],{"class":81,"line":1145},[360,1885,482],{"class":429},[360,1887,433],{"class":369},[360,1889,1890],{"class":398},"\"Wrote band_stats.csv\"",[360,1892,408],{"class":369},[14,1894,1895,1897,1898,1901],{},[335,1896,637],{}," The standard-library ",[18,1899,1900],{},"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.",[307,1903,1905],{"id":1904},"what-the-summary-statistics-hide","What the summary statistics hide",[14,1907,1908],{},"A mean and a standard deviation describe a distribution only if the distribution is roughly symmetric. Elevation, rainfall and land-cover rasters rarely are, and a summary that ignores the shape produces classifications nobody can defend.",[14,1910,1911],{},[42,1912,1915,1918,1921,1924,1928,1993,2044],{"viewBox":1913,"role":45,"ariaLabel":1914,"xmlns":47},"0 0 760 248","Three raster value distributions with identical means: a symmetric one, a strongly skewed one and a bimodal one, showing that the mean describes only the first",[49,1916,1917],{},"Three distributions, one mean",[53,1919,1920],{},"Three histograms share the same arithmetic mean, marked with a vertical line on each. The symmetric distribution centres on it. The skewed distribution has most of its mass well below the mean, which sits in the sparse tail. The bimodal distribution has two clusters and almost no values at the mean at all.",[57,1922],{"x":59,"y":59,"width":1923,"height":151,"fill":62},"760",[93,1925,1927],{"x":190,"y":96,"style":1926,"fill":98,"textAnchor":99},"text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","Same mean, three completely different rasters",[107,1929,1930,1937,1942,1983,1988],{},[57,1931],{"x":1932,"y":1933,"width":1934,"height":1935,"rx":152,"fill":117,"stroke":1936,"style":154},"12","46","236","188","#15803d",[93,1938,1941],{"x":1939,"y":114,"style":1940,"fill":1936,"textAnchor":99},"130","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","symmetric",[107,1943,1945,1950,1954,1958,1962,1966,1971,1975,1980],{"fill":1936,"fillOpacity":1944},0.45,[57,1946],{"x":1947,"y":1948,"width":1949,"height":1949},"40","176","18",[57,1951],{"x":1952,"y":1953,"width":1949,"height":84},"60","160",[57,1955],{"x":1956,"y":1957,"width":1949,"height":1952},"80","134",[57,1959],{"x":1960,"y":113,"width":1949,"height":1961},"100","84",[57,1963],{"x":164,"y":1964,"width":1949,"height":1965},"102","92",[57,1967],{"x":1968,"y":1969,"width":1949,"height":1970},"140","112","82",[57,1972],{"x":1953,"y":1973,"width":1949,"height":1974},"138","56",[57,1976],{"x":1977,"y":1978,"width":1949,"height":1979},"180","162","32",[57,1981],{"x":115,"y":121,"width":1949,"height":1982},"16",[81,1984],{"x1":1985,"y1":1986,"x2":1985,"y2":115,"stroke":1987,"style":154},"129","90","#b45309",[93,1989,1992],{"x":1939,"y":1990,"style":1991,"fill":1936,"textAnchor":99},"218","text-anchor:middle;font-size:11px;font-family:sans-serif","mean describes it well",[107,1994,1995,1998,2001,2038,2041],{},[57,1996],{"x":1997,"y":1933,"width":1934,"height":1935,"rx":152,"fill":117,"stroke":1987,"style":154},"262",[93,1999,2000],{"x":190,"y":114,"style":1940,"fill":1987,"textAnchor":99},"skewed",[107,2002,2003,2007,2010,2015,2019,2024,2029,2032,2035],{"fill":1987,"fillOpacity":1944},[57,2004],{"x":248,"y":2005,"width":1949,"height":2006},"96","98",[57,2008],{"x":134,"y":2009,"width":1949,"height":1986},"104",[57,2011],{"x":2012,"y":2013,"width":1949,"height":2014},"330","128","66",[57,2016],{"x":2017,"y":120,"width":1949,"height":2018},"350","44",[57,2020],{"x":2021,"y":2022,"width":1949,"height":2023},"370","164","30",[57,2025],{"x":2026,"y":2027,"width":1949,"height":2028},"390","174","20",[57,2030],{"x":83,"y":1977,"width":1949,"height":2031},"14",[57,2033],{"x":61,"y":2034,"width":1949,"height":152},"184",[57,2036],{"x":2037,"y":1935,"width":1949,"height":90},"450",[81,2039],{"x1":2040,"y1":1986,"x2":2040,"y2":115,"stroke":1987,"style":154},"379",[93,2042,2043],{"x":190,"y":1990,"style":1991,"fill":1987,"textAnchor":99},"mean sits in the thin tail",[107,2045,2046,2050,2054,2090,2093],{},[57,2047],{"x":2048,"y":1933,"width":1934,"height":1935,"rx":152,"fill":117,"stroke":2049,"style":154},"512","#b91c1c",[93,2051,2053],{"x":2052,"y":114,"style":1940,"fill":2049,"textAnchor":99},"630","bimodal",[107,2055,2057,2061,2064,2069,2072,2076,2079,2084,2087],{"fill":2056,"fillOpacity":1944},"#2563eb",[57,2058],{"x":2059,"y":164,"width":1949,"height":2060},"540","74",[57,2062],{"x":2063,"y":1964,"width":1949,"height":1965},"560",[57,2065],{"x":2066,"y":2067,"width":1949,"height":2068},"580","132","62",[57,2070],{"x":2071,"y":1977,"width":1949,"height":2031},"600",[57,2073],{"x":2074,"y":2075,"width":1949,"height":71},"620","186",[57,2077],{"x":2078,"y":121,"width":1949,"height":1982},"640",[57,2080],{"x":2081,"y":2082,"width":1949,"height":2083},"660","126","68",[57,2085],{"x":2086,"y":2009,"width":1949,"height":1986},"680",[57,2088],{"x":2089,"y":2013,"width":1949,"height":2014},"700",[81,2091],{"x1":2092,"y1":1986,"x2":2092,"y2":115,"stroke":1987,"style":154},"629",[93,2094,2095],{"x":2052,"y":1990,"style":1991,"fill":2049,"textAnchor":99},"almost no pixels at the mean",[14,2097,2098],{},"The histogram section above is therefore not optional detail — it is what tells you whether the mean is meaningful. A bimodal raster is usually two things mixed together, such as land and water, and the right response is to mask one out rather than to average across both.",[307,2100,2102],{"id":2101},"choosing-between-band-and-zonal-statistics","Choosing between band and zonal statistics",[14,2104,2105],{},"The two questions look similar and answer very different things.",[14,2107,2108],{},[42,2109,2112,2115,2118,2120,2128,2131,2182],{"viewBox":2110,"role":45,"ariaLabel":2111,"xmlns":47},"0 0 760 236","Band statistics summarising the whole raster into one row, compared with zonal statistics producing one row per polygon",[49,2113,2114],{},"Whole-raster statistics versus per-zone statistics",[53,2116,2117],{},"On the left, band statistics reduce every pixel in the raster to a single row of minimum, maximum, mean and standard deviation. On the right, zonal statistics overlay a polygon layer and produce one row of the same measures for each polygon, which is what a report by administrative area needs.",[57,2119],{"x":59,"y":59,"width":1923,"height":1934,"fill":62},[64,2121,2122],{},[67,2123,2125],{"id":2124,"viewBox":70,"refX":71,"refY":72,"markerWidth":73,"markerHeight":73,"orient":74},"rastStatArrow",[76,2126],{"d":78,"fill":2127},"#0f766e",[93,2129,2130],{"x":190,"y":96,"style":1926,"fill":98,"textAnchor":99},"One number for the raster, or one number per area?",[107,2132,2133,2136,2141,2144,2161,2165,2169,2174,2178],{},[57,2134],{"x":1982,"y":1933,"width":2135,"height":1948,"rx":152,"fill":117,"stroke":2056,"style":154},"356",[93,2137,2140],{"x":2138,"y":114,"style":2139,"fill":2056,"textAnchor":99},"194","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","bandStatistics()",[57,2142],{"x":2018,"y":2143,"width":164,"height":2005,"fill":2056,"fillOpacity":227,"stroke":2056,"style":110},"86",[107,2145,2148,2151,2153,2155,2157,2159],{"stroke":2056,"style":2146,"strokeOpacity":2147},"stroke-width:0.8",0.5,[81,2149],{"x1":2060,"y1":2143,"x2":2060,"y2":2150},"182",[81,2152],{"x1":2009,"y1":2143,"x2":2009,"y2":2150},[81,2154],{"x1":1957,"y1":2143,"x2":1957,"y2":2150},[81,2156],{"x1":2018,"y1":113,"x2":2022,"y2":113},[81,2158],{"x1":2018,"y1":1957,"x2":2022,"y2":1957},[81,2160],{"x1":2018,"y1":285,"x2":2022,"y2":285},[57,2162],{"x":2163,"y":133,"width":1968,"height":1974,"rx":90,"fill":117,"stroke":2164,"style":87},"212","#59645f",[93,2166,2168],{"x":2167,"y":2013,"style":1991,"fill":79,"textAnchor":99},"282","min max mean sd",[93,2170,2173],{"x":2167,"y":2171,"style":2172,"fill":2056,"textAnchor":99},"148","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","one row",[81,2175],{"x1":2022,"y1":1957,"x2":2176,"y2":1957,"stroke":2127,"style":2177},"208","stroke-width:2;marker-end:url(#rastStatArrow)",[93,2179,2181],{"x":2138,"y":2180,"style":1991,"fill":79,"textAnchor":99},"206","\"what is this raster like?\"",[107,2183,2184,2187,2191,2195,2199,2202,2206,2220,2223],{},[57,2185],{"x":2186,"y":1933,"width":2135,"height":1948,"rx":152,"fill":117,"stroke":1936,"style":154},"388",[93,2188,2190],{"x":2189,"y":114,"style":2139,"fill":1936,"textAnchor":99},"566","zonal statistics",[57,2192],{"x":2193,"y":2143,"width":164,"height":2005,"fill":2056,"fillOpacity":2194,"stroke":2056,"style":110},"416",0.12,[76,2196],{"d":2197,"fill":1936,"fillOpacity":2198,"stroke":1936,"style":110},"M424 176 L426 96 L472 92 L482 134 L456 180 Z",0.3,[76,2200],{"d":2201,"fill":1936,"fillOpacity":2198,"stroke":1936,"style":110},"M488 178 L486 110 L530 106 L532 176 Z",[57,2203],{"x":2204,"y":1986,"width":1968,"height":2205,"rx":90,"fill":117,"stroke":2164,"style":87},"584","88",[107,2207,2209,2213,2216],{"style":2208},"font-size:10.5px;font-family:monospace",[93,2210,2212],{"x":2211,"y":1969,"fill":79},"598","zone A  min max mean",[93,2214,2215],{"x":2211,"y":1957,"fill":79},"zone B  min max mean",[93,2217,2219],{"x":2211,"y":2218,"fill":1936},"156","one row per polygon",[81,2221],{"x1":2222,"y1":1957,"x2":2066,"y2":1957,"stroke":2127,"style":2177},"536",[93,2224,2225],{"x":2189,"y":2180,"style":1991,"fill":79,"textAnchor":99},"\"what is each area like?\"",[14,2227,2228],{},"Zonal statistics is the one reports actually need, and it is also where the nodata handling described above matters most — a polygon that overlaps the raster's edge will otherwise average real values against the fill value and report a mean nobody can explain.",[307,2230,2232],{"id":2231},"qgis-version-compatibility","QGIS Version Compatibility",[14,2234,2235,2236,2239],{},"The code targets ",[335,2237,2238],{},"QGIS 3.34 LTR"," (Python 3.12).",[2241,2242,2243,2259],"table",{},[2244,2245,2246],"thead",{},[2247,2248,2249,2253,2256],"tr",{},[2250,2251,2252],"th",{},"QGIS version",[2250,2254,2255],{},"Python",[2250,2257,2258],{},"Notes",[2260,2261,2262,2281,2292],"tbody",{},[2247,2263,2264,2268,2271],{},[2265,2266,2267],"td",{},"3.28 LTR",[2265,2269,2270],{},"3.9",[2265,2272,2273,1101,2275,2277,2278,2280],{},[18,2274,348],{},[18,2276,958],{}," identical; ",[18,2279,28],{}," available.",[2247,2282,2283,2286,2289],{},[2265,2284,2285],{},"3.34 LTR",[2265,2287,2288],{},"3.12",[2265,2290,2291],{},"Baseline for this page.",[2247,2293,2294,2297,2299],{},[2265,2295,2296],{},"3.40 \u002F 3.44",[2265,2298,2288],{},[2265,2300,2301],{},"Same APIs; minor speedups on large-raster scans.",[14,2303,2304,2305,2308,2309,2311,2312,2315],{},"The older ",[18,2306,2307],{},"native:zonalstatistics"," algorithm edited the input layer in place and is deprecated in favor of ",[18,2310,28],{},". Prefer the ",[18,2313,2314],{},"fb"," variant on all current versions; it leaves the source untouched and returns a clean output layer.",[307,2317,2319],{"id":2318},"troubleshooting","Troubleshooting",[312,2321,2322,2334,2343,2349,2368],{},[315,2323,2324,2327,2328,2330,2331,1432],{},[335,2325,2326],{},"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,2329,468],{}," for the first band and confirm the nodata value with ",[18,2332,2333],{},"provider.sourceNoDataValue(1)",[315,2335,2336,2339,2340,2342],{},[335,2337,2338],{},"Statistics seem wrong on a huge raster."," You may have passed a ",[18,2341,758],{},", giving an approximation. Drop the sample arguments for an exact full-scan result.",[315,2344,2345,2348],{},[335,2346,2347],{},"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.",[315,2350,2351,2357,2358,2360,2361,2364,2365,2367],{},[335,2352,2353,2356],{},[18,2354,2355],{},"AttributeError"," on stats object."," Use attributes (",[18,2359,549],{},") not method calls (",[18,2362,2363],{},"stats.mean()","). ",[18,2366,24],{}," exposes plain fields.",[315,2369,2370,2373,2374,2376,2377,2379],{},[335,2371,2372],{},"Histogram bins look skewed."," You did not bound the range; pass ",[18,2375,496],{}," and ",[18,2378,524],{}," so bins span the real data range.",[307,2381,2383],{"id":2382},"conclusion","Conclusion",[14,2385,2386,2387,2389,2390,2392,2393,2395],{},"PyQGIS gives you raster statistics at two scales. ",[18,2388,348],{}," reads whole-band min, max, mean, and standard deviation in one call, and ",[18,2391,958],{}," reveals the value distribution behind those numbers — both exact or sampled for speed. For per-feature summaries, ",[18,2394,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.",[307,2397,2399],{"id":2398},"frequently-asked-questions","Frequently Asked Questions",[14,2401,2402,2405,2406,2409,2410,2412],{},[335,2403,2404],{},"Are raster band indices 0-based or 1-based?","\n1-based. Band 1 is the first band in both ",[18,2407,2408],{},"bandStatistics(1, ...)"," and the ",[18,2411,1426],{}," parameter of zonal statistics. Using 0 raises an error or returns empty results.",[14,2414,2415,2418,2419,755,2421,2423,2424,2426],{},[335,2416,2417],{},"How do I compute statistics faster on a large raster?","\nPass an ",[18,2420,754],{},[18,2422,758],{}," to ",[18,2425,348],{},". 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,2428,2429,2432,2433,2435,2436,2438],{},[335,2430,2431],{},"What is the difference between zonalstatistics and zonalstatisticsfb?","\nThe legacy ",[18,2434,2307],{}," modifies the input vector layer in place. ",[18,2437,28],{}," returns a new feature-based output and leaves the source untouched — the recommended choice on current QGIS.",[14,2440,2441,2444],{},[335,2442,2443],{},"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.",[307,2446,2448],{"id":2447},"related","Related",[312,2450,2451,2455,2459],{},[315,2452,2453],{},[31,2454,34],{"href":33},[315,2456,2457],{},[31,2458,1442],{"href":1441},[315,2460,2461],{},[31,2462,766],{"href":765},[2464,2465,2466],"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 .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":356,"searchDepth":379,"depth":379,"links":2468},[2469,2470,2471,2472,2473,2474,2475,2476,2477,2478,2479,2480,2481,2482],{"id":309,"depth":379,"text":310},{"id":341,"depth":379,"text":342},{"id":770,"depth":379,"text":771},{"id":951,"depth":379,"text":952},{"id":1216,"depth":379,"text":1217},{"id":1446,"depth":379,"text":1447},{"id":1618,"depth":379,"text":1619},{"id":1904,"depth":379,"text":1905},{"id":2101,"depth":379,"text":2102},{"id":2231,"depth":379,"text":2232},{"id":2318,"depth":379,"text":2319},{"id":2382,"depth":379,"text":2383},{"id":2398,"depth":379,"text":2399},{"id":2447,"depth":379,"text":2448},"Calculate raster statistics in PyQGIS. Read band min, max, mean and stddev with QgsRasterBandStats, run zonal statistics, and build a histogram.","md",{"slug":2486,"type":2487,"breadcrumb":2488,"datePublished":2489,"dateModified":2490},"calculate-raster-statistics-pyqgis","article","Calculate Raster Statistics","2025-03-18","2026-07-18","\u002Fspatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fcalculate-raster-statistics-pyqgis",{"title":5,"description":2483},"spatial-data-processing-automation\u002Fraster-analysis-workflows\u002Fcalculate-raster-statistics-pyqgis\u002Findex","A9N9NJ3HCVA8mNvsWmD3eOax8FaM222ySDMyYJSqOCQ",1787823363770]