[{"data":1,"prerenderedAt":1866},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut":1836,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut":1845,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut":1850,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut":1851},{"id":4,"title":5,"body":6,"dateModified":1802,"datePublished":1802,"description":1803,"extension":1804,"faq":1805,"keyword":1814,"kind":1815,"meta":1816,"navigation":241,"path":1824,"seo":1825,"slug":1828,"stem":1829,"topics":1830,"type":1834,"__hash__":1835},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut\u002Findex.md","Bin Values into Ranges Like Excel FREQUENCY with pd.cut",{"type":7,"value":8,"toc":1787},"minimark",[9,39,143,148,176,182,186,200,370,384,388,402,518,528,656,660,675,799,809,813,819,899,910,1039,1043,1046,1247,1257,1261,1268,1463,1478,1482,1489,1609,1620,1624,1675,1679,1687,1691,1718,1722,1728,1734,1744,1750,1754,1783],[10,11,12,13,17,18,21,22,25,26,29,30,35,36,38],"p",{},"Reports turn continuous numbers into bands all the time: order values into \"small, medium, large\", ages into brackets, delivery times into \"same day, 1-3 days, later\". Excel users do it with ",[14,15,16],"code",{},"FREQUENCY",", a nested ",[14,19,20],{},"IF",", or a lookup against a band table; in pandas it is ",[14,23,24],{},"pd.cut",". The function is simple, but three details decide whether its answer is right: which side of each band includes its edge, what happens to values outside the outermost edges — they silently become ",[14,27,28],{},"NaN"," — and whether the bands keep their order in summaries. This page, part of ",[31,32,34],"a",{"href":33},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002F","Creating Pivot Tables from Excel Data with Pandas",", bins a column of order values, matches Excel's ",[14,37,16],{}," exactly and covers quantile bins.",[40,41,49,50,49,54,49,58,49,66,49,73,49,83,49,89,49,96,49,101,49,104,49,108,49,111,49,115,49,118,49,122,49,124,49,126,49,129,49,133,49,138],"svg",{"viewBox":42,"role":43,"ariaLabelledBy":44,"xmlns":47,"style":48},"4 0 744 212","img",[45,46],"bin-counts-t","bin-counts-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:744px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[51,52,53],"title",{"id":45},"Twelve order values counted into four bands",[55,56,57],"desc",{"id":46},"Bars counting order values in four bands with edges at 10, 50 and 100: three values up to 10, five from over 10 to 50, three from over 50 to 100, and one above 100. These are the same counts Excel's FREQUENCY function returns for the same bins.",[59,60],"rect",{"x":61,"y":62,"width":63,"height":64,"fill":65},"4","0","744","212","#ffffff",[67,68,72],"text",{"x":69,"y":70,"style":71},"380.0","31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Edges 10, 50, 100 — same counts as FREQUENCY",[59,74],{"x":75,"y":76,"width":77,"height":78,"rx":79,"fill":80,"stroke":81,"style":82},"20.0","48.0","83.5","28.0","7","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[67,84,88],{"x":85,"y":86,"style":87},"61.8","66.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","up to 10",[59,90],{"x":91,"y":76,"width":92,"height":78,"rx":79,"fill":93,"stroke":94,"style":95},"113.5","361.4","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[67,97,100],{"x":98,"y":86,"style":99},"482.9","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start","3",[59,102],{"x":75,"y":103,"width":77,"height":78,"rx":79,"fill":80,"stroke":81,"style":82},"88.0",[67,105,107],{"x":85,"y":106,"style":87},"106.5","10 – 50",[59,109],{"x":91,"y":103,"width":110,"height":78,"rx":79,"fill":93,"stroke":94,"style":95},"602.3",[67,112,114],{"x":113,"y":106,"style":99},"723.9","5",[59,116],{"x":75,"y":117,"width":77,"height":78,"rx":79,"fill":80,"stroke":81,"style":82},"128.0",[67,119,121],{"x":85,"y":120,"style":87},"146.5","50 – 100",[59,123],{"x":91,"y":117,"width":92,"height":78,"rx":79,"fill":93,"stroke":94,"style":95},[67,125,100],{"x":98,"y":120,"style":99},[59,127],{"x":75,"y":128,"width":77,"height":78,"rx":79,"fill":80,"stroke":81,"style":82},"168.0",[67,130,132],{"x":85,"y":131,"style":87},"186.5","over 100",[59,134],{"x":91,"y":128,"width":135,"height":78,"rx":79,"fill":136,"stroke":137,"style":95},"120.5","#fdefd8","var(--gold,#b4740a)",[67,139,142],{"x":140,"y":131,"style":141},"242.0","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","1",[144,145,147],"h2",{"id":146},"prerequisites","Prerequisites",[149,150,155],"pre",{"className":151,"code":152,"language":153,"meta":154,"style":154},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,156,157],{"__ignoreMap":154},[158,159,162,166,170,173],"span",{"class":160,"line":161},"line",1,[158,163,165],{"class":164},"sMTad","pip",[158,167,169],{"class":168},"srMev"," install",[158,171,172],{"class":168}," pandas",[158,174,175],{"class":168}," openpyxl\n",[10,177,178,181],{},[14,179,180],{},"orders.xlsx"," holds twelve order values chosen to sit on and around the band edges: 0, 5, 10, 10.5, 20, 25, 49, 50, 51, 99, 100 and 150.",[144,183,185],{"id":184},"step-1-bin-with-explicit-edges-and-labels","Step 1 — Bin with explicit edges and labels",[10,187,188,189,191,192,195,196,199],{},"Give ",[14,190,24],{}," the band edges and, ideally, labels a reader will understand. ",[14,193,194],{},"len(labels)"," must be one less than ",[14,197,198],{},"len(bins)",":",[149,201,205],{"className":202,"code":203,"language":204,"meta":154,"style":154},"language-python shiki shiki-themes github-light github-dark-high-contrast","import numpy as np\nimport pandas as pd\n\norders = pd.read_excel(\"orders.xlsx\")\nedges = [0, 10, 50, 100]\norders[\"band\"] = pd.cut(orders[\"value\"], bins=edges, labels=[\"0-10\", \"10-50\", \"50-100\"])\nprint(orders.to_string(index=False))\n","python",[14,206,207,223,236,243,261,294,350],{"__ignoreMap":154},[158,208,209,213,217,220],{"class":160,"line":161},[158,210,212],{"class":211},"s-kum","import",[158,214,216],{"class":215},"skGVy"," numpy ",[158,218,219],{"class":211},"as",[158,221,222],{"class":215}," np\n",[158,224,226,228,231,233],{"class":160,"line":225},2,[158,227,212],{"class":211},[158,229,230],{"class":215}," pandas ",[158,232,219],{"class":211},[158,234,235],{"class":215}," pd\n",[158,237,239],{"class":160,"line":238},3,[158,240,242],{"emptyLinePlaceholder":241},true,"\n",[158,244,246,249,252,255,258],{"class":160,"line":245},4,[158,247,248],{"class":215},"orders ",[158,250,251],{"class":211},"=",[158,253,254],{"class":215}," pd.read_excel(",[158,256,257],{"class":168},"\"orders.xlsx\"",[158,259,260],{"class":215},")\n",[158,262,264,267,269,272,275,278,281,283,286,288,291],{"class":160,"line":263},5,[158,265,266],{"class":215},"edges ",[158,268,251],{"class":211},[158,270,271],{"class":215}," [",[158,273,62],{"class":274},"sP0c6",[158,276,277],{"class":215},", ",[158,279,280],{"class":274},"10",[158,282,277],{"class":215},[158,284,285],{"class":274},"50",[158,287,277],{"class":215},[158,289,290],{"class":274},"100",[158,292,293],{"class":215},"]\n",[158,295,297,300,303,306,308,311,314,317,321,323,326,329,331,334,337,339,342,344,347],{"class":160,"line":296},6,[158,298,299],{"class":215},"orders[",[158,301,302],{"class":168},"\"band\"",[158,304,305],{"class":215},"] ",[158,307,251],{"class":211},[158,309,310],{"class":215}," pd.cut(orders[",[158,312,313],{"class":168},"\"value\"",[158,315,316],{"class":215},"], ",[158,318,320],{"class":319},"sa561","bins",[158,322,251],{"class":211},[158,324,325],{"class":215},"edges, ",[158,327,328],{"class":319},"labels",[158,330,251],{"class":211},[158,332,333],{"class":215},"[",[158,335,336],{"class":168},"\"0-10\"",[158,338,277],{"class":215},[158,340,341],{"class":168},"\"10-50\"",[158,343,277],{"class":215},[158,345,346],{"class":168},"\"50-100\"",[158,348,349],{"class":215},"])\n",[158,351,353,356,359,362,364,367],{"class":160,"line":352},7,[158,354,355],{"class":274},"print",[158,357,358],{"class":215},"(orders.to_string(",[158,360,361],{"class":319},"index",[158,363,251],{"class":211},[158,365,366],{"class":274},"False",[158,368,369],{"class":215},"))\n",[10,371,372,373,375,376,380,381,383],{},"Look at the first and last rows. The value 0 and the value 150 have no band — both are ",[14,374,28],{},". 150 is above the last edge, so no band contains it; 0 sits exactly ",[377,378,379],"em",{},"on"," the first edge, and with right-closed intervals the first band is (0, 10], which excludes 0. Neither case raises a warning, which is why checking for ",[14,382,28],{}," after binning is essential.",[144,385,387],{"id":386},"step-2-decide-which-side-of-each-band-is-closed","Step 2 — Decide which side of each band is closed",[10,389,390,391,394,395,397,398,401],{},"By default, intervals are closed on the right: ",[14,392,393],{},"(10, 50]"," includes 50 and excludes 10. Excel's ",[14,396,16],{}," and most \"up to\" bands use this convention. Bands described as \"from … to under …\" are closed on the left, which ",[14,399,400],{},"right=False"," gives:",[149,403,405],{"className":202,"code":404,"language":204,"meta":154,"style":154},"for right in (True, False):\n    bands = pd.cut(orders[\"value\"], bins=[0, 10, 50, 100, np.inf], right=right)\n    print(\"right=%s\" % right, bands.value_counts(sort=False, dropna=False).to_dict())\n",[14,406,407,431,475],{"__ignoreMap":154},[158,408,409,412,415,418,421,424,426,428],{"class":160,"line":161},[158,410,411],{"class":211},"for",[158,413,414],{"class":215}," right ",[158,416,417],{"class":211},"in",[158,419,420],{"class":215}," (",[158,422,423],{"class":274},"True",[158,425,277],{"class":215},[158,427,366],{"class":274},[158,429,430],{"class":215},"):\n",[158,432,433,436,438,440,442,444,446,448,450,452,454,456,458,460,462,464,467,470,472],{"class":160,"line":225},[158,434,435],{"class":215},"    bands ",[158,437,251],{"class":211},[158,439,310],{"class":215},[158,441,313],{"class":168},[158,443,316],{"class":215},[158,445,320],{"class":319},[158,447,251],{"class":211},[158,449,333],{"class":215},[158,451,62],{"class":274},[158,453,277],{"class":215},[158,455,280],{"class":274},[158,457,277],{"class":215},[158,459,285],{"class":274},[158,461,277],{"class":215},[158,463,290],{"class":274},[158,465,466],{"class":215},", np.inf], ",[158,468,469],{"class":319},"right",[158,471,251],{"class":211},[158,473,474],{"class":215},"right)\n",[158,476,477,480,483,486,490,493,496,499,502,504,506,508,511,513,515],{"class":160,"line":238},[158,478,479],{"class":274},"    print",[158,481,482],{"class":215},"(",[158,484,485],{"class":168},"\"right=",[158,487,489],{"class":488},"sSjpA","%s",[158,491,492],{"class":168},"\"",[158,494,495],{"class":211}," %",[158,497,498],{"class":215}," right, bands.value_counts(",[158,500,501],{"class":319},"sort",[158,503,251],{"class":211},[158,505,366],{"class":274},[158,507,277],{"class":215},[158,509,510],{"class":319},"dropna",[158,512,251],{"class":211},[158,514,366],{"class":274},[158,516,517],{"class":215},").to_dict())\n",[10,519,520,521,524,525,527],{},"With ",[14,522,523],{},"right=True",", 10 falls in the first band and 50 in the second; with ",[14,526,400],{},", 10 moves up to the second band and 50 to the third. Neither is wrong, but the labels must match: \"10-50\" with right-closed intervals means \"over 10, up to and including 50\". Write the convention into the labels — \"10 \u003C x ≤ 50\" — when readers might care.",[40,529,49,535,49,538,49,541,49,545,49,550,49,555,49,561,49,564,49,567,49,571,49,577,49,579,49,584,49,586,49,589,49,592,49,595,49,597,49,601,49,603,49,606,49,609,49,612,49,614,49,617,49,619,49,622,49,625,49,628,49,630,49,634,49,636,49,639,49,642,49,646,49,648,49,652,49,654],{"viewBox":530,"role":43,"ariaLabelledBy":531,"xmlns":47,"style":534},"4 0 752 242",[532,533],"bin-edges-t","bin-edges-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[51,536,537],{"id":532},"Where edge values land under each convention",[55,539,540],{"id":533},"A table of values that sit on band edges. With right-closed intervals, 0 is outside unless include_lowest is set, 10 goes in the first band, 50 in the second, 100 in the third and 150 outside unless the last edge is infinity. With left-closed intervals, 0 goes in the first band, 10 in the second, 50 in the third and 100 in the last, open-ended band.",[59,542],{"x":61,"y":62,"width":543,"height":544,"fill":65},"752","242",[59,546],{"x":75,"y":547,"width":548,"height":549,"rx":62,"fill":65,"stroke":81,"style":82},"16.0","165.1","40.0",[59,551],{"x":552,"y":547,"width":553,"height":549,"rx":62,"fill":93,"stroke":94,"style":554},"185.1","277.5","stroke-width:1.2px",[67,556,560],{"x":557,"y":558,"style":559},"323.8","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","right=True (default)",[59,562],{"x":563,"y":547,"width":553,"height":549,"rx":62,"fill":93,"stroke":94,"style":554},"462.5",[67,565,400],{"x":566,"y":558,"style":559},"601.3",[59,568],{"x":75,"y":569,"width":548,"height":570,"rx":62,"fill":80,"stroke":81,"style":82},"56.0","34.0",[67,572,576],{"x":573,"y":574,"style":575},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","0 (lowest edge)",[59,578],{"x":552,"y":569,"width":553,"height":570,"rx":62,"fill":65,"stroke":81,"style":82},[67,580,583],{"x":557,"y":581,"style":582},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","NaN unless include_lowest",[59,585],{"x":563,"y":569,"width":553,"height":570,"rx":62,"fill":65,"stroke":81,"style":82},[67,587,588],{"x":566,"y":581,"style":582},"first band",[59,590],{"x":75,"y":591,"width":548,"height":570,"rx":62,"fill":80,"stroke":81,"style":82},"90.0",[67,593,280],{"x":573,"y":594,"style":575},"112.0",[59,596],{"x":552,"y":591,"width":553,"height":570,"rx":62,"fill":65,"stroke":81,"style":82},[67,598,600],{"x":557,"y":599,"style":582},"111.0","(0, 10]",[59,602],{"x":563,"y":591,"width":553,"height":570,"rx":62,"fill":65,"stroke":81,"style":82},[67,604,605],{"x":566,"y":599,"style":582},"[10, 50)",[59,607],{"x":75,"y":608,"width":548,"height":570,"rx":62,"fill":80,"stroke":81,"style":82},"124.0",[67,610,285],{"x":573,"y":611,"style":575},"146.0",[59,613],{"x":552,"y":608,"width":553,"height":570,"rx":62,"fill":65,"stroke":81,"style":82},[67,615,393],{"x":557,"y":616,"style":582},"145.0",[59,618],{"x":563,"y":608,"width":553,"height":570,"rx":62,"fill":65,"stroke":81,"style":82},[67,620,621],{"x":566,"y":616,"style":582},"[50, 100)",[59,623],{"x":75,"y":624,"width":548,"height":570,"rx":62,"fill":80,"stroke":81,"style":82},"158.0",[67,626,290],{"x":573,"y":627,"style":575},"180.0",[59,629],{"x":552,"y":624,"width":553,"height":570,"rx":62,"fill":65,"stroke":81,"style":82},[67,631,633],{"x":557,"y":632,"style":582},"179.0","(50, 100]",[59,635],{"x":563,"y":624,"width":553,"height":570,"rx":62,"fill":65,"stroke":81,"style":82},[67,637,638],{"x":566,"y":632,"style":582},"[100, inf)",[59,640],{"x":75,"y":641,"width":548,"height":570,"rx":62,"fill":80,"stroke":81,"style":82},"192.0",[67,643,645],{"x":573,"y":644,"style":575},"214.0","150 (above last edge)",[59,647],{"x":552,"y":641,"width":553,"height":570,"rx":62,"fill":65,"stroke":81,"style":82},[67,649,651],{"x":557,"y":650,"style":582},"213.0","NaN unless last edge is inf",[59,653],{"x":563,"y":641,"width":553,"height":570,"rx":62,"fill":65,"stroke":81,"style":82},[67,655,638],{"x":566,"y":650,"style":582},[144,657,659],{"id":658},"step-3-stop-values-falling-outside-the-bins","Step 3 — Stop values falling outside the bins",[10,661,662,663,666,667,670,671,674],{},"Two tools cover the gaps. ",[14,664,665],{},"include_lowest=True"," closes the first band on the left too, so a value exactly on the lowest edge is included. Open-ended outer edges — ",[14,668,669],{},"-np.inf"," and ",[14,672,673],{},"np.inf"," — catch everything below the first edge and above the last:",[149,676,678],{"className":202,"code":677,"language":204,"meta":154,"style":154},"edges = [-np.inf, 10, 50, 100, np.inf]\nlabels = [\"up to 10\", \"10-50\", \"50-100\", \"over 100\"]\norders[\"band\"] = pd.cut(orders[\"value\"], bins=edges, labels=labels)\nprint(orders[\"band\"].isna().sum(), \"values without a band\")\nprint(orders[\"band\"].value_counts(sort=False).to_dict())\n",[14,679,680,707,734,763,780],{"__ignoreMap":154},[158,681,682,684,686,688,691,694,696,698,700,702,704],{"class":160,"line":161},[158,683,266],{"class":215},[158,685,251],{"class":211},[158,687,271],{"class":215},[158,689,690],{"class":211},"-",[158,692,693],{"class":215},"np.inf, ",[158,695,280],{"class":274},[158,697,277],{"class":215},[158,699,285],{"class":274},[158,701,277],{"class":215},[158,703,290],{"class":274},[158,705,706],{"class":215},", np.inf]\n",[158,708,709,712,714,716,719,721,723,725,727,729,732],{"class":160,"line":225},[158,710,711],{"class":215},"labels ",[158,713,251],{"class":211},[158,715,271],{"class":215},[158,717,718],{"class":168},"\"up to 10\"",[158,720,277],{"class":215},[158,722,341],{"class":168},[158,724,277],{"class":215},[158,726,346],{"class":168},[158,728,277],{"class":215},[158,730,731],{"class":168},"\"over 100\"",[158,733,293],{"class":215},[158,735,736,738,740,742,744,746,748,750,752,754,756,758,760],{"class":160,"line":238},[158,737,299],{"class":215},[158,739,302],{"class":168},[158,741,305],{"class":215},[158,743,251],{"class":211},[158,745,310],{"class":215},[158,747,313],{"class":168},[158,749,316],{"class":215},[158,751,320],{"class":319},[158,753,251],{"class":211},[158,755,325],{"class":215},[158,757,328],{"class":319},[158,759,251],{"class":211},[158,761,762],{"class":215},"labels)\n",[158,764,765,767,770,772,775,778],{"class":160,"line":245},[158,766,355],{"class":274},[158,768,769],{"class":215},"(orders[",[158,771,302],{"class":168},[158,773,774],{"class":215},"].isna().sum(), ",[158,776,777],{"class":168},"\"values without a band\"",[158,779,260],{"class":215},[158,781,782,784,786,788,791,793,795,797],{"class":160,"line":263},[158,783,355],{"class":274},[158,785,769],{"class":215},[158,787,302],{"class":168},[158,789,790],{"class":215},"].value_counts(",[158,792,501],{"class":319},[158,794,251],{"class":211},[158,796,366],{"class":274},[158,798,517],{"class":215},[10,800,801,802,805,806,808],{},"Every value now has a band. The open-ended form is the safer default for reports, because next month's data will contain a value nobody expected; an \"over 100\" band shows it, where a fixed last edge would drop it. Keep the check on ",[14,803,804],{},"isna()"," anyway — missing input values stay ",[14,807,28],{}," whatever the edges.",[144,810,812],{"id":811},"step-4-match-excels-frequency-exactly","Step 4 — Match Excel's FREQUENCY exactly",[10,814,815,818],{},[14,816,817],{},"FREQUENCY(data, bins)"," returns one count per bin boundary — values up to and including each boundary, after the previous one — plus a final count for values above the last boundary. That is precisely right-closed intervals with open outer edges:",[149,820,822],{"className":202,"code":821,"language":204,"meta":154,"style":154},"bins_array = [10, 50, 100]                       # the bins_array you would give FREQUENCY\ncounts = pd.cut(orders[\"value\"], bins=[-np.inf, *bins_array, np.inf]).value_counts(sort=False)\nprint(counts.tolist())\n# [3, 5, 3, 1]\n",[14,823,824,850,887,894],{"__ignoreMap":154},[158,825,826,829,831,833,835,837,839,841,843,846],{"class":160,"line":161},[158,827,828],{"class":215},"bins_array ",[158,830,251],{"class":211},[158,832,271],{"class":215},[158,834,280],{"class":274},[158,836,277],{"class":215},[158,838,285],{"class":274},[158,840,277],{"class":215},[158,842,290],{"class":274},[158,844,845],{"class":215},"]                       ",[158,847,849],{"class":848},"s-wDw","# the bins_array you would give FREQUENCY\n",[158,851,852,855,857,859,861,863,865,867,869,871,873,876,879,881,883,885],{"class":160,"line":225},[158,853,854],{"class":215},"counts ",[158,856,251],{"class":211},[158,858,310],{"class":215},[158,860,313],{"class":168},[158,862,316],{"class":215},[158,864,320],{"class":319},[158,866,251],{"class":211},[158,868,333],{"class":215},[158,870,690],{"class":211},[158,872,693],{"class":215},[158,874,875],{"class":211},"*",[158,877,878],{"class":215},"bins_array, np.inf]).value_counts(",[158,880,501],{"class":319},[158,882,251],{"class":211},[158,884,366],{"class":274},[158,886,260],{"class":215},[158,888,889,891],{"class":160,"line":238},[158,890,355],{"class":274},[158,892,893],{"class":215},"(counts.tolist())\n",[158,895,896],{"class":160,"line":245},[158,897,898],{"class":848},"# [3, 5, 3, 1]\n",[10,900,901,902,905,906,909],{},"Entering ",[14,903,904],{},"=FREQUENCY(A2:A13, {10;50;100})"," over the same twelve values and recalculating the workbook in LibreOffice gives the same four counts. ",[14,907,908],{},"value_counts(sort=False)"," keeps the bands in their natural order; the default sorts by count, which scrambles a histogram.",[40,911,49,916,49,919,49,922,49,925,49,931,49,942,49,948,49,952,49,956,49,961,49,966,49,969,49,976,49,982,49,986,49,990,49,993,49,996,49,1002,49,1006,49,1009,49,1013,49,1016,49,1021,49,1028,49,1033,49,1036],{"viewBox":912,"role":43,"ariaLabelledBy":913,"xmlns":47,"style":534},"4 0 752 142",[914,915],"bin-freq-t","bin-freq-d",[51,917,918],{"id":914},"FREQUENCY and pd.cut side by side",[55,920,921],{"id":915},"Excel's FREQUENCY takes a data range and a bins array of 10, 50 and 100 and returns four counts. In pandas the same bins become edges from minus infinity through 10, 50 and 100 to infinity; pd.cut with right-closed intervals followed by value_counts with sort=False returns the same four counts: 3, 5, 3 and 1.",[59,923],{"x":61,"y":62,"width":543,"height":924,"fill":65},"142",[59,926],{"x":75,"y":547,"width":927,"height":928,"rx":929,"fill":80,"stroke":81,"style":930},"153.0","110.5","12","stroke-width:2px",[932,933,935],"g",{"transform":934},"translate(84.5,29.0) scale(1.000)",[936,937],"path",{"d":938,"fill":939,"stroke":940,"style":941},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[67,943,947],{"x":944,"y":945,"style":946},"96.5","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Bins 10, 50, 100",[67,949,951],{"x":944,"y":950,"style":582},"95.0","FREQUENCY's",[67,953,955],{"x":944,"y":954,"style":582},"112.5","bins_array",[160,957],{"x1":958,"y1":959,"x2":960,"y2":959,"stroke":94,"style":930},"178.0","71.2","200.0",[962,963],"polygon",{"points":964,"fill":965},"204.0,71.2 195.0,76.2 195.0,66.2","#5b5cf0",[59,967],{"x":968,"y":547,"width":927,"height":928,"rx":929,"fill":93,"stroke":94,"style":930},"209.0",[932,970,972],{"transform":971},"translate(273.5,37.8) scale(1.000)",[936,973],{"d":974,"fill":939,"stroke":975,"style":941},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--brand-strong,#4338ca)",[67,977,981],{"x":978,"y":979,"style":980},"285.5","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Edges -inf … inf",[67,983,985],{"x":978,"y":984,"style":582},"103.8","add the open ends",[160,987],{"x1":988,"y1":959,"x2":989,"y2":959,"stroke":94,"style":930},"367.0","389.0",[962,991],{"points":992,"fill":965},"393.0,71.2 384.0,76.2 384.0,66.2",[59,994],{"x":995,"y":547,"width":927,"height":928,"rx":929,"fill":93,"stroke":94,"style":930},"398.0",[932,997,999],{"transform":998},"translate(462.5,37.8) scale(1.000)",[936,1000],{"d":1001,"fill":939,"stroke":975,"style":941},"M3 5h18l-7 8v6l-4 2v-8z",[67,1003,1005],{"x":1004,"y":979,"style":980},"474.5","pd.cut, right=True",[67,1007,1008],{"x":1004,"y":984,"style":582},"values ≤ each edge",[160,1010],{"x1":1011,"y1":959,"x2":1012,"y2":959,"stroke":94,"style":930},"556.0","578.0",[962,1014],{"points":1015,"fill":965},"582.0,71.2 573.0,76.2 573.0,66.2",[59,1017],{"x":1018,"y":547,"width":927,"height":928,"rx":929,"fill":1019,"stroke":1020,"style":930},"587.0","#d9f4f1","var(--teal,#0f9488)",[932,1022,1024],{"transform":1023},"translate(651.5,29.0) scale(1.000)",[936,1025],{"d":1026,"fill":939,"stroke":1027,"style":941},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[67,1029,1032],{"x":1030,"y":945,"style":1031},"663.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Count in order",[67,1034,1035],{"x":1030,"y":950,"style":582},"value_counts,",[67,1037,1038],{"x":1030,"y":954,"style":582},"sort=False: 3, 5, 3, 1",[144,1040,1042],{"id":1041},"keep-the-band-definitions-in-a-sheet","Keep the band definitions in a sheet",[10,1044,1045],{},"When the business changes its bands — \"medium\" now starts at 25 — the edges should not be buried in code. Keep them in a small table, one row per band with its upper limit, and build the edges and labels from it:",[149,1047,1049],{"className":202,"code":1048,"language":204,"meta":154,"style":154},"bands = pd.DataFrame({\"label\": [\"up to 10\", \"10-50\", \"50-100\", \"over 100\"],\n                      \"upper\": [10, 50, 100, None]})\nbands.to_excel(\"bands.xlsx\", index=False)\n\ntable = pd.read_excel(\"bands.xlsx\")\nedges = [-np.inf, *table[\"upper\"].dropna().tolist(), np.inf]\nif not pd.Series(edges).is_monotonic_increasing:\n    raise ValueError(\"band limits must increase from row to row\")\norders[\"band\"] = pd.cut(orders[\"value\"], bins=edges, labels=table[\"label\"].tolist())\nprint(orders[\"band\"].value_counts(sort=False).to_dict())\n",[14,1050,1051,1084,1109,1127,1131,1144,1167,1178,1194,1228],{"__ignoreMap":154},[158,1052,1053,1056,1058,1061,1064,1067,1069,1071,1073,1075,1077,1079,1081],{"class":160,"line":161},[158,1054,1055],{"class":215},"bands ",[158,1057,251],{"class":211},[158,1059,1060],{"class":215}," pd.DataFrame({",[158,1062,1063],{"class":168},"\"label\"",[158,1065,1066],{"class":215},": [",[158,1068,718],{"class":168},[158,1070,277],{"class":215},[158,1072,341],{"class":168},[158,1074,277],{"class":215},[158,1076,346],{"class":168},[158,1078,277],{"class":215},[158,1080,731],{"class":168},[158,1082,1083],{"class":215},"],\n",[158,1085,1086,1089,1091,1093,1095,1097,1099,1101,1103,1106],{"class":160,"line":225},[158,1087,1088],{"class":168},"                      \"upper\"",[158,1090,1066],{"class":215},[158,1092,280],{"class":274},[158,1094,277],{"class":215},[158,1096,285],{"class":274},[158,1098,277],{"class":215},[158,1100,290],{"class":274},[158,1102,277],{"class":215},[158,1104,1105],{"class":274},"None",[158,1107,1108],{"class":215},"]})\n",[158,1110,1111,1114,1117,1119,1121,1123,1125],{"class":160,"line":238},[158,1112,1113],{"class":215},"bands.to_excel(",[158,1115,1116],{"class":168},"\"bands.xlsx\"",[158,1118,277],{"class":215},[158,1120,361],{"class":319},[158,1122,251],{"class":211},[158,1124,366],{"class":274},[158,1126,260],{"class":215},[158,1128,1129],{"class":160,"line":245},[158,1130,242],{"emptyLinePlaceholder":241},[158,1132,1133,1136,1138,1140,1142],{"class":160,"line":263},[158,1134,1135],{"class":215},"table ",[158,1137,251],{"class":211},[158,1139,254],{"class":215},[158,1141,1116],{"class":168},[158,1143,260],{"class":215},[158,1145,1146,1148,1150,1152,1154,1156,1158,1161,1164],{"class":160,"line":296},[158,1147,266],{"class":215},[158,1149,251],{"class":211},[158,1151,271],{"class":215},[158,1153,690],{"class":211},[158,1155,693],{"class":215},[158,1157,875],{"class":211},[158,1159,1160],{"class":215},"table[",[158,1162,1163],{"class":168},"\"upper\"",[158,1165,1166],{"class":215},"].dropna().tolist(), np.inf]\n",[158,1168,1169,1172,1175],{"class":160,"line":352},[158,1170,1171],{"class":211},"if",[158,1173,1174],{"class":211}," not",[158,1176,1177],{"class":215}," pd.Series(edges).is_monotonic_increasing:\n",[158,1179,1181,1184,1187,1189,1192],{"class":160,"line":1180},8,[158,1182,1183],{"class":211},"    raise",[158,1185,1186],{"class":274}," ValueError",[158,1188,482],{"class":215},[158,1190,1191],{"class":168},"\"band limits must increase from row to row\"",[158,1193,260],{"class":215},[158,1195,1197,1199,1201,1203,1205,1207,1209,1211,1213,1215,1217,1219,1221,1223,1225],{"class":160,"line":1196},9,[158,1198,299],{"class":215},[158,1200,302],{"class":168},[158,1202,305],{"class":215},[158,1204,251],{"class":211},[158,1206,310],{"class":215},[158,1208,313],{"class":168},[158,1210,316],{"class":215},[158,1212,320],{"class":319},[158,1214,251],{"class":211},[158,1216,325],{"class":215},[158,1218,328],{"class":319},[158,1220,251],{"class":211},[158,1222,1160],{"class":215},[158,1224,1063],{"class":168},[158,1226,1227],{"class":215},"].tolist())\n",[158,1229,1231,1233,1235,1237,1239,1241,1243,1245],{"class":160,"line":1230},10,[158,1232,355],{"class":274},[158,1234,769],{"class":215},[158,1236,302],{"class":168},[158,1238,790],{"class":215},[158,1240,501],{"class":319},[158,1242,251],{"class":211},[158,1244,366],{"class":274},[158,1246,517],{"class":215},[10,1248,1249,1250,1253,1254,1256],{},"The last band's empty upper limit means \"no limit\", which maps naturally onto ",[14,1251,1252],{},"inf",". Checking that the limits increase catches the most likely editing mistake — a row typed out of order — before it produces a confusing error from ",[14,1255,24],{},".",[144,1258,1260],{"id":1259},"step-5-use-qcut-for-equal-sized-groups","Step 5 — Use qcut for equal-sized groups",[10,1262,1263,1264,1267],{},"Sometimes the question is not \"how many orders are under 50?\" but \"which quarter of orders is each one in?\". ",[14,1265,1266],{},"pd.qcut"," picks the edges from the data so each band holds about the same number of values:",[149,1269,1271],{"className":202,"code":1270,"language":204,"meta":154,"style":154},"orders[\"quartile\"] = pd.qcut(orders[\"value\"], 4, labels=[\"Q1\", \"Q2\", \"Q3\", \"Q4\"])\nprint(orders[\"quartile\"].value_counts(sort=False).to_dict())\n\nskewed = pd.Series([1, 1, 1, 1, 1, 2, 3])\ntry:\n    pd.qcut(skewed, 4)\nexcept ValueError as exc:\n    print(\"qcut:\", str(exc).splitlines()[0])\nprint(pd.qcut(skewed, 4, duplicates=\"drop\").value_counts(sort=False).to_dict())\n",[14,1272,1273,1321,1339,1343,1382,1390,1399,1412,1433],{"__ignoreMap":154},[158,1274,1275,1277,1280,1282,1284,1287,1289,1291,1293,1295,1297,1299,1301,1304,1306,1309,1311,1314,1316,1319],{"class":160,"line":161},[158,1276,299],{"class":215},[158,1278,1279],{"class":168},"\"quartile\"",[158,1281,305],{"class":215},[158,1283,251],{"class":211},[158,1285,1286],{"class":215}," pd.qcut(orders[",[158,1288,313],{"class":168},[158,1290,316],{"class":215},[158,1292,61],{"class":274},[158,1294,277],{"class":215},[158,1296,328],{"class":319},[158,1298,251],{"class":211},[158,1300,333],{"class":215},[158,1302,1303],{"class":168},"\"Q1\"",[158,1305,277],{"class":215},[158,1307,1308],{"class":168},"\"Q2\"",[158,1310,277],{"class":215},[158,1312,1313],{"class":168},"\"Q3\"",[158,1315,277],{"class":215},[158,1317,1318],{"class":168},"\"Q4\"",[158,1320,349],{"class":215},[158,1322,1323,1325,1327,1329,1331,1333,1335,1337],{"class":160,"line":225},[158,1324,355],{"class":274},[158,1326,769],{"class":215},[158,1328,1279],{"class":168},[158,1330,790],{"class":215},[158,1332,501],{"class":319},[158,1334,251],{"class":211},[158,1336,366],{"class":274},[158,1338,517],{"class":215},[158,1340,1341],{"class":160,"line":238},[158,1342,242],{"emptyLinePlaceholder":241},[158,1344,1345,1348,1350,1353,1355,1357,1359,1361,1363,1365,1367,1369,1371,1373,1376,1378,1380],{"class":160,"line":245},[158,1346,1347],{"class":215},"skewed ",[158,1349,251],{"class":211},[158,1351,1352],{"class":215}," pd.Series([",[158,1354,142],{"class":274},[158,1356,277],{"class":215},[158,1358,142],{"class":274},[158,1360,277],{"class":215},[158,1362,142],{"class":274},[158,1364,277],{"class":215},[158,1366,142],{"class":274},[158,1368,277],{"class":215},[158,1370,142],{"class":274},[158,1372,277],{"class":215},[158,1374,1375],{"class":274},"2",[158,1377,277],{"class":215},[158,1379,100],{"class":274},[158,1381,349],{"class":215},[158,1383,1384,1387],{"class":160,"line":263},[158,1385,1386],{"class":211},"try",[158,1388,1389],{"class":215},":\n",[158,1391,1392,1395,1397],{"class":160,"line":296},[158,1393,1394],{"class":215},"    pd.qcut(skewed, ",[158,1396,61],{"class":274},[158,1398,260],{"class":215},[158,1400,1401,1404,1406,1409],{"class":160,"line":352},[158,1402,1403],{"class":211},"except",[158,1405,1186],{"class":274},[158,1407,1408],{"class":211}," as",[158,1410,1411],{"class":215}," exc:\n",[158,1413,1414,1416,1418,1421,1423,1426,1429,1431],{"class":160,"line":1180},[158,1415,479],{"class":274},[158,1417,482],{"class":215},[158,1419,1420],{"class":168},"\"qcut:\"",[158,1422,277],{"class":215},[158,1424,1425],{"class":274},"str",[158,1427,1428],{"class":215},"(exc).splitlines()[",[158,1430,62],{"class":274},[158,1432,349],{"class":215},[158,1434,1435,1437,1440,1442,1444,1447,1449,1452,1455,1457,1459,1461],{"class":160,"line":1196},[158,1436,355],{"class":274},[158,1438,1439],{"class":215},"(pd.qcut(skewed, ",[158,1441,61],{"class":274},[158,1443,277],{"class":215},[158,1445,1446],{"class":319},"duplicates",[158,1448,251],{"class":211},[158,1450,1451],{"class":168},"\"drop\"",[158,1453,1454],{"class":215},").value_counts(",[158,1456,501],{"class":319},[158,1458,251],{"class":211},[158,1460,366],{"class":274},[158,1462,517],{"class":215},[10,1464,1465,1466,1469,1470,1473,1474,1477],{},"Twelve values give three per quartile. On data with many repeated values, several quantiles coincide and ",[14,1467,1468],{},"qcut"," raises \"Bin edges must be unique\"; ",[14,1471,1472],{},"duplicates=\"drop\""," merges the repeated edges and returns fewer, unequal bands. Remember that quantile edges move with the data — \"Q4\" this month and \"Q4\" last month are different ranges — so use ",[14,1475,1476],{},"cut"," with fixed edges for anything compared over time.",[144,1479,1481],{"id":1480},"step-6-summarise-bands-in-order","Step 6 — Summarise bands in order",[10,1483,1484,1485,1488],{},"A binned column is a pandas ",[377,1486,1487],{},"ordered categorical",", so pivots and groupbys keep the bands in their defined order and include empty bands when asked. That is exactly what a banded report needs:",[149,1490,1492],{"className":202,"code":1491,"language":204,"meta":154,"style":154},"summary = (orders.groupby(\"band\", observed=False)\n                 .agg(orders=(\"order\", \"count\"), total=(\"value\", \"sum\")))\nsummary[\"share\"] = (summary[\"orders\"] \u002F summary[\"orders\"].sum()).round(3)\nprint(summary)\nsummary.to_excel(\"value_bands.xlsx\")\n",[14,1493,1494,1517,1557,1592,1599],{"__ignoreMap":154},[158,1495,1496,1499,1501,1504,1506,1508,1511,1513,1515],{"class":160,"line":161},[158,1497,1498],{"class":215},"summary ",[158,1500,251],{"class":211},[158,1502,1503],{"class":215}," (orders.groupby(",[158,1505,302],{"class":168},[158,1507,277],{"class":215},[158,1509,1510],{"class":319},"observed",[158,1512,251],{"class":211},[158,1514,366],{"class":274},[158,1516,260],{"class":215},[158,1518,1519,1522,1525,1527,1529,1532,1534,1537,1540,1543,1545,1547,1549,1551,1554],{"class":160,"line":225},[158,1520,1521],{"class":215},"                 .agg(",[158,1523,1524],{"class":319},"orders",[158,1526,251],{"class":211},[158,1528,482],{"class":215},[158,1530,1531],{"class":168},"\"order\"",[158,1533,277],{"class":215},[158,1535,1536],{"class":168},"\"count\"",[158,1538,1539],{"class":215},"), ",[158,1541,1542],{"class":319},"total",[158,1544,251],{"class":211},[158,1546,482],{"class":215},[158,1548,313],{"class":168},[158,1550,277],{"class":215},[158,1552,1553],{"class":168},"\"sum\"",[158,1555,1556],{"class":215},")))\n",[158,1558,1559,1562,1565,1567,1569,1572,1575,1577,1580,1583,1585,1588,1590],{"class":160,"line":238},[158,1560,1561],{"class":215},"summary[",[158,1563,1564],{"class":168},"\"share\"",[158,1566,305],{"class":215},[158,1568,251],{"class":211},[158,1570,1571],{"class":215}," (summary[",[158,1573,1574],{"class":168},"\"orders\"",[158,1576,305],{"class":215},[158,1578,1579],{"class":211},"\u002F",[158,1581,1582],{"class":215}," summary[",[158,1584,1574],{"class":168},[158,1586,1587],{"class":215},"].sum()).round(",[158,1589,100],{"class":274},[158,1591,260],{"class":215},[158,1593,1594,1596],{"class":160,"line":245},[158,1595,355],{"class":274},[158,1597,1598],{"class":215},"(summary)\n",[158,1600,1601,1604,1607],{"class":160,"line":263},[158,1602,1603],{"class":215},"summary.to_excel(",[158,1605,1606],{"class":168},"\"value_bands.xlsx\"",[158,1608,260],{"class":215},[10,1610,1611,1614,1615,1619],{},[14,1612,1613],{},"observed=False"," keeps bands with no orders in the table as zero rows — a band that is empty this month is information, not noise. Written to Excel, the bands appear in order from \"up to 10\" to \"over 100\", ready for a column chart. ",[31,1616,1618],{"href":1617},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fcreate-a-histogram-in-excel-with-python\u002F","Create a Histogram in Excel with Python"," builds the chart from exactly this kind of table.",[144,1621,1623],{"id":1622},"common-pitfalls","Common pitfalls",[1625,1626,1627,1642,1651,1663,1669],"ul",{},[1628,1629,1630,1634,1635,1637,1638,1641],"li",{},[1631,1632,1633],"strong",{},"Values on the lowest edge becoming NaN."," Use ",[14,1636,665],{}," or ",[14,1639,1640],{},"-inf"," as the first edge.",[1628,1643,1644,1647,1648,1650],{},[1631,1645,1646],{},"Values above the last edge becoming NaN."," End the edges with ",[14,1649,1252],{}," and an \"over …\" label.",[1628,1652,1653,1662],{},[1631,1654,1655,1658,1659,1256],{},[14,1656,1657],{},"value_counts()"," without ",[14,1660,1661],{},"sort=False"," Bands come out ordered by count.",[1628,1664,1665,1668],{},[1631,1666,1667],{},"Labels that contradict the interval side."," \"10-50\" is ambiguous; say which edge is included.",[1628,1670,1671,1674],{},[1631,1672,1673],{},"qcut for comparisons over time."," Its edges change with the data.",[144,1676,1678],{"id":1677},"performance-notes","Performance notes",[10,1680,1681,1683,1684,1686],{},[14,1682,24],{}," is vectorised and bins millions of values in a fraction of a second; the categorical it returns is also compact in memory, one small integer per value. ",[14,1685,1468],{}," first computes quantiles, which requires a sort, but is still fast for any report-sized column.",[144,1688,1690],{"id":1689},"conclusion","Conclusion",[10,1692,1693,1694,1697,1698,1579,1700,1702,1703,1705,1706,1708,1709,1711,1712,1714,1715,1717],{},"Bin with ",[14,1695,1696],{},"pd.cut(values, bins=edges, labels=...)",", choose right- or left-closed intervals deliberately, and use ",[14,1699,1640],{},[14,1701,1252],{}," outer edges (or ",[14,1704,665],{},") so no value silently becomes ",[14,1707,28],{},". Right-closed bins with open outer edges and ",[14,1710,908],{}," reproduce Excel's ",[14,1713,16],{}," exactly; ",[14,1716,1468],{}," gives equal-count groups when ranking matters more than fixed ranges; and grouping on the resulting ordered categorical keeps bands, including empty ones, in order.",[144,1719,1721],{"id":1720},"frequently-asked-questions","Frequently asked questions",[10,1723,1724,1727],{},[1631,1725,1726],{},"Which values fall into which bin with pd.cut?","\nBy default intervals are closed on the right: with edges 10 and 50, a value of 10 falls in the band ending at 10 and 50 falls in the band ending at 50. Pass right=False to close them on the left instead, so each band includes its lower edge.",[10,1729,1730,1733],{},[1631,1731,1732],{},"Why did some values become NaN?","\nThey lie outside the outermost edges. pd.cut does not extend the first or last band, so a value below the lowest edge or above the highest is simply not assigned. Use -inf and inf as the outer edges, or include_lowest=True for a value exactly on the lowest edge.",[10,1735,1736,1739,1740,1743],{},[1631,1737,1738],{},"How do I reproduce Excel's FREQUENCY function?","\nFREQUENCY counts values up to and including each bin boundary, plus one extra count for values above the last. pd.cut with edges ",[158,1741,1742],{},"-inf, b1, b2, …, inf"," and the default right-closed intervals, followed by value_counts(sort=False), gives the same numbers.",[10,1745,1746,1749],{},[1631,1747,1748],{},"What is the difference between cut and qcut?","\ncut uses edges you choose, so bands have fixed widths and uneven counts. qcut chooses edges from the data so each band holds roughly the same number of values — quartiles, deciles — which is useful for ranking but gives band edges that change with the data.",[144,1751,1753],{"id":1752},"related","Related",[1625,1755,1756,1762,1769,1776],{},[1628,1757,1758,1759,1761],{},"Up one level: ",[31,1760,34],{"href":33}," — pivots and summaries.",[1628,1763,1764,1768],{},[31,1765,1767],{"href":1766},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fapproximate-match-vlookup-in-pandas\u002F","Approximate-Match VLOOKUP for Tiered Rates in pandas"," — looking up a rate per band.",[1628,1770,1771,1775],{},[31,1772,1774],{"href":1773},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fcountif-and-countifs-equivalent-in-pandas\u002F","COUNTIF and COUNTIFS Equivalent in pandas"," — counting with conditions.",[1628,1777,1778,1782],{},[31,1779,1781],{"href":1780},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-age-and-tenure-from-dates-with-pandas\u002F","Calculate Age and Tenure from Dates with pandas (DATEDIF Equivalent)"," — tenure bands in practice.",[1784,1785,1786],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":154,"searchDepth":225,"depth":225,"links":1788},[1789,1790,1791,1792,1793,1794,1795,1796,1797,1798,1799,1800,1801],{"id":146,"depth":225,"text":147},{"id":184,"depth":225,"text":185},{"id":386,"depth":225,"text":387},{"id":658,"depth":225,"text":659},{"id":811,"depth":225,"text":812},{"id":1041,"depth":225,"text":1042},{"id":1259,"depth":225,"text":1260},{"id":1480,"depth":225,"text":1481},{"id":1622,"depth":225,"text":1623},{"id":1677,"depth":225,"text":1678},{"id":1689,"depth":225,"text":1690},{"id":1720,"depth":225,"text":1721},{"id":1752,"depth":225,"text":1753},"2026-09-25","Group order values, ages or delivery times into bands such as \"0-10\", \"10-50\" and \"over 100\". Use pd.cut with explicit edges and labels, decide which side of each band is closed, stop values outside the edges from silently becoming NaN, match Excel's FREQUENCY counts exactly, and use qcut for equal-sized groups.","md",[1806,1808,1810,1812],{"q":1726,"a":1807},"By default intervals are closed on the right: with edges 10 and 50, a value of 10 falls in the band ending at 10 and 50 falls in the band ending at 50. Pass right=False to close them on the left instead, so each band includes its lower edge.",{"q":1732,"a":1809},"They lie outside the outermost edges. pd.cut does not extend the first or last band, so a value below the lowest edge or above the highest is simply not assigned. Use -inf and inf as the outer edges, or include_lowest=True for a value exactly on the lowest edge.",{"q":1738,"a":1811},"FREQUENCY counts values up to and including each bin boundary, plus one extra count for values above the last. pd.cut with edges [-inf, b1, b2, …, inf] and the default right-closed intervals, followed by value_counts(sort=False), gives the same numbers.",{"q":1748,"a":1813},"cut uses edges you choose, so bands have fixed widths and uneven counts. qcut chooses edges from the data so each band holds roughly the same number of values — quartiles, deciles — which is useful for ranking but gives band edges that change with the data.","bin values into ranges like excel frequency with pd.cut",null,{"breadcrumb":1817},[1818,1820,1823],{"name":1819,"item":1579},"Home",{"name":1821,"item":1822},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":34,"item":33},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut",{"title":1826,"description":1827},"pandas cut: Bin Numbers into Ranges (Excel FREQUENCY and Histogram Bins)","Bin values into ranges with pandas pd.cut: bins and labels, right-closed versus left-closed intervals, include_lowest, -inf and inf edges, matching Excel FREQUENCY, qcut quantile bins and ordered band counts for reports.","bin-values-into-ranges-with-pandas-cut","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut\u002Findex",[1831,1832,1833],"pivots-and-summaries","formula-equivalents","pandas","how-to","mvDUk1mrE4P6YRk5SRVhzk5WbWMDlX5bW-GzTy3xuiI",[1837,1841],{"title":1838,"path":1839,"stem":1840,"children":-1},"Add Subtotals and Grand Totals to a pandas Pivot Table","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table\u002Findex",{"title":1842,"path":1843,"stem":1844,"children":-1},"Build a Cohort Retention Table from Excel Data","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-cohort-retention-table-from-excel-data","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-cohort-retention-table-from-excel-data\u002Findex",[1846,1848],{"path":1847,"title":34},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data",{"path":1849,"title":1821},"\u002Fadvanced-data-transformation-and-cleaning",[],[1852,1855,1858,1861,1862,1863],{"path":1853,"title":1854,"kind":1834},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-customer-churn-report-from-subscription-data","Build a Customer Churn Report from Subscription Data",{"path":1856,"title":1857,"kind":1834},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-xlwings-basics\u002Frefresh-pivot-tables-and-queries-in-excel-with-python","Refresh Excel Pivot Tables and Queries with Python",{"path":1859,"title":1860,"kind":1834},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python","Add a Native Excel Pivot Table with Python",{"path":1839,"title":1838,"kind":1834},{"path":1843,"title":1842,"kind":1834},{"path":1864,"title":1865,"kind":1834},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-crosstab-from-excel-data-with-pandas","Build a Crosstab from Excel Data with pandas",1790972086161]