[{"data":1,"prerenderedAt":2171},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fcount-features-by-attribute-pyqgis":3},{"id":4,"title":5,"body":6,"description":2160,"extension":2161,"meta":2162,"navigation":233,"path":2167,"seo":2168,"stem":2169,"__hash__":2170},"docs\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fcount-features-by-attribute-pyqgis\u002Findex.md","Count and Summarise Features by Attribute in PyQGIS",{"type":7,"value":8,"toc":2147},"minimark",[9,13,17,26,150,155,177,181,188,429,462,466,469,878,898,902,909,1131,1151,1155,1162,1325,1459,1506,1510,1513,1674,1830,1849,1853,1859,1921,1927,1931,2006,2010,2020,2024,2043,2052,2061,2081,2093,2102,2108,2117,2121,2143],[10,11,5],"h1",{"id":12},"count-and-summarise-features-by-attribute-in-pyqgis",[14,15,16],"p",{},"\"How many parcels per zoning class?\" is the most common reporting question in GIS, and the naive answer — iterate the layer and increment a dictionary — is correct but usually reads far more data than it needs. On a polygon layer the geometry decoding alone can account for most of the runtime, and none of it contributes to a count.",[14,18,19,20,25],{},"This page is a focused recipe within ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002F","Attribute Tables and Field Management in PyQGIS",". It covers counting efficiently, computing sums and means alongside the counts, letting the provider do the work when it can, and producing a summary as a layer rather than console output.",[14,27,28],{},[29,30,35,39,43,50,59,66,103,107,122,126,135,142,146],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 250","img","A full feature read compared with a narrowed request: skipping geometry and unused columns leaves only the category column to be read","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"What a narrowed feature request avoids reading",[40,41,42],"desc",{},"A full read of a polygon layer transfers geometry, which dominates, plus every attribute column. Adding the NoGeometry flag removes the geometry entirely, and restricting the attribute subset to the single category column removes the remaining unused columns, leaving a small fraction of the original data volume.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","250","#f6f3ea",[51,52,58],"text",{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","A count needs one column — the default read fetches everything",[51,60,65],{"x":61,"y":62,"style":63,"fill":56,"textAnchor":64},"196","70","text-anchor:end;font-size:12px;font-weight:bold;font-family:sans-serif","end","getFeatures()",[67,68,69,77,84,89,93,99],"g",{},[44,70],{"x":71,"y":72,"width":53,"height":54,"rx":73,"fill":74,"fillOpacity":75,"stroke":74,"style":76},"208","54","4","#b91c1c",0.3,"stroke-width:1.8",[51,78,83],{"x":79,"y":80,"style":81,"fill":82,"textAnchor":57},"398","72","text-anchor:middle;font-size:11px;font-family:sans-serif","#2f3b35","geometry",[44,85],{"x":86,"y":72,"width":87,"height":54,"rx":73,"fill":88,"fillOpacity":75,"stroke":88,"style":76},"588","90","#b45309",[51,90,92],{"x":91,"y":80,"style":81,"fill":82,"textAnchor":57},"633","other cols",[44,94],{"x":95,"y":72,"width":96,"height":54,"rx":73,"fill":97,"fillOpacity":98,"stroke":97,"style":76},"678","60","#15803d",0.4,[51,100,102],{"x":101,"y":80,"style":81,"fill":82,"textAnchor":57},"708","zoning",[51,104,106],{"x":61,"y":105,"style":63,"fill":56,"textAnchor":64},"130","+ NoGeometry",[67,108,109,112,116,119],{},[44,110],{"x":71,"y":111,"width":87,"height":54,"rx":73,"fill":88,"fillOpacity":75,"stroke":88,"style":76},"114",[51,113,92],{"x":114,"y":115,"style":81,"fill":82,"textAnchor":57},"253","132",[44,117],{"x":118,"y":111,"width":96,"height":54,"rx":73,"fill":97,"fillOpacity":98,"stroke":97,"style":76},"298",[51,120,102],{"x":121,"y":115,"style":81,"fill":82,"textAnchor":57},"328",[51,123,125],{"x":61,"y":124,"style":63,"fill":56,"textAnchor":64},"190","+ attribute subset",[67,127,128,131],{},[44,129],{"x":71,"y":130,"width":96,"height":54,"rx":73,"fill":97,"fillOpacity":98,"stroke":97,"style":76},"174",[51,132,102],{"x":133,"y":134,"style":81,"fill":82,"textAnchor":57},"238","192",[136,137],"line",{"x1":71,"y1":138,"x2":139,"y2":138,"stroke":140,"style":141},"218","738","#59645f","stroke-width:1.5",[51,143,145],{"x":71,"y":133,"style":144,"fill":140},"font-size:11px;font-family:sans-serif","less data transferred",[51,147,149],{"x":139,"y":133,"style":148,"fill":140,"textAnchor":64},"text-anchor:end;font-size:11px;font-family:sans-serif","more",[151,152,154],"h2",{"id":153},"prerequisites","Prerequisites",[156,157,158,166,169],"ul",{},[159,160,161,165],"li",{},[162,163,164],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[159,167,168],{},"A vector layer with a categorical field to group by and, for the aggregate examples, a numeric field.",[159,170,171,172,176],{},"Familiarity with feature requests — see ",[21,173,175],{"href":174},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-expressions\u002F","Working with QGIS Expressions in PyQGIS",".",[151,178,180],{"id":179},"count-per-category","Count per category",[14,182,183,187],{},[184,185,186],"code",{},"collections.Counter"," fed by a narrowed request is the shortest correct form.",[189,190,195],"pre",{"className":191,"code":192,"language":193,"meta":194,"style":194},"language-python shiki shiki-themes github-dark","from collections import Counter\nfrom qgis.core import QgsFeatureRequest, QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\n\nrequest = QgsFeatureRequest()\nrequest.setFlags(QgsFeatureRequest.NoGeometry)\nrequest.setSubsetOfAttributes([\"zoning\"], layer.fields())\n\ncounts = Counter(f[\"zoning\"] for f in layer.getFeatures(request))\n\nfor zoning, count in counts.most_common():\n    print(f\"{zoning or '(no value)':\u003C16} {count:>6}\")\nprint(f\"{'total':\u003C16} {sum(counts.values()):>6}\")\n","python","",[184,196,197,215,228,235,260,265,276,282,294,299,327,332,345,394],{"__ignoreMap":194},[198,199,201,205,209,212],"span",{"class":136,"line":200},1,[198,202,204],{"class":203},"snl16","from",[198,206,208],{"class":207},"s95oV"," collections ",[198,210,211],{"class":203},"import",[198,213,214],{"class":207}," Counter\n",[198,216,218,220,223,225],{"class":136,"line":217},2,[198,219,204],{"class":203},[198,221,222],{"class":207}," qgis.core ",[198,224,211],{"class":203},[198,226,227],{"class":207}," QgsFeatureRequest, QgsProject\n",[198,229,231],{"class":136,"line":230},3,[198,232,234],{"emptyLinePlaceholder":233},true,"\n",[198,236,238,241,244,247,251,254,257],{"class":136,"line":237},4,[198,239,240],{"class":207},"layer ",[198,242,243],{"class":203},"=",[198,245,246],{"class":207}," QgsProject.instance().mapLayersByName(",[198,248,250],{"class":249},"sU2Wk","\"parcels\"",[198,252,253],{"class":207},")[",[198,255,46],{"class":256},"sDLfK",[198,258,259],{"class":207},"]\n",[198,261,263],{"class":136,"line":262},5,[198,264,234],{"emptyLinePlaceholder":233},[198,266,268,271,273],{"class":136,"line":267},6,[198,269,270],{"class":207},"request ",[198,272,243],{"class":203},[198,274,275],{"class":207}," QgsFeatureRequest()\n",[198,277,279],{"class":136,"line":278},7,[198,280,281],{"class":207},"request.setFlags(QgsFeatureRequest.NoGeometry)\n",[198,283,285,288,291],{"class":136,"line":284},8,[198,286,287],{"class":207},"request.setSubsetOfAttributes([",[198,289,290],{"class":249},"\"zoning\"",[198,292,293],{"class":207},"], layer.fields())\n",[198,295,297],{"class":136,"line":296},9,[198,298,234],{"emptyLinePlaceholder":233},[198,300,302,305,307,310,312,315,318,321,324],{"class":136,"line":301},10,[198,303,304],{"class":207},"counts ",[198,306,243],{"class":203},[198,308,309],{"class":207}," Counter(f[",[198,311,290],{"class":249},[198,313,314],{"class":207},"] ",[198,316,317],{"class":203},"for",[198,319,320],{"class":207}," f ",[198,322,323],{"class":203},"in",[198,325,326],{"class":207}," layer.getFeatures(request))\n",[198,328,330],{"class":136,"line":329},11,[198,331,234],{"emptyLinePlaceholder":233},[198,333,335,337,340,342],{"class":136,"line":334},12,[198,336,317],{"class":203},[198,338,339],{"class":207}," zoning, count ",[198,341,323],{"class":203},[198,343,344],{"class":207}," counts.most_common():\n",[198,346,348,351,354,357,360,363,366,369,372,375,378,381,384,387,389,391],{"class":136,"line":347},13,[198,349,350],{"class":256},"    print",[198,352,353],{"class":207},"(",[198,355,356],{"class":203},"f",[198,358,359],{"class":249},"\"",[198,361,362],{"class":256},"{",[198,364,365],{"class":207},"zoning ",[198,367,368],{"class":203},"or",[198,370,371],{"class":249}," '(no value)'",[198,373,374],{"class":203},":\u003C16",[198,376,377],{"class":256},"}",[198,379,380],{"class":256}," {",[198,382,383],{"class":207},"count",[198,385,386],{"class":203},":>6",[198,388,377],{"class":256},[198,390,359],{"class":249},[198,392,393],{"class":207},")\n",[198,395,397,400,402,404,406,408,411,413,415,418,421,423,425,427],{"class":136,"line":396},14,[198,398,399],{"class":256},"print",[198,401,353],{"class":207},[198,403,356],{"class":203},[198,405,359],{"class":249},[198,407,362],{"class":256},[198,409,410],{"class":249},"'total'",[198,412,374],{"class":203},[198,414,377],{"class":256},[198,416,417],{"class":256}," {sum",[198,419,420],{"class":207},"(counts.values())",[198,422,386],{"class":203},[198,424,377],{"class":256},[198,426,359],{"class":249},[198,428,393],{"class":207},[14,430,431,434,435,438,439,442,443,446,447,450,451,454,455,458,459,461],{},[162,432,433],{},"Breakdown:"," ",[184,436,437],{},"NoGeometry"," tells the provider not to read or decode geometry, which on a polygon layer is typically the largest single cost of iteration. ",[184,440,441],{},"setSubsetOfAttributes()"," narrows the columns to the one being counted. ",[184,444,445],{},"Counter"," consumes a generator, so nothing is held in memory beyond the tally itself. The ",[184,448,449],{},"or '(no value)'"," renders ",[184,452,453],{},"NULL"," readably instead of printing ",[184,456,457],{},"None",", and keeps ",[184,460,453],{}," visible as its own category rather than hiding it.",[151,463,465],{"id":464},"sum-and-average-alongside-the-count","Sum and average alongside the count",[14,467,468],{},"One pass can produce several statistics. Building a dictionary of accumulators keeps it readable and still single-pass.",[189,470,472],{"className":191,"code":471,"language":193,"meta":194,"style":194},"from collections import defaultdict\nfrom qgis.core import QgsFeatureRequest, QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\n\nrequest = QgsFeatureRequest()\nrequest.setFlags(QgsFeatureRequest.NoGeometry)\nrequest.setSubsetOfAttributes([\"zoning\", \"assessed_value\"], layer.fields())\n\nstats = defaultdict(lambda: {\"n\": 0, \"total\": 0.0, \"valued\": 0})\n\nfor feature in layer.getFeatures(request):\n    entry = stats[feature[\"zoning\"]]\n    entry[\"n\"] += 1\n    value = feature[\"assessed_value\"]\n    if value is not None:\n        entry[\"total\"] += value\n        entry[\"valued\"] += 1\n\nfor zoning, entry in sorted(stats.items(), key=lambda kv: -kv[1][\"n\"]):\n    mean = entry[\"total\"] \u002F entry[\"valued\"] if entry[\"valued\"] else 0\n    print(f\"{zoning or '(none)':\u003C14} n={entry['n']:>5}  \"\n          f\"total={entry['total']:>14,.0f}  mean={mean:>10,.0f}\")\n",[184,473,474,485,495,499,515,519,527,531,545,549,595,599,611,626,641,656,677,692,705,710,753,792,839],{"__ignoreMap":194},[198,475,476,478,480,482],{"class":136,"line":200},[198,477,204],{"class":203},[198,479,208],{"class":207},[198,481,211],{"class":203},[198,483,484],{"class":207}," defaultdict\n",[198,486,487,489,491,493],{"class":136,"line":217},[198,488,204],{"class":203},[198,490,222],{"class":207},[198,492,211],{"class":203},[198,494,227],{"class":207},[198,496,497],{"class":136,"line":230},[198,498,234],{"emptyLinePlaceholder":233},[198,500,501,503,505,507,509,511,513],{"class":136,"line":237},[198,502,240],{"class":207},[198,504,243],{"class":203},[198,506,246],{"class":207},[198,508,250],{"class":249},[198,510,253],{"class":207},[198,512,46],{"class":256},[198,514,259],{"class":207},[198,516,517],{"class":136,"line":262},[198,518,234],{"emptyLinePlaceholder":233},[198,520,521,523,525],{"class":136,"line":267},[198,522,270],{"class":207},[198,524,243],{"class":203},[198,526,275],{"class":207},[198,528,529],{"class":136,"line":278},[198,530,281],{"class":207},[198,532,533,535,537,540,543],{"class":136,"line":284},[198,534,287],{"class":207},[198,536,290],{"class":249},[198,538,539],{"class":207},", ",[198,541,542],{"class":249},"\"assessed_value\"",[198,544,293],{"class":207},[198,546,547],{"class":136,"line":296},[198,548,234],{"emptyLinePlaceholder":233},[198,550,551,554,556,559,562,565,568,571,573,575,578,580,583,585,588,590,592],{"class":136,"line":301},[198,552,553],{"class":207},"stats ",[198,555,243],{"class":203},[198,557,558],{"class":207}," defaultdict(",[198,560,561],{"class":203},"lambda",[198,563,564],{"class":207},": {",[198,566,567],{"class":249},"\"n\"",[198,569,570],{"class":207},": ",[198,572,46],{"class":256},[198,574,539],{"class":207},[198,576,577],{"class":249},"\"total\"",[198,579,570],{"class":207},[198,581,582],{"class":256},"0.0",[198,584,539],{"class":207},[198,586,587],{"class":249},"\"valued\"",[198,589,570],{"class":207},[198,591,46],{"class":256},[198,593,594],{"class":207},"})\n",[198,596,597],{"class":136,"line":329},[198,598,234],{"emptyLinePlaceholder":233},[198,600,601,603,606,608],{"class":136,"line":334},[198,602,317],{"class":203},[198,604,605],{"class":207}," feature ",[198,607,323],{"class":203},[198,609,610],{"class":207}," layer.getFeatures(request):\n",[198,612,613,616,618,621,623],{"class":136,"line":347},[198,614,615],{"class":207},"    entry ",[198,617,243],{"class":203},[198,619,620],{"class":207}," stats[feature[",[198,622,290],{"class":249},[198,624,625],{"class":207},"]]\n",[198,627,628,631,633,635,638],{"class":136,"line":396},[198,629,630],{"class":207},"    entry[",[198,632,567],{"class":249},[198,634,314],{"class":207},[198,636,637],{"class":203},"+=",[198,639,640],{"class":256}," 1\n",[198,642,644,647,649,652,654],{"class":136,"line":643},15,[198,645,646],{"class":207},"    value ",[198,648,243],{"class":203},[198,650,651],{"class":207}," feature[",[198,653,542],{"class":249},[198,655,259],{"class":207},[198,657,659,662,665,668,671,674],{"class":136,"line":658},16,[198,660,661],{"class":203},"    if",[198,663,664],{"class":207}," value ",[198,666,667],{"class":203},"is",[198,669,670],{"class":203}," not",[198,672,673],{"class":256}," None",[198,675,676],{"class":207},":\n",[198,678,680,683,685,687,689],{"class":136,"line":679},17,[198,681,682],{"class":207},"        entry[",[198,684,577],{"class":249},[198,686,314],{"class":207},[198,688,637],{"class":203},[198,690,691],{"class":207}," value\n",[198,693,695,697,699,701,703],{"class":136,"line":694},18,[198,696,682],{"class":207},[198,698,587],{"class":249},[198,700,314],{"class":207},[198,702,637],{"class":203},[198,704,640],{"class":256},[198,706,708],{"class":136,"line":707},19,[198,709,234],{"emptyLinePlaceholder":233},[198,711,713,715,718,720,723,726,730,733,736,739,742,745,748,750],{"class":136,"line":712},20,[198,714,317],{"class":203},[198,716,717],{"class":207}," zoning, entry ",[198,719,323],{"class":203},[198,721,722],{"class":256}," sorted",[198,724,725],{"class":207},"(stats.items(), ",[198,727,729],{"class":728},"s9osk","key",[198,731,732],{"class":203},"=lambda",[198,734,735],{"class":207}," kv: ",[198,737,738],{"class":203},"-",[198,740,741],{"class":207},"kv[",[198,743,744],{"class":256},"1",[198,746,747],{"class":207},"][",[198,749,567],{"class":249},[198,751,752],{"class":207},"]):\n",[198,754,756,759,761,764,766,768,771,773,775,777,780,782,784,786,789],{"class":136,"line":755},21,[198,757,758],{"class":207},"    mean ",[198,760,243],{"class":203},[198,762,763],{"class":207}," entry[",[198,765,577],{"class":249},[198,767,314],{"class":207},[198,769,770],{"class":203},"\u002F",[198,772,763],{"class":207},[198,774,587],{"class":249},[198,776,314],{"class":207},[198,778,779],{"class":203},"if",[198,781,763],{"class":207},[198,783,587],{"class":249},[198,785,314],{"class":207},[198,787,788],{"class":203},"else",[198,790,791],{"class":256}," 0\n",[198,793,795,797,799,801,803,805,807,809,812,815,817,820,822,825,828,831,834,836],{"class":136,"line":794},22,[198,796,350],{"class":256},[198,798,353],{"class":207},[198,800,356],{"class":203},[198,802,359],{"class":249},[198,804,362],{"class":256},[198,806,365],{"class":207},[198,808,368],{"class":203},[198,810,811],{"class":249}," '(none)'",[198,813,814],{"class":203},":\u003C14",[198,816,377],{"class":256},[198,818,819],{"class":249}," n=",[198,821,362],{"class":256},[198,823,824],{"class":207},"entry[",[198,826,827],{"class":249},"'n'",[198,829,830],{"class":207},"]",[198,832,833],{"class":203},":>5",[198,835,377],{"class":256},[198,837,838],{"class":249},"  \"\n",[198,840,842,845,848,850,852,854,856,859,861,864,866,869,872,874,876],{"class":136,"line":841},23,[198,843,844],{"class":203},"          f",[198,846,847],{"class":249},"\"total=",[198,849,362],{"class":256},[198,851,824],{"class":207},[198,853,410],{"class":249},[198,855,830],{"class":207},[198,857,858],{"class":203},":>14,.0f",[198,860,377],{"class":256},[198,862,863],{"class":249},"  mean=",[198,865,362],{"class":256},[198,867,868],{"class":207},"mean",[198,870,871],{"class":203},":>10,.0f",[198,873,377],{"class":256},[198,875,359],{"class":249},[198,877,393],{"class":207},[14,879,880,882,883,886,887,890,891,893,894,897],{},[162,881,433],{}," Counting ",[184,884,885],{},"n"," and ",[184,888,889],{},"valued"," separately is the point — the mean must divide by the number of features that actually had a value, not by the number in the category, or every ",[184,892,453],{}," silently drags the average down. ",[184,895,896],{},"defaultdict"," with a factory avoids a membership check on every iteration. Sorting by descending count puts the categories that matter at the top of the report.",[151,899,901],{"id":900},"let-the-provider-aggregate","Let the provider aggregate",[14,903,904,905,908],{},"For a database-backed layer, ",[184,906,907],{},"QgsVectorLayer.aggregate()"," can push the work down to the server, which never transfers the rows at all.",[189,910,912],{"className":191,"code":911,"language":193,"meta":194,"style":194},"from qgis.core import QgsAggregateCalculator, QgsProject\n\nlayer = QgsProject.instance().mapLayersByName(\"parcels\")[0]\n\ntotal, ok = layer.aggregate(QgsAggregateCalculator.Sum, \"assessed_value\")\nmean, _ = layer.aggregate(QgsAggregateCalculator.Mean, \"assessed_value\")\ndistinct, _ = layer.aggregate(QgsAggregateCalculator.CountDistinct, \"zoning\")\n\nprint(f\"sum={total:,.0f} mean={mean:,.0f} distinct zonings={distinct}\")\n\nparams = QgsAggregateCalculator.AggregateParameters()\nparams.filter = '\"zoning\" = \\'residential\\''\nres_total, _ = layer.aggregate(\n    QgsAggregateCalculator.Sum, \"assessed_value\", parameters=params\n)\nprint(f\"residential total={res_total:,.0f}\")\n",[184,913,914,925,929,945,949,963,977,991,995,1041,1045,1055,1076,1086,1103,1107],{"__ignoreMap":194},[198,915,916,918,920,922],{"class":136,"line":200},[198,917,204],{"class":203},[198,919,222],{"class":207},[198,921,211],{"class":203},[198,923,924],{"class":207}," QgsAggregateCalculator, QgsProject\n",[198,926,927],{"class":136,"line":217},[198,928,234],{"emptyLinePlaceholder":233},[198,930,931,933,935,937,939,941,943],{"class":136,"line":230},[198,932,240],{"class":207},[198,934,243],{"class":203},[198,936,246],{"class":207},[198,938,250],{"class":249},[198,940,253],{"class":207},[198,942,46],{"class":256},[198,944,259],{"class":207},[198,946,947],{"class":136,"line":237},[198,948,234],{"emptyLinePlaceholder":233},[198,950,951,954,956,959,961],{"class":136,"line":262},[198,952,953],{"class":207},"total, ok ",[198,955,243],{"class":203},[198,957,958],{"class":207}," layer.aggregate(QgsAggregateCalculator.Sum, ",[198,960,542],{"class":249},[198,962,393],{"class":207},[198,964,965,968,970,973,975],{"class":136,"line":267},[198,966,967],{"class":207},"mean, _ ",[198,969,243],{"class":203},[198,971,972],{"class":207}," layer.aggregate(QgsAggregateCalculator.Mean, ",[198,974,542],{"class":249},[198,976,393],{"class":207},[198,978,979,982,984,987,989],{"class":136,"line":278},[198,980,981],{"class":207},"distinct, _ ",[198,983,243],{"class":203},[198,985,986],{"class":207}," layer.aggregate(QgsAggregateCalculator.CountDistinct, ",[198,988,290],{"class":249},[198,990,393],{"class":207},[198,992,993],{"class":136,"line":284},[198,994,234],{"emptyLinePlaceholder":233},[198,996,997,999,1001,1003,1006,1008,1011,1014,1016,1019,1021,1023,1025,1027,1030,1032,1035,1037,1039],{"class":136,"line":296},[198,998,399],{"class":256},[198,1000,353],{"class":207},[198,1002,356],{"class":203},[198,1004,1005],{"class":249},"\"sum=",[198,1007,362],{"class":256},[198,1009,1010],{"class":207},"total",[198,1012,1013],{"class":203},":,.0f",[198,1015,377],{"class":256},[198,1017,1018],{"class":249}," mean=",[198,1020,362],{"class":256},[198,1022,868],{"class":207},[198,1024,1013],{"class":203},[198,1026,377],{"class":256},[198,1028,1029],{"class":249}," distinct zonings=",[198,1031,362],{"class":256},[198,1033,1034],{"class":207},"distinct",[198,1036,377],{"class":256},[198,1038,359],{"class":249},[198,1040,393],{"class":207},[198,1042,1043],{"class":136,"line":301},[198,1044,234],{"emptyLinePlaceholder":233},[198,1046,1047,1050,1052],{"class":136,"line":329},[198,1048,1049],{"class":207},"params ",[198,1051,243],{"class":203},[198,1053,1054],{"class":207}," QgsAggregateCalculator.AggregateParameters()\n",[198,1056,1057,1060,1062,1065,1068,1071,1073],{"class":136,"line":334},[198,1058,1059],{"class":207},"params.filter ",[198,1061,243],{"class":203},[198,1063,1064],{"class":249}," '\"zoning\" = ",[198,1066,1067],{"class":256},"\\'",[198,1069,1070],{"class":249},"residential",[198,1072,1067],{"class":256},[198,1074,1075],{"class":249},"'\n",[198,1077,1078,1081,1083],{"class":136,"line":347},[198,1079,1080],{"class":207},"res_total, _ ",[198,1082,243],{"class":203},[198,1084,1085],{"class":207}," layer.aggregate(\n",[198,1087,1088,1091,1093,1095,1098,1100],{"class":136,"line":396},[198,1089,1090],{"class":207},"    QgsAggregateCalculator.Sum, ",[198,1092,542],{"class":249},[198,1094,539],{"class":207},[198,1096,1097],{"class":728},"parameters",[198,1099,243],{"class":203},[198,1101,1102],{"class":207},"params\n",[198,1104,1105],{"class":136,"line":643},[198,1106,393],{"class":207},[198,1108,1109,1111,1113,1115,1118,1120,1123,1125,1127,1129],{"class":136,"line":658},[198,1110,399],{"class":256},[198,1112,353],{"class":207},[198,1114,356],{"class":203},[198,1116,1117],{"class":249},"\"residential total=",[198,1119,362],{"class":256},[198,1121,1122],{"class":207},"res_total",[198,1124,1013],{"class":203},[198,1126,377],{"class":256},[198,1128,359],{"class":249},[198,1130,393],{"class":207},[14,1132,1133,434,1135,1138,1139,1142,1143,1146,1147,1150],{},[162,1134,433],{},[184,1136,1137],{},"aggregate()"," returns a ",[184,1140,1141],{},"(value, ok)"," tuple — the second element is ",[184,1144,1145],{},"False"," when the aggregate could not be computed, for example on a field of the wrong type. ",[184,1148,1149],{},"AggregateParameters.filter"," is an expression that restricts the rows, so one call can answer a per-category question without a Python loop. On PostGIS this becomes a single SQL aggregate; on a file-based provider QGIS iterates internally, which is still faster than doing it in Python.",[151,1152,1154],{"id":1153},"produce-a-summary-layer","Produce a summary layer",[14,1156,1157,1158,1161],{},"Console output is fine for exploration. A report or a join needs a table, and ",[184,1159,1160],{},"native:statisticsbycategories"," produces one directly.",[14,1163,1164],{},[29,1165,1168,1171,1174,1177,1194,1197,1207,1212,1240,1247,1254,1258,1262,1266,1270,1275,1315,1321],{"viewBox":1166,"role":32,"ariaLabel":1167,"xmlns":34},"0 0 760 244","A parcels layer grouped by zoning producing a summary table with one row per category holding count, sum and mean",[36,1169,1170],{},"From features to a one-row-per-category summary",[40,1172,1173],{},"Eight parcel features carrying a zoning value and an assessed value are grouped by the zoning field. The statistics by categories algorithm produces a table with one row per distinct zoning value, each holding the number of features, the sum of the assessed values and their mean.",[44,1175],{"x":46,"y":46,"width":47,"height":1176,"fill":49},"244",[1178,1179,1180],"defs",{},[1181,1182,1189],"marker",{"id":1183,"viewBox":1184,"refX":1185,"refY":1186,"markerWidth":1187,"markerHeight":1187,"orient":1188},"cntSummaryArrow","0 0 10 10","8","5","7","auto-start-reverse",[1190,1191],"path",{"d":1192,"fill":1193},"M0 0 L10 5 L0 10 z","#0f766e",[51,1195,1196],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"One row per category, ready to join back or export",[44,1198],{"x":1199,"y":1200,"width":1201,"height":1202,"rx":1203,"fill":1204,"stroke":1205,"style":1206},"16","46","212","180","10","#fffdf7","#2563eb","stroke-width:2.5",[51,1208,1211],{"x":1209,"y":62,"style":1210,"fill":1205,"textAnchor":57},"122","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","parcels",[67,1213,1215,1220,1224,1228,1232,1236],{"style":1214},"font-size:11px;font-family:monospace",[51,1216,1219],{"x":1217,"y":1218,"fill":82},"36","96","residential  420 000",[51,1221,1223],{"x":1217,"y":1222,"fill":82},"118","commercial 1 200 000",[51,1225,1227],{"x":1217,"y":1226,"fill":82},"140","residential  380 000",[51,1229,1231],{"x":1217,"y":1230,"fill":82},"162","industrial   760 000",[51,1233,1235],{"x":1217,"y":1234,"fill":82},"184","residential  455 000",[51,1237,1239],{"x":1217,"y":1238,"fill":82},"206","commercial   980 000",[44,1241],{"x":1242,"y":1243,"width":1244,"height":1245,"rx":1203,"fill":1246,"stroke":1193,"style":1206},"272","98","176","76","#26322d",[51,1248,1253],{"x":1249,"y":1250,"style":1251,"fill":1252,"textAnchor":57},"360","126","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","#d9f99d","statisticsbycategories",[51,1255,1257],{"x":1249,"y":1256,"style":81,"fill":1252,"textAnchor":57},"148","CATEGORIES_FIELD_NAME",[51,1259,1261],{"x":1249,"y":1260,"style":81,"fill":1252,"textAnchor":57},"164","= zoning",[44,1263],{"x":1264,"y":1200,"width":1265,"height":1202,"rx":1203,"fill":1204,"stroke":97,"style":1206},"492","252",[51,1267,1269],{"x":1268,"y":62,"style":1210,"fill":97,"textAnchor":57},"618","summary table",[136,1271],{"x1":1272,"y1":1273,"x2":1274,"y2":1273,"stroke":97,"style":141},"508","82","728",[67,1276,1277,1282,1286,1290,1292,1295,1298,1301,1304,1307,1310,1312],{"style":144},[51,1278,1281],{"x":1279,"y":1280,"fill":140},"514","102","category",[51,1283,383],{"x":1284,"y":1280,"style":1285,"fill":140,"textAnchor":64},"626","text-anchor:end",[51,1287,1289],{"x":1288,"y":1280,"style":1285,"fill":140,"textAnchor":64},"726","sum",[51,1291,1070],{"x":1279,"y":115,"fill":82},[51,1293,1294],{"x":1284,"y":115,"style":1285,"fill":82,"textAnchor":64},"3",[51,1296,1297],{"x":1288,"y":115,"style":1285,"fill":82,"textAnchor":64},"1 255 000",[51,1299,1300],{"x":1279,"y":1230,"fill":82},"commercial",[51,1302,1303],{"x":1284,"y":1230,"style":1285,"fill":82,"textAnchor":64},"2",[51,1305,1306],{"x":1288,"y":1230,"style":1285,"fill":82,"textAnchor":64},"2 180 000",[51,1308,1309],{"x":1279,"y":134,"fill":82},"industrial",[51,1311,744],{"x":1284,"y":134,"style":1285,"fill":82,"textAnchor":64},[51,1313,1314],{"x":1288,"y":134,"style":1285,"fill":82,"textAnchor":64},"760 000",[136,1316],{"x1":1317,"y1":1318,"x2":1319,"y2":1318,"stroke":1193,"style":1320},"228","136","268","stroke-width:2.5;marker-end:url(#cntSummaryArrow)",[136,1322],{"x1":1323,"y1":1318,"x2":1324,"y2":1318,"stroke":1193,"style":1320},"448","488",[189,1326,1328],{"className":191,"code":1327,"language":193,"meta":194,"style":194},"import processing\n\nsummary = processing.run(\"qgis:statisticsbycategories\", {\n    \"INPUT\": \"\u002Fdata\u002Fparcels.gpkg|layername=parcels\",\n    \"VALUES_FIELD_NAME\": \"assessed_value\",\n    \"CATEGORIES_FIELD_NAME\": [\"zoning\"],\n    \"OUTPUT\": \"\u002Fdata\u002Foutput\u002Fzoning_summary.gpkg\",\n})[\"OUTPUT\"]\n\nfor feature in summary.getFeatures():\n    print(feature[\"zoning\"], feature[\"count\"], feature[\"sum\"], feature[\"mean\"])\n",[184,1329,1330,1337,1341,1357,1370,1381,1394,1406,1416,1420,1431],{"__ignoreMap":194},[198,1331,1332,1334],{"class":136,"line":200},[198,1333,211],{"class":203},[198,1335,1336],{"class":207}," processing\n",[198,1338,1339],{"class":136,"line":217},[198,1340,234],{"emptyLinePlaceholder":233},[198,1342,1343,1346,1348,1351,1354],{"class":136,"line":230},[198,1344,1345],{"class":207},"summary ",[198,1347,243],{"class":203},[198,1349,1350],{"class":207}," processing.run(",[198,1352,1353],{"class":249},"\"qgis:statisticsbycategories\"",[198,1355,1356],{"class":207},", {\n",[198,1358,1359,1362,1364,1367],{"class":136,"line":237},[198,1360,1361],{"class":249},"    \"INPUT\"",[198,1363,570],{"class":207},[198,1365,1366],{"class":249},"\"\u002Fdata\u002Fparcels.gpkg|layername=parcels\"",[198,1368,1369],{"class":207},",\n",[198,1371,1372,1375,1377,1379],{"class":136,"line":262},[198,1373,1374],{"class":249},"    \"VALUES_FIELD_NAME\"",[198,1376,570],{"class":207},[198,1378,542],{"class":249},[198,1380,1369],{"class":207},[198,1382,1383,1386,1389,1391],{"class":136,"line":267},[198,1384,1385],{"class":249},"    \"CATEGORIES_FIELD_NAME\"",[198,1387,1388],{"class":207},": [",[198,1390,290],{"class":249},[198,1392,1393],{"class":207},"],\n",[198,1395,1396,1399,1401,1404],{"class":136,"line":278},[198,1397,1398],{"class":249},"    \"OUTPUT\"",[198,1400,570],{"class":207},[198,1402,1403],{"class":249},"\"\u002Fdata\u002Foutput\u002Fzoning_summary.gpkg\"",[198,1405,1369],{"class":207},[198,1407,1408,1411,1414],{"class":136,"line":284},[198,1409,1410],{"class":207},"})[",[198,1412,1413],{"class":249},"\"OUTPUT\"",[198,1415,259],{"class":207},[198,1417,1418],{"class":136,"line":296},[198,1419,234],{"emptyLinePlaceholder":233},[198,1421,1422,1424,1426,1428],{"class":136,"line":301},[198,1423,317],{"class":203},[198,1425,605],{"class":207},[198,1427,323],{"class":203},[198,1429,1430],{"class":207}," summary.getFeatures():\n",[198,1432,1433,1435,1438,1440,1443,1446,1448,1451,1453,1456],{"class":136,"line":329},[198,1434,350],{"class":256},[198,1436,1437],{"class":207},"(feature[",[198,1439,290],{"class":249},[198,1441,1442],{"class":207},"], feature[",[198,1444,1445],{"class":249},"\"count\"",[198,1447,1442],{"class":207},[198,1449,1450],{"class":249},"\"sum\"",[198,1452,1442],{"class":207},[198,1454,1455],{"class":249},"\"mean\"",[198,1457,1458],{"class":207},"])\n",[14,1460,1461,434,1463,1465,1466,1470,1471,1474,1475,539,1477,539,1480,539,1483,539,1486,539,1488,539,1490,539,1493,1496,1497,1501,1502,176],{},[162,1462,433],{},[184,1464,1257],{}," takes a ",[1467,1468,1469],"em",{},"list",", so passing two fields groups by their combination — zoning and district, for example. ",[184,1472,1473],{},"VALUES_FIELD_NAME"," is the numeric field being summarised; omit it and you get counts only. The output carries ",[184,1476,383],{},[184,1478,1479],{},"unique",[184,1481,1482],{},"min",[184,1484,1485],{},"max",[184,1487,1289],{},[184,1489,868],{},[184,1491,1492],{},"median",[184,1494,1495],{},"stddev"," and the quartiles, which is usually more than you need but costs nothing extra. Because the result is a layer, it can be joined straight back onto the source polygons — see ",[21,1498,1500],{"href":1499},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fjoin-attributes-by-field-value-pyqgis\u002F","Join Attributes by Field Value in PyQGIS"," — or exported with ",[21,1503,1505],{"href":1504},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fexport-attribute-table-to-csv-pyqgis\u002F","Export an Attribute Table to CSV",[151,1507,1509],{"id":1508},"count-within-a-spatial-extent","Count within a spatial extent",[14,1511,1512],{},"A summary is often only wanted for part of the map — the current view, a study area, a single district. Adding a rectangle or a geometry filter to the request keeps the count spatial without loading anything extra.",[14,1514,1515],{},[29,1516,1518,1521,1524,1526,1529,1572,1624],{"viewBox":31,"role":32,"ariaLabel":1517,"xmlns":34},"A full-layer count compared with counts restricted first to a bounding rectangle and then to an exact study-area polygon",[36,1519,1520],{},"Counting the whole layer, a rectangle, and an exact area",[40,1522,1523],{},"Three panels over the same scatter of parcels. The first counts every feature in the layer. The second restricts to a bounding rectangle, which is fast but includes corners outside the study area. The third restricts to the exact study-area polygon, which requires a second geometric test and returns a smaller, correct count.",[44,1525],{"x":46,"y":46,"width":47,"height":48,"fill":49},[51,1527,1528],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"Rectangle is fast; the polygon is correct",[67,1530,1531,1537,1541,1569],{},[44,1532],{"x":1533,"y":1534,"width":1535,"height":134,"rx":1203,"fill":1204,"stroke":140,"style":1536},"12","44","236","stroke-width:2",[51,1538,1540],{"x":105,"y":1539,"style":1210,"fill":56,"textAnchor":57},"68","whole layer",[67,1542,1543,1547,1549,1551,1553,1556,1559,1562,1564,1566],{"fill":140},[1544,1545],"circle",{"cx":1200,"cy":1546,"r":1186},"100",[1544,1548],{"cx":87,"cy":105,"r":1186},[1544,1550],{"cx":115,"cy":1243,"r":1186},[1544,1552],{"cx":1244,"cy":1250,"r":1186},[1544,1554],{"cx":138,"cy":1555,"r":1186},"104",[1544,1557],{"cx":96,"cy":1558,"r":1186},"166",[1544,1560],{"cx":1555,"cy":1561,"r":1186},"194",[1544,1563],{"cx":1256,"cy":1230,"r":1186},[1544,1565],{"cx":134,"cy":124,"r":1186},[1544,1567],{"cx":1568,"cy":1558,"r":1186},"224",[51,1570,1571],{"x":105,"y":1568,"style":1251,"fill":56,"textAnchor":57},"10 features",[67,1573,1574,1577,1580,1587,1598,1621],{},[44,1575],{"x":1576,"y":1534,"width":1535,"height":134,"rx":1203,"fill":1204,"stroke":88,"style":1206},"262",[51,1578,1579],{"x":53,"y":1539,"style":1210,"fill":88,"textAnchor":57},"setFilterRect()",[44,1581],{"x":1582,"y":1583,"width":1226,"height":1584,"rx":73,"fill":88,"fillOpacity":1585,"stroke":88,"style":1586},"322","86","120",0.1,"stroke-width:2;stroke-dasharray:6 4",[67,1588,1589,1592,1595],{"fill":140},[1544,1590],{"cx":1591,"cy":1546,"r":1186},"296",[1544,1593],{"cx":1594,"cy":1558,"r":1186},"310",[1544,1596],{"cx":1597,"cy":1555,"r":1186},"474",[67,1599,1600,1604,1607,1610,1613,1615,1618],{"fill":88},[1544,1601],{"cx":1602,"cy":105,"r":1603},"340","6",[1544,1605],{"cx":1606,"cy":1243,"r":1603},"382",[1544,1608],{"cx":1609,"cy":1250,"r":1603},"426",[1544,1611],{"cx":1612,"cy":1561,"r":1603},"354",[1544,1614],{"cx":79,"cy":1230,"r":1603},[1544,1616],{"cx":1617,"cy":124,"r":1603},"442",[1544,1619],{"cx":1620,"cy":1218,"r":1603},"440",[51,1622,1623],{"x":53,"y":1568,"style":1251,"fill":88,"textAnchor":57},"7 — includes corners",[67,1625,1626,1629,1633,1638,1655,1671],{},[44,1627],{"x":1628,"y":1534,"width":1535,"height":134,"rx":1203,"fill":1204,"stroke":97,"style":1206},"512",[51,1630,1632],{"x":1631,"y":1539,"style":1210,"fill":97,"textAnchor":57},"630","exact polygon",[1190,1634],{"d":1635,"fill":97,"fillOpacity":1636,"stroke":97,"style":1637},"M572 96 L678 90 L706 150 L648 202 L576 178 Z",0.12,"stroke-width:2.5;stroke-linejoin:round",[67,1639,1640,1643,1646,1649,1652],{"fill":140},[1544,1641],{"cx":1642,"cy":1546,"r":1186},"546",[1544,1644],{"cx":1645,"cy":1558,"r":1186},"560",[1544,1647],{"cx":1648,"cy":1555,"r":1186},"724",[1544,1650],{"cx":1651,"cy":1561,"r":1186},"604",[1544,1653],{"cx":1654,"cy":1218,"r":1186},"690",[67,1656,1657,1660,1664,1667],{"fill":97},[1544,1658],{"cx":1659,"cy":105,"r":1603},"590",[1544,1661],{"cx":1662,"cy":1663,"r":1603},"632","112",[1544,1665],{"cx":1666,"cy":1226,"r":1603},"676",[1544,1668],{"cx":1669,"cy":1670,"r":1603},"648","172",[51,1672,1673],{"x":1631,"y":1568,"style":1251,"fill":97,"textAnchor":57},"4 — the real answer",[189,1675,1677],{"className":191,"code":1676,"language":193,"meta":194,"style":194},"from collections import Counter\nfrom qgis.core import QgsFeatureRequest, QgsProject\n\nparcels = QgsProject.instance().mapLayersByName(\"parcels\")[0]\nareas = QgsProject.instance().mapLayersByName(\"study_areas\")[0]\nstudy = next(areas.getFeatures()).geometry()\n\nrequest = QgsFeatureRequest().setFilterRect(study.boundingBox())\nrequest.setSubsetOfAttributes([\"zoning\"], parcels.fields())\n\ncounts = Counter(\n    f[\"zoning\"]\n    for f in parcels.getFeatures(request)\n    if f.geometry().intersects(study)\n)\nprint(dict(counts))\n",[184,1678,1679,1689,1699,1703,1720,1738,1751,1755,1764,1773,1777,1786,1795,1807,1814,1818],{"__ignoreMap":194},[198,1680,1681,1683,1685,1687],{"class":136,"line":200},[198,1682,204],{"class":203},[198,1684,208],{"class":207},[198,1686,211],{"class":203},[198,1688,214],{"class":207},[198,1690,1691,1693,1695,1697],{"class":136,"line":217},[198,1692,204],{"class":203},[198,1694,222],{"class":207},[198,1696,211],{"class":203},[198,1698,227],{"class":207},[198,1700,1701],{"class":136,"line":230},[198,1702,234],{"emptyLinePlaceholder":233},[198,1704,1705,1708,1710,1712,1714,1716,1718],{"class":136,"line":237},[198,1706,1707],{"class":207},"parcels ",[198,1709,243],{"class":203},[198,1711,246],{"class":207},[198,1713,250],{"class":249},[198,1715,253],{"class":207},[198,1717,46],{"class":256},[198,1719,259],{"class":207},[198,1721,1722,1725,1727,1729,1732,1734,1736],{"class":136,"line":262},[198,1723,1724],{"class":207},"areas ",[198,1726,243],{"class":203},[198,1728,246],{"class":207},[198,1730,1731],{"class":249},"\"study_areas\"",[198,1733,253],{"class":207},[198,1735,46],{"class":256},[198,1737,259],{"class":207},[198,1739,1740,1743,1745,1748],{"class":136,"line":267},[198,1741,1742],{"class":207},"study ",[198,1744,243],{"class":203},[198,1746,1747],{"class":256}," next",[198,1749,1750],{"class":207},"(areas.getFeatures()).geometry()\n",[198,1752,1753],{"class":136,"line":278},[198,1754,234],{"emptyLinePlaceholder":233},[198,1756,1757,1759,1761],{"class":136,"line":284},[198,1758,270],{"class":207},[198,1760,243],{"class":203},[198,1762,1763],{"class":207}," QgsFeatureRequest().setFilterRect(study.boundingBox())\n",[198,1765,1766,1768,1770],{"class":136,"line":296},[198,1767,287],{"class":207},[198,1769,290],{"class":249},[198,1771,1772],{"class":207},"], parcels.fields())\n",[198,1774,1775],{"class":136,"line":301},[198,1776,234],{"emptyLinePlaceholder":233},[198,1778,1779,1781,1783],{"class":136,"line":329},[198,1780,304],{"class":207},[198,1782,243],{"class":203},[198,1784,1785],{"class":207}," Counter(\n",[198,1787,1788,1791,1793],{"class":136,"line":334},[198,1789,1790],{"class":207},"    f[",[198,1792,290],{"class":249},[198,1794,259],{"class":207},[198,1796,1797,1800,1802,1804],{"class":136,"line":347},[198,1798,1799],{"class":203},"    for",[198,1801,320],{"class":207},[198,1803,323],{"class":203},[198,1805,1806],{"class":207}," parcels.getFeatures(request)\n",[198,1808,1809,1811],{"class":136,"line":396},[198,1810,661],{"class":203},[198,1812,1813],{"class":207}," f.geometry().intersects(study)\n",[198,1815,1816],{"class":136,"line":643},[198,1817,393],{"class":207},[198,1819,1820,1822,1824,1827],{"class":136,"line":658},[198,1821,399],{"class":256},[198,1823,353],{"class":207},[198,1825,1826],{"class":256},"dict",[198,1828,1829],{"class":207},"(counts))\n",[14,1831,1832,434,1834,1836,1837,1840,1841,1845,1846,1848],{},[162,1833,433],{},[184,1835,1579],{}," narrows the read to the study area's bounding box, which the provider can satisfy from its own spatial index. The ",[184,1838,1839],{},"if f.geometry().intersects(study)"," test then removes the features that fell in the box but outside the actual polygon — the same two-stage pattern described in ",[21,1842,1844],{"href":1843},"\u002Fspatial-data-processing-automation\u002Fgeometry-operations-and-predicates\u002Fcheck-geometry-intersects-pyqgis\u002F","Test Whether Two Geometries Intersect in PyQGIS",". Note that ",[184,1847,437],{}," cannot be used here, because the exact test needs the geometry; the attribute subset still applies.",[151,1850,1852],{"id":1851},"qgis-version-compatibility","QGIS version compatibility",[14,1854,1855,1856,1858],{},"The examples target ",[162,1857,164],{}," (Python 3.12).",[1860,1861,1862,1878],"table",{},[1863,1864,1865],"thead",{},[1866,1867,1868,1872,1875],"tr",{},[1869,1870,1871],"th",{},"QGIS version",[1869,1873,1874],{},"Python",[1869,1876,1877],{},"Notes",[1879,1880,1881,1897,1908],"tbody",{},[1866,1882,1883,1887,1890],{},[1884,1885,1886],"td",{},"3.28 LTR",[1884,1888,1889],{},"3.9",[1884,1891,1892,1893,1896],{},"Identical API. Algorithm ID is ",[184,1894,1895],{},"qgis:statisticsbycategories"," in all 3.x.",[1866,1898,1899,1902,1905],{},[1884,1900,1901],{},"3.34 LTR",[1884,1903,1904],{},"3.12",[1884,1906,1907],{},"Baseline for this page.",[1866,1909,1910,1913,1915],{},[1884,1911,1912],{},"3.40 \u002F 3.44",[1884,1914,1904],{},[1884,1916,1917,1920],{},[184,1918,1919],{},"QgsFeatureRequest.Flag.NoGeometry"," is the newer spelling; the old constant still resolves.",[14,1922,1923,1926],{},[184,1924,1925],{},"QgsAggregateCalculator"," and the feature-request flags are unchanged across 3.x.",[151,1928,1930],{"id":1929},"troubleshooting","Troubleshooting",[156,1932,1933,1943,1958,1966,1981,1992],{},[159,1934,1935,1938,1939,1942],{},[162,1936,1937],{},"The count is slow on a polygon layer."," Geometry is being decoded. Add ",[184,1940,1941],{},"QgsFeatureRequest.NoGeometry"," and a column subset.",[159,1944,1945,1950,1951,1953,1954,1957],{},[162,1946,1947,1949],{},[184,1948,457],{}," appears as a category."," That is a ",[184,1952,453],{}," in the data, and keeping it visible is usually right. Render it as ",[184,1955,1956],{},"'(no value)'"," rather than dropping it.",[159,1959,1960,434,1963,1965],{},[162,1961,1962],{},"The mean is lower than expected.",[184,1964,453],{}," values were counted in the denominator. Track the number of non-null values separately and divide by that.",[159,1967,1968,1976,1977,1980],{},[162,1969,1970,1972,1973,176],{},[184,1971,1137],{}," returns ",[184,1974,1975],{},"(None, False)"," The field is the wrong type for that aggregate — ",[184,1978,1979],{},"Sum"," on a text column, for example — or the field name is misspelled.",[159,1982,1983,434,1989,1991],{},[162,1984,1985,1986,1988],{},"The summary layer has no ",[184,1987,1289],{}," column.",[184,1990,1473],{}," was omitted, so only count-based statistics were produced.",[159,1993,1994,1997,1998,2001,2002,2005],{},[162,1995,1996],{},"Counts differ from the attribute table."," A filter or a ",[184,1999,2000],{},"subsetString"," is active on the layer. Check ",[184,2003,2004],{},"layer.subsetString()"," before trusting the number.",[151,2007,2009],{"id":2008},"conclusion","Conclusion",[14,2011,2012,2013,2015,2016,2019],{},"Narrow the request before you count — ",[184,2014,437],{}," plus an attribute subset is usually a large saving for a one-line change. Track non-null counts separately when computing means, push aggregates down to the provider where the data lives in a database, and produce a summary ",[1467,2017,2018],{},"layer"," rather than console output whenever the answer needs to be joined, exported or mapped.",[151,2021,2023],{"id":2022},"frequently-asked-questions","Frequently Asked Questions",[14,2025,2026,2029,2030,2033,2034,2036,2037,2039,2040,2042],{},[162,2027,2028],{},"What is the fastest way to count features per category?","\nA ",[184,2031,2032],{},"QgsFeatureRequest"," with the ",[184,2035,437],{}," flag and ",[184,2038,441],{}," limited to the category column, fed into ",[184,2041,186],{},". Geometry decoding is usually the dominant cost and contributes nothing to a count.",[14,2044,2045,2048,2049,2051],{},[162,2046,2047],{},"How do I stop NULL values skewing my average?","\nCount the features with a non-null value separately from the total count in the category, and divide the sum by that. Dividing by the category size treats every ",[184,2050,453],{}," as a zero.",[14,2053,2054,2057,2058,2060],{},[162,2055,2056],{},"When should I use aggregate() instead of a Python loop?","\nWhen the layer is database-backed. ",[184,2059,907],{}," can push the computation to the server so no rows are transferred. On file-based providers it still avoids the Python-level loop.",[14,2062,2063,2066,2067,2069,2070,2073,2074,2076,2077,2080],{},[162,2064,2065],{},"Why does my count differ from the number in the attribute table?","\nA provider-level filter is probably active on the layer. ",[184,2068,2004],{}," behaves like a permanent ",[184,2071,2072],{},"WHERE"," clause and affects iteration, rendering and the attribute table alike, so a count taken while one is set reflects the filtered subset rather than the whole dataset. Print ",[184,2075,2004],{}," before trusting a total, and clear it with ",[184,2078,2079],{},"layer.setSubsetString(\"\")"," if it was left behind by earlier code.",[14,2082,2083,2086,2087,2089,2090,2092],{},[162,2084,2085],{},"How do I group by two fields at once?","\nPass both names in the ",[184,2088,1257],{}," list of ",[184,2091,1895],{},". The output has one row per distinct combination of the two values.",[14,2094,2095,2098,2099,2101],{},[162,2096,2097],{},"Does featureCount() respect a filter?","\nYes — it reflects any active ",[184,2100,2000],{},", so it can differ from the number of rows in the underlying file.",[14,2103,2104,2107],{},[162,2105,2106],{},"Is Counter faster than a plain dictionary?","\nMarginally, and it is considerably clearer to read.",[14,2109,2110,2113,2116],{},[162,2111,2112],{},"Can I count features without iterating at all?",[184,2114,2115],{},"layer.featureCount()"," gives the total directly from the provider, which is instant. Anything broken down by category still needs a pass over the values, but restricting that pass to one column keeps it cheap.",[151,2118,2120],{"id":2119},"related","Related",[156,2122,2123,2128,2133,2137],{},[159,2124,2125,2127],{},[21,2126,24],{"href":23}," — the guide this recipe belongs to",[159,2129,2130],{},[21,2131,2132],{"href":1504},"Export an Attribute Table to CSV with PyQGIS",[159,2134,2135],{},[21,2136,1500],{"href":1499},[159,2138,2139],{},[21,2140,2142],{"href":2141},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fcreate-choropleth-map-pyqgis\u002F","Create a Choropleth Map with PyQGIS",[2144,2145,2146],"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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":194,"searchDepth":217,"depth":217,"links":2148},[2149,2150,2151,2152,2153,2154,2155,2156,2157,2158,2159],{"id":153,"depth":217,"text":154},{"id":179,"depth":217,"text":180},{"id":464,"depth":217,"text":465},{"id":900,"depth":217,"text":901},{"id":1153,"depth":217,"text":1154},{"id":1508,"depth":217,"text":1509},{"id":1851,"depth":217,"text":1852},{"id":1929,"depth":217,"text":1930},{"id":2008,"depth":217,"text":2009},{"id":2022,"depth":217,"text":2023},{"id":2119,"depth":217,"text":2120},"Count features per category in PyQGIS with Counter and a feature request, compute sums and means, and produce a summary layer with native:statisticsbycategories.","md",{"slug":2163,"type":2164,"breadcrumb":2165,"datePublished":2166,"dateModified":2166},"count-features-by-attribute-pyqgis","article","Count by Attribute","2026-08-01","\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fcount-features-by-attribute-pyqgis",{"title":5,"description":2160},"spatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fcount-features-by-attribute-pyqgis\u002Findex","TbOMe6VBWT9DGRpy3b5xDKyUaq6D-sJ1pP84UiAHKsk",1785585990475]