[{"data":1,"prerenderedAt":2280},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fclassify-layer-natural-breaks-jenks-pyqgis":3},{"id":4,"title":5,"body":6,"description":2268,"extension":2269,"meta":2270,"navigation":515,"path":2276,"seo":2277,"stem":2278,"__hash__":2279},"docs\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fclassify-layer-natural-breaks-jenks-pyqgis\u002Findex.md","Classify a Layer with Natural Breaks (Jenks) in PyQGIS",{"type":7,"value":8,"toc":2252},"minimark",[9,13,33,376,379,384,417,421,447,713,739,743,750,946,962,966,969,1157,1166,1219,1223,1230,1343,1354,1358,1361,1697,1709,1713,1716,1791,1815,1819,1822,1980,1984,1987,2055,2059,2066,2127,2137,2141,2152,2166,2175,2186,2190,2199,2203,2209,2215,2221,2230,2234,2248],[10,11,5],"h1",{"id":12},"classify-a-layer-with-natural-breaks-jenks-in-pyqgis",[14,15,16,17,21,22,26,27,32],"p",{},"Natural breaks (the Jenks optimization) is the classification method that respects the ",[18,19,20],"em",{},"shape"," of your data. Instead of cutting a numeric field into equal-width bins or equal-count groups, Jenks searches for class boundaries that sit in the natural gaps of the distribution — minimizing variance within each class and maximizing it between classes. For real-world data with dense groupings and outliers, it produces the most faithful thematic map. This task-focused guide applies Jenks programmatically with ",[23,24,25],"code",{},"QgsClassificationJenks",", then compares it against the alternatives — a companion to the broader ",[28,29,31],"a",{"href":30},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002F","Graduated & Categorized Renderers in PyQGIS"," guide, which frames where each classification method fits.",[34,35,40,41,40,45,40,49,40,56,40,40,65,40,72,40,80,40,86,40,143,40,149,40,40,153,40,159,40,165,40,170,40,208,40,40,233,40,237,40,242,40,244,40,280,40,40,302,40,306,40,310,40,313,40,349,40,372],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 720 430","img","Comparison of where equal interval, quantile, and Jenks natural breaks place class boundaries on the same skewed field","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[42,43,44],"title",{},"Where three classification methods place their breaks",[46,47,48],"desc",{},"The same skewed field — a dense cluster of low values and a sparse high tail — shown four times. Equal interval spaces four breaks evenly and ignores the data. Quantile crowds its breaks into the dense low region to balance feature counts. Jenks settles its breaks into the natural gaps between the tail values.",[50,51],"rect",{"x":52,"y":52,"width":53,"height":54,"fill":55},"0","720","430","#f6f3ea",[57,58,64],"text",{"x":59,"y":60,"fill":61,"style":62,"textAnchor":63},"360","30","#17211d","text-anchor:middle;font-family:sans-serif;font-size:16px;font-weight:bold","middle","Same field, four ways to cut it into 5 classes",[57,66,71],{"x":67,"y":68,"fill":69,"style":70},"140","60","#2f3b35","font-family:sans-serif;font-size:12px;font-weight:bold","Value distribution — 17 features",[50,73],{"x":74,"y":75,"width":76,"height":60,"rx":77,"fill":78,"opacity":79},"150","74","146","4","#0f766e","0.09",[81,82],"line",{"x1":67,"y1":83,"x2":84,"y2":83,"stroke":69,"style":85},"104","680","stroke-width:2",[87,88,89,90,95,98,101,104,107,110,113,116,119,122,125,128,131,134,137,140,40],"g",{"fill":61},"\n    ",[91,92],"circle",{"cx":93,"cy":83,"r":94},"156","3.5",[91,96],{"cx":97,"cy":83,"r":94},"167",[91,99],{"cx":100,"cy":83,"r":94},"172",[91,102],{"cx":103,"cy":83,"r":94},"183",[91,105],{"cx":106,"cy":83,"r":94},"189",[91,108],{"cx":109,"cy":83,"r":94},"199",[91,111],{"cx":112,"cy":83,"r":94},"205",[91,114],{"cx":115,"cy":83,"r":94},"216",[91,117],{"cx":118,"cy":83,"r":94},"226",[91,120],{"cx":121,"cy":83,"r":94},"237",[91,123],{"cx":124,"cy":83,"r":94},"253",[91,126],{"cx":127,"cy":83,"r":94},"270",[91,129],{"cx":130,"cy":83,"r":94},"286",[91,132],{"cx":133,"cy":83,"r":94},"378",[91,135],{"cx":136,"cy":83,"r":94},"464",[91,138],{"cx":139,"cy":83,"r":94},"561",[91,141],{"cx":142,"cy":83,"r":94},"648",[57,144,148],{"x":145,"y":146,"fill":69,"style":147,"textAnchor":63},"223","126","text-anchor:middle;font-family:sans-serif;font-size:11px","dense low values",[57,150,152],{"x":151,"y":146,"fill":69,"style":147,"textAnchor":63},"500","sparse high tail",[57,154,158],{"x":146,"y":155,"fill":61,"style":156,"textAnchor":157},"181","text-anchor:end;font-family:sans-serif;font-size:12px;font-weight:bold","end","Equal interval",[57,160,164],{"x":146,"y":161,"fill":162,"style":163,"textAnchor":157},"196","#b45309","text-anchor:end;font-family:sans-serif;font-size:10px","evenly spaced",[81,166],{"x1":67,"y1":167,"x2":84,"y2":167,"stroke":69,"style":168,"opacity":169},"185","stroke-width:1.5","0.5",[87,171,89,173,176,178,180,182,184,186,188,190,192,194,196,198,200,202,204,206,40],{"fill":69,"opacity":172},"0.4",[91,174],{"cx":93,"cy":167,"r":175},"2.5",[91,177],{"cx":97,"cy":167,"r":175},[91,179],{"cx":100,"cy":167,"r":175},[91,181],{"cx":103,"cy":167,"r":175},[91,183],{"cx":106,"cy":167,"r":175},[91,185],{"cx":109,"cy":167,"r":175},[91,187],{"cx":112,"cy":167,"r":175},[91,189],{"cx":115,"cy":167,"r":175},[91,191],{"cx":118,"cy":167,"r":175},[91,193],{"cx":121,"cy":167,"r":175},[91,195],{"cx":124,"cy":167,"r":175},[91,197],{"cx":127,"cy":167,"r":175},[91,199],{"cx":130,"cy":167,"r":175},[91,201],{"cx":133,"cy":167,"r":175},[91,203],{"cx":136,"cy":167,"r":175},[91,205],{"cx":139,"cy":167,"r":175},[91,207],{"cx":142,"cy":167,"r":175},[87,209,89,211,216,89,219,222,89,224,226,89,228,231,40],{"stroke":162,"fill":162,"style":210},"stroke-width:2.5",[81,212],{"x1":213,"y1":214,"x2":213,"y2":215},"248","163","191",[91,217],{"cx":213,"cy":214,"r":94,"stroke":218},"none",[81,220],{"x1":221,"y1":214,"x2":221,"y2":215},"356",[91,223],{"cx":221,"cy":214,"r":94,"stroke":218},[81,225],{"x1":136,"y1":214,"x2":136,"y2":215},[91,227],{"cx":136,"cy":214,"r":94,"stroke":218},[81,229],{"x1":230,"y1":214,"x2":230,"y2":215},"572",[91,232],{"cx":230,"cy":214,"r":94,"stroke":218},[57,234,236],{"x":146,"y":235,"fill":61,"style":156,"textAnchor":157},"266","Quantile",[57,238,241],{"x":146,"y":239,"fill":240,"style":163,"textAnchor":157},"281","#2563eb","equal counts",[81,243],{"x1":67,"y1":127,"x2":84,"y2":127,"stroke":69,"style":168,"opacity":169},[87,245,89,246,248,250,252,254,256,258,260,262,264,266,268,270,272,274,276,278,40],{"fill":69,"opacity":172},[91,247],{"cx":93,"cy":127,"r":175},[91,249],{"cx":97,"cy":127,"r":175},[91,251],{"cx":100,"cy":127,"r":175},[91,253],{"cx":103,"cy":127,"r":175},[91,255],{"cx":106,"cy":127,"r":175},[91,257],{"cx":109,"cy":127,"r":175},[91,259],{"cx":112,"cy":127,"r":175},[91,261],{"cx":115,"cy":127,"r":175},[91,263],{"cx":118,"cy":127,"r":175},[91,265],{"cx":121,"cy":127,"r":175},[91,267],{"cx":124,"cy":127,"r":175},[91,269],{"cx":127,"cy":127,"r":175},[91,271],{"cx":130,"cy":127,"r":175},[91,273],{"cx":133,"cy":127,"r":175},[91,275],{"cx":136,"cy":127,"r":175},[91,277],{"cx":139,"cy":127,"r":175},[91,279],{"cx":142,"cy":127,"r":175},[87,281,89,282,285,89,287,290,89,292,295,89,297,300,40],{"stroke":240,"fill":240,"style":210},[81,283],{"x1":106,"y1":213,"x2":106,"y2":284},"276",[91,286],{"cx":106,"cy":213,"r":94,"stroke":218},[81,288],{"x1":289,"y1":213,"x2":289,"y2":284},"221",[91,291],{"cx":289,"cy":213,"r":94,"stroke":218},[81,293],{"x1":294,"y1":213,"x2":294,"y2":284},"264",[91,296],{"cx":294,"cy":213,"r":94,"stroke":218},[81,298],{"x1":299,"y1":213,"x2":299,"y2":284},"421",[91,301],{"cx":299,"cy":213,"r":94,"stroke":218},[57,303,305],{"x":146,"y":304,"fill":61,"style":156,"textAnchor":157},"351","Jenks",[57,307,309],{"x":146,"y":308,"fill":78,"style":163,"textAnchor":157},"366","natural gaps",[81,311],{"x1":67,"y1":312,"x2":84,"y2":312,"stroke":69,"style":168,"opacity":169},"355",[87,314,89,315,317,319,321,323,325,327,329,331,333,335,337,339,341,343,345,347,40],{"fill":69,"opacity":172},[91,316],{"cx":93,"cy":312,"r":175},[91,318],{"cx":97,"cy":312,"r":175},[91,320],{"cx":100,"cy":312,"r":175},[91,322],{"cx":103,"cy":312,"r":175},[91,324],{"cx":106,"cy":312,"r":175},[91,326],{"cx":109,"cy":312,"r":175},[91,328],{"cx":112,"cy":312,"r":175},[91,330],{"cx":115,"cy":312,"r":175},[91,332],{"cx":118,"cy":312,"r":175},[91,334],{"cx":121,"cy":312,"r":175},[91,336],{"cx":124,"cy":312,"r":175},[91,338],{"cx":127,"cy":312,"r":175},[91,340],{"cx":130,"cy":312,"r":175},[91,342],{"cx":133,"cy":312,"r":175},[91,344],{"cx":136,"cy":312,"r":175},[91,346],{"cx":139,"cy":312,"r":175},[91,348],{"cx":142,"cy":312,"r":175},[87,350,89,351,356,89,358,360,89,362,365,89,367,370,40],{"stroke":78,"fill":78,"style":210},[81,352],{"x1":353,"y1":354,"x2":353,"y2":355},"332","333","361",[91,357],{"cx":353,"cy":354,"r":94,"stroke":218},[81,359],{"x1":299,"y1":354,"x2":299,"y2":355},[91,361],{"cx":299,"cy":354,"r":94,"stroke":218},[81,363],{"x1":364,"y1":354,"x2":364,"y2":355},"513",[91,366],{"cx":364,"cy":354,"r":94,"stroke":218},[81,368],{"x1":369,"y1":354,"x2":369,"y2":355},"604",[91,371],{"cx":369,"cy":354,"r":94,"stroke":218},[57,373,375],{"x":59,"y":374,"fill":69,"style":147,"textAnchor":63},"404","Coloured marks are class boundaries — Jenks alone drops them into the empty gaps between the tail values.",[14,377,378],{},"The rest of this guide is a runnable recipe. If you are choosing between shading methods for a polygon map generally, start from the parent guide; if you already know you want natural breaks, work straight through the steps below.",[380,381,383],"h2",{"id":382},"prerequisites","Prerequisites",[385,386,387,395,398,409],"ul",{},[388,389,390,394],"li",{},[391,392,393],"strong",{},"QGIS 3.34 LTR"," (Python 3.12) with PyQGIS, or a comparable 3.x release.",[388,396,397],{},"A vector layer loaded in the project with a numeric field that has a non-uniform (clustered or skewed) distribution — that is where Jenks shines.",[388,399,400,401,404,405,408],{},"The ",[391,402,403],{},"QGIS Python Console"," open (",[23,406,407],{},"Ctrl+Alt+P",").",[388,410,411,412,416],{},"Optional: familiarity with graduated renderers and sequential ramps, as used when you ",[28,413,415],{"href":414},"\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fcreate-choropleth-map-pyqgis\u002F","create a choropleth map in PyQGIS",".",[380,418,420],{"id":419},"step-1-build-a-graduated-renderer-with-the-jenks-method","Step 1: Build a Graduated Renderer with the Jenks Method",[14,422,423,424,427,428,431,432,435,436,438,439,442,443,446],{},"The modern PyQGIS pattern separates the ",[18,425,426],{},"renderer"," from the ",[18,429,430],{},"classification method",". You instantiate ",[23,433,434],{},"QgsGraduatedSymbolRenderer"," on a field, hand it a ",[23,437,25],{}," instance via ",[23,440,441],{},"setClassificationMethod()",", then call ",[23,444,445],{},"updateClasses()"," to compute the breaks.",[448,449,454],"pre",{"className":450,"code":451,"language":452,"meta":453,"style":453},"language-python shiki shiki-themes github-dark","from qgis.core import (\n    QgsProject,\n    QgsGraduatedSymbolRenderer,\n    QgsClassificationJenks,\n    QgsSymbol,\n    QgsStyle,\n)\n\nlayer = QgsProject.instance().mapLayersByName(\"counties\")[0]\nfield_name = \"median_income\"\nclass_count = 5\n\nrenderer = QgsGraduatedSymbolRenderer(field_name)\nrenderer.setSourceSymbol(QgsSymbol.defaultSymbol(layer.geometryType()))\n\n# Plug in Jenks natural breaks as the classification algorithm.\nrenderer.setClassificationMethod(QgsClassificationJenks())\n\n# Compute the break values from the layer's field.\nrenderer.updateClasses(layer, class_count)\n\nrenderer.updateColorRamp(QgsStyle.defaultStyle().colorRamp(\"YlOrRd\"))\nlayer.setRenderer(renderer)\nlayer.triggerRepaint()\n\nfor r in renderer.ranges():\n    print(f\"{r.lowerValue():.0f} – {r.upperValue():.0f}\")\n","python","",[23,455,456,474,480,486,492,498,504,510,517,542,553,564,569,580,586,591,598,604,609,615,621,626,638,644,650,655,670],{"__ignoreMap":453},[457,458,460,464,468,471],"span",{"class":81,"line":459},1,[457,461,463],{"class":462},"snl16","from",[457,465,467],{"class":466},"s95oV"," qgis.core ",[457,469,470],{"class":462},"import",[457,472,473],{"class":466}," (\n",[457,475,477],{"class":81,"line":476},2,[457,478,479],{"class":466},"    QgsProject,\n",[457,481,483],{"class":81,"line":482},3,[457,484,485],{"class":466},"    QgsGraduatedSymbolRenderer,\n",[457,487,489],{"class":81,"line":488},4,[457,490,491],{"class":466},"    QgsClassificationJenks,\n",[457,493,495],{"class":81,"line":494},5,[457,496,497],{"class":466},"    QgsSymbol,\n",[457,499,501],{"class":81,"line":500},6,[457,502,503],{"class":466},"    QgsStyle,\n",[457,505,507],{"class":81,"line":506},7,[457,508,509],{"class":466},")\n",[457,511,513],{"class":81,"line":512},8,[457,514,516],{"emptyLinePlaceholder":515},true,"\n",[457,518,520,523,526,529,533,536,539],{"class":81,"line":519},9,[457,521,522],{"class":466},"layer ",[457,524,525],{"class":462},"=",[457,527,528],{"class":466}," QgsProject.instance().mapLayersByName(",[457,530,532],{"class":531},"sU2Wk","\"counties\"",[457,534,535],{"class":466},")[",[457,537,52],{"class":538},"sDLfK",[457,540,541],{"class":466},"]\n",[457,543,545,548,550],{"class":81,"line":544},10,[457,546,547],{"class":466},"field_name ",[457,549,525],{"class":462},[457,551,552],{"class":531}," \"median_income\"\n",[457,554,556,559,561],{"class":81,"line":555},11,[457,557,558],{"class":466},"class_count ",[457,560,525],{"class":462},[457,562,563],{"class":538}," 5\n",[457,565,567],{"class":81,"line":566},12,[457,568,516],{"emptyLinePlaceholder":515},[457,570,572,575,577],{"class":81,"line":571},13,[457,573,574],{"class":466},"renderer ",[457,576,525],{"class":462},[457,578,579],{"class":466}," QgsGraduatedSymbolRenderer(field_name)\n",[457,581,583],{"class":81,"line":582},14,[457,584,585],{"class":466},"renderer.setSourceSymbol(QgsSymbol.defaultSymbol(layer.geometryType()))\n",[457,587,589],{"class":81,"line":588},15,[457,590,516],{"emptyLinePlaceholder":515},[457,592,594],{"class":81,"line":593},16,[457,595,597],{"class":596},"sjoCn","# Plug in Jenks natural breaks as the classification algorithm.\n",[457,599,601],{"class":81,"line":600},17,[457,602,603],{"class":466},"renderer.setClassificationMethod(QgsClassificationJenks())\n",[457,605,607],{"class":81,"line":606},18,[457,608,516],{"emptyLinePlaceholder":515},[457,610,612],{"class":81,"line":611},19,[457,613,614],{"class":596},"# Compute the break values from the layer's field.\n",[457,616,618],{"class":81,"line":617},20,[457,619,620],{"class":466},"renderer.updateClasses(layer, class_count)\n",[457,622,624],{"class":81,"line":623},21,[457,625,516],{"emptyLinePlaceholder":515},[457,627,629,632,635],{"class":81,"line":628},22,[457,630,631],{"class":466},"renderer.updateColorRamp(QgsStyle.defaultStyle().colorRamp(",[457,633,634],{"class":531},"\"YlOrRd\"",[457,636,637],{"class":466},"))\n",[457,639,641],{"class":81,"line":640},23,[457,642,643],{"class":466},"layer.setRenderer(renderer)\n",[457,645,647],{"class":81,"line":646},24,[457,648,649],{"class":466},"layer.triggerRepaint()\n",[457,651,653],{"class":81,"line":652},25,[457,654,516],{"emptyLinePlaceholder":515},[457,656,658,661,664,667],{"class":81,"line":657},26,[457,659,660],{"class":462},"for",[457,662,663],{"class":466}," r ",[457,665,666],{"class":462},"in",[457,668,669],{"class":466}," renderer.ranges():\n",[457,671,673,676,679,682,685,688,691,694,697,700,702,705,707,709,711],{"class":81,"line":672},27,[457,674,675],{"class":538},"    print",[457,677,678],{"class":466},"(",[457,680,681],{"class":462},"f",[457,683,684],{"class":531},"\"",[457,686,687],{"class":538},"{",[457,689,690],{"class":466},"r.lowerValue()",[457,692,693],{"class":462},":.0f",[457,695,696],{"class":538},"}",[457,698,699],{"class":531}," – ",[457,701,687],{"class":538},[457,703,704],{"class":466},"r.upperValue()",[457,706,693],{"class":462},[457,708,696],{"class":538},[457,710,684],{"class":531},[457,712,509],{"class":466},[14,714,715,718,719,722,723,726,727,730,731,734,735,738],{},[391,716,717],{},"Breakdown:"," ",[23,720,721],{},"setClassificationMethod(QgsClassificationJenks())"," is the line that selects natural breaks; swapping the class object is all it takes to change strategy. ",[23,724,725],{},"updateClasses(layer, class_count)"," runs the Jenks optimization over ",[23,728,729],{},"median_income"," and creates five ",[23,732,733],{},"QgsRendererRange"," objects whose boundaries fall in the data's natural gaps. The sequential ",[23,736,737],{},"YlOrRd"," ramp encodes the ordered magnitude. Printing the ranges lets you eyeball where Jenks placed the breaks before trusting the map.",[380,740,742],{"id":741},"step-2-handle-nulls-and-invalid-values","Step 2: Handle Nulls and Invalid Values",[14,744,745,746,749],{},"Jenks operates only on valid numeric values; ",[23,747,748],{},"NULL"," entries are excluded from the optimization but still render as \"no value\" (typically grey) on the map. Decide explicitly how to treat them rather than letting them disappear silently.",[448,751,753],{"className":450,"code":752,"language":452,"meta":453,"style":453},"# Count how many features carry a usable value.\ntotal = layer.featureCount()\nvalid = sum(\n    1 for f in layer.getFeatures()\n    if f[field_name] is not None and f[field_name] != \"\"\n)\nprint(f\"{valid} of {total} features have a value; {total - valid} are null\")\n\n# Option A: build a clean subset by filtering, then classify on it.\nlayer.setSubsetString(f'\"{field_name}\" IS NOT NULL')\nrenderer.updateClasses(layer, class_count)\nlayer.setSubsetString(\"\")  # restore full layer for display\n\n# Option B: keep nulls visible but labelled distinctly.\n# (Leave them unclassified; QGIS draws them with the layer's\n#  \"no value\" symbol, which you can style separately.)\n",[23,754,755,760,770,783,799,827,831,879,883,888,910,914,927,931,936,941],{"__ignoreMap":453},[457,756,757],{"class":81,"line":459},[457,758,759],{"class":596},"# Count how many features carry a usable value.\n",[457,761,762,765,767],{"class":81,"line":476},[457,763,764],{"class":466},"total ",[457,766,525],{"class":462},[457,768,769],{"class":466}," layer.featureCount()\n",[457,771,772,775,777,780],{"class":81,"line":482},[457,773,774],{"class":466},"valid ",[457,776,525],{"class":462},[457,778,779],{"class":538}," sum",[457,781,782],{"class":466},"(\n",[457,784,785,788,791,794,796],{"class":81,"line":488},[457,786,787],{"class":538},"    1",[457,789,790],{"class":462}," for",[457,792,793],{"class":466}," f ",[457,795,666],{"class":462},[457,797,798],{"class":466}," layer.getFeatures()\n",[457,800,801,804,807,810,813,816,819,821,824],{"class":81,"line":494},[457,802,803],{"class":462},"    if",[457,805,806],{"class":466}," f[field_name] ",[457,808,809],{"class":462},"is",[457,811,812],{"class":462}," not",[457,814,815],{"class":538}," None",[457,817,818],{"class":462}," and",[457,820,806],{"class":466},[457,822,823],{"class":462},"!=",[457,825,826],{"class":531}," \"\"\n",[457,828,829],{"class":81,"line":500},[457,830,509],{"class":466},[457,832,833,836,838,840,842,844,847,849,852,854,857,859,862,864,866,869,872,874,877],{"class":81,"line":506},[457,834,835],{"class":538},"print",[457,837,678],{"class":466},[457,839,681],{"class":462},[457,841,684],{"class":531},[457,843,687],{"class":538},[457,845,846],{"class":466},"valid",[457,848,696],{"class":538},[457,850,851],{"class":531}," of ",[457,853,687],{"class":538},[457,855,856],{"class":466},"total",[457,858,696],{"class":538},[457,860,861],{"class":531}," features have a value; ",[457,863,687],{"class":538},[457,865,764],{"class":466},[457,867,868],{"class":462},"-",[457,870,871],{"class":466}," valid",[457,873,696],{"class":538},[457,875,876],{"class":531}," are null\"",[457,878,509],{"class":466},[457,880,881],{"class":81,"line":512},[457,882,516],{"emptyLinePlaceholder":515},[457,884,885],{"class":81,"line":519},[457,886,887],{"class":596},"# Option A: build a clean subset by filtering, then classify on it.\n",[457,889,890,893,895,898,900,903,905,908],{"class":81,"line":544},[457,891,892],{"class":466},"layer.setSubsetString(",[457,894,681],{"class":462},[457,896,897],{"class":531},"'\"",[457,899,687],{"class":538},[457,901,902],{"class":466},"field_name",[457,904,696],{"class":538},[457,906,907],{"class":531},"\" IS NOT NULL'",[457,909,509],{"class":466},[457,911,912],{"class":81,"line":555},[457,913,620],{"class":466},[457,915,916,918,921,924],{"class":81,"line":566},[457,917,892],{"class":466},[457,919,920],{"class":531},"\"\"",[457,922,923],{"class":466},")  ",[457,925,926],{"class":596},"# restore full layer for display\n",[457,928,929],{"class":81,"line":571},[457,930,516],{"emptyLinePlaceholder":515},[457,932,933],{"class":81,"line":582},[457,934,935],{"class":596},"# Option B: keep nulls visible but labelled distinctly.\n",[457,937,938],{"class":81,"line":588},[457,939,940],{"class":596},"# (Leave them unclassified; QGIS draws them with the layer's\n",[457,942,943],{"class":81,"line":593},[457,944,945],{"class":596},"#  \"no value\" symbol, which you can style separately.)\n",[14,947,948,950,951,954,955,957,958,416],{},[391,949,717],{}," Counting first tells you whether nulls are a rounding issue or a data-quality problem. ",[23,952,953],{},"setSubsetString()"," applies a temporary filter so ",[23,956,445],{}," computes breaks from clean data only; clearing it afterward keeps every feature visible. Because Jenks break positions depend on the value set it sees, classifying on the filtered subset prevents a single stray null-coerced zero from distorting the lowest class. To style the null features' fill explicitly, manipulate the symbol as shown in ",[28,959,961],{"href":960},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fset-vector-layer-symbol-color-pyqgis\u002F","Set a Vector Layer Symbol Color in PyQGIS",[380,963,965],{"id":964},"step-3-compare-jenks-against-equal-interval-and-quantile","Step 3: Compare Jenks Against Equal Interval and Quantile",[14,967,968],{},"The value of Jenks is clearest when you see the alternatives on the same data. Loop the three methods and print where each puts its breaks — identical data, three very different maps.",[448,970,972],{"className":450,"code":971,"language":452,"meta":453,"style":453},"from qgis.core import (\n    QgsClassificationEqualInterval,\n    QgsClassificationQuantile,\n    QgsClassificationJenks,\n)\n\nmethods = {\n    \"Equal Interval\": QgsClassificationEqualInterval(),\n    \"Quantile\": QgsClassificationQuantile(),\n    \"Jenks\": QgsClassificationJenks(),\n}\n\nfor name, method in methods.items():\n    renderer.setClassificationMethod(method)\n    renderer.updateClasses(layer, class_count)\n    breaks = [round(r.upperValue(), 1) for r in renderer.ranges()]\n    print(f\"{name:>14}: upper bounds {breaks}\")\n\n# Settle on Jenks for the final map.\nrenderer.setClassificationMethod(QgsClassificationJenks())\nrenderer.updateClasses(layer, class_count)\nlayer.triggerRepaint()\n",[23,973,974,984,989,994,998,1002,1006,1016,1024,1032,1040,1045,1049,1061,1066,1071,1102,1136,1140,1145,1149,1153],{"__ignoreMap":453},[457,975,976,978,980,982],{"class":81,"line":459},[457,977,463],{"class":462},[457,979,467],{"class":466},[457,981,470],{"class":462},[457,983,473],{"class":466},[457,985,986],{"class":81,"line":476},[457,987,988],{"class":466},"    QgsClassificationEqualInterval,\n",[457,990,991],{"class":81,"line":482},[457,992,993],{"class":466},"    QgsClassificationQuantile,\n",[457,995,996],{"class":81,"line":488},[457,997,491],{"class":466},[457,999,1000],{"class":81,"line":494},[457,1001,509],{"class":466},[457,1003,1004],{"class":81,"line":500},[457,1005,516],{"emptyLinePlaceholder":515},[457,1007,1008,1011,1013],{"class":81,"line":506},[457,1009,1010],{"class":466},"methods ",[457,1012,525],{"class":462},[457,1014,1015],{"class":466}," {\n",[457,1017,1018,1021],{"class":81,"line":512},[457,1019,1020],{"class":531},"    \"Equal Interval\"",[457,1022,1023],{"class":466},": QgsClassificationEqualInterval(),\n",[457,1025,1026,1029],{"class":81,"line":519},[457,1027,1028],{"class":531},"    \"Quantile\"",[457,1030,1031],{"class":466},": QgsClassificationQuantile(),\n",[457,1033,1034,1037],{"class":81,"line":544},[457,1035,1036],{"class":531},"    \"Jenks\"",[457,1038,1039],{"class":466},": QgsClassificationJenks(),\n",[457,1041,1042],{"class":81,"line":555},[457,1043,1044],{"class":466},"}\n",[457,1046,1047],{"class":81,"line":566},[457,1048,516],{"emptyLinePlaceholder":515},[457,1050,1051,1053,1056,1058],{"class":81,"line":571},[457,1052,660],{"class":462},[457,1054,1055],{"class":466}," name, method ",[457,1057,666],{"class":462},[457,1059,1060],{"class":466}," methods.items():\n",[457,1062,1063],{"class":81,"line":582},[457,1064,1065],{"class":466},"    renderer.setClassificationMethod(method)\n",[457,1067,1068],{"class":81,"line":588},[457,1069,1070],{"class":466},"    renderer.updateClasses(layer, class_count)\n",[457,1072,1073,1076,1078,1081,1084,1087,1090,1093,1095,1097,1099],{"class":81,"line":593},[457,1074,1075],{"class":466},"    breaks ",[457,1077,525],{"class":462},[457,1079,1080],{"class":466}," [",[457,1082,1083],{"class":538},"round",[457,1085,1086],{"class":466},"(r.upperValue(), ",[457,1088,1089],{"class":538},"1",[457,1091,1092],{"class":466},") ",[457,1094,660],{"class":462},[457,1096,663],{"class":466},[457,1098,666],{"class":462},[457,1100,1101],{"class":466}," renderer.ranges()]\n",[457,1103,1104,1106,1108,1110,1112,1114,1117,1120,1122,1125,1127,1130,1132,1134],{"class":81,"line":600},[457,1105,675],{"class":538},[457,1107,678],{"class":466},[457,1109,681],{"class":462},[457,1111,684],{"class":531},[457,1113,687],{"class":538},[457,1115,1116],{"class":466},"name",[457,1118,1119],{"class":462},":>14",[457,1121,696],{"class":538},[457,1123,1124],{"class":531},": upper bounds ",[457,1126,687],{"class":538},[457,1128,1129],{"class":466},"breaks",[457,1131,696],{"class":538},[457,1133,684],{"class":531},[457,1135,509],{"class":466},[457,1137,1138],{"class":81,"line":606},[457,1139,516],{"emptyLinePlaceholder":515},[457,1141,1142],{"class":81,"line":611},[457,1143,1144],{"class":596},"# Settle on Jenks for the final map.\n",[457,1146,1147],{"class":81,"line":617},[457,1148,603],{"class":466},[457,1150,1151],{"class":81,"line":623},[457,1152,620],{"class":466},[457,1154,1155],{"class":81,"line":628},[457,1156,649],{"class":466},[14,1158,1159,1161,1162,1165],{},[391,1160,717],{}," Equal interval will return evenly spaced upper bounds regardless of where the data actually sits, so a skewed field leaves some classes nearly empty. Quantile returns bounds that put equal feature counts in each class, often splitting visually identical values across a boundary. Jenks returns bounds clustered around the data's real gaps. Comparing the printed ",[23,1163,1164],{},"upper bounds"," lists makes the trade-off concrete before you commit.",[1167,1168,1169,1185],"table",{},[1170,1171,1172],"thead",{},[1173,1174,1175,1179,1182],"tr",{},[1176,1177,1178],"th",{},"Method",[1176,1180,1181],{},"How breaks are placed",[1176,1183,1184],{},"When it wins",[1186,1187,1188,1199,1209],"tbody",{},[1173,1189,1190,1193,1196],{},[1191,1192,158],"td",{},[1191,1194,1195],{},"Range divided into equal widths",[1191,1197,1198],{},"Uniform data; legends that must read as round numbers",[1173,1200,1201,1203,1206],{},[1191,1202,236],{},[1191,1204,1205],{},"Equal feature count per class",[1191,1207,1208],{},"Ranked maps; balancing map ink",[1173,1210,1211,1213,1216],{},[1191,1212,305],{},[1191,1214,1215],{},"Minimizes within-class variance at natural gaps",[1191,1217,1218],{},"Clustered or skewed real-world data",[380,1220,1222],{"id":1221},"step-4-choosing-the-class-count-for-jenks","Step 4: Choosing the Class Count for Jenks",[14,1224,1225,1226,1229],{},"Unlike equal interval, Jenks break positions ",[18,1227,1228],{},"change"," with the class count because the optimization re-partitions the whole distribution each time. Test a few counts and inspect the goodness of variance fit (GVF) implicitly by checking how tight the printed ranges are around clusters.",[448,1231,1233],{"className":450,"code":1232,"language":452,"meta":453,"style":453},"for n in (4, 5, 6, 7):\n    renderer.setClassificationMethod(QgsClassificationJenks())\n    renderer.updateClasses(layer, n)\n    spans = [round(r.upperValue() - r.lowerValue(), 1)\n             for r in renderer.ranges()]\n    print(f\"{n} classes -> span widths {spans}\")\n",[23,1234,1235,1268,1273,1278,1301,1312],{"__ignoreMap":453},[457,1236,1237,1239,1242,1244,1247,1249,1252,1255,1257,1260,1262,1265],{"class":81,"line":459},[457,1238,660],{"class":462},[457,1240,1241],{"class":466}," n ",[457,1243,666],{"class":462},[457,1245,1246],{"class":466}," (",[457,1248,77],{"class":538},[457,1250,1251],{"class":466},", ",[457,1253,1254],{"class":538},"5",[457,1256,1251],{"class":466},[457,1258,1259],{"class":538},"6",[457,1261,1251],{"class":466},[457,1263,1264],{"class":538},"7",[457,1266,1267],{"class":466},"):\n",[457,1269,1270],{"class":81,"line":476},[457,1271,1272],{"class":466},"    renderer.setClassificationMethod(QgsClassificationJenks())\n",[457,1274,1275],{"class":81,"line":482},[457,1276,1277],{"class":466},"    renderer.updateClasses(layer, n)\n",[457,1279,1280,1283,1285,1287,1289,1292,1294,1297,1299],{"class":81,"line":488},[457,1281,1282],{"class":466},"    spans ",[457,1284,525],{"class":462},[457,1286,1080],{"class":466},[457,1288,1083],{"class":538},[457,1290,1291],{"class":466},"(r.upperValue() ",[457,1293,868],{"class":462},[457,1295,1296],{"class":466}," r.lowerValue(), ",[457,1298,1089],{"class":538},[457,1300,509],{"class":466},[457,1302,1303,1306,1308,1310],{"class":81,"line":494},[457,1304,1305],{"class":462},"             for",[457,1307,663],{"class":466},[457,1309,666],{"class":462},[457,1311,1101],{"class":466},[457,1313,1314,1316,1318,1320,1322,1324,1327,1329,1332,1334,1337,1339,1341],{"class":81,"line":500},[457,1315,675],{"class":538},[457,1317,678],{"class":466},[457,1319,681],{"class":462},[457,1321,684],{"class":531},[457,1323,687],{"class":538},[457,1325,1326],{"class":466},"n",[457,1328,696],{"class":538},[457,1330,1331],{"class":531}," classes -> span widths ",[457,1333,687],{"class":538},[457,1335,1336],{"class":466},"spans",[457,1338,696],{"class":538},[457,1340,684],{"class":531},[457,1342,509],{"class":466},[14,1344,1345,1347,1348,1350,1351,1353],{},[391,1346,717],{}," Re-running ",[23,1349,445],{}," with different ",[23,1352,1326],{}," shows how the partition shifts. Look for the smallest class count where each class still maps to a meaningful group; adding classes past that point yields diminishing returns and a harder-to-read legend. Five classes is a reliable default, but clustered data with three obvious tiers may read better with four.",[380,1355,1357],{"id":1356},"step-5-measure-the-fit-yourself","Step 5: Measure the Fit Yourself",[14,1359,1360],{},"QGIS does not expose a built-in goodness of variance fit (GVF) score, but it is cheap to compute and it turns \"this map looks better\" into a number you can defend. GVF compares the variance the classification removes against the total variance in the data; values closer to 1 mean tighter, more faithful classes.",[448,1362,1364],{"className":450,"code":1363,"language":452,"meta":453,"style":453},"def gvf(values, ranges):\n    \"\"\"Goodness of variance fit for a set of classification ranges.\"\"\"\n    mean = sum(values) \u002F len(values)\n    sdam = sum((v - mean) ** 2 for v in values)  # total variance\n    sdcm = 0.0\n    for r in ranges:\n        members = [v for v in values\n                   if r.lowerValue() \u003C= v \u003C= r.upperValue()]\n        if members:\n            cmean = sum(members) \u002F len(members)\n            sdcm += sum((v - cmean) ** 2 for v in members)\n    return 1 - (sdcm \u002F sdam) if sdam else 0.0\n\n\nvalues = [f[field_name] for f in layer.getFeatures()\n          if f[field_name] is not None]\n\nfor method_cls in (QgsClassificationEqualInterval,\n                   QgsClassificationQuantile,\n                   QgsClassificationJenks):\n    renderer.setClassificationMethod(method_cls())\n    renderer.updateClasses(layer, class_count)\n    print(f\"{method_cls.__name__}: GVF = {gvf(values, renderer.ranges()):.3f}\")\n",[23,1365,1366,1378,1383,1404,1440,1450,1462,1481,1499,1507,1526,1556,1586,1590,1594,1612,1627,1631,1643,1648,1653,1658,1662],{"__ignoreMap":453},[457,1367,1368,1371,1375],{"class":81,"line":459},[457,1369,1370],{"class":462},"def",[457,1372,1374],{"class":1373},"svObZ"," gvf",[457,1376,1377],{"class":466},"(values, ranges):\n",[457,1379,1380],{"class":81,"line":476},[457,1381,1382],{"class":531},"    \"\"\"Goodness of variance fit for a set of classification ranges.\"\"\"\n",[457,1384,1385,1388,1390,1392,1395,1398,1401],{"class":81,"line":482},[457,1386,1387],{"class":466},"    mean ",[457,1389,525],{"class":462},[457,1391,779],{"class":538},[457,1393,1394],{"class":466},"(values) ",[457,1396,1397],{"class":462},"\u002F",[457,1399,1400],{"class":538}," len",[457,1402,1403],{"class":466},"(values)\n",[457,1405,1406,1409,1411,1413,1416,1418,1421,1424,1427,1429,1432,1434,1437],{"class":81,"line":488},[457,1407,1408],{"class":466},"    sdam ",[457,1410,525],{"class":462},[457,1412,779],{"class":538},[457,1414,1415],{"class":466},"((v ",[457,1417,868],{"class":462},[457,1419,1420],{"class":466}," mean) ",[457,1422,1423],{"class":462},"**",[457,1425,1426],{"class":538}," 2",[457,1428,790],{"class":462},[457,1430,1431],{"class":466}," v ",[457,1433,666],{"class":462},[457,1435,1436],{"class":466}," values)  ",[457,1438,1439],{"class":596},"# total variance\n",[457,1441,1442,1445,1447],{"class":81,"line":494},[457,1443,1444],{"class":466},"    sdcm ",[457,1446,525],{"class":462},[457,1448,1449],{"class":538}," 0.0\n",[457,1451,1452,1455,1457,1459],{"class":81,"line":500},[457,1453,1454],{"class":462},"    for",[457,1456,663],{"class":466},[457,1458,666],{"class":462},[457,1460,1461],{"class":466}," ranges:\n",[457,1463,1464,1467,1469,1472,1474,1476,1478],{"class":81,"line":506},[457,1465,1466],{"class":466},"        members ",[457,1468,525],{"class":462},[457,1470,1471],{"class":466}," [v ",[457,1473,660],{"class":462},[457,1475,1431],{"class":466},[457,1477,666],{"class":462},[457,1479,1480],{"class":466}," values\n",[457,1482,1483,1486,1489,1492,1494,1496],{"class":81,"line":512},[457,1484,1485],{"class":462},"                   if",[457,1487,1488],{"class":466}," r.lowerValue() ",[457,1490,1491],{"class":462},"\u003C=",[457,1493,1431],{"class":466},[457,1495,1491],{"class":462},[457,1497,1498],{"class":466}," r.upperValue()]\n",[457,1500,1501,1504],{"class":81,"line":519},[457,1502,1503],{"class":462},"        if",[457,1505,1506],{"class":466}," members:\n",[457,1508,1509,1512,1514,1516,1519,1521,1523],{"class":81,"line":544},[457,1510,1511],{"class":466},"            cmean ",[457,1513,525],{"class":462},[457,1515,779],{"class":538},[457,1517,1518],{"class":466},"(members) ",[457,1520,1397],{"class":462},[457,1522,1400],{"class":538},[457,1524,1525],{"class":466},"(members)\n",[457,1527,1528,1531,1534,1536,1538,1540,1543,1545,1547,1549,1551,1553],{"class":81,"line":555},[457,1529,1530],{"class":466},"            sdcm ",[457,1532,1533],{"class":462},"+=",[457,1535,779],{"class":538},[457,1537,1415],{"class":466},[457,1539,868],{"class":462},[457,1541,1542],{"class":466}," cmean) ",[457,1544,1423],{"class":462},[457,1546,1426],{"class":538},[457,1548,790],{"class":462},[457,1550,1431],{"class":466},[457,1552,666],{"class":462},[457,1554,1555],{"class":466}," members)\n",[457,1557,1558,1561,1564,1567,1570,1572,1575,1578,1581,1584],{"class":81,"line":566},[457,1559,1560],{"class":462},"    return",[457,1562,1563],{"class":538}," 1",[457,1565,1566],{"class":462}," -",[457,1568,1569],{"class":466}," (sdcm ",[457,1571,1397],{"class":462},[457,1573,1574],{"class":466}," sdam) ",[457,1576,1577],{"class":462},"if",[457,1579,1580],{"class":466}," sdam ",[457,1582,1583],{"class":462},"else",[457,1585,1449],{"class":538},[457,1587,1588],{"class":81,"line":571},[457,1589,516],{"emptyLinePlaceholder":515},[457,1591,1592],{"class":81,"line":582},[457,1593,516],{"emptyLinePlaceholder":515},[457,1595,1596,1599,1601,1604,1606,1608,1610],{"class":81,"line":588},[457,1597,1598],{"class":466},"values ",[457,1600,525],{"class":462},[457,1602,1603],{"class":466}," [f[field_name] ",[457,1605,660],{"class":462},[457,1607,793],{"class":466},[457,1609,666],{"class":462},[457,1611,798],{"class":466},[457,1613,1614,1617,1619,1621,1623,1625],{"class":81,"line":593},[457,1615,1616],{"class":462},"          if",[457,1618,806],{"class":466},[457,1620,809],{"class":462},[457,1622,812],{"class":462},[457,1624,815],{"class":538},[457,1626,541],{"class":466},[457,1628,1629],{"class":81,"line":600},[457,1630,516],{"emptyLinePlaceholder":515},[457,1632,1633,1635,1638,1640],{"class":81,"line":606},[457,1634,660],{"class":462},[457,1636,1637],{"class":466}," method_cls ",[457,1639,666],{"class":462},[457,1641,1642],{"class":466}," (QgsClassificationEqualInterval,\n",[457,1644,1645],{"class":81,"line":611},[457,1646,1647],{"class":466},"                   QgsClassificationQuantile,\n",[457,1649,1650],{"class":81,"line":617},[457,1651,1652],{"class":466},"                   QgsClassificationJenks):\n",[457,1654,1655],{"class":81,"line":623},[457,1656,1657],{"class":466},"    renderer.setClassificationMethod(method_cls())\n",[457,1659,1660],{"class":81,"line":628},[457,1661,1070],{"class":466},[457,1663,1664,1666,1668,1670,1672,1674,1677,1680,1683,1685,1688,1691,1693,1695],{"class":81,"line":640},[457,1665,675],{"class":538},[457,1667,678],{"class":466},[457,1669,681],{"class":462},[457,1671,684],{"class":531},[457,1673,687],{"class":538},[457,1675,1676],{"class":466},"method_cls.",[457,1678,1679],{"class":538},"__name__}",[457,1681,1682],{"class":531},": GVF = ",[457,1684,687],{"class":538},[457,1686,1687],{"class":466},"gvf(values, renderer.ranges())",[457,1689,1690],{"class":462},":.3f",[457,1692,696],{"class":538},[457,1694,684],{"class":531},[457,1696,509],{"class":466},[14,1698,1699,718,1701,1704,1705,1708],{},[391,1700,717],{},[23,1702,1703],{},"sdam"," is the sum of squared deviations about the grand mean — the total variance to \"explain.\" ",[23,1706,1707],{},"sdcm"," is the residual variance left inside the classes after partitioning. Their ratio subtracted from 1 is the GVF. Run it across the three methods and Jenks will almost always post the highest score on clustered data, giving you objective evidence that its breaks fit the distribution better than equal interval or quantile. Note the inclusive comparison on both bounds can double-count a value that lands exactly on a boundary; for production accuracy use the half-open intervals QGIS itself applies.",[380,1710,1712],{"id":1711},"step-6-apply-the-final-renderer-to-the-map","Step 6: Apply the Final Renderer to the Map",[14,1714,1715],{},"Once Jenks is your chosen method and nulls are handled, commit the renderer and refresh the display so the canvas and legend reflect the natural-breaks classification.",[448,1717,1719],{"className":450,"code":1718,"language":452,"meta":453,"style":453},"from qgis.utils import iface\n\nrenderer.setClassificationMethod(QgsClassificationJenks())\nrenderer.updateClasses(layer, class_count)\nrenderer.updateColorRamp(QgsStyle.defaultStyle().colorRamp(\"YlOrRd\"))\n\nlayer.setRenderer(renderer)\nlayer.triggerRepaint()\nif iface is not None:\n    iface.layerTreeView().refreshLayerSymbology(layer.id())\n    iface.mapCanvas().refresh()\n",[23,1720,1721,1733,1737,1741,1745,1753,1757,1761,1765,1781,1786],{"__ignoreMap":453},[457,1722,1723,1725,1728,1730],{"class":81,"line":459},[457,1724,463],{"class":462},[457,1726,1727],{"class":466}," qgis.utils ",[457,1729,470],{"class":462},[457,1731,1732],{"class":466}," iface\n",[457,1734,1735],{"class":81,"line":476},[457,1736,516],{"emptyLinePlaceholder":515},[457,1738,1739],{"class":81,"line":482},[457,1740,603],{"class":466},[457,1742,1743],{"class":81,"line":488},[457,1744,620],{"class":466},[457,1746,1747,1749,1751],{"class":81,"line":494},[457,1748,631],{"class":466},[457,1750,634],{"class":531},[457,1752,637],{"class":466},[457,1754,1755],{"class":81,"line":500},[457,1756,516],{"emptyLinePlaceholder":515},[457,1758,1759],{"class":81,"line":506},[457,1760,643],{"class":466},[457,1762,1763],{"class":81,"line":512},[457,1764,649],{"class":466},[457,1766,1767,1769,1772,1774,1776,1778],{"class":81,"line":519},[457,1768,1577],{"class":462},[457,1770,1771],{"class":466}," iface ",[457,1773,809],{"class":462},[457,1775,812],{"class":462},[457,1777,815],{"class":538},[457,1779,1780],{"class":466},":\n",[457,1782,1783],{"class":81,"line":544},[457,1784,1785],{"class":466},"    iface.layerTreeView().refreshLayerSymbology(layer.id())\n",[457,1787,1788],{"class":81,"line":555},[457,1789,1790],{"class":466},"    iface.mapCanvas().refresh()\n",[14,1792,1793,1347,1795,1798,1799,1802,1803,1806,1807,1810,1811,1814],{},[391,1794,717],{},[23,1796,1797],{},"setClassificationMethod"," and ",[23,1800,1801],{},"updateClasses"," immediately before ",[23,1804,1805],{},"setRenderer"," guarantees the committed renderer uses Jenks even after the comparison loops in earlier steps changed the method. ",[23,1808,1809],{},"refreshLayerSymbology"," rebuilds the legend swatches so they match the natural-breaks ranges. The result is ready to feed into a polygon choropleth — see ",[28,1812,1813],{"href":414},"Create a Choropleth Map in PyQGIS"," for the full thematic-map workflow.",[380,1816,1818],{"id":1817},"where-jenks-puts-its-breaks","Where Jenks puts its breaks",[14,1820,1821],{},"Jenks looks for the gaps in the data rather than dividing the range or the count evenly. On a distribution with natural clusters that is exactly right; on a smooth distribution it has little to work with and the advantage largely disappears.",[14,1823,1824],{},[34,1825,1828,1831,1834,1837,1843,1849,1901,1906,1909,1914,1919,1923,1926,1930,1966,1970,1973,1975],{"viewBox":1826,"role":37,"ariaLabel":1827,"xmlns":39},"0 0 760 248","A clustered value distribution with Jenks breaks falling in the empty gaps, compared with equal-interval breaks that cut through the middle of clusters",[42,1829,1830],{},"Jenks finds the gaps; equal interval ignores them",[46,1832,1833],{},"A distribution of values forms three distinct clusters separated by sparse gaps. Jenks places its class boundaries inside those gaps, so each class corresponds to one real cluster. Equal interval divides the value range into equal widths, placing boundaries in the middle of two clusters and splitting them across classes.",[50,1835],{"x":52,"y":52,"width":1836,"height":213,"fill":55},"760",[57,1838,1842],{"x":1839,"y":1840,"style":1841,"fill":61,"textAnchor":63},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","Same values, two ways of drawing the lines",[57,1844,1848],{"x":1839,"y":1845,"style":1846,"fill":1847,"textAnchor":63},"52","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","#15803d","Jenks — breaks land in the gaps",[87,1850,1852,1856,1860,1864,1868,1872,1875,1878,1881,1884,1886,1889,1892,1895,1898],{"fill":1847,"fillOpacity":1851},0.5,[91,1853],{"cx":1854,"cy":1855,"r":1254},"80","84",[91,1857],{"cx":1858,"cy":1859,"r":1254},"98","94",[91,1861],{"cx":1862,"cy":1863,"r":1254},"116","82",[91,1865],{"cx":1866,"cy":1867,"r":1254},"134","92",[91,1869],{"cx":1870,"cy":1871,"r":1254},"152","86",[91,1873],{"cx":1874,"cy":1855,"r":1254},"308",[91,1876],{"cx":1877,"cy":1859,"r":1254},"326",[91,1879],{"cx":1880,"cy":1863,"r":1254},"344",[91,1882],{"cx":1883,"cy":1867,"r":1254},"362",[91,1885],{"cx":1839,"cy":1871,"r":1254},[91,1887],{"cx":1888,"cy":1867,"r":1254},"398",[91,1890],{"cx":1891,"cy":1855,"r":1254},"576",[91,1893],{"cx":1894,"cy":1859,"r":1254},"594",[91,1896],{"cx":1897,"cy":1863,"r":1254},"612",[91,1899],{"cx":1900,"cy":1867,"r":1254},"630",[81,1902],{"x1":1903,"y1":1904,"x2":1903,"y2":1905,"stroke":1847,"style":210},"228","66","112",[81,1907],{"x1":1908,"y1":1904,"x2":1908,"y2":1905,"stroke":1847,"style":210},"488",[81,1910],{"x1":1911,"y1":1905,"x2":53,"y2":1905,"stroke":1912,"style":1913},"40","#59645f","stroke-width:1.2",[57,1915,1918],{"x":1866,"y":1916,"style":1917,"fill":1847,"textAnchor":63},"130","text-anchor:middle;font-size:10.5px;font-family:sans-serif","class 1 intact",[57,1920,1922],{"x":1921,"y":1916,"style":1917,"fill":1847,"textAnchor":63},"358","class 2 intact",[57,1924,1925],{"x":369,"y":1916,"style":1917,"fill":1847,"textAnchor":63},"class 3 intact",[57,1927,1929],{"x":1839,"y":1928,"style":1846,"fill":162,"textAnchor":63},"164","equal interval — breaks land anywhere",[87,1931,1932,1934,1937,1940,1943,1946,1948,1950,1952,1954,1956,1958,1960,1962,1964],{"fill":162,"fillOpacity":1851},[91,1933],{"cx":1854,"cy":161,"r":1254},[91,1935],{"cx":1858,"cy":1936,"r":1254},"206",[91,1938],{"cx":1862,"cy":1939,"r":1254},"194",[91,1941],{"cx":1866,"cy":1942,"r":1254},"204",[91,1944],{"cx":1870,"cy":1945,"r":1254},"198",[91,1947],{"cx":1874,"cy":161,"r":1254},[91,1949],{"cx":1877,"cy":1936,"r":1254},[91,1951],{"cx":1880,"cy":1939,"r":1254},[91,1953],{"cx":1883,"cy":1942,"r":1254},[91,1955],{"cx":1839,"cy":1945,"r":1254},[91,1957],{"cx":1888,"cy":1942,"r":1254},[91,1959],{"cx":1891,"cy":161,"r":1254},[91,1961],{"cx":1894,"cy":1936,"r":1254},[91,1963],{"cx":1897,"cy":1939,"r":1254},[91,1965],{"cx":1900,"cy":1942,"r":1254},[81,1967],{"x1":235,"y1":1968,"x2":235,"y2":1969,"stroke":162,"style":210},"178","224",[81,1971],{"x1":1972,"y1":1968,"x2":1972,"y2":1969,"stroke":162,"style":210},"493",[81,1974],{"x1":1911,"y1":1969,"x2":53,"y2":1969,"stroke":1912,"style":1913},[57,1976,1979],{"x":1977,"y":1978,"style":1917,"fill":162,"textAnchor":63},"352","242","the middle cluster is split across two classes",[380,1981,1983],{"id":1982},"why-the-class-count-matters-more-than-the-method","Why the class count matters more than the method",[14,1985,1986],{},"Adding classes always improves the statistical fit and eventually destroys the map. Past about seven classes a reader can no longer match a polygon's shade to a legend swatch, so the extra precision is invisible.",[14,1988,1989],{},[34,1990,1993,1996,1999,2002,2005,2010,2016,2020,2022,2027,2030,2036,2040,2044,2046,2050],{"viewBox":1991,"role":37,"ariaLabel":1992,"xmlns":39},"0 0 760 236","Goodness of variance fit rising with class count while legend readability falls, crossing in the four to seven class range",[42,1994,1995],{},"Statistical fit versus legend readability",[46,1997,1998],{},"Two curves plotted against class count from two to twelve. Goodness of variance fit rises steeply at first and then flattens. Readability falls slowly at first and then steeply. The two are both acceptable in a band between four and seven classes, which is highlighted.",[50,2000],{"x":52,"y":52,"width":1836,"height":2001,"fill":55},"236",[57,2003,2004],{"x":1839,"y":1840,"style":1841,"fill":61,"textAnchor":63},"More classes always fit better and read worse",[50,2006],{"x":1903,"y":1845,"width":2007,"height":1916,"fill":1847,"fillOpacity":2008,"stroke":1847,"style":2009},"188",0.1,"stroke-width:1.6;stroke-dasharray:5 4",[57,2011,2015],{"x":2012,"y":2013,"style":2014,"fill":1847,"textAnchor":63},"322","72","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","4–7 classes",[81,2017],{"x1":2013,"y1":2018,"x2":53,"y2":2018,"stroke":1912,"style":2019},"182","stroke-width:1.6",[81,2021],{"x1":2013,"y1":1845,"x2":2013,"y2":2018,"stroke":1912,"style":2019},[2023,2024],"path",{"d":2025,"fill":218,"stroke":240,"style":2026},"M96 174 L164 128 L228 104 L290 92 L352 86 L416 82 L488 79 L560 77 L640 76 L700 75","stroke-width:3",[2023,2028],{"d":2029,"fill":218,"stroke":162,"style":2026},"M96 66 L164 70 L228 78 L290 90 L352 104 L416 122 L488 142 L560 158 L640 170 L700 176",[57,2031,2035],{"x":2032,"y":2033,"style":2034,"fill":240,"textAnchor":157},"710","70","text-anchor:end;font-size:11px;font-weight:bold;font-family:sans-serif","fit (GVF)",[57,2037,2039],{"x":2032,"y":2038,"style":2034,"fill":162,"textAnchor":157},"192","readability",[57,2041,2043],{"x":2042,"y":1942,"style":1917,"fill":1912,"textAnchor":63},"96","2",[57,2045,1259],{"x":2012,"y":1942,"style":1917,"fill":1912,"textAnchor":63},[57,2047,2049],{"x":2048,"y":1942,"style":1917,"fill":1912,"textAnchor":63},"700","12",[57,2051,2054],{"x":2052,"y":1969,"style":2053,"fill":1912,"textAnchor":63},"396","text-anchor:middle;font-size:11px;font-family:sans-serif","number of classes",[380,2056,2058],{"id":2057},"qgis-version-compatibility","QGIS Version Compatibility",[14,2060,2061,2062,2065],{},"This recipe targets ",[391,2063,2064],{},"QGIS 3.34 LTR (Python 3.12)"," and relies on the method-object classification API.",[1167,2067,2068,2078],{},[1170,2069,2070],{},[1173,2071,2072,2075],{},[1176,2073,2074],{},"QGIS \u002F Python",[1176,2076,2077],{},"Notes",[1186,2079,2080,2097,2105,2113],{},[1173,2081,2082,2087],{},[1191,2083,2084],{},[391,2085,2086],{},"3.34 LTR (Python 3.12)",[1191,2088,2089,2090,1251,2092,1251,2094,2096],{},"Baseline. ",[23,2091,25],{},[23,2093,1797],{},[23,2095,1801],{}," all current.",[1173,2098,2099,2102],{},[1191,2100,2101],{},"3.28 LTR (Python 3.9)",[1191,2103,2104],{},"Fully supported with identical code.",[1173,2106,2107,2110],{},[1191,2108,2109],{},"3.40 \u002F 3.44",[1191,2111,2112],{},"Same API; Jenks performance improved for large layers.",[1173,2114,2115,2118],{},[1191,2116,2117],{},"Pre-3.10",[1191,2119,2120,2121,2123,2124,416],{},"No ",[23,2122,1797],{},". Legacy code used ",[23,2125,2126],{},"QgsGraduatedSymbolRenderer.createRenderer(layer, field, classes, QgsGraduatedSymbolRenderer.Jenks, symbol, ramp)",[14,2128,2129,2130,2132,2133,2136],{},"The deprecated ",[23,2131,305],{}," mode enum still resolves on the LTR builds, but new code should use ",[23,2134,2135],{},"QgsClassificationJenks()"," so it keeps working as the enum is phased out.",[380,2138,2140],{"id":2139},"troubleshooting","Troubleshooting",[14,2142,2143,2148,2149,2151],{},[391,2144,2145,2147],{},[23,2146,1801],{}," is slow or freezes on a huge layer."," Jenks is computationally heavier than equal interval or quantile. Pre-filter with ",[23,2150,953],{},", sample the layer, or reduce the class count. On layers over a few hundred thousand features, consider classifying on a representative sample.",[14,2153,2154,2157,2158,2161,2162,2165],{},[391,2155,2156],{},"Breaks change every time I run it."," That is expected behavior, not a bug, ",[18,2159,2160],{},"only"," if you changed the class count or the underlying values. With identical inputs Jenks is deterministic. If values shift, check for an active ",[23,2163,2164],{},"setSubsetString"," filter affecting the value set.",[14,2167,2168,2171,2172,2174],{},[391,2169,2170],{},"Some polygons render grey."," They hold ",[23,2173,748],{}," values excluded from classification. Use the null-handling pattern in step 2 to either filter them or style them deliberately.",[14,2176,2177,2182,2183,416],{},[391,2178,2179,416],{},[23,2180,2181],{},"AttributeError: colorRamp returned None"," The ramp name is not installed. List valid names with ",[23,2184,2185],{},"QgsStyle.defaultStyle().colorRampNames()",[380,2187,2189],{"id":2188},"conclusion","Conclusion",[14,2191,2192,2193,2195,2196,2198],{},"Jenks natural breaks gives you a classification that mirrors the real structure of your data rather than imposing an arbitrary grid on it. The PyQGIS workflow is compact: instantiate the renderer, call ",[23,2194,721],{},", run ",[23,2197,445],{},", and handle nulls deliberately. Compare it against equal interval and quantile on your own field, and let the printed break values — not habit — guide the final choice. From here, apply the result to a polygon layer to produce a faithful choropleth.",[380,2200,2202],{"id":2201},"frequently-asked-questions","Frequently Asked Questions",[14,2204,2205,2208],{},[391,2206,2207],{},"What exactly does Jenks optimize?","\nIt minimizes the sum of squared deviations within each class and maximizes the deviation between classes, placing boundaries in the natural gaps of the value distribution.",[14,2210,2211,2214],{},[391,2212,2213],{},"Is Jenks always the best choice?","\nNo. For uniform data, equal interval is simpler and yields rounder legend numbers. For ranked maps where balanced feature counts matter, quantile is better. Jenks excels specifically on clustered or skewed data.",[14,2216,2217,2220],{},[391,2218,2219],{},"Why do my Jenks breaks differ from QGIS 3.10?","\nThe algorithm was refined for performance and edge cases across releases, and break positions depend on the exact value set. Small differences on identical data across major versions are expected.",[14,2222,2223,2226,2227,2229],{},[391,2224,2225],{},"Does the class count affect Jenks more than other methods?","\nYes. Jenks re-partitions the entire distribution for each class count, so breaks move when you change ",[23,2228,1326],{},", whereas equal-interval bounds simply subdivide a fixed range.",[380,2231,2233],{"id":2232},"related","Related",[385,2235,2236,2240,2244],{},[388,2237,2238],{},[28,2239,31],{"href":30},[388,2241,2242],{},[28,2243,1813],{"href":414},[388,2245,2246],{},[28,2247,961],{"href":960},[2249,2250,2251],"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 .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}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 .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":453,"searchDepth":476,"depth":476,"links":2253},[2254,2255,2256,2257,2258,2259,2260,2261,2262,2263,2264,2265,2266,2267],{"id":382,"depth":476,"text":383},{"id":419,"depth":476,"text":420},{"id":741,"depth":476,"text":742},{"id":964,"depth":476,"text":965},{"id":1221,"depth":476,"text":1222},{"id":1356,"depth":476,"text":1357},{"id":1711,"depth":476,"text":1712},{"id":1817,"depth":476,"text":1818},{"id":1982,"depth":476,"text":1983},{"id":2057,"depth":476,"text":2058},{"id":2139,"depth":476,"text":2140},{"id":2188,"depth":476,"text":2189},{"id":2201,"depth":476,"text":2202},{"id":2232,"depth":476,"text":2233},"Apply Jenks natural breaks in PyQGIS with QgsClassificationJenks, setClassificationMethod, and updateClasses, compare to equal interval and quantile, and handle nulls.","md",{"slug":2271,"type":2272,"breadcrumb":2273,"datePublished":2274,"dateModified":2275},"classify-layer-natural-breaks-jenks-pyqgis","article","Natural Breaks (Jenks) Classification","2025-06-11","2026-07-18","\u002Fpyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fclassify-layer-natural-breaks-jenks-pyqgis",{"title":5,"description":2268},"pyqgis-cartography-visualization\u002Fgraduated-categorized-renderers\u002Fclassify-layer-natural-breaks-jenks-pyqgis\u002Findex","YLjhSakMX2RoO3RWeprRMm8tc2nvR4EtT5ZdyEWCfy0",1787823360556]