[{"data":1,"prerenderedAt":1975},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fcreate-hexagon-grid-and-count-points-pyqgis":3},{"id":4,"title":5,"body":6,"description":1964,"extension":1965,"meta":1966,"navigation":364,"path":1971,"seo":1972,"stem":1973,"__hash__":1974},"docs\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fcreate-hexagon-grid-and-count-points-pyqgis\u002Findex.md","Create a Hexagon Grid and Count Points in PyQGIS",{"type":7,"value":8,"toc":1952},"minimark",[9,13,17,26,185,190,211,215,218,306,831,849,853,856,1095,1121,1193,1197,1200,1417,1438,1442,1445,1611,1620,1792,1806,1810,1838,1842,1878,1882,1885,1889,1895,1901,1909,1915,1919,1948],[10,11,5],"h1",{"id":12},"create-a-hexagon-grid-and-count-points-in-pyqgis",[14,15,16],"p",{},"Forty thousand crime reports, traffic collisions or tree records plotted as dots tell you almost nothing: the dense areas become a solid smear and every symbol overlaps its neighbours. Aggregating them into a grid of equal cells turns the smear into a readable density surface, and because every cell has the same area, the counts are directly comparable — something administrative boundaries of wildly different sizes can never offer. Hexagons are the usual choice because every neighbour is the same distance away and the grid has no strong horizontal or vertical lines to draw the eye.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002F","Vector Data Manipulation in PyQGIS",". It creates a hexagon grid over a study area, counts points per cell with and without weights, handles empty and partial cells, and styles the counts as a map.",[14,27,28],{},[29,30,35,39,43,50,59,69,106,113,116,181],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 300","img","Points aggregated into hexagons: on the left thousands of overlapping dots form an unreadable cluster, on the right the same points counted into hexagon cells shaded from light to dark by count","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"From overlapping dots to comparable cells",[40,41,42],"desc",{},"Left panel: a dense cloud of small orange dots where the centre is a solid mass and individual density differences are invisible. Right panel: the same area covered by a hexagon grid, each cell shaded by the number of points it contains, from pale for few points to dark green for many, revealing a hotspot and a secondary cluster.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","300","#f6f3ea",[51,52,58],"text",{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Same 40,000 points, now comparable",[44,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"24","48","344","232","10","#fffdf7","#59645f","stroke-width:2",[70,71,74,80,85,90,95,100,102],"g",{"fill":72,"style":73},"#b45309","opacity:0.55",[75,76],"circle",{"cx":77,"cy":78,"r":79},"190","160","46",[75,81],{"cx":82,"cy":83,"r":84},"150","140","30",[75,86],{"cx":87,"cy":88,"r":89},"230","180","34",[75,91],{"cx":92,"cy":93,"r":94},"280","110","18",[75,96],{"cx":97,"cy":98,"r":99},"100","220","12",[75,101],{"cx":48,"cy":87,"r":65},[75,103],{"cx":104,"cy":97,"r":105},"80","8",[51,107,112],{"x":108,"y":109,"style":110,"fill":111,"textAnchor":57},"196","266","text-anchor:middle;font-size:10.5px;font-family:sans-serif","#2f3b35","dots: density is guesswork",[44,114],{"x":115,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"392",[70,117,119,124,128,131,135,138,141,144,148,151,154,157,160,163,166,169,172,175,178],{"stroke":66,"style":118},"stroke-width:1.5",[120,121],"path",{"d":122,"fill":123},"M452 90 L472 78 L492 90 L492 114 L472 126 L452 114 z","#edf8e9",[120,125],{"d":126,"fill":127},"M492 90 L512 78 L532 90 L532 114 L512 126 L492 114 z","#c9d6cf",[120,129],{"d":130,"fill":127},"M532 90 L552 78 L572 90 L572 114 L552 126 L532 114 z",[120,132],{"d":133,"fill":134},"M572 90 L592 78 L612 90 L612 114 L592 126 L572 114 z","#4b7f52",[120,136],{"d":137,"fill":123},"M612 90 L632 78 L652 90 L652 114 L632 126 L612 114 z",[120,139],{"d":140,"fill":127},"M472 126 L492 114 L512 126 L512 150 L492 162 L472 150 z",[120,142],{"d":143,"fill":134},"M512 126 L532 114 L552 126 L552 150 L532 162 L512 150 z",[120,145],{"d":146,"fill":147},"M552 126 L572 114 L592 126 L592 150 L572 162 L552 150 z","#166534",[120,149],{"d":150,"fill":134},"M592 126 L612 114 L632 126 L632 150 L612 162 L592 150 z",[120,152],{"d":153,"fill":123},"M632 126 L652 114 L672 126 L672 150 L652 162 L632 150 z",[120,155],{"d":156,"fill":123},"M452 162 L472 150 L492 162 L492 186 L472 198 L452 186 z",[120,158],{"d":159,"fill":134},"M492 162 L512 150 L532 162 L532 186 L512 198 L492 186 z",[120,161],{"d":162,"fill":147},"M532 162 L552 150 L572 162 L572 186 L552 198 L532 186 z",[120,164],{"d":165,"fill":134},"M572 162 L592 150 L612 162 L612 186 L592 198 L572 186 z",[120,167],{"d":168,"fill":127},"M612 162 L632 150 L652 162 L652 186 L632 198 L612 186 z",[120,170],{"d":171,"fill":123},"M472 198 L492 186 L512 198 L512 222 L492 234 L472 222 z",[120,173],{"d":174,"fill":127},"M512 198 L532 186 L552 198 L552 222 L532 234 L512 222 z",[120,176],{"d":177,"fill":127},"M552 198 L572 186 L592 198 L592 222 L572 234 L552 222 z",[120,179],{"d":180,"fill":123},"M592 198 L612 186 L632 198 L632 222 L612 234 L592 222 z",[51,182,184],{"x":183,"y":109,"style":110,"fill":111,"textAnchor":57},"564","hexagons: counts per equal area",[186,187,189],"h2",{"id":188},"prerequisites","Prerequisites",[191,192,193,201,208],"ul",{},[194,195,196,200],"li",{},[197,198,199],"strong",{},"QGIS 3.40 LTR"," or newer, or the QGIS 4 series.",[194,202,203,204,207],{},"A point layer and a study-area polygon, both in a ",[197,205,206],{},"projected CRS"," with metre units. A grid built in degrees has cells whose ground area shrinks towards the poles, which defeats the purpose.",[194,209,210],{},"A rough idea of the scale readers care about — a street, a neighbourhood, a district — because that sets the cell size.",[186,212,214],{"id":213},"choose-a-cell-size","Choose a cell size",[14,216,217],{},"There is no correct cell size, only one that suits the question and the data. Too small and most cells hold zero or one point, so the map shows noise; too large and genuine patterns are averaged away. A useful starting point is a size at which the median non-empty cell holds somewhere between five and fifty points.",[14,219,220],{},[29,221,224,227,230,233,237,244,250,255,259,264,268,272,275,278,281,284,287,290,294,297,300,303],{"viewBox":222,"role":32,"ariaLabel":223,"xmlns":34},"0 0 760 256","The same points counted into hexagons of 100 m, 400 m and 1,600 m: the smallest grid is mostly empty and noisy, the middle grid shows clear clusters, the largest grid merges everything into a few cells",[36,225,226],{},"Cell size changes the story",[40,228,229],{},"Three panels. With 100 metre hexagons, 82 percent of cells are empty and the rest hold one or two points, so the pattern is speckle. With 400 metre hexagons the median occupied cell holds 14 points and two clusters are visible. With 1,600 metre hexagons the whole town falls into six cells and the clusters disappear.",[44,231],{"x":46,"y":46,"width":47,"height":232,"fill":49},"256",[51,234,236],{"x":53,"y":235,"style":55,"fill":56,"textAnchor":57},"26","Too fine is noise, too coarse is a blur",[44,238],{"x":61,"y":239,"width":240,"height":241,"rx":65,"fill":66,"stroke":242,"style":243},"44","222","192","#b91c1c","stroke-width:2.5",[51,245,249],{"x":246,"y":247,"style":248,"fill":242,"textAnchor":57},"135","68","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","100 m",[51,251,254],{"x":246,"y":252,"style":253,"fill":56,"textAnchor":57},"120","text-anchor:middle;font-size:22px;font-weight:bold;font-family:sans-serif","82%",[51,256,258],{"x":246,"y":257,"style":110,"fill":111,"textAnchor":57},"144","cells empty",[51,260,263],{"x":246,"y":261,"style":262,"fill":67,"textAnchor":57},"186","text-anchor:middle;font-size:10px;font-family:sans-serif","median occupied: 1",[51,265,267],{"x":246,"y":266,"style":262,"fill":242,"textAnchor":57},"210","speckle",[44,269],{"x":270,"y":239,"width":240,"height":241,"rx":65,"fill":66,"stroke":271,"style":243},"269","#15803d",[51,273,274],{"x":53,"y":247,"style":248,"fill":271,"textAnchor":57},"400 m",[51,276,277],{"x":53,"y":252,"style":253,"fill":56,"textAnchor":57},"14",[51,279,280],{"x":53,"y":257,"style":110,"fill":111,"textAnchor":57},"median occupied cell",[51,282,283],{"x":53,"y":261,"style":262,"fill":67,"textAnchor":57},"31% empty",[51,285,286],{"x":53,"y":266,"style":262,"fill":271,"textAnchor":57},"two clusters visible",[44,288],{"x":289,"y":239,"width":240,"height":241,"rx":65,"fill":66,"stroke":72,"style":243},"514",[51,291,293],{"x":292,"y":247,"style":248,"fill":72,"textAnchor":57},"625","1,600 m",[51,295,296],{"x":292,"y":252,"style":253,"fill":56,"textAnchor":57},"6",[51,298,299],{"x":292,"y":257,"style":110,"fill":111,"textAnchor":57},"cells cover the town",[51,301,302],{"x":292,"y":261,"style":262,"fill":67,"textAnchor":57},"median occupied: 5,900",[51,304,305],{"x":292,"y":266,"style":262,"fill":72,"textAnchor":57},"pattern averaged away",[307,308,313],"pre",{"className":309,"code":310,"language":311,"meta":312,"style":312},"language-python shiki shiki-themes github-dark","import math\nimport statistics\nimport processing\nfrom qgis.core import QgsProject\n\nincidents = QgsProject.instance().mapLayersByName(\"incidents_2025\")[0]\nstudy = QgsProject.instance().mapLayersByName(\"borough_boundary\")[0]\nassert not incidents.crs().isGeographic()\n\ndef trial(size):\n    grid = processing.run(\"native:creategrid\", {\n        \"TYPE\": 4, \"EXTENT\": study.extent(), \"HSPACING\": size, \"VSPACING\": size,\n        \"HOVERLAY\": 0, \"VOVERLAY\": 0, \"CRS\": incidents.crs(), \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n    })[\"OUTPUT\"]\n    counted = processing.run(\"native:countpointsinpolygon\", {\n        \"POLYGONS\": grid, \"POINTS\": incidents, \"FIELD\": \"n\", \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n    })[\"OUTPUT\"]\n    counts = [f[\"n\"] for f in counted.getFeatures()]\n    occupied = [c for c in counts if c > 0]\n    return (len(counts), 1 - len(occupied) \u002F len(counts),\n            statistics.median(occupied) if occupied else 0)\n\nfor size in (100, 200, 400, 800, 1600):\n    cells, empty, median = trial(size)\n    print(f\"{size:>5} m: {cells:>6} cells, {empty:5.0%} empty, median occupied {median}\")\n","python","",[314,315,316,329,337,345,359,366,391,410,422,427,440,457,490,528,538,553,586,595,623,657,692,711,716,753,764],"code",{"__ignoreMap":312},[317,318,321,325],"span",{"class":319,"line":320},"line",1,[317,322,324],{"class":323},"snl16","import",[317,326,328],{"class":327},"s95oV"," math\n",[317,330,332,334],{"class":319,"line":331},2,[317,333,324],{"class":323},[317,335,336],{"class":327}," statistics\n",[317,338,340,342],{"class":319,"line":339},3,[317,341,324],{"class":323},[317,343,344],{"class":327}," processing\n",[317,346,348,351,354,356],{"class":319,"line":347},4,[317,349,350],{"class":323},"from",[317,352,353],{"class":327}," qgis.core ",[317,355,324],{"class":323},[317,357,358],{"class":327}," QgsProject\n",[317,360,362],{"class":319,"line":361},5,[317,363,365],{"emptyLinePlaceholder":364},true,"\n",[317,367,369,372,375,378,382,385,388],{"class":319,"line":368},6,[317,370,371],{"class":327},"incidents ",[317,373,374],{"class":323},"=",[317,376,377],{"class":327}," QgsProject.instance().mapLayersByName(",[317,379,381],{"class":380},"sU2Wk","\"incidents_2025\"",[317,383,384],{"class":327},")[",[317,386,46],{"class":387},"sDLfK",[317,389,390],{"class":327},"]\n",[317,392,394,397,399,401,404,406,408],{"class":319,"line":393},7,[317,395,396],{"class":327},"study ",[317,398,374],{"class":323},[317,400,377],{"class":327},[317,402,403],{"class":380},"\"borough_boundary\"",[317,405,384],{"class":327},[317,407,46],{"class":387},[317,409,390],{"class":327},[317,411,413,416,419],{"class":319,"line":412},8,[317,414,415],{"class":323},"assert",[317,417,418],{"class":323}," not",[317,420,421],{"class":327}," incidents.crs().isGeographic()\n",[317,423,425],{"class":319,"line":424},9,[317,426,365],{"emptyLinePlaceholder":364},[317,428,430,433,437],{"class":319,"line":429},10,[317,431,432],{"class":323},"def",[317,434,436],{"class":435},"svObZ"," trial",[317,438,439],{"class":327},"(size):\n",[317,441,443,446,448,451,454],{"class":319,"line":442},11,[317,444,445],{"class":327},"    grid ",[317,447,374],{"class":323},[317,449,450],{"class":327}," processing.run(",[317,452,453],{"class":380},"\"native:creategrid\"",[317,455,456],{"class":327},", {\n",[317,458,460,463,466,469,472,475,478,481,484,487],{"class":319,"line":459},12,[317,461,462],{"class":380},"        \"TYPE\"",[317,464,465],{"class":327},": ",[317,467,468],{"class":387},"4",[317,470,471],{"class":327},", ",[317,473,474],{"class":380},"\"EXTENT\"",[317,476,477],{"class":327},": study.extent(), ",[317,479,480],{"class":380},"\"HSPACING\"",[317,482,483],{"class":327},": size, ",[317,485,486],{"class":380},"\"VSPACING\"",[317,488,489],{"class":327},": size,\n",[317,491,493,496,498,500,502,505,507,509,511,514,517,520,522,525],{"class":319,"line":492},13,[317,494,495],{"class":380},"        \"HOVERLAY\"",[317,497,465],{"class":327},[317,499,46],{"class":387},[317,501,471],{"class":327},[317,503,504],{"class":380},"\"VOVERLAY\"",[317,506,465],{"class":327},[317,508,46],{"class":387},[317,510,471],{"class":327},[317,512,513],{"class":380},"\"CRS\"",[317,515,516],{"class":327},": incidents.crs(), ",[317,518,519],{"class":380},"\"OUTPUT\"",[317,521,465],{"class":327},[317,523,524],{"class":380},"\"TEMPORARY_OUTPUT\"",[317,526,527],{"class":327},",\n",[317,529,531,534,536],{"class":319,"line":530},14,[317,532,533],{"class":327},"    })[",[317,535,519],{"class":380},[317,537,390],{"class":327},[317,539,541,544,546,548,551],{"class":319,"line":540},15,[317,542,543],{"class":327},"    counted ",[317,545,374],{"class":323},[317,547,450],{"class":327},[317,549,550],{"class":380},"\"native:countpointsinpolygon\"",[317,552,456],{"class":327},[317,554,556,559,562,565,568,571,573,576,578,580,582,584],{"class":319,"line":555},16,[317,557,558],{"class":380},"        \"POLYGONS\"",[317,560,561],{"class":327},": grid, ",[317,563,564],{"class":380},"\"POINTS\"",[317,566,567],{"class":327},": incidents, ",[317,569,570],{"class":380},"\"FIELD\"",[317,572,465],{"class":327},[317,574,575],{"class":380},"\"n\"",[317,577,471],{"class":327},[317,579,519],{"class":380},[317,581,465],{"class":327},[317,583,524],{"class":380},[317,585,527],{"class":327},[317,587,589,591,593],{"class":319,"line":588},17,[317,590,533],{"class":327},[317,592,519],{"class":380},[317,594,390],{"class":327},[317,596,598,601,603,606,608,611,614,617,620],{"class":319,"line":597},18,[317,599,600],{"class":327},"    counts ",[317,602,374],{"class":323},[317,604,605],{"class":327}," [f[",[317,607,575],{"class":380},[317,609,610],{"class":327},"] ",[317,612,613],{"class":323},"for",[317,615,616],{"class":327}," f ",[317,618,619],{"class":323},"in",[317,621,622],{"class":327}," counted.getFeatures()]\n",[317,624,626,629,631,634,636,639,641,644,647,649,652,655],{"class":319,"line":625},19,[317,627,628],{"class":327},"    occupied ",[317,630,374],{"class":323},[317,632,633],{"class":327}," [c ",[317,635,613],{"class":323},[317,637,638],{"class":327}," c ",[317,640,619],{"class":323},[317,642,643],{"class":327}," counts ",[317,645,646],{"class":323},"if",[317,648,638],{"class":327},[317,650,651],{"class":323},">",[317,653,654],{"class":387}," 0",[317,656,390],{"class":327},[317,658,660,663,666,669,672,675,678,681,684,687,689],{"class":319,"line":659},20,[317,661,662],{"class":323},"    return",[317,664,665],{"class":327}," (",[317,667,668],{"class":387},"len",[317,670,671],{"class":327},"(counts), ",[317,673,674],{"class":387},"1",[317,676,677],{"class":323}," -",[317,679,680],{"class":387}," len",[317,682,683],{"class":327},"(occupied) ",[317,685,686],{"class":323},"\u002F",[317,688,680],{"class":387},[317,690,691],{"class":327},"(counts),\n",[317,693,695,698,700,703,706,708],{"class":319,"line":694},21,[317,696,697],{"class":327},"            statistics.median(occupied) ",[317,699,646],{"class":323},[317,701,702],{"class":327}," occupied ",[317,704,705],{"class":323},"else",[317,707,654],{"class":387},[317,709,710],{"class":327},")\n",[317,712,714],{"class":319,"line":713},22,[317,715,365],{"emptyLinePlaceholder":364},[317,717,719,721,724,726,728,730,732,735,737,740,742,745,747,750],{"class":319,"line":718},23,[317,720,613],{"class":323},[317,722,723],{"class":327}," size ",[317,725,619],{"class":323},[317,727,665],{"class":327},[317,729,97],{"class":387},[317,731,471],{"class":327},[317,733,734],{"class":387},"200",[317,736,471],{"class":327},[317,738,739],{"class":387},"400",[317,741,471],{"class":327},[317,743,744],{"class":387},"800",[317,746,471],{"class":327},[317,748,749],{"class":387},"1600",[317,751,752],{"class":327},"):\n",[317,754,756,759,761],{"class":319,"line":755},24,[317,757,758],{"class":327},"    cells, empty, median ",[317,760,374],{"class":323},[317,762,763],{"class":327}," trial(size)\n",[317,765,767,770,773,776,779,782,785,788,791,794,796,799,802,804,807,809,812,815,817,820,822,825,827,829],{"class":319,"line":766},25,[317,768,769],{"class":387},"    print",[317,771,772],{"class":327},"(",[317,774,775],{"class":323},"f",[317,777,778],{"class":380},"\"",[317,780,781],{"class":387},"{",[317,783,784],{"class":327},"size",[317,786,787],{"class":323},":>5",[317,789,790],{"class":387},"}",[317,792,793],{"class":380}," m: ",[317,795,781],{"class":387},[317,797,798],{"class":327},"cells",[317,800,801],{"class":323},":>6",[317,803,790],{"class":387},[317,805,806],{"class":380}," cells, ",[317,808,781],{"class":387},[317,810,811],{"class":327},"empty",[317,813,814],{"class":323},":5.0%",[317,816,790],{"class":387},[317,818,819],{"class":380}," empty, median occupied ",[317,821,781],{"class":387},[317,823,824],{"class":327},"median",[317,826,790],{"class":387},[317,828,778],{"class":380},[317,830,710],{"class":327},[14,832,833,836,837,840,841,844,845,848],{},[197,834,835],{},"Breakdown:"," ",[314,838,839],{},"TYPE"," 4 is a hexagon grid (0 point, 1 line, 2 rectangle, 3 diamond). For hexagons, ",[314,842,843],{},"HSPACING"," and ",[314,846,847],{},"VSPACING"," are the distances between cell centres; setting them equal gives regular hexagons. Running a handful of sizes on temporary outputs takes seconds on most datasets and turns the choice into an informed one rather than a guess. Write down the size you settle on and why — it is a methodological decision a reader is entitled to question.",[186,850,852],{"id":851},"build-the-grid-over-the-study-area","Build the grid over the study area",[14,854,855],{},"A grid built on the study area's bounding box covers far more than the area itself: corners of sea, neighbouring districts, empty land that will show as zero and make the map look emptier than it is. Keep only cells that intersect the study area, then count.",[307,857,859],{"className":309,"code":858,"language":311,"meta":312,"style":312},"SIZE = 400\n\ngrid = processing.run(\"native:creategrid\", {\n    \"TYPE\": 4, \"EXTENT\": study.extent(), \"HSPACING\": SIZE, \"VSPACING\": SIZE,\n    \"HOVERLAY\": 0, \"VOVERLAY\": 0, \"CRS\": study.crs(), \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nin_area = processing.run(\"native:extractbylocation\", {\n    \"INPUT\": grid, \"PREDICATE\": [0], \"INTERSECT\": study, \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nhexes = processing.run(\"native:countpointsinpolygon\", {\n    \"POLYGONS\": in_area, \"POINTS\": incidents, \"WEIGHT\": \"\", \"CLASSFIELD\": \"\",\n    \"FIELD\": \"n\", \"OUTPUT\": \"\u002Fdata\u002Fwork\u002Fincidents_hex400.gpkg\",\n})[\"OUTPUT\"]\n",[314,860,861,872,876,889,920,952,961,965,979,1011,1019,1023,1036,1067,1087],{"__ignoreMap":312},[317,862,863,866,869],{"class":319,"line":320},[317,864,865],{"class":387},"SIZE",[317,867,868],{"class":323}," =",[317,870,871],{"class":387}," 400\n",[317,873,874],{"class":319,"line":331},[317,875,365],{"emptyLinePlaceholder":364},[317,877,878,881,883,885,887],{"class":319,"line":339},[317,879,880],{"class":327},"grid ",[317,882,374],{"class":323},[317,884,450],{"class":327},[317,886,453],{"class":380},[317,888,456],{"class":327},[317,890,891,894,896,898,900,902,904,906,908,910,912,914,916,918],{"class":319,"line":347},[317,892,893],{"class":380},"    \"TYPE\"",[317,895,465],{"class":327},[317,897,468],{"class":387},[317,899,471],{"class":327},[317,901,474],{"class":380},[317,903,477],{"class":327},[317,905,480],{"class":380},[317,907,465],{"class":327},[317,909,865],{"class":387},[317,911,471],{"class":327},[317,913,486],{"class":380},[317,915,465],{"class":327},[317,917,865],{"class":387},[317,919,527],{"class":327},[317,921,922,925,927,929,931,933,935,937,939,941,944,946,948,950],{"class":319,"line":361},[317,923,924],{"class":380},"    \"HOVERLAY\"",[317,926,465],{"class":327},[317,928,46],{"class":387},[317,930,471],{"class":327},[317,932,504],{"class":380},[317,934,465],{"class":327},[317,936,46],{"class":387},[317,938,471],{"class":327},[317,940,513],{"class":380},[317,942,943],{"class":327},": study.crs(), ",[317,945,519],{"class":380},[317,947,465],{"class":327},[317,949,524],{"class":380},[317,951,527],{"class":327},[317,953,954,957,959],{"class":319,"line":368},[317,955,956],{"class":327},"})[",[317,958,519],{"class":380},[317,960,390],{"class":327},[317,962,963],{"class":319,"line":393},[317,964,365],{"emptyLinePlaceholder":364},[317,966,967,970,972,974,977],{"class":319,"line":412},[317,968,969],{"class":327},"in_area ",[317,971,374],{"class":323},[317,973,450],{"class":327},[317,975,976],{"class":380},"\"native:extractbylocation\"",[317,978,456],{"class":327},[317,980,981,984,986,989,992,994,997,1000,1003,1005,1007,1009],{"class":319,"line":424},[317,982,983],{"class":380},"    \"INPUT\"",[317,985,561],{"class":327},[317,987,988],{"class":380},"\"PREDICATE\"",[317,990,991],{"class":327},": [",[317,993,46],{"class":387},[317,995,996],{"class":327},"], ",[317,998,999],{"class":380},"\"INTERSECT\"",[317,1001,1002],{"class":327},": study, ",[317,1004,519],{"class":380},[317,1006,465],{"class":327},[317,1008,524],{"class":380},[317,1010,527],{"class":327},[317,1012,1013,1015,1017],{"class":319,"line":429},[317,1014,956],{"class":327},[317,1016,519],{"class":380},[317,1018,390],{"class":327},[317,1020,1021],{"class":319,"line":442},[317,1022,365],{"emptyLinePlaceholder":364},[317,1024,1025,1028,1030,1032,1034],{"class":319,"line":459},[317,1026,1027],{"class":327},"hexes ",[317,1029,374],{"class":323},[317,1031,450],{"class":327},[317,1033,550],{"class":380},[317,1035,456],{"class":327},[317,1037,1038,1041,1044,1046,1048,1051,1053,1056,1058,1061,1063,1065],{"class":319,"line":492},[317,1039,1040],{"class":380},"    \"POLYGONS\"",[317,1042,1043],{"class":327},": in_area, ",[317,1045,564],{"class":380},[317,1047,567],{"class":327},[317,1049,1050],{"class":380},"\"WEIGHT\"",[317,1052,465],{"class":327},[317,1054,1055],{"class":380},"\"\"",[317,1057,471],{"class":327},[317,1059,1060],{"class":380},"\"CLASSFIELD\"",[317,1062,465],{"class":327},[317,1064,1055],{"class":380},[317,1066,527],{"class":327},[317,1068,1069,1072,1074,1076,1078,1080,1082,1085],{"class":319,"line":530},[317,1070,1071],{"class":380},"    \"FIELD\"",[317,1073,465],{"class":327},[317,1075,575],{"class":380},[317,1077,471],{"class":327},[317,1079,519],{"class":380},[317,1081,465],{"class":327},[317,1083,1084],{"class":380},"\"\u002Fdata\u002Fwork\u002Fincidents_hex400.gpkg\"",[317,1086,527],{"class":327},[317,1088,1089,1091,1093],{"class":319,"line":540},[317,1090,956],{"class":327},[317,1092,519],{"class":380},[317,1094,390],{"class":327},[14,1096,1097,1099,1100,1104,1105,471,1108,471,1111,471,1114,844,1117,1120],{},[197,1098,835],{}," Extracting cells that intersect the boundary — rather than clipping them — keeps every hexagon whole, so all cells have equal area and counts stay comparable; the boundary is drawn on top to show where the study area ends. Clipping instead would leave partial cells along the edge with less area and artificially low counts. The extraction is the ",[21,1101,1103],{"href":1102},"\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fselect-features-by-location-pyqgis\u002F","select-by-location"," pattern with predicate 0, intersect. Grid cells carry ",[314,1106,1107],{},"left",[314,1109,1110],{},"top",[314,1112,1113],{},"right",[314,1115,1116],{},"bottom",[314,1118,1119],{},"id"," fields, which are handy for joining results from different years onto the same cells.",[14,1122,1123],{},[29,1124,1127,1130,1133,1136,1139,1141,1144,1159,1165,1168,1170,1173,1187,1190],{"viewBox":1125,"role":32,"ariaLabel":1126,"xmlns":34},"0 0 760 244","Edge handling for grid cells: keeping whole intersecting cells preserves equal area, clipping cells to the boundary creates partial cells with undercounts, so whole cells plus a boundary outline is the default",[36,1128,1129],{},"Keep whole cells at the edge",[40,1131,1132],{},"Left: hexagons along a coastline kept whole, extending slightly past the boundary, each with the same area so counts compare fairly. Right: the same hexagons clipped to the coastline; edge cells shrink to a fraction of their area and show misleadingly low counts, which would need dividing by area to correct.",[44,1134],{"x":46,"y":46,"width":47,"height":1135,"fill":49},"244",[51,1137,1138],{"x":53,"y":235,"style":55,"fill":56,"textAnchor":57},"Clipped cells undercount; whole cells compare fairly",[44,1140],{"x":61,"y":239,"width":63,"height":88,"rx":65,"fill":66,"stroke":271,"style":243},[51,1142,1143],{"x":108,"y":247,"style":248,"fill":271,"textAnchor":57},"whole cells + outline",[70,1145,1147,1150,1153,1156],{"stroke":271,"style":1146},"stroke-width:1.2",[120,1148],{"d":1149,"fill":127},"M90 110 L114 96 L138 110 L138 138 L114 152 L90 138 z",[120,1151],{"d":1152,"fill":134},"M138 110 L162 96 L186 110 L186 138 L162 152 L138 138 z",[120,1154],{"d":1155,"fill":127},"M186 110 L210 96 L234 110 L234 138 L210 152 L186 138 z",[120,1157],{"d":1158,"fill":127},"M234 110 L258 96 L282 110 L282 138 L258 152 L234 138 z",[120,1160],{"d":1161,"fill":1162,"stroke":1163,"style":1164},"M60 150 Q150 120 220 146 T330 130","none","#2563eb","stroke-width:3",[51,1166,1167],{"x":108,"y":108,"style":262,"fill":111,"textAnchor":57},"every cell the same area",[44,1169],{"x":115,"y":239,"width":63,"height":88,"rx":65,"fill":66,"stroke":242,"style":243},[51,1171,1172],{"x":183,"y":247,"style":248,"fill":242,"textAnchor":57},"clipped to coastline",[70,1174,1175,1178,1181,1184],{"stroke":242,"style":1146},[120,1176],{"d":1177,"fill":123},"M458 110 L482 96 L506 110 L506 128 L482 134 L458 136 z",[120,1179],{"d":1180,"fill":127},"M506 110 L530 96 L554 110 L554 132 L530 124 L506 128 z",[120,1182],{"d":1183,"fill":123},"M554 110 L578 96 L602 110 L602 140 L578 138 L554 132 z",[120,1185],{"d":1186,"fill":123},"M602 110 L626 96 L650 110 L650 132 L626 136 L602 140 z",[120,1188],{"d":1189,"fill":1162,"stroke":1163,"style":1164},"M428 150 Q518 120 588 146 T698 130",[51,1191,1192],{"x":183,"y":108,"style":262,"fill":111,"textAnchor":57},"edge cells look quieter than they are",[186,1194,1196],{"id":1195},"weighted-counts-and-category-counts","Weighted counts and category counts",[14,1198,1199],{},"A plain count treats every point as one. Often a point stands for more — a collision with several casualties, a household with several residents — or you want counts split by type.",[307,1201,1203],{"className":309,"code":1202,"language":311,"meta":312,"style":312},"casualties = processing.run(\"native:countpointsinpolygon\", {\n    \"POLYGONS\": in_area, \"POINTS\": incidents, \"WEIGHT\": \"casualties\",\n    \"FIELD\": \"casualties\", \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\ntypes = processing.run(\"native:countpointsinpolygon\", {\n    \"POLYGONS\": in_area, \"POINTS\": incidents, \"CLASSFIELD\": \"category\",\n    \"FIELD\": \"n_categories\", \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n\nby_type = processing.run(\"native:joinbylocationsummary\", {\n    \"INPUT\": in_area, \"JOIN\": incidents, \"PREDICATE\": [0],\n    \"JOIN_FIELDS\": [\"category\"], \"SUMMARIES\": [0, 3],\n    \"DISCARD_NONMATCHING\": False, \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\n",[314,1204,1205,1218,1237,1255,1263,1267,1280,1299,1318,1326,1330,1344,1364,1389,1409],{"__ignoreMap":312},[317,1206,1207,1210,1212,1214,1216],{"class":319,"line":320},[317,1208,1209],{"class":327},"casualties ",[317,1211,374],{"class":323},[317,1213,450],{"class":327},[317,1215,550],{"class":380},[317,1217,456],{"class":327},[317,1219,1220,1222,1224,1226,1228,1230,1232,1235],{"class":319,"line":331},[317,1221,1040],{"class":380},[317,1223,1043],{"class":327},[317,1225,564],{"class":380},[317,1227,567],{"class":327},[317,1229,1050],{"class":380},[317,1231,465],{"class":327},[317,1233,1234],{"class":380},"\"casualties\"",[317,1236,527],{"class":327},[317,1238,1239,1241,1243,1245,1247,1249,1251,1253],{"class":319,"line":339},[317,1240,1071],{"class":380},[317,1242,465],{"class":327},[317,1244,1234],{"class":380},[317,1246,471],{"class":327},[317,1248,519],{"class":380},[317,1250,465],{"class":327},[317,1252,524],{"class":380},[317,1254,527],{"class":327},[317,1256,1257,1259,1261],{"class":319,"line":347},[317,1258,956],{"class":327},[317,1260,519],{"class":380},[317,1262,390],{"class":327},[317,1264,1265],{"class":319,"line":361},[317,1266,365],{"emptyLinePlaceholder":364},[317,1268,1269,1272,1274,1276,1278],{"class":319,"line":368},[317,1270,1271],{"class":327},"types ",[317,1273,374],{"class":323},[317,1275,450],{"class":327},[317,1277,550],{"class":380},[317,1279,456],{"class":327},[317,1281,1282,1284,1286,1288,1290,1292,1294,1297],{"class":319,"line":393},[317,1283,1040],{"class":380},[317,1285,1043],{"class":327},[317,1287,564],{"class":380},[317,1289,567],{"class":327},[317,1291,1060],{"class":380},[317,1293,465],{"class":327},[317,1295,1296],{"class":380},"\"category\"",[317,1298,527],{"class":327},[317,1300,1301,1303,1305,1308,1310,1312,1314,1316],{"class":319,"line":412},[317,1302,1071],{"class":380},[317,1304,465],{"class":327},[317,1306,1307],{"class":380},"\"n_categories\"",[317,1309,471],{"class":327},[317,1311,519],{"class":380},[317,1313,465],{"class":327},[317,1315,524],{"class":380},[317,1317,527],{"class":327},[317,1319,1320,1322,1324],{"class":319,"line":424},[317,1321,956],{"class":327},[317,1323,519],{"class":380},[317,1325,390],{"class":327},[317,1327,1328],{"class":319,"line":429},[317,1329,365],{"emptyLinePlaceholder":364},[317,1331,1332,1335,1337,1339,1342],{"class":319,"line":442},[317,1333,1334],{"class":327},"by_type ",[317,1336,374],{"class":323},[317,1338,450],{"class":327},[317,1340,1341],{"class":380},"\"native:joinbylocationsummary\"",[317,1343,456],{"class":327},[317,1345,1346,1348,1350,1353,1355,1357,1359,1361],{"class":319,"line":459},[317,1347,983],{"class":380},[317,1349,1043],{"class":327},[317,1351,1352],{"class":380},"\"JOIN\"",[317,1354,567],{"class":327},[317,1356,988],{"class":380},[317,1358,991],{"class":327},[317,1360,46],{"class":387},[317,1362,1363],{"class":327},"],\n",[317,1365,1366,1369,1371,1373,1375,1378,1380,1382,1384,1387],{"class":319,"line":492},[317,1367,1368],{"class":380},"    \"JOIN_FIELDS\"",[317,1370,991],{"class":327},[317,1372,1296],{"class":380},[317,1374,996],{"class":327},[317,1376,1377],{"class":380},"\"SUMMARIES\"",[317,1379,991],{"class":327},[317,1381,46],{"class":387},[317,1383,471],{"class":327},[317,1385,1386],{"class":387},"3",[317,1388,1363],{"class":327},[317,1390,1391,1394,1396,1399,1401,1403,1405,1407],{"class":319,"line":530},[317,1392,1393],{"class":380},"    \"DISCARD_NONMATCHING\"",[317,1395,465],{"class":327},[317,1397,1398],{"class":387},"False",[317,1400,471],{"class":327},[317,1402,519],{"class":380},[317,1404,465],{"class":327},[317,1406,524],{"class":380},[317,1408,527],{"class":327},[317,1410,1411,1413,1415],{"class":319,"line":540},[317,1412,956],{"class":327},[317,1414,519],{"class":380},[317,1416,390],{"class":327},[14,1418,1419,836,1421,1424,1425,1428,1429,1433,1434,1437],{},[197,1420,835],{},[314,1422,1423],{},"WEIGHT"," sums a numeric field instead of counting points, so the output field holds total casualties per cell. ",[314,1426,1427],{},"CLASSFIELD"," does something people often do not expect: it counts the number of ",[1430,1431,1432],"em",{},"distinct"," values of that field in each cell, not the number of points per value — a measure of variety, useful and easily misread. For counts per category, ",[314,1435,1436],{},"native:joinbylocationsummary"," with count and unique-values summaries, or a filtered count per category, gives the breakdown. Note that a point exactly on the shared edge of two hexagons is counted in both; with real-world coordinates this is rare, but with points snapped to a grid that coincides with the hexagon grid it is not.",[186,1439,1441],{"id":1440},"normalise-class-and-style-the-result","Normalise, class and style the result",[14,1443,1444],{},"Raw counts mostly map where people are. Dividing by a denominator — population, road length, number of properties — turns a density map into a rate map, and a sensible classification stops a single extreme cell from flattening everything else.",[307,1446,1448],{"className":309,"code":1447,"language":311,"meta":312,"style":312},"rates = processing.run(\"native:joinbylocationsummary\", {\n    \"INPUT\": hexes, \"JOIN\": QgsProject.instance().mapLayersByName(\"address_points\")[0],\n    \"PREDICATE\": [0], \"SUMMARIES\": [0], \"DISCARD_NONMATCHING\": False,\n    \"OUTPUT\": \"TEMPORARY_OUTPUT\",\n})[\"OUTPUT\"]\nrates = processing.run(\"native:fieldcalculator\", {\n    \"INPUT\": rates, \"FIELD_NAME\": \"per_1000_addr\", \"FIELD_TYPE\": 0, \"FIELD_PRECISION\": 2,\n    \"FORMULA\": 'CASE WHEN \"fid_count\" >= 20 THEN 1000.0 * \"n\" \u002F \"fid_count\" END',\n    \"OUTPUT\": \"\u002Fdata\u002Fwork\u002Fincidents_hex400_rates.gpkg\",\n})[\"OUTPUT\"]\n",[314,1449,1450,1463,1484,1512,1523,1531,1544,1580,1592,1603],{"__ignoreMap":312},[317,1451,1452,1455,1457,1459,1461],{"class":319,"line":320},[317,1453,1454],{"class":327},"rates ",[317,1456,374],{"class":323},[317,1458,450],{"class":327},[317,1460,1341],{"class":380},[317,1462,456],{"class":327},[317,1464,1465,1467,1470,1472,1475,1478,1480,1482],{"class":319,"line":331},[317,1466,983],{"class":380},[317,1468,1469],{"class":327},": hexes, ",[317,1471,1352],{"class":380},[317,1473,1474],{"class":327},": QgsProject.instance().mapLayersByName(",[317,1476,1477],{"class":380},"\"address_points\"",[317,1479,384],{"class":327},[317,1481,46],{"class":387},[317,1483,1363],{"class":327},[317,1485,1486,1489,1491,1493,1495,1497,1499,1501,1503,1506,1508,1510],{"class":319,"line":339},[317,1487,1488],{"class":380},"    \"PREDICATE\"",[317,1490,991],{"class":327},[317,1492,46],{"class":387},[317,1494,996],{"class":327},[317,1496,1377],{"class":380},[317,1498,991],{"class":327},[317,1500,46],{"class":387},[317,1502,996],{"class":327},[317,1504,1505],{"class":380},"\"DISCARD_NONMATCHING\"",[317,1507,465],{"class":327},[317,1509,1398],{"class":387},[317,1511,527],{"class":327},[317,1513,1514,1517,1519,1521],{"class":319,"line":347},[317,1515,1516],{"class":380},"    \"OUTPUT\"",[317,1518,465],{"class":327},[317,1520,524],{"class":380},[317,1522,527],{"class":327},[317,1524,1525,1527,1529],{"class":319,"line":361},[317,1526,956],{"class":327},[317,1528,519],{"class":380},[317,1530,390],{"class":327},[317,1532,1533,1535,1537,1539,1542],{"class":319,"line":368},[317,1534,1454],{"class":327},[317,1536,374],{"class":323},[317,1538,450],{"class":327},[317,1540,1541],{"class":380},"\"native:fieldcalculator\"",[317,1543,456],{"class":327},[317,1545,1546,1548,1551,1554,1556,1559,1561,1564,1566,1568,1570,1573,1575,1578],{"class":319,"line":393},[317,1547,983],{"class":380},[317,1549,1550],{"class":327},": rates, ",[317,1552,1553],{"class":380},"\"FIELD_NAME\"",[317,1555,465],{"class":327},[317,1557,1558],{"class":380},"\"per_1000_addr\"",[317,1560,471],{"class":327},[317,1562,1563],{"class":380},"\"FIELD_TYPE\"",[317,1565,465],{"class":327},[317,1567,46],{"class":387},[317,1569,471],{"class":327},[317,1571,1572],{"class":380},"\"FIELD_PRECISION\"",[317,1574,465],{"class":327},[317,1576,1577],{"class":387},"2",[317,1579,527],{"class":327},[317,1581,1582,1585,1587,1590],{"class":319,"line":412},[317,1583,1584],{"class":380},"    \"FORMULA\"",[317,1586,465],{"class":327},[317,1588,1589],{"class":380},"'CASE WHEN \"fid_count\" >= 20 THEN 1000.0 * \"n\" \u002F \"fid_count\" END'",[317,1591,527],{"class":327},[317,1593,1594,1596,1598,1601],{"class":319,"line":424},[317,1595,1516],{"class":380},[317,1597,465],{"class":327},[317,1599,1600],{"class":380},"\"\u002Fdata\u002Fwork\u002Fincidents_hex400_rates.gpkg\"",[317,1602,527],{"class":327},[317,1604,1605,1607,1609],{"class":319,"line":429},[317,1606,956],{"class":327},[317,1608,519],{"class":380},[317,1610,390],{"class":327},[14,1612,1613,1615,1616,1619],{},[197,1614,835],{}," Counting address points per cell gives a denominator on exactly the same geometry as the incident counts, and the rate expression divides the two. Cells with fewer than twenty addresses get a null rate rather than a number, because a rate built on three households swings wildly on a single event and would dominate any colour scale; the threshold is a judgement worth stating on the map. The summary field name — ",[314,1617,1618],{},"fid_count"," here — depends on which field the count summary is taken from, so print the output's field names once before relying on it.",[307,1621,1623],{"className":309,"code":1622,"language":311,"meta":312,"style":312},"from qgis.core import (\n    QgsVectorLayer, QgsGraduatedSymbolRenderer, QgsClassificationQuantile,\n    QgsStyle, QgsRendererRange, QgsFillSymbol,\n)\n\nlayer = QgsVectorLayer(hexes, \"incidents per 400 m hexagon\", \"ogr\")\nlayer.setSubsetString('\"n\" > 0')\n\nrenderer = QgsGraduatedSymbolRenderer(\"n\")\nrenderer.setClassificationMethod(QgsClassificationQuantile())\nrenderer.updateClasses(layer, 6)\nrenderer.updateColorRamp(QgsStyle.defaultStyle().colorRamp(\"Greens\"))\nfor i, r in enumerate(renderer.ranges()):\n    symbol = r.symbol().clone()\n    symbol.symbolLayer(0).setStrokeColor(symbol.color().darker(115))\n    renderer.updateRangeSymbol(i, symbol)\nlayer.setRenderer(renderer)\nlayer.setOpacity(0.85)\nQgsProject.instance().addMapLayer(layer)\n",[314,1624,1625,1636,1641,1646,1650,1654,1674,1684,1688,1702,1707,1716,1727,1742,1752,1767,1772,1777,1787],{"__ignoreMap":312},[317,1626,1627,1629,1631,1633],{"class":319,"line":320},[317,1628,350],{"class":323},[317,1630,353],{"class":327},[317,1632,324],{"class":323},[317,1634,1635],{"class":327}," (\n",[317,1637,1638],{"class":319,"line":331},[317,1639,1640],{"class":327},"    QgsVectorLayer, QgsGraduatedSymbolRenderer, QgsClassificationQuantile,\n",[317,1642,1643],{"class":319,"line":339},[317,1644,1645],{"class":327},"    QgsStyle, QgsRendererRange, QgsFillSymbol,\n",[317,1647,1648],{"class":319,"line":347},[317,1649,710],{"class":327},[317,1651,1652],{"class":319,"line":361},[317,1653,365],{"emptyLinePlaceholder":364},[317,1655,1656,1659,1661,1664,1667,1669,1672],{"class":319,"line":368},[317,1657,1658],{"class":327},"layer ",[317,1660,374],{"class":323},[317,1662,1663],{"class":327}," QgsVectorLayer(hexes, ",[317,1665,1666],{"class":380},"\"incidents per 400 m hexagon\"",[317,1668,471],{"class":327},[317,1670,1671],{"class":380},"\"ogr\"",[317,1673,710],{"class":327},[317,1675,1676,1679,1682],{"class":319,"line":393},[317,1677,1678],{"class":327},"layer.setSubsetString(",[317,1680,1681],{"class":380},"'\"n\" > 0'",[317,1683,710],{"class":327},[317,1685,1686],{"class":319,"line":412},[317,1687,365],{"emptyLinePlaceholder":364},[317,1689,1690,1693,1695,1698,1700],{"class":319,"line":424},[317,1691,1692],{"class":327},"renderer ",[317,1694,374],{"class":323},[317,1696,1697],{"class":327}," QgsGraduatedSymbolRenderer(",[317,1699,575],{"class":380},[317,1701,710],{"class":327},[317,1703,1704],{"class":319,"line":429},[317,1705,1706],{"class":327},"renderer.setClassificationMethod(QgsClassificationQuantile())\n",[317,1708,1709,1712,1714],{"class":319,"line":442},[317,1710,1711],{"class":327},"renderer.updateClasses(layer, ",[317,1713,296],{"class":387},[317,1715,710],{"class":327},[317,1717,1718,1721,1724],{"class":319,"line":459},[317,1719,1720],{"class":327},"renderer.updateColorRamp(QgsStyle.defaultStyle().colorRamp(",[317,1722,1723],{"class":380},"\"Greens\"",[317,1725,1726],{"class":327},"))\n",[317,1728,1729,1731,1734,1736,1739],{"class":319,"line":492},[317,1730,613],{"class":323},[317,1732,1733],{"class":327}," i, r ",[317,1735,619],{"class":323},[317,1737,1738],{"class":387}," enumerate",[317,1740,1741],{"class":327},"(renderer.ranges()):\n",[317,1743,1744,1747,1749],{"class":319,"line":530},[317,1745,1746],{"class":327},"    symbol ",[317,1748,374],{"class":323},[317,1750,1751],{"class":327}," r.symbol().clone()\n",[317,1753,1754,1757,1759,1762,1765],{"class":319,"line":540},[317,1755,1756],{"class":327},"    symbol.symbolLayer(",[317,1758,46],{"class":387},[317,1760,1761],{"class":327},").setStrokeColor(symbol.color().darker(",[317,1763,1764],{"class":387},"115",[317,1766,1726],{"class":327},[317,1768,1769],{"class":319,"line":555},[317,1770,1771],{"class":327},"    renderer.updateRangeSymbol(i, symbol)\n",[317,1773,1774],{"class":319,"line":588},[317,1775,1776],{"class":327},"layer.setRenderer(renderer)\n",[317,1778,1779,1782,1785],{"class":319,"line":597},[317,1780,1781],{"class":327},"layer.setOpacity(",[317,1783,1784],{"class":387},"0.85",[317,1786,710],{"class":327},[317,1788,1789],{"class":319,"line":625},[317,1790,1791],{"class":327},"QgsProject.instance().addMapLayer(layer)\n",[14,1793,1794,1796,1797,844,1801,1805],{},[197,1795,835],{}," Hiding zero cells with a subset string keeps the map focused on where incidents happened; show them in a neutral grey instead if the absence of events is itself the finding. Quantile classes put an equal number of cells in each colour, which suits skewed count data far better than equal intervals, where one busy town centre cell would push every other cell into the lightest class. Slightly darker outlines separate adjacent cells of the same class. For choosing breaks more deliberately — natural breaks, or fixed thresholds agreed with the people who use the map — see ",[21,1798,1800],{"href":1799},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fcreate-choropleth-map-pyqgis\u002F","creating a choropleth map",[21,1802,1804],{"href":1803},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fclassify-layer-natural-breaks-jenks-pyqgis\u002F","classifying with natural breaks",".",[186,1807,1809],{"id":1808},"qgis-version-compatibility","QGIS version compatibility",[14,1811,1812,1815,1816,1819,1820,1823,1824,1826,1827,1830,1831,844,1834,1837],{},[314,1813,1814],{},"native:creategrid"," has been available since QGIS 3.0 and ",[314,1817,1818],{},"native:countpointsinpolygon"," since 3.10; earlier releases use ",[314,1821,1822],{},"qgis:countpointsinpolygon"," with the same parameters. ",[314,1825,1436],{}," replaced its ",[314,1828,1829],{},"qgis:"," predecessor in 3.20. ",[314,1832,1833],{},"QgsClassificationQuantile",[314,1835,1836],{},"setClassificationMethod"," date from 3.10. Everything shown runs unchanged on 3.40, 3.44 and the QGIS 4 series.",[186,1839,1841],{"id":1840},"troubleshooting","Troubleshooting",[191,1843,1844,1854,1860,1866,1872],{},[194,1845,1846,836,1849,844,1851,1853],{},[197,1847,1848],{},"Hexagons look squashed.",[314,1850,843],{},[314,1852,847],{}," differ, or the layer is in a geographic CRS.",[194,1855,1856,1859],{},[197,1857,1858],{},"Every count is zero."," Points and grid are in different CRSs; the grid CRS parameter did not match the points.",[194,1861,1862,1865],{},[197,1863,1864],{},"The grid covers the sea."," It was built on the extent; extract cells intersecting the study area.",[194,1867,1868,1871],{},[197,1869,1870],{},"Totals exceed the number of points."," Points on shared cell edges are counted twice, or a weight field was used.",[194,1873,1874,1877],{},[197,1875,1876],{},"The map is one colour with a single dark cell."," Equal-interval classes on skewed data; use quantiles or natural breaks.",[186,1879,1881],{"id":1880},"conclusion","Conclusion",[14,1883,1884],{},"Work in a projected CRS, test a few cell sizes and pick the one where occupied cells hold a meaningful number of points, keep whole cells that intersect the study area, and count with weights or categories as the question needs. Then normalise where a denominator exists, classify with quantiles or natural breaks, and style the result so readers compare equal areas rather than administrative accidents.",[186,1886,1888],{"id":1887},"frequently-asked-questions","Frequently Asked Questions",[14,1890,1891,1894],{},[197,1892,1893],{},"Why hexagons instead of squares?","\nEvery hexagon neighbour is equidistant, cells approximate circles more closely, and the grid lacks long straight lines that draw the eye. Squares are fine when the output must align with a raster.",[14,1896,1897,1900],{},[197,1898,1899],{},"Can I produce the same thing as a raster?","\nYes — a heatmap or a point-density raster, but those smooth across cell boundaries. Hexagons keep discrete, countable units that can be joined and tabulated.",[14,1902,1903,1906,1907,1805],{},[197,1904,1905],{},"How do I compare two years on the same grid?","\nBuild the grid once, save it, and count each year's points into copies of it; join the results on the grid cell ",[314,1908,1119],{},[14,1910,1911,1914],{},[197,1912,1913],{},"Is there a hexagon index like H3 in QGIS?","\nNot built in. Plugins provide H3; for most maps a Processing grid in a local projected CRS is simpler and has equal-area cells by construction.",[186,1916,1918],{"id":1917},"related","Related",[191,1920,1921,1926,1931,1937,1943],{},[194,1922,1923,1925],{},[21,1924,24],{"href":23}," — the guide this recipe belongs to",[194,1927,1928],{},[21,1929,1930],{"href":1102},"Select Features by Location in PyQGIS",[194,1932,1933],{},[21,1934,1936],{"href":1935},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fcount-features-by-attribute-pyqgis\u002F","Count and Summarise Features by Attribute in PyQGIS",[194,1938,1939],{},[21,1940,1942],{"href":1941},"\u002Fpyqgis-cartography-visualization\u002Fsymbol-layers-and-advanced-symbology\u002Fcreate-heatmap-renderer-pyqgis\u002F","Build a Heatmap Renderer in PyQGIS",[194,1944,1945],{},[21,1946,1947],{"href":1799},"Create a Choropleth Map in PyQGIS",[1949,1950,1951],"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 pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}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);}",{"title":312,"searchDepth":331,"depth":331,"links":1953},[1954,1955,1956,1957,1958,1959,1960,1961,1962,1963],{"id":188,"depth":331,"text":189},{"id":213,"depth":331,"text":214},{"id":851,"depth":331,"text":852},{"id":1195,"depth":331,"text":1196},{"id":1440,"depth":331,"text":1441},{"id":1808,"depth":331,"text":1809},{"id":1840,"depth":331,"text":1841},{"id":1880,"depth":331,"text":1881},{"id":1887,"depth":331,"text":1888},{"id":1917,"depth":331,"text":1918},"Aggregate thousands of points into an even hexagon grid with native:creategrid and native:countpointsinpolygon — picking a cell size, clipping the grid to a study area, weighting and classing counts, normalising by area, and styling the result as a choropleth.","md",{"slug":1967,"type":1968,"breadcrumb":1969,"datePublished":1970,"dateModified":1970},"create-hexagon-grid-and-count-points-pyqgis","article","Hexagon Grid and Point Counts","2026-09-17","\u002Fspatial-data-processing-automation\u002Fvector-data-manipulation\u002Fcreate-hexagon-grid-and-count-points-pyqgis",{"title":5,"description":1964},"spatial-data-processing-automation\u002Fvector-data-manipulation\u002Fcreate-hexagon-grid-and-count-points-pyqgis\u002Findex","UE8KUOPArk6Vi23Gv_aLbXbXCAZt2qx8U1vwHR7F3r0",1789632908611]