[{"data":1,"prerenderedAt":2923},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-headcount-and-attrition-report":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-headcount-and-attrition-report":2888,"ancestors:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-headcount-and-attrition-report":2897,"children:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-headcount-and-attrition-report":2902,"related:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-headcount-and-attrition-report":2903},{"id":4,"title":5,"body":6,"dateModified":2854,"datePublished":2854,"description":2855,"extension":2856,"faq":2857,"keyword":2866,"kind":2867,"meta":2868,"navigation":334,"path":2877,"seo":2878,"slug":2881,"stem":2882,"topics":2883,"type":2886,"__hash__":2887},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-headcount-and-attrition-report\u002Findex.md","Build a Headcount and Attrition Report in Excel with Python",{"type":7,"value":8,"toc":2841},"minimark",[9,19,24,56,62,66,69,107,299,303,306,549,559,563,808,811,1031,1035,1038,1148,1151,1362,1366,1369,1543,1546,1807,1810,1998,2002,2005,2706,2722,2726,2764,2768,2771,2775,2781,2787,2793,2799,2803,2837],[10,11,12,13,18],"p",{},"\"How many people do we have, and how many are we losing?\" sounds like a simple count until you try to answer it from an HR export. Headcount changes every day, so it has to be measured at a point in time; attrition is a rate, so it needs a denominator; and the answer everyone argues about depends on whether a transfer, a contractor or a leaver on the last day of the month counts. A Python script makes those definitions explicit and computes the report the same way every month. This page is part of ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002F","Building Finance and Operations Reports",".",[20,21,23],"h2",{"id":22},"prerequisites","Prerequisites",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl xlsxwriter\n","bash","",[32,33,34],"code",{"__ignoreMap":30},[35,36,39,43,47,50,53],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"sMTad","pip",[35,44,46],{"class":45},"srMev"," install",[35,48,49],{"class":45}," pandas",[35,51,52],{"class":45}," openpyxl",[35,54,55],{"class":45}," xlsxwriter\n",[10,57,58,61],{},[32,59,60],{},"hr_export.xlsx"," is one row per employment: employee ID, department, contract type, hire date, and for people who have left, the leave date and whether it was voluntary or involuntary. Current staff have an empty leave date. The report covers September 2025 to August 2026.",[20,63,65],{"id":64},"definitions-first","Definitions first",[10,67,68],{},"Write the definitions down before counting anything; most disagreements about HR numbers are disagreements about these:",[70,71,72,80,90,101],"ul",{},[73,74,75,79],"li",{},[76,77,78],"strong",{},"Headcount"," is the number of people employed at the end of the month: hired on or before that day and not yet left. A leaver whose last day is the 31st is still counted on the 31st.",[73,81,82,85,86,89],{},[76,83,84],{},"Hires"," and ",[76,87,88],{},"leavers"," in a month are people whose hire date or leave date falls in it.",[73,91,92,95,96,100],{},[76,93,94],{},"Monthly attrition"," is leavers divided by ",[97,98,99],"em",{},"average"," headcount, the mean of the opening and closing headcount. Dividing by the closing figure alone overstates attrition in a shrinking team.",[73,102,103,106],{},[76,104,105],{},"Rolling 12-month attrition"," is the sum of leavers over the last twelve months divided by the average headcount over those months. It is the number HR benchmarks use, and it smooths out one bad month.",[108,109,117,118,117,122,117,126,117,134,117,144,117,155,117,162,117,166,117,171,117,175,117,181,117,186,117,190,117,197,117,202,117,206,117,210,117,214,117,217,117,222,117,229,117,234,117,237,117,240,117,244,117,247,117,252,117,259,117,264,117,267,117,270,117,274,117,277,117,280,117,286,117,290,117,293,117,296],"svg",{"viewBox":110,"role":111,"ariaLabelledBy":112,"xmlns":115,"style":116},"4 0 752 160","img",[113,114],"hc-defs-t","hc-defs-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[119,120,121],"title",{"id":113},"From employment records to an attrition rate",[123,124,125],"desc",{"id":114},"Each employment record has a hire date and possibly a leave date. Counting records active on each month end gives headcount. Counting leave dates in each month gives leavers. Leavers divided by the average of opening and closing headcount gives monthly attrition, and twelve months summed gives the rolling rate.",[127,128],"rect",{"x":129,"y":130,"width":131,"height":132,"fill":133},"4","0","752","160","#ffffff",[127,135],{"x":136,"y":137,"width":138,"height":139,"rx":140,"fill":141,"stroke":142,"style":143},"20.0","16.0","115.2","128.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[145,146,148],"g",{"transform":147},"translate(65.6,29.0) scale(1.000)",[149,150],"path",{"d":151,"fill":152,"stroke":153,"style":154},"M9 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z M2.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6 M16 4.5a3.3 3.3 0 0 1 0 6.3 M18 14.3c2.2.7 3.5 2.8 3.5 5.7","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[156,157,161],"text",{"x":158,"y":159,"style":160},"77.6","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Employment",[156,163,165],{"x":158,"y":164,"style":160},"93.0","rows",[156,167,170],{"x":158,"y":168,"style":169},"112.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","hire and leave",[156,172,174],{"x":158,"y":173,"style":169},"130.0","dates",[37,176],{"x1":177,"y1":178,"x2":179,"y2":178,"stroke":180,"style":143},"140.2","80.0","162.2","var(--brand,#5b5cf0)",[182,183],"polygon",{"points":184,"fill":185},"166.2,80.0 157.2,85.0 157.2,75.0","#5b5cf0",[127,187],{"x":188,"y":137,"width":138,"height":139,"rx":140,"fill":189,"stroke":180,"style":143},"171.2","#f0f4ff",[145,191,193],{"transform":192},"translate(216.8,37.8) scale(1.000)",[149,194],{"d":195,"fill":152,"stroke":196,"style":154},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","var(--brand-strong,#4338ca)",[156,198,78],{"x":199,"y":200,"style":201},"228.8","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[156,203,205],{"x":199,"y":204,"style":169},"103.8","active at month",[156,207,209],{"x":199,"y":208,"style":169},"121.2","end",[37,211],{"x1":212,"y1":178,"x2":213,"y2":178,"stroke":180,"style":143},"291.4","313.4",[182,215],{"points":216,"fill":185},"317.4,80.0 308.4,85.0 308.4,75.0",[127,218],{"x":219,"y":137,"width":138,"height":139,"rx":140,"fill":220,"stroke":221,"style":143},"322.4","#fdefd8","var(--gold,#b4740a)",[145,223,225],{"transform":224},"translate(368.0,37.8) scale(1.000)",[149,226],{"d":227,"fill":152,"stroke":228,"style":154},"M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8z M4 21c0-4 3.6-7 8-7s8 3 8 7","var(--gold-ink,#7a4e06)",[156,230,233],{"x":231,"y":200,"style":232},"380.0","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Leavers",[156,235,236],{"x":231,"y":204,"style":169},"leave date in",[156,238,239],{"x":231,"y":208,"style":169},"month",[37,241],{"x1":242,"y1":178,"x2":243,"y2":178,"stroke":180,"style":143},"442.6","464.6",[182,245],{"points":246,"fill":185},"468.6,80.0 459.6,85.0 459.6,75.0",[127,248],{"x":249,"y":137,"width":138,"height":139,"rx":140,"fill":250,"stroke":251,"style":143},"473.6","#d9f4f1","var(--teal,#0f9488)",[145,253,255],{"transform":254},"translate(519.2,37.8) scale(1.000)",[149,256],{"d":257,"fill":152,"stroke":258,"style":154},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16","var(--teal-ink,#0b6157)",[156,260,263],{"x":261,"y":200,"style":262},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Monthly rate",[156,265,266],{"x":261,"y":204,"style":169},"leavers \u002F avg",[156,268,269],{"x":261,"y":208,"style":169},"headcount",[37,271],{"x1":272,"y1":178,"x2":273,"y2":178,"stroke":180,"style":143},"593.8","615.8",[182,275],{"points":276,"fill":185},"619.8,80.0 610.8,85.0 610.8,75.0",[127,278],{"x":279,"y":137,"width":138,"height":139,"rx":140,"fill":189,"stroke":180,"style":143},"624.8",[145,281,283],{"transform":282},"translate(670.4,29.0) scale(1.000)",[149,284],{"d":285,"fill":152,"stroke":196,"style":154},"M4 20h16 M4 20V4 M6 16l4-5 4 3 6-7",[156,287,289],{"x":288,"y":159,"style":201},"682.4","Rolling 12",[156,291,292],{"x":288,"y":164,"style":201},"months",[156,294,295],{"x":288,"y":168,"style":169},"benchmark",[156,297,298],{"x":288,"y":173,"style":169},"figure",[20,300,302],{"id":301},"step-1-headcount-at-each-month-end","Step 1 — Headcount at each month end",[10,304,305],{},"For each month end, count the rows active on that day. A comparison per month is clear and fast enough for tens of thousands of employees:",[25,307,311],{"className":308,"code":309,"language":310,"meta":30,"style":30},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nstaff = pd.read_excel(\"hr_export.xlsx\", parse_dates=[\"HireDate\", \"LeaveDate\"])\nmonth_ends = pd.date_range(\"2024-08-31\", \"2026-08-31\", freq=\"ME\")   # two years, for the rolling rate\n\ndef headcount_on(day, df=staff):\n    active = (df[\"HireDate\"] \u003C= day) & (df[\"LeaveDate\"].isna() | (df[\"LeaveDate\"] >= day))\n    return int(active.sum())\n\nheadcount = pd.Series([headcount_on(d) for d in month_ends], index=month_ends.to_period(\"M\"))\nprint(headcount.tail(4).to_string())\n","python",[32,312,313,329,336,374,410,415,433,480,493,498,535],{"__ignoreMap":30},[35,314,315,319,323,326],{"class":37,"line":38},[35,316,318],{"class":317},"s-kum","import",[35,320,322],{"class":321},"skGVy"," pandas ",[35,324,325],{"class":317},"as",[35,327,328],{"class":321}," pd\n",[35,330,332],{"class":37,"line":331},2,[35,333,335],{"emptyLinePlaceholder":334},true,"\n",[35,337,339,342,345,348,351,354,358,360,363,366,368,371],{"class":37,"line":338},3,[35,340,341],{"class":321},"staff ",[35,343,344],{"class":317},"=",[35,346,347],{"class":321}," pd.read_excel(",[35,349,350],{"class":45},"\"hr_export.xlsx\"",[35,352,353],{"class":321},", ",[35,355,357],{"class":356},"sa561","parse_dates",[35,359,344],{"class":317},[35,361,362],{"class":321},"[",[35,364,365],{"class":45},"\"HireDate\"",[35,367,353],{"class":321},[35,369,370],{"class":45},"\"LeaveDate\"",[35,372,373],{"class":321},"])\n",[35,375,377,380,382,385,388,390,393,395,398,400,403,406],{"class":37,"line":376},4,[35,378,379],{"class":321},"month_ends ",[35,381,344],{"class":317},[35,383,384],{"class":321}," pd.date_range(",[35,386,387],{"class":45},"\"2024-08-31\"",[35,389,353],{"class":321},[35,391,392],{"class":45},"\"2026-08-31\"",[35,394,353],{"class":321},[35,396,397],{"class":356},"freq",[35,399,344],{"class":317},[35,401,402],{"class":45},"\"ME\"",[35,404,405],{"class":321},")   ",[35,407,409],{"class":408},"s-wDw","# two years, for the rolling rate\n",[35,411,413],{"class":37,"line":412},5,[35,414,335],{"emptyLinePlaceholder":334},[35,416,418,421,425,428,430],{"class":37,"line":417},6,[35,419,420],{"class":317},"def",[35,422,424],{"class":423},"s_Opv"," headcount_on",[35,426,427],{"class":321},"(day, df",[35,429,344],{"class":317},[35,431,432],{"class":321},"staff):\n",[35,434,436,439,441,444,446,449,452,455,458,460,462,465,468,470,472,474,477],{"class":37,"line":435},7,[35,437,438],{"class":321},"    active ",[35,440,344],{"class":317},[35,442,443],{"class":321}," (df[",[35,445,365],{"class":45},[35,447,448],{"class":321},"] ",[35,450,451],{"class":317},"\u003C=",[35,453,454],{"class":321}," day) ",[35,456,457],{"class":317},"&",[35,459,443],{"class":321},[35,461,370],{"class":45},[35,463,464],{"class":321},"].isna() ",[35,466,467],{"class":317},"|",[35,469,443],{"class":321},[35,471,370],{"class":45},[35,473,448],{"class":321},[35,475,476],{"class":317},">=",[35,478,479],{"class":321}," day))\n",[35,481,483,486,490],{"class":37,"line":482},8,[35,484,485],{"class":317},"    return",[35,487,489],{"class":488},"sP0c6"," int",[35,491,492],{"class":321},"(active.sum())\n",[35,494,496],{"class":37,"line":495},9,[35,497,335],{"emptyLinePlaceholder":334},[35,499,501,504,506,509,512,515,518,521,524,526,529,532],{"class":37,"line":500},10,[35,502,503],{"class":321},"headcount ",[35,505,344],{"class":317},[35,507,508],{"class":321}," pd.Series([headcount_on(d) ",[35,510,511],{"class":317},"for",[35,513,514],{"class":321}," d ",[35,516,517],{"class":317},"in",[35,519,520],{"class":321}," month_ends], ",[35,522,523],{"class":356},"index",[35,525,344],{"class":317},[35,527,528],{"class":321},"month_ends.to_period(",[35,530,531],{"class":45},"\"M\"",[35,533,534],{"class":321},"))\n",[35,536,538,541,544,546],{"class":37,"line":537},11,[35,539,540],{"class":488},"print",[35,542,543],{"class":321},"(headcount.tail(",[35,545,129],{"class":488},[35,547,548],{"class":321},").to_string())\n",[10,550,551,554,555,558],{},[32,552,553],{},"LeaveDate >= day"," is the \"still counted on their last day\" rule. Change it to ",[32,556,557],{},">"," and anyone leaving on a month end disappears from that month's headcount; either is defensible, but pick one and keep it.",[20,560,562],{"id":561},"step-2-hires-leavers-and-the-monthly-rate","Step 2 — Hires, leavers and the monthly rate",[25,564,566],{"className":308,"code":565,"language":310,"meta":30,"style":30},"months = headcount.index\nhires = staff[\"HireDate\"].dt.to_period(\"M\").value_counts().reindex(months, fill_value=0)\nleavers = staff[\"LeaveDate\"].dropna().dt.to_period(\"M\").value_counts().reindex(months, fill_value=0)\nopening = headcount.shift(1)\n\nreport = pd.DataFrame({\n    \"Opening\": opening,\n    \"Hires\": hires,\n    \"Leavers\": leavers,\n    \"Closing\": headcount,\n})\nreport[\"Avg headcount\"] = (report[\"Opening\"] + report[\"Closing\"]) \u002F 2\nreport[\"Attrition %\"] = report[\"Leavers\"] \u002F report[\"Avg headcount\"] * 100\nreport = report.iloc[1:]                      # the first month has no opening figure\nprint(report.tail(6).round(1).to_string())\n",[32,567,568,578,608,634,649,653,663,671,679,687,695,700,739,772,790],{"__ignoreMap":30},[35,569,570,573,575],{"class":37,"line":38},[35,571,572],{"class":321},"months ",[35,574,344],{"class":317},[35,576,577],{"class":321}," headcount.index\n",[35,579,580,583,585,588,590,593,595,598,601,603,605],{"class":37,"line":331},[35,581,582],{"class":321},"hires ",[35,584,344],{"class":317},[35,586,587],{"class":321}," staff[",[35,589,365],{"class":45},[35,591,592],{"class":321},"].dt.to_period(",[35,594,531],{"class":45},[35,596,597],{"class":321},").value_counts().reindex(months, ",[35,599,600],{"class":356},"fill_value",[35,602,344],{"class":317},[35,604,130],{"class":488},[35,606,607],{"class":321},")\n",[35,609,610,613,615,617,619,622,624,626,628,630,632],{"class":37,"line":338},[35,611,612],{"class":321},"leavers ",[35,614,344],{"class":317},[35,616,587],{"class":321},[35,618,370],{"class":45},[35,620,621],{"class":321},"].dropna().dt.to_period(",[35,623,531],{"class":45},[35,625,597],{"class":321},[35,627,600],{"class":356},[35,629,344],{"class":317},[35,631,130],{"class":488},[35,633,607],{"class":321},[35,635,636,639,641,644,647],{"class":37,"line":376},[35,637,638],{"class":321},"opening ",[35,640,344],{"class":317},[35,642,643],{"class":321}," headcount.shift(",[35,645,646],{"class":488},"1",[35,648,607],{"class":321},[35,650,651],{"class":37,"line":412},[35,652,335],{"emptyLinePlaceholder":334},[35,654,655,658,660],{"class":37,"line":417},[35,656,657],{"class":321},"report ",[35,659,344],{"class":317},[35,661,662],{"class":321}," pd.DataFrame({\n",[35,664,665,668],{"class":37,"line":435},[35,666,667],{"class":45},"    \"Opening\"",[35,669,670],{"class":321},": opening,\n",[35,672,673,676],{"class":37,"line":482},[35,674,675],{"class":45},"    \"Hires\"",[35,677,678],{"class":321},": hires,\n",[35,680,681,684],{"class":37,"line":495},[35,682,683],{"class":45},"    \"Leavers\"",[35,685,686],{"class":321},": leavers,\n",[35,688,689,692],{"class":37,"line":500},[35,690,691],{"class":45},"    \"Closing\"",[35,693,694],{"class":321},": headcount,\n",[35,696,697],{"class":37,"line":537},[35,698,699],{"class":321},"})\n",[35,701,703,706,709,711,713,716,719,721,724,727,730,733,736],{"class":37,"line":702},12,[35,704,705],{"class":321},"report[",[35,707,708],{"class":45},"\"Avg headcount\"",[35,710,448],{"class":321},[35,712,344],{"class":317},[35,714,715],{"class":321}," (report[",[35,717,718],{"class":45},"\"Opening\"",[35,720,448],{"class":321},[35,722,723],{"class":317},"+",[35,725,726],{"class":321}," report[",[35,728,729],{"class":45},"\"Closing\"",[35,731,732],{"class":321},"]) ",[35,734,735],{"class":317},"\u002F",[35,737,738],{"class":488}," 2\n",[35,740,742,744,747,749,751,753,756,758,760,762,764,766,769],{"class":37,"line":741},13,[35,743,705],{"class":321},[35,745,746],{"class":45},"\"Attrition %\"",[35,748,448],{"class":321},[35,750,344],{"class":317},[35,752,726],{"class":321},[35,754,755],{"class":45},"\"Leavers\"",[35,757,448],{"class":321},[35,759,735],{"class":317},[35,761,726],{"class":321},[35,763,708],{"class":45},[35,765,448],{"class":321},[35,767,768],{"class":317},"*",[35,770,771],{"class":488}," 100\n",[35,773,775,777,779,782,784,787],{"class":37,"line":774},14,[35,776,657],{"class":321},[35,778,344],{"class":317},[35,780,781],{"class":321}," report.iloc[",[35,783,646],{"class":488},[35,785,786],{"class":321},":]                      ",[35,788,789],{"class":408},"# the first month has no opening figure\n",[35,791,793,795,798,801,804,806],{"class":37,"line":792},15,[35,794,540],{"class":488},[35,796,797],{"class":321},"(report.tail(",[35,799,800],{"class":488},"6",[35,802,803],{"class":321},").round(",[35,805,646],{"class":488},[35,807,548],{"class":321},[10,809,810],{},"Opening plus hires minus leavers should equal closing. It almost does: a leaver on the last day of the month is counted at that month end and removed in the next, so the identity is off by one in that month and off by one the other way in the following month. Check that this is the only difference rather than assuming it:",[25,812,814],{"className":308,"code":813,"language":310,"meta":30,"style":30},"month_end_days = report.index.to_timestamp(how=\"end\").normalize()\non_last_day = staff[\"LeaveDate\"].isin(month_end_days)\nlast_day_leavers = on_last_day.groupby(staff[\"LeaveDate\"].dt.to_period(\"M\")).sum()\nldl_this = last_day_leavers.reindex(report.index, fill_value=0)\nldl_prev = last_day_leavers.reindex(report.index - 1, fill_value=0).values\ngap = report[\"Opening\"] + report[\"Hires\"] - report[\"Leavers\"] - report[\"Closing\"]\nprint(\"months with a gap:\", {str(m): int(g) for m, g in gap[gap != 0].items()})\nprint(\"months not explained by last-day leavers:\", int(((gap + ldl_this - ldl_prev) != 0).sum()))\n",[32,815,816,837,851,870,888,915,954,998],{"__ignoreMap":30},[35,817,818,821,823,826,829,831,834],{"class":37,"line":38},[35,819,820],{"class":321},"month_end_days ",[35,822,344],{"class":317},[35,824,825],{"class":321}," report.index.to_timestamp(",[35,827,828],{"class":356},"how",[35,830,344],{"class":317},[35,832,833],{"class":45},"\"end\"",[35,835,836],{"class":321},").normalize()\n",[35,838,839,842,844,846,848],{"class":37,"line":331},[35,840,841],{"class":321},"on_last_day ",[35,843,344],{"class":317},[35,845,587],{"class":321},[35,847,370],{"class":45},[35,849,850],{"class":321},"].isin(month_end_days)\n",[35,852,853,856,858,861,863,865,867],{"class":37,"line":338},[35,854,855],{"class":321},"last_day_leavers ",[35,857,344],{"class":317},[35,859,860],{"class":321}," on_last_day.groupby(staff[",[35,862,370],{"class":45},[35,864,592],{"class":321},[35,866,531],{"class":45},[35,868,869],{"class":321},")).sum()\n",[35,871,872,875,877,880,882,884,886],{"class":37,"line":376},[35,873,874],{"class":321},"ldl_this ",[35,876,344],{"class":317},[35,878,879],{"class":321}," last_day_leavers.reindex(report.index, ",[35,881,600],{"class":356},[35,883,344],{"class":317},[35,885,130],{"class":488},[35,887,607],{"class":321},[35,889,890,893,895,898,901,904,906,908,910,912],{"class":37,"line":412},[35,891,892],{"class":321},"ldl_prev ",[35,894,344],{"class":317},[35,896,897],{"class":321}," last_day_leavers.reindex(report.index ",[35,899,900],{"class":317},"-",[35,902,903],{"class":488}," 1",[35,905,353],{"class":321},[35,907,600],{"class":356},[35,909,344],{"class":317},[35,911,130],{"class":488},[35,913,914],{"class":321},").values\n",[35,916,917,920,922,924,926,928,930,932,935,937,939,941,943,945,947,949,951],{"class":37,"line":417},[35,918,919],{"class":321},"gap ",[35,921,344],{"class":317},[35,923,726],{"class":321},[35,925,718],{"class":45},[35,927,448],{"class":321},[35,929,723],{"class":317},[35,931,726],{"class":321},[35,933,934],{"class":45},"\"Hires\"",[35,936,448],{"class":321},[35,938,900],{"class":317},[35,940,726],{"class":321},[35,942,755],{"class":45},[35,944,448],{"class":321},[35,946,900],{"class":317},[35,948,726],{"class":321},[35,950,729],{"class":45},[35,952,953],{"class":321},"]\n",[35,955,956,958,961,964,967,970,973,976,979,981,984,986,989,992,995],{"class":37,"line":435},[35,957,540],{"class":488},[35,959,960],{"class":321},"(",[35,962,963],{"class":45},"\"months with a gap:\"",[35,965,966],{"class":321},", {",[35,968,969],{"class":488},"str",[35,971,972],{"class":321},"(m): ",[35,974,975],{"class":488},"int",[35,977,978],{"class":321},"(g) ",[35,980,511],{"class":317},[35,982,983],{"class":321}," m, g ",[35,985,517],{"class":317},[35,987,988],{"class":321}," gap[gap ",[35,990,991],{"class":317},"!=",[35,993,994],{"class":488}," 0",[35,996,997],{"class":321},"].items()})\n",[35,999,1000,1002,1004,1007,1009,1011,1014,1016,1019,1021,1024,1026,1028],{"class":37,"line":482},[35,1001,540],{"class":488},[35,1003,960],{"class":321},[35,1005,1006],{"class":45},"\"months not explained by last-day leavers:\"",[35,1008,353],{"class":321},[35,1010,975],{"class":488},[35,1012,1013],{"class":321},"(((gap ",[35,1015,723],{"class":317},[35,1017,1018],{"class":321}," ldl_this ",[35,1020,900],{"class":317},[35,1022,1023],{"class":321}," ldl_prev) ",[35,1025,991],{"class":317},[35,1027,994],{"class":488},[35,1029,1030],{"class":321},").sum()))\n",[20,1032,1034],{"id":1033},"step-3-the-rolling-twelve-month-rate","Step 3 — The rolling twelve-month rate",[10,1036,1037],{},"Monthly rates jump around; a team of twenty losing two people in one month shows 10%. The rolling rate is what to put in front of management:",[25,1039,1041],{"className":308,"code":1040,"language":310,"meta":30,"style":30},"rolling = pd.DataFrame({\n    \"Leavers 12m\": report[\"Leavers\"].rolling(12).sum(),\n    \"Avg headcount 12m\": report[\"Avg headcount\"].rolling(12).mean(),\n})\nrolling[\"Attrition 12m %\"] = rolling[\"Leavers 12m\"] \u002F rolling[\"Avg headcount 12m\"] * 100\nlast12 = rolling.dropna()\nprint(last12.tail(4).round(1).to_string())\n",[32,1042,1043,1052,1070,1086,1090,1123,1133],{"__ignoreMap":30},[35,1044,1045,1048,1050],{"class":37,"line":38},[35,1046,1047],{"class":321},"rolling ",[35,1049,344],{"class":317},[35,1051,662],{"class":321},[35,1053,1054,1057,1060,1062,1065,1067],{"class":37,"line":331},[35,1055,1056],{"class":45},"    \"Leavers 12m\"",[35,1058,1059],{"class":321},": report[",[35,1061,755],{"class":45},[35,1063,1064],{"class":321},"].rolling(",[35,1066,140],{"class":488},[35,1068,1069],{"class":321},").sum(),\n",[35,1071,1072,1075,1077,1079,1081,1083],{"class":37,"line":338},[35,1073,1074],{"class":45},"    \"Avg headcount 12m\"",[35,1076,1059],{"class":321},[35,1078,708],{"class":45},[35,1080,1064],{"class":321},[35,1082,140],{"class":488},[35,1084,1085],{"class":321},").mean(),\n",[35,1087,1088],{"class":37,"line":376},[35,1089,699],{"class":321},[35,1091,1092,1095,1098,1100,1102,1105,1108,1110,1112,1114,1117,1119,1121],{"class":37,"line":412},[35,1093,1094],{"class":321},"rolling[",[35,1096,1097],{"class":45},"\"Attrition 12m %\"",[35,1099,448],{"class":321},[35,1101,344],{"class":317},[35,1103,1104],{"class":321}," rolling[",[35,1106,1107],{"class":45},"\"Leavers 12m\"",[35,1109,448],{"class":321},[35,1111,735],{"class":317},[35,1113,1104],{"class":321},[35,1115,1116],{"class":45},"\"Avg headcount 12m\"",[35,1118,448],{"class":321},[35,1120,768],{"class":317},[35,1122,771],{"class":488},[35,1124,1125,1128,1130],{"class":37,"line":417},[35,1126,1127],{"class":321},"last12 ",[35,1129,344],{"class":317},[35,1131,1132],{"class":321}," rolling.dropna()\n",[35,1134,1135,1137,1140,1142,1144,1146],{"class":37,"line":435},[35,1136,540],{"class":488},[35,1138,1139],{"class":321},"(last12.tail(",[35,1141,129],{"class":488},[35,1143,803],{"class":321},[35,1145,646],{"class":488},[35,1147,548],{"class":321},[10,1149,1150],{},"The first rolling value needs twelve months of history, which is why the headcount started two years back: the report shows September 2025 to August 2026, each with a full year behind it.",[108,1152,117,1158,117,1161,117,1164,117,1168,117,1174,117,1179,117,1183,117,1187,117,1190,117,1194,117,1197,117,1200,117,1203,117,1207,117,1211,117,1216,117,1220,117,1224,117,1228,117,1232,117,1236,117,1240,117,1244,117,1248,117,1251,117,1255,117,1259,117,1266,117,1271,117,1274,117,1276,117,1281,117,1285,117,1290,117,1294,117,1299,117,1303,117,1308,117,1312,117,1317,117,1321,117,1326,117,1330,117,1335,117,1339,117,1344,117,1348,117,1353,117,1357,117,1360],{"viewBox":1153,"role":111,"ariaLabelledBy":1154,"xmlns":115,"style":1157},"4 0 748 252",[1155,1156],"hc-trend-t","hc-trend-d","width:100%;max-width:748px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[119,1159,1160],{"id":1155},"Month-end headcount",[123,1162,1163],{"id":1156},"A column chart of month-end headcount from September 2025 to August 2026, growing from 148 to 166. Over the same period the rolling twelve-month attrition rate moved between about 10 and 13 percent.",[127,1165],{"x":129,"y":130,"width":1166,"height":1167,"fill":133},"748","252",[37,1169],{"x1":1170,"y1":1171,"x2":1172,"y2":1171,"stroke":142,"style":1173},"53.6","214.0","736.0","stroke-width:1px",[156,1175,130],{"x":1176,"y":1177,"style":1178},"30.8","218.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle",[37,1180],{"x1":1170,"y1":1181,"x2":1172,"y2":1181,"stroke":142,"style":1182},"166.5","stroke-width:1px;stroke-dasharray:5 4",[156,1184,1186],{"x":1176,"y":1185,"style":1178},"170.5","50",[37,1188],{"x1":1170,"y1":1189,"x2":1172,"y2":1189,"stroke":142,"style":1182},"119.0",[156,1191,1193],{"x":1176,"y":1192,"style":1178},"123.0","100",[37,1195],{"x1":1170,"y1":1196,"x2":1172,"y2":1196,"stroke":142,"style":1182},"71.5",[156,1198,1199],{"x":1176,"y":159,"style":1178},"150",[37,1201],{"x1":1170,"y1":1202,"x2":1172,"y2":1202,"stroke":142,"style":1182},"24.0",[156,1204,1206],{"x":1176,"y":1205,"style":1178},"28.0","200",[37,1208],{"x1":1170,"y1":1171,"x2":1172,"y2":1171,"stroke":1209,"style":1210},"var(--muted,#5b6780)","stroke-width:1.5px",[156,1212,1215],{"x":1213,"y":1214,"style":169},"82.1","232.0","Sep",[156,1217,1219],{"x":1218,"y":1214,"style":169},"138.9","Oct",[156,1221,1223],{"x":1222,"y":1214,"style":169},"195.8","Nov",[156,1225,1227],{"x":1226,"y":1214,"style":169},"252.6","Dec",[156,1229,1231],{"x":1230,"y":1214,"style":169},"309.5","Jan",[156,1233,1235],{"x":1234,"y":1214,"style":169},"366.4","Feb",[156,1237,1239],{"x":1238,"y":1214,"style":169},"423.2","Mar",[156,1241,1243],{"x":1242,"y":1214,"style":169},"480.1","Apr",[156,1245,1247],{"x":1246,"y":1214,"style":169},"537.0","May",[156,1249,1250],{"x":272,"y":1214,"style":169},"Jun",[156,1252,1254],{"x":1253,"y":1214,"style":169},"650.7","Jul",[156,1256,1258],{"x":1257,"y":1214,"style":169},"707.6","Aug",[127,1260],{"x":1261,"y":1262,"width":1263,"height":1264,"rx":1265,"fill":189,"stroke":180,"style":1210},"64.1","73.4","36.0","140.6","3",[156,1267,1270],{"x":1213,"y":1268,"style":1269},"67.4","font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","148",[127,1272],{"x":1273,"y":1262,"width":1263,"height":1264,"rx":1265,"fill":189,"stroke":180,"style":1210},"120.9",[156,1275,1270],{"x":1218,"y":1268,"style":1269},[127,1277],{"x":1278,"y":1279,"width":1263,"height":1280,"rx":1265,"fill":189,"stroke":180,"style":1210},"177.8","68.7","145.3",[156,1282,1284],{"x":1222,"y":1283,"style":1269},"62.7","153",[127,1286],{"x":1287,"y":1288,"width":1263,"height":1289,"rx":1265,"fill":189,"stroke":180,"style":1210},"234.6","69.6","144.4",[156,1291,1293],{"x":1226,"y":1292,"style":1269},"63.6","152",[127,1295],{"x":1296,"y":1297,"width":1263,"height":1298,"rx":1265,"fill":189,"stroke":180,"style":1210},"291.5","67.7","146.3",[156,1300,1302],{"x":1230,"y":1301,"style":1269},"61.7","154",[127,1304],{"x":1305,"y":1306,"width":1263,"height":1307,"rx":1265,"fill":189,"stroke":180,"style":1210},"348.4","63.9","150.1",[156,1309,1311],{"x":1234,"y":1310,"style":1269},"57.9","158",[127,1313],{"x":1314,"y":1315,"width":1263,"height":1316,"rx":1265,"fill":189,"stroke":180,"style":1210},"405.2","60.1","153.9",[156,1318,1320],{"x":1238,"y":1319,"style":1269},"54.1","162",[127,1322],{"x":1323,"y":1324,"width":1263,"height":1325,"rx":1265,"fill":189,"stroke":180,"style":1210},"462.1","58.2","155.8",[156,1327,1329],{"x":1242,"y":1328,"style":1269},"52.2","164",[127,1331],{"x":1332,"y":1333,"width":1263,"height":1334,"rx":1265,"fill":189,"stroke":180,"style":1210},"519.0","61.0","153.0",[156,1336,1338],{"x":1246,"y":1337,"style":1269},"55.0","161",[127,1340],{"x":1341,"y":1342,"width":1263,"height":1343,"rx":1265,"fill":189,"stroke":180,"style":1210},"575.8","56.3","157.7",[156,1345,1347],{"x":272,"y":1346,"style":1269},"50.3","166",[127,1349],{"x":1350,"y":1351,"width":1263,"height":1352,"rx":1265,"fill":189,"stroke":180,"style":1210},"632.7","57.2","156.8",[156,1354,1356],{"x":1253,"y":1355,"style":1269},"51.2","165",[127,1358],{"x":1359,"y":1342,"width":1263,"height":1343,"rx":1265,"fill":189,"stroke":180,"style":1210},"689.6",[156,1361,1347],{"x":1257,"y":1346,"style":1269},[20,1363,1365],{"id":1364},"step-4-why-people-leave-and-how-soon","Step 4 — Why people leave, and how soon",[10,1367,1368],{},"The rate says how many; the breakdown says where to look. Split the last twelve months' leavers by reason, by department, and by tenure at exit. People who leave in their first year point at hiring or onboarding rather than pay or management:",[25,1370,1372],{"className":308,"code":1371,"language":310,"meta":30,"style":30},"window = staff[staff[\"LeaveDate\"].between(\"2025-09-01\", \"2026-08-31\")].copy()\nwindow[\"Tenure (years)\"] = (window[\"LeaveDate\"] - window[\"HireDate\"]).dt.days \u002F 365.25\nwindow[\"First year\"] = window[\"Tenure (years)\"] \u003C 1\n\nprint(window[\"LeaveReason\"].value_counts().to_string())\nprint(f\"left within their first year: {window['First year'].sum()} of {len(window)} \"\n      f\"({window['First year'].mean():.0%})\")\n",[32,1373,1374,1399,1433,1456,1460,1473,1516],{"__ignoreMap":30},[35,1375,1376,1379,1381,1384,1386,1389,1392,1394,1396],{"class":37,"line":38},[35,1377,1378],{"class":321},"window ",[35,1380,344],{"class":317},[35,1382,1383],{"class":321}," staff[staff[",[35,1385,370],{"class":45},[35,1387,1388],{"class":321},"].between(",[35,1390,1391],{"class":45},"\"2025-09-01\"",[35,1393,353],{"class":321},[35,1395,392],{"class":45},[35,1397,1398],{"class":321},")].copy()\n",[35,1400,1401,1404,1407,1409,1411,1414,1416,1418,1420,1423,1425,1428,1430],{"class":37,"line":331},[35,1402,1403],{"class":321},"window[",[35,1405,1406],{"class":45},"\"Tenure (years)\"",[35,1408,448],{"class":321},[35,1410,344],{"class":317},[35,1412,1413],{"class":321}," (window[",[35,1415,370],{"class":45},[35,1417,448],{"class":321},[35,1419,900],{"class":317},[35,1421,1422],{"class":321}," window[",[35,1424,365],{"class":45},[35,1426,1427],{"class":321},"]).dt.days ",[35,1429,735],{"class":317},[35,1431,1432],{"class":488}," 365.25\n",[35,1434,1435,1437,1440,1442,1444,1446,1448,1450,1453],{"class":37,"line":338},[35,1436,1403],{"class":321},[35,1438,1439],{"class":45},"\"First year\"",[35,1441,448],{"class":321},[35,1443,344],{"class":317},[35,1445,1422],{"class":321},[35,1447,1406],{"class":45},[35,1449,448],{"class":321},[35,1451,1452],{"class":317},"\u003C",[35,1454,1455],{"class":488}," 1\n",[35,1457,1458],{"class":37,"line":376},[35,1459,335],{"emptyLinePlaceholder":334},[35,1461,1462,1464,1467,1470],{"class":37,"line":412},[35,1463,540],{"class":488},[35,1465,1466],{"class":321},"(window[",[35,1468,1469],{"class":45},"\"LeaveReason\"",[35,1471,1472],{"class":321},"].value_counts().to_string())\n",[35,1474,1475,1477,1479,1482,1485,1489,1491,1494,1497,1500,1503,1505,1508,1511,1513],{"class":37,"line":417},[35,1476,540],{"class":488},[35,1478,960],{"class":321},[35,1480,1481],{"class":317},"f",[35,1483,1484],{"class":45},"\"left within their first year: ",[35,1486,1488],{"class":1487},"sSjpA","{",[35,1490,1403],{"class":321},[35,1492,1493],{"class":45},"'First year'",[35,1495,1496],{"class":321},"].sum()",[35,1498,1499],{"class":1487},"}",[35,1501,1502],{"class":45}," of ",[35,1504,1488],{"class":1487},[35,1506,1507],{"class":488},"len",[35,1509,1510],{"class":321},"(window)",[35,1512,1499],{"class":1487},[35,1514,1515],{"class":45}," \"\n",[35,1517,1518,1521,1524,1526,1528,1530,1533,1536,1538,1541],{"class":37,"line":435},[35,1519,1520],{"class":317},"      f",[35,1522,1523],{"class":45},"\"(",[35,1525,1488],{"class":1487},[35,1527,1403],{"class":321},[35,1529,1493],{"class":45},[35,1531,1532],{"class":321},"].mean()",[35,1534,1535],{"class":317},":.0%",[35,1537,1499],{"class":1487},[35,1539,1540],{"class":45},")\"",[35,1542,607],{"class":321},[10,1544,1545],{},"Department rates need their own denominators: leavers in the department over the department's average headcount. Reuse the headcount function on each department's rows:",[25,1547,1549],{"className":308,"code":1548,"language":310,"meta":30,"style":30},"def dept_rate(df):\n    ends = pd.date_range(\"2025-08-31\", \"2026-08-31\", freq=\"ME\")\n    hc = pd.Series([headcount_on(d, df) for d in ends])\n    avg = ((hc + hc.shift(1)) \u002F 2).iloc[1:].mean()\n    left = df[\"LeaveDate\"].between(\"2025-09-01\", \"2026-08-31\").sum()\n    vol = ((df[\"LeaveReason\"] == \"Voluntary\") & df[\"LeaveDate\"].between(\"2025-09-01\", \"2026-08-31\")).sum()\n    return pd.Series({\"Headcount now\": hc.iloc[-1], \"Leavers\": left,\n                      \"Attrition %\": left \u002F avg * 100, \"Voluntary %\": vol \u002F avg * 100})\n\nby_dept = staff.groupby(\"Department\")[staff.columns].apply(dept_rate).sort_values(\"Attrition %\", ascending=False)\nprint(by_dept.round(1).to_string())\n",[32,1550,1551,1561,1587,1606,1639,1662,1701,1726,1762,1766,1796],{"__ignoreMap":30},[35,1552,1553,1555,1558],{"class":37,"line":38},[35,1554,420],{"class":317},[35,1556,1557],{"class":423}," dept_rate",[35,1559,1560],{"class":321},"(df):\n",[35,1562,1563,1566,1568,1570,1573,1575,1577,1579,1581,1583,1585],{"class":37,"line":331},[35,1564,1565],{"class":321},"    ends ",[35,1567,344],{"class":317},[35,1569,384],{"class":321},[35,1571,1572],{"class":45},"\"2025-08-31\"",[35,1574,353],{"class":321},[35,1576,392],{"class":45},[35,1578,353],{"class":321},[35,1580,397],{"class":356},[35,1582,344],{"class":317},[35,1584,402],{"class":45},[35,1586,607],{"class":321},[35,1588,1589,1592,1594,1597,1599,1601,1603],{"class":37,"line":338},[35,1590,1591],{"class":321},"    hc ",[35,1593,344],{"class":317},[35,1595,1596],{"class":321}," pd.Series([headcount_on(d, df) ",[35,1598,511],{"class":317},[35,1600,514],{"class":321},[35,1602,517],{"class":317},[35,1604,1605],{"class":321}," ends])\n",[35,1607,1608,1611,1613,1616,1618,1621,1623,1626,1628,1631,1634,1636],{"class":37,"line":376},[35,1609,1610],{"class":321},"    avg ",[35,1612,344],{"class":317},[35,1614,1615],{"class":321}," ((hc ",[35,1617,723],{"class":317},[35,1619,1620],{"class":321}," hc.shift(",[35,1622,646],{"class":488},[35,1624,1625],{"class":321},")) ",[35,1627,735],{"class":317},[35,1629,1630],{"class":488}," 2",[35,1632,1633],{"class":321},").iloc[",[35,1635,646],{"class":488},[35,1637,1638],{"class":321},":].mean()\n",[35,1640,1641,1644,1646,1649,1651,1653,1655,1657,1659],{"class":37,"line":412},[35,1642,1643],{"class":321},"    left ",[35,1645,344],{"class":317},[35,1647,1648],{"class":321}," df[",[35,1650,370],{"class":45},[35,1652,1388],{"class":321},[35,1654,1391],{"class":45},[35,1656,353],{"class":321},[35,1658,392],{"class":45},[35,1660,1661],{"class":321},").sum()\n",[35,1663,1664,1667,1669,1672,1674,1676,1679,1682,1685,1687,1689,1691,1693,1695,1697,1699],{"class":37,"line":417},[35,1665,1666],{"class":321},"    vol ",[35,1668,344],{"class":317},[35,1670,1671],{"class":321}," ((df[",[35,1673,1469],{"class":45},[35,1675,448],{"class":321},[35,1677,1678],{"class":317},"==",[35,1680,1681],{"class":45}," \"Voluntary\"",[35,1683,1684],{"class":321},") ",[35,1686,457],{"class":317},[35,1688,1648],{"class":321},[35,1690,370],{"class":45},[35,1692,1388],{"class":321},[35,1694,1391],{"class":45},[35,1696,353],{"class":321},[35,1698,392],{"class":45},[35,1700,869],{"class":321},[35,1702,1703,1705,1708,1711,1714,1716,1718,1721,1723],{"class":37,"line":435},[35,1704,485],{"class":317},[35,1706,1707],{"class":321}," pd.Series({",[35,1709,1710],{"class":45},"\"Headcount now\"",[35,1712,1713],{"class":321},": hc.iloc[",[35,1715,900],{"class":317},[35,1717,646],{"class":488},[35,1719,1720],{"class":321},"], ",[35,1722,755],{"class":45},[35,1724,1725],{"class":321},": left,\n",[35,1727,1728,1731,1734,1736,1739,1741,1744,1746,1749,1752,1754,1756,1758,1760],{"class":37,"line":482},[35,1729,1730],{"class":45},"                      \"Attrition %\"",[35,1732,1733],{"class":321},": left ",[35,1735,735],{"class":317},[35,1737,1738],{"class":321}," avg ",[35,1740,768],{"class":317},[35,1742,1743],{"class":488}," 100",[35,1745,353],{"class":321},[35,1747,1748],{"class":45},"\"Voluntary %\"",[35,1750,1751],{"class":321},": vol ",[35,1753,735],{"class":317},[35,1755,1738],{"class":321},[35,1757,768],{"class":317},[35,1759,1743],{"class":488},[35,1761,699],{"class":321},[35,1763,1764],{"class":37,"line":495},[35,1765,335],{"emptyLinePlaceholder":334},[35,1767,1768,1771,1773,1776,1779,1782,1784,1786,1789,1791,1794],{"class":37,"line":500},[35,1769,1770],{"class":321},"by_dept ",[35,1772,344],{"class":317},[35,1774,1775],{"class":321}," staff.groupby(",[35,1777,1778],{"class":45},"\"Department\"",[35,1780,1781],{"class":321},")[staff.columns].apply(dept_rate).sort_values(",[35,1783,746],{"class":45},[35,1785,353],{"class":321},[35,1787,1788],{"class":356},"ascending",[35,1790,344],{"class":317},[35,1792,1793],{"class":488},"False",[35,1795,607],{"class":321},[35,1797,1798,1800,1803,1805],{"class":37,"line":537},[35,1799,540],{"class":488},[35,1801,1802],{"class":321},"(by_dept.round(",[35,1804,646],{"class":488},[35,1806,548],{"class":321},[10,1808,1809],{},"Support has the highest rate and every one of its leavers resigned; in Engineering and Finance, half of the leavers were involuntary exits. The counts matter as much as the rates: Support's 16% is three people out of about twenty, and one more or one fewer leaver would move it by five points. Report both, and be wary of ranking small teams on rates alone.",[108,1811,117,1817,117,1820,117,1823,117,1826,117,1830,117,1836,117,1840,117,1843,117,1847,117,1850,117,1854,117,1858,117,1862,117,1866,117,1869,117,1874,117,1876,117,1879,117,1881,117,1883,117,1887,117,1891,117,1893,117,1895,117,1898,117,1902,117,1904,117,1907,117,1909,117,1911,117,1913,117,1916,117,1918,117,1921,117,1924,117,1927,117,1929,117,1932,117,1934,117,1937,117,1939,117,1942,117,1944,117,1947,117,1949,117,1953,117,1955,117,1958,117,1960,117,1962,117,1964,117,1967,117,1969,117,1972,117,1975,117,1979,117,1981,117,1983,117,1985,117,1988,117,1990,117,1993,117,1995],{"viewBox":1812,"role":111,"ariaLabelledBy":1813,"xmlns":115,"style":1816},"165 0 430 200",[1814,1815],"hc-dept-t","hc-dept-d","width:100%;max-width:430px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[119,1818,1819],{"id":1814},"The department breakdown",[123,1821,1822],{"id":1815},"A table of departments with current headcount, leavers in the last twelve months, attrition and voluntary attrition. Support has the highest attrition at 16 percent, all voluntary, from three leavers; Finance has the lowest at 9.6 percent.",[127,1824],{"x":1356,"y":130,"width":1825,"height":1206,"fill":133},"430",[127,1827],{"x":1828,"y":137,"width":1829,"height":1205,"rx":130,"fill":189,"stroke":180,"style":1210},"181.3","91.9",[156,1831,1835],{"x":1832,"y":1833,"style":1834},"227.3","35.0","font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Department",[127,1837],{"x":1838,"y":137,"width":1839,"height":1205,"rx":130,"fill":189,"stroke":180,"style":1210},"273.2","86.0",[156,1841,78],{"x":1842,"y":1833,"style":1834},"316.2",[127,1844],{"x":1845,"y":137,"width":1846,"height":1205,"rx":130,"fill":189,"stroke":180,"style":1210},"359.2","69.1",[156,1848,233],{"x":1849,"y":1833,"style":1834},"393.8",[127,1851],{"x":1852,"y":137,"width":1853,"height":1205,"rx":130,"fill":189,"stroke":180,"style":1210},"428.3","70.5",[156,1855,1857],{"x":1856,"y":1833,"style":1834},"463.6","Attrition",[127,1859],{"x":1860,"y":137,"width":1861,"height":1205,"rx":130,"fill":189,"stroke":180,"style":1210},"498.9","79.8",[156,1863,1865],{"x":1864,"y":1833,"style":1834},"538.8","Voluntary",[127,1867],{"x":1828,"y":1868,"width":1829,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},"44.0",[156,1870,1873],{"x":1832,"y":1871,"style":1872},"63.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Support",[127,1875],{"x":1838,"y":1868,"width":1839,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1877,1878],{"x":1842,"y":1871,"style":1872},"20",[127,1880],{"x":1845,"y":1868,"width":1846,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1882,1265],{"x":1849,"y":1871,"style":1872},[127,1884],{"x":1852,"y":1868,"width":1853,"height":1205,"rx":130,"fill":1885,"stroke":1886,"style":1210},"#fce9e9","var(--danger,#dc2626)",[156,1888,1890],{"x":1856,"y":1871,"style":1889},"font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","16.0%",[127,1892],{"x":1860,"y":1868,"width":1861,"height":1205,"rx":130,"fill":1885,"stroke":1886,"style":1210},[156,1894,1890],{"x":1864,"y":1871,"style":1889},[127,1896],{"x":1828,"y":1897,"width":1829,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},"72.0",[156,1899,1901],{"x":1832,"y":1900,"style":1872},"91.0","Operations",[127,1903],{"x":1838,"y":1897,"width":1839,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1905,1906],{"x":1842,"y":1900,"style":1872},"21",[127,1908],{"x":1845,"y":1897,"width":1846,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1910,1265],{"x":1849,"y":1900,"style":1872},[127,1912],{"x":1852,"y":1897,"width":1853,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1914,1915],{"x":1856,"y":1900,"style":1872},"15.3%",[127,1917],{"x":1860,"y":1897,"width":1861,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1919,1920],{"x":1864,"y":1900,"style":1872},"10.2%",[127,1922],{"x":1828,"y":1923,"width":1829,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},"100.0",[156,1925,1926],{"x":1832,"y":1189,"style":1872},"Sales",[127,1928],{"x":1838,"y":1923,"width":1839,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1930,1931],{"x":1842,"y":1189,"style":1872},"44",[127,1933],{"x":1845,"y":1923,"width":1846,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1935,1936],{"x":1849,"y":1189,"style":1872},"5",[127,1938],{"x":1852,"y":1923,"width":1853,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1940,1941],{"x":1856,"y":1189,"style":1872},"12.4%",[127,1943],{"x":1860,"y":1923,"width":1861,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1945,1946],{"x":1864,"y":1189,"style":1872},"9.9%",[127,1948],{"x":1828,"y":139,"width":1829,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1950,1952],{"x":1832,"y":1951,"style":1872},"147.0","Engineering",[127,1954],{"x":1838,"y":139,"width":1839,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1956,1957],{"x":1842,"y":1951,"style":1872},"60",[127,1959],{"x":1845,"y":139,"width":1846,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1961,800],{"x":1849,"y":1951,"style":1872},[127,1963],{"x":1852,"y":139,"width":1853,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1965,1966],{"x":1856,"y":1951,"style":1872},"10.4%",[127,1968],{"x":1860,"y":139,"width":1861,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1970,1971],{"x":1864,"y":1951,"style":1872},"5.2%",[127,1973],{"x":1828,"y":1974,"width":1829,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},"156.0",[156,1976,1978],{"x":1832,"y":1977,"style":1872},"175.0","Finance",[127,1980],{"x":1838,"y":1974,"width":1839,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1982,1906],{"x":1842,"y":1977,"style":1872},[127,1984],{"x":1845,"y":1974,"width":1846,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1986,1987],{"x":1849,"y":1977,"style":1872},"2",[127,1989],{"x":1852,"y":1974,"width":1853,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1991,1992],{"x":1856,"y":1977,"style":1872},"9.6%",[127,1994],{"x":1860,"y":1974,"width":1861,"height":1205,"rx":130,"fill":133,"stroke":142,"style":1173},[156,1996,1997],{"x":1864,"y":1977,"style":1872},"4.8%",[20,1999,2001],{"id":2000},"step-5-write-the-report","Step 5 — Write the report",[10,2003,2004],{},"One sheet with the monthly table and chart, one with the department breakdown, and one listing the period's leavers for HR business partners to check. Percentages are written as fractions with a percent format, so Excel can chart and sort them:",[25,2006,2008],{"className":308,"code":2007,"language":310,"meta":30,"style":30},"out = report.join(last12[\"Attrition 12m %\"]).loc[\"2025-09\":]\nout.index = out.index.strftime(\"%b %Y\")\n\nwith pd.ExcelWriter(\"headcount_attrition_2026-08.xlsx\", engine=\"xlsxwriter\") as xw:\n    table = out.assign(**{c: out[c] \u002F 100 for c in [\"Attrition %\", \"Attrition 12m %\"]})\n    table.to_excel(xw, sheet_name=\"Monthly\", index_label=\"Month\")\n    by_dept.assign(**{c: by_dept[c] \u002F 100 for c in [\"Attrition %\", \"Voluntary %\"]}) \\\n        .to_excel(xw, sheet_name=\"By department\")\n    window.sort_values(\"LeaveDate\")[[\"EmployeeID\", \"Department\", \"HireDate\", \"LeaveDate\", \"LeaveReason\", \"Tenure (years)\"]] \\\n        .round({\"Tenure (years)\": 1}).to_excel(xw, sheet_name=\"Leavers\", index=False)\n\n    book, ws = xw.book, xw.sheets[\"Monthly\"]\n    pct = book.add_format({\"num_format\": \"0.0%\"})\n    one_dp = book.add_format({\"num_format\": \"0.0\"})\n    ws.set_column(\"A:A\", 11)\n    ws.set_column(\"B:E\", 9)\n    ws.set_column(\"F:F\", 13, one_dp)\n    ws.set_column(\"G:H\", 14, pct)\n    xw.sheets[\"By department\"].set_column(\"A:A\", 14)\n    xw.sheets[\"By department\"].set_column(\"B:C\", 13)\n    xw.sheets[\"By department\"].set_column(\"D:E\", 13, pct)\n\n    n = len(table)\n    chart = book.add_chart({\"type\": \"column\"})\n    chart.add_series({\"name\": \"Headcount\", \"categories\": [\"Monthly\", 1, 0, n, 0],\n                      \"values\": [\"Monthly\", 1, 4, n, 4]})\n    line = book.add_chart({\"type\": \"line\"})\n    line.add_series({\"name\": \"Rolling 12m attrition\", \"categories\": [\"Monthly\", 1, 0, n, 0],\n                     \"values\": [\"Monthly\", 1, 7, n, 7], \"y2_axis\": True})\n    chart.combine(line)\n    chart.set_y2_axis({\"num_format\": \"0%\"})\n    chart.set_title({\"name\": \"Headcount and rolling attrition\"})\n    ws.insert_chart(\"J2\", chart, {\"x_scale\": 1.4})\nprint(table[[\"Closing\", \"Attrition 12m %\"]].round(3).to_string())\n",[32,2009,2010,2031,2052,2056,2084,2124,2149,2180,2194,2230,2261,2265,2279,2299,2317,2332,2347,2363,2379,2398,2416,2434,2439,2453,2474,2514,2538,2557,2592,2627,2633,2648,2663,2685],{"__ignoreMap":30},[35,2011,2012,2015,2017,2020,2022,2025,2028],{"class":37,"line":38},[35,2013,2014],{"class":321},"out ",[35,2016,344],{"class":317},[35,2018,2019],{"class":321}," report.join(last12[",[35,2021,1097],{"class":45},[35,2023,2024],{"class":321},"]).loc[",[35,2026,2027],{"class":45},"\"2025-09\"",[35,2029,2030],{"class":321},":]\n",[35,2032,2033,2036,2038,2041,2044,2047,2050],{"class":37,"line":331},[35,2034,2035],{"class":321},"out.index ",[35,2037,344],{"class":317},[35,2039,2040],{"class":321}," out.index.strftime(",[35,2042,2043],{"class":45},"\"",[35,2045,2046],{"class":1487},"%b",[35,2048,2049],{"class":45}," %Y\"",[35,2051,607],{"class":321},[35,2053,2054],{"class":37,"line":338},[35,2055,335],{"emptyLinePlaceholder":334},[35,2057,2058,2061,2064,2067,2069,2072,2074,2077,2079,2081],{"class":37,"line":376},[35,2059,2060],{"class":317},"with",[35,2062,2063],{"class":321}," pd.ExcelWriter(",[35,2065,2066],{"class":45},"\"headcount_attrition_2026-08.xlsx\"",[35,2068,353],{"class":321},[35,2070,2071],{"class":356},"engine",[35,2073,344],{"class":317},[35,2075,2076],{"class":45},"\"xlsxwriter\"",[35,2078,1684],{"class":321},[35,2080,325],{"class":317},[35,2082,2083],{"class":321}," xw:\n",[35,2085,2086,2089,2091,2094,2097,2100,2102,2104,2107,2110,2112,2115,2117,2119,2121],{"class":37,"line":412},[35,2087,2088],{"class":321},"    table ",[35,2090,344],{"class":317},[35,2092,2093],{"class":321}," out.assign(",[35,2095,2096],{"class":317},"**",[35,2098,2099],{"class":321},"{c: out[c] ",[35,2101,735],{"class":317},[35,2103,1743],{"class":488},[35,2105,2106],{"class":317}," for",[35,2108,2109],{"class":321}," c ",[35,2111,517],{"class":317},[35,2113,2114],{"class":321}," [",[35,2116,746],{"class":45},[35,2118,353],{"class":321},[35,2120,1097],{"class":45},[35,2122,2123],{"class":321},"]})\n",[35,2125,2126,2129,2132,2134,2137,2139,2142,2144,2147],{"class":37,"line":417},[35,2127,2128],{"class":321},"    table.to_excel(xw, ",[35,2130,2131],{"class":356},"sheet_name",[35,2133,344],{"class":317},[35,2135,2136],{"class":45},"\"Monthly\"",[35,2138,353],{"class":321},[35,2140,2141],{"class":356},"index_label",[35,2143,344],{"class":317},[35,2145,2146],{"class":45},"\"Month\"",[35,2148,607],{"class":321},[35,2150,2151,2154,2156,2159,2161,2163,2165,2167,2169,2171,2173,2175,2177],{"class":37,"line":435},[35,2152,2153],{"class":321},"    by_dept.assign(",[35,2155,2096],{"class":317},[35,2157,2158],{"class":321},"{c: by_dept[c] ",[35,2160,735],{"class":317},[35,2162,1743],{"class":488},[35,2164,2106],{"class":317},[35,2166,2109],{"class":321},[35,2168,517],{"class":317},[35,2170,2114],{"class":321},[35,2172,746],{"class":45},[35,2174,353],{"class":321},[35,2176,1748],{"class":45},[35,2178,2179],{"class":321},"]}) \\\n",[35,2181,2182,2185,2187,2189,2192],{"class":37,"line":482},[35,2183,2184],{"class":321},"        .to_excel(xw, ",[35,2186,2131],{"class":356},[35,2188,344],{"class":317},[35,2190,2191],{"class":45},"\"By department\"",[35,2193,607],{"class":321},[35,2195,2196,2199,2201,2204,2207,2209,2211,2213,2215,2217,2219,2221,2223,2225,2227],{"class":37,"line":495},[35,2197,2198],{"class":321},"    window.sort_values(",[35,2200,370],{"class":45},[35,2202,2203],{"class":321},")[[",[35,2205,2206],{"class":45},"\"EmployeeID\"",[35,2208,353],{"class":321},[35,2210,1778],{"class":45},[35,2212,353],{"class":321},[35,2214,365],{"class":45},[35,2216,353],{"class":321},[35,2218,370],{"class":45},[35,2220,353],{"class":321},[35,2222,1469],{"class":45},[35,2224,353],{"class":321},[35,2226,1406],{"class":45},[35,2228,2229],{"class":321},"]] \\\n",[35,2231,2232,2235,2237,2240,2242,2245,2247,2249,2251,2253,2255,2257,2259],{"class":37,"line":500},[35,2233,2234],{"class":321},"        .round({",[35,2236,1406],{"class":45},[35,2238,2239],{"class":321},": ",[35,2241,646],{"class":488},[35,2243,2244],{"class":321},"}).to_excel(xw, ",[35,2246,2131],{"class":356},[35,2248,344],{"class":317},[35,2250,755],{"class":45},[35,2252,353],{"class":321},[35,2254,523],{"class":356},[35,2256,344],{"class":317},[35,2258,1793],{"class":488},[35,2260,607],{"class":321},[35,2262,2263],{"class":37,"line":537},[35,2264,335],{"emptyLinePlaceholder":334},[35,2266,2267,2270,2272,2275,2277],{"class":37,"line":702},[35,2268,2269],{"class":321},"    book, ws ",[35,2271,344],{"class":317},[35,2273,2274],{"class":321}," xw.book, xw.sheets[",[35,2276,2136],{"class":45},[35,2278,953],{"class":321},[35,2280,2281,2284,2286,2289,2292,2294,2297],{"class":37,"line":741},[35,2282,2283],{"class":321},"    pct ",[35,2285,344],{"class":317},[35,2287,2288],{"class":321}," book.add_format({",[35,2290,2291],{"class":45},"\"num_format\"",[35,2293,2239],{"class":321},[35,2295,2296],{"class":45},"\"0.0%\"",[35,2298,699],{"class":321},[35,2300,2301,2304,2306,2308,2310,2312,2315],{"class":37,"line":774},[35,2302,2303],{"class":321},"    one_dp ",[35,2305,344],{"class":317},[35,2307,2288],{"class":321},[35,2309,2291],{"class":45},[35,2311,2239],{"class":321},[35,2313,2314],{"class":45},"\"0.0\"",[35,2316,699],{"class":321},[35,2318,2319,2322,2325,2327,2330],{"class":37,"line":792},[35,2320,2321],{"class":321},"    ws.set_column(",[35,2323,2324],{"class":45},"\"A:A\"",[35,2326,353],{"class":321},[35,2328,2329],{"class":488},"11",[35,2331,607],{"class":321},[35,2333,2335,2337,2340,2342,2345],{"class":37,"line":2334},16,[35,2336,2321],{"class":321},[35,2338,2339],{"class":45},"\"B:E\"",[35,2341,353],{"class":321},[35,2343,2344],{"class":488},"9",[35,2346,607],{"class":321},[35,2348,2350,2352,2355,2357,2360],{"class":37,"line":2349},17,[35,2351,2321],{"class":321},[35,2353,2354],{"class":45},"\"F:F\"",[35,2356,353],{"class":321},[35,2358,2359],{"class":488},"13",[35,2361,2362],{"class":321},", one_dp)\n",[35,2364,2366,2368,2371,2373,2376],{"class":37,"line":2365},18,[35,2367,2321],{"class":321},[35,2369,2370],{"class":45},"\"G:H\"",[35,2372,353],{"class":321},[35,2374,2375],{"class":488},"14",[35,2377,2378],{"class":321},", pct)\n",[35,2380,2382,2385,2387,2390,2392,2394,2396],{"class":37,"line":2381},19,[35,2383,2384],{"class":321},"    xw.sheets[",[35,2386,2191],{"class":45},[35,2388,2389],{"class":321},"].set_column(",[35,2391,2324],{"class":45},[35,2393,353],{"class":321},[35,2395,2375],{"class":488},[35,2397,607],{"class":321},[35,2399,2401,2403,2405,2407,2410,2412,2414],{"class":37,"line":2400},20,[35,2402,2384],{"class":321},[35,2404,2191],{"class":45},[35,2406,2389],{"class":321},[35,2408,2409],{"class":45},"\"B:C\"",[35,2411,353],{"class":321},[35,2413,2359],{"class":488},[35,2415,607],{"class":321},[35,2417,2419,2421,2423,2425,2428,2430,2432],{"class":37,"line":2418},21,[35,2420,2384],{"class":321},[35,2422,2191],{"class":45},[35,2424,2389],{"class":321},[35,2426,2427],{"class":45},"\"D:E\"",[35,2429,353],{"class":321},[35,2431,2359],{"class":488},[35,2433,2378],{"class":321},[35,2435,2437],{"class":37,"line":2436},22,[35,2438,335],{"emptyLinePlaceholder":334},[35,2440,2442,2445,2447,2450],{"class":37,"line":2441},23,[35,2443,2444],{"class":321},"    n ",[35,2446,344],{"class":317},[35,2448,2449],{"class":488}," len",[35,2451,2452],{"class":321},"(table)\n",[35,2454,2456,2459,2461,2464,2467,2469,2472],{"class":37,"line":2455},24,[35,2457,2458],{"class":321},"    chart ",[35,2460,344],{"class":317},[35,2462,2463],{"class":321}," book.add_chart({",[35,2465,2466],{"class":45},"\"type\"",[35,2468,2239],{"class":321},[35,2470,2471],{"class":45},"\"column\"",[35,2473,699],{"class":321},[35,2475,2477,2480,2483,2485,2488,2490,2493,2496,2498,2500,2502,2504,2506,2509,2511],{"class":37,"line":2476},25,[35,2478,2479],{"class":321},"    chart.add_series({",[35,2481,2482],{"class":45},"\"name\"",[35,2484,2239],{"class":321},[35,2486,2487],{"class":45},"\"Headcount\"",[35,2489,353],{"class":321},[35,2491,2492],{"class":45},"\"categories\"",[35,2494,2495],{"class":321},": [",[35,2497,2136],{"class":45},[35,2499,353],{"class":321},[35,2501,646],{"class":488},[35,2503,353],{"class":321},[35,2505,130],{"class":488},[35,2507,2508],{"class":321},", n, ",[35,2510,130],{"class":488},[35,2512,2513],{"class":321},"],\n",[35,2515,2517,2520,2522,2524,2526,2528,2530,2532,2534,2536],{"class":37,"line":2516},26,[35,2518,2519],{"class":45},"                      \"values\"",[35,2521,2495],{"class":321},[35,2523,2136],{"class":45},[35,2525,353],{"class":321},[35,2527,646],{"class":488},[35,2529,353],{"class":321},[35,2531,129],{"class":488},[35,2533,2508],{"class":321},[35,2535,129],{"class":488},[35,2537,2123],{"class":321},[35,2539,2541,2544,2546,2548,2550,2552,2555],{"class":37,"line":2540},27,[35,2542,2543],{"class":321},"    line ",[35,2545,344],{"class":317},[35,2547,2463],{"class":321},[35,2549,2466],{"class":45},[35,2551,2239],{"class":321},[35,2553,2554],{"class":45},"\"line\"",[35,2556,699],{"class":321},[35,2558,2560,2563,2565,2567,2570,2572,2574,2576,2578,2580,2582,2584,2586,2588,2590],{"class":37,"line":2559},28,[35,2561,2562],{"class":321},"    line.add_series({",[35,2564,2482],{"class":45},[35,2566,2239],{"class":321},[35,2568,2569],{"class":45},"\"Rolling 12m attrition\"",[35,2571,353],{"class":321},[35,2573,2492],{"class":45},[35,2575,2495],{"class":321},[35,2577,2136],{"class":45},[35,2579,353],{"class":321},[35,2581,646],{"class":488},[35,2583,353],{"class":321},[35,2585,130],{"class":488},[35,2587,2508],{"class":321},[35,2589,130],{"class":488},[35,2591,2513],{"class":321},[35,2593,2595,2598,2600,2602,2604,2606,2608,2611,2613,2615,2617,2620,2622,2625],{"class":37,"line":2594},29,[35,2596,2597],{"class":45},"                     \"values\"",[35,2599,2495],{"class":321},[35,2601,2136],{"class":45},[35,2603,353],{"class":321},[35,2605,646],{"class":488},[35,2607,353],{"class":321},[35,2609,2610],{"class":488},"7",[35,2612,2508],{"class":321},[35,2614,2610],{"class":488},[35,2616,1720],{"class":321},[35,2618,2619],{"class":45},"\"y2_axis\"",[35,2621,2239],{"class":321},[35,2623,2624],{"class":488},"True",[35,2626,699],{"class":321},[35,2628,2630],{"class":37,"line":2629},30,[35,2631,2632],{"class":321},"    chart.combine(line)\n",[35,2634,2636,2639,2641,2643,2646],{"class":37,"line":2635},31,[35,2637,2638],{"class":321},"    chart.set_y2_axis({",[35,2640,2291],{"class":45},[35,2642,2239],{"class":321},[35,2644,2645],{"class":45},"\"0%\"",[35,2647,699],{"class":321},[35,2649,2651,2654,2656,2658,2661],{"class":37,"line":2650},32,[35,2652,2653],{"class":321},"    chart.set_title({",[35,2655,2482],{"class":45},[35,2657,2239],{"class":321},[35,2659,2660],{"class":45},"\"Headcount and rolling attrition\"",[35,2662,699],{"class":321},[35,2664,2666,2669,2672,2675,2678,2680,2683],{"class":37,"line":2665},33,[35,2667,2668],{"class":321},"    ws.insert_chart(",[35,2670,2671],{"class":45},"\"J2\"",[35,2673,2674],{"class":321},", chart, {",[35,2676,2677],{"class":45},"\"x_scale\"",[35,2679,2239],{"class":321},[35,2681,2682],{"class":488},"1.4",[35,2684,699],{"class":321},[35,2686,2688,2690,2693,2695,2697,2699,2702,2704],{"class":37,"line":2687},34,[35,2689,540],{"class":488},[35,2691,2692],{"class":321},"(table[[",[35,2694,729],{"class":45},[35,2696,353],{"class":321},[35,2698,1097],{"class":45},[35,2700,2701],{"class":321},"]].round(",[35,2703,1265],{"class":488},[35,2705,548],{"class":321},[10,2707,2708,2709,2713,2714,2717,2718,2721],{},"The column-and-line combination puts headcount on the left axis and the rolling rate on the right. ",[14,2710,2712],{"href":2711},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fadd-a-combo-chart-with-a-secondary-axis-in-openpyxl\u002F","Add a Combo Chart with a Secondary Axis in openpyxl"," covers the same technique with openpyxl; xlsxwriter's ",[32,2715,2716],{},"combine()"," with ",[32,2719,2720],{},"y2_axis"," does it in two calls.",[20,2723,2725],{"id":2724},"common-pitfalls","Common pitfalls",[70,2727,2728,2734,2740,2746,2752,2758],{},[73,2729,2730,2733],{},[76,2731,2732],{},"Counting headcount over a month instead of at a point."," \"Everyone employed at any time in March\" double-counts replacements; measure at the month end.",[73,2735,2736,2739],{},[76,2737,2738],{},"Dividing by closing headcount."," Use the average of opening and closing, or the rate moves with hiring as well as leaving.",[73,2741,2742,2745],{},[76,2743,2744],{},"Transfers as leavers."," An internal move changes department, not employment; check whether the export records transfers as a leave and a hire.",[73,2747,2748,2751],{},[76,2749,2750],{},"Leave date conventions."," Some systems store the last day worked, others the first day not employed. A one-day difference moves people across month ends.",[73,2753,2754,2757],{},[76,2755,2756],{},"Small teams."," A department of eight losing two people shows 25%; show the counts beside the rates.",[73,2759,2760,2763],{},[76,2761,2762],{},"Personal data."," The leavers sheet names individuals; restrict who receives it, or send the aggregate sheets only.",[20,2765,2767],{"id":2766},"conclusion","Conclusion",[10,2769,2770],{},"Define headcount as people active at each month end, count hires and leavers by the month of their dates, and divide leavers by average headcount for the monthly rate and by the twelve-month average for the rolling rate. Break the last year's leavers down by reason, department and tenure at exit, compute department rates with their own denominators, and write the monthly table, a combined headcount and attrition chart, and the leaver detail to Excel.",[20,2772,2774],{"id":2773},"frequently-asked-questions","Frequently asked questions",[10,2776,2777,2780],{},[76,2778,2779],{},"How do I calculate employee attrition rate?","\nDivide the number of leavers in the period by the average headcount over the period, usually the mean of opening and closing headcount for a month, or the mean of monthly averages for twelve months.",[10,2782,2783,2786],{},[76,2784,2785],{},"What is the difference between monthly and annual attrition?","\nMonthly attrition is leavers in one month over that month's average headcount. Annual or rolling 12-month attrition sums twelve months of leavers over the average headcount for those months; it is roughly twelve times a typical monthly rate and is the usual benchmark.",[10,2788,2789,2792],{},[76,2790,2791],{},"Should headcount include part-time staff?","\nHeadcount counts people, so yes. For cost or capacity, report full-time equivalents (FTE) separately by weighting each person by their contracted hours.",[10,2794,2795,2798],{},[76,2796,2797],{},"How do I count someone who leaves on the last day of the month?","\nPick a rule and document it. Counting them in that month's closing headcount and as a leaver in the same month is common; the reconciliation between opening, hires, leavers and closing then differs by exactly those people.",[20,2800,2802],{"id":2801},"related","Related",[70,2804,2805,2811,2818,2825,2832],{},[73,2806,2807,2808,2810],{},"Up one level: ",[14,2809,17],{"href":16}," — the rest of the finance and operations reports.",[73,2812,2813,2817],{},[14,2814,2816],{"href":2815},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fconsolidate-timesheets-from-many-workbooks\u002F","Consolidate Timesheets from Many Workbooks"," — another people report.",[73,2819,2820,2824],{},[14,2821,2823],{"href":2822},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fanonymise-personal-data-in-an-excel-file-with-python\u002F","Anonymise Personal Data in an Excel File with Python"," — before sharing the leaver list.",[73,2826,2827,2831],{},[14,2828,2830],{"href":2829},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-cohort-retention-table-from-excel-data\u002F","Build a Cohort Retention Table from Excel Data"," — retention by hire cohort.",[73,2833,2834,2836],{},[14,2835,2712],{"href":2711}," — the chart used here.",[2838,2839,2840],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":30,"searchDepth":331,"depth":331,"links":2842},[2843,2844,2845,2846,2847,2848,2849,2850,2851,2852,2853],{"id":22,"depth":331,"text":23},{"id":64,"depth":331,"text":65},{"id":301,"depth":331,"text":302},{"id":561,"depth":331,"text":562},{"id":1033,"depth":331,"text":1034},{"id":1364,"depth":331,"text":1365},{"id":2000,"depth":331,"text":2001},{"id":2724,"depth":331,"text":2725},{"id":2766,"depth":331,"text":2767},{"id":2773,"depth":331,"text":2774},{"id":2801,"depth":331,"text":2802},"2026-09-29","Turn an HR export of hire and leave dates into a monthly headcount and attrition report: point-in-time headcount at each month end, hires and leavers, attrition against average headcount, a rolling twelve-month rate, voluntary versus involuntary exits, first-year leavers, and a department breakdown in Excel with a chart.","md",[2858,2860,2862,2864],{"q":2779,"a":2859},"Divide the number of leavers in the period by the average headcount over the period, usually the mean of opening and closing headcount for a month, or the mean of monthly averages for twelve months.",{"q":2785,"a":2861},"Monthly attrition is leavers in one month over that month's average headcount. Annual or rolling 12-month attrition sums twelve months of leavers over the average headcount for those months; it is roughly twelve times a typical monthly rate and is the usual benchmark.",{"q":2791,"a":2863},"Headcount counts people, so yes. For cost or capacity, report full-time equivalents (FTE) separately by weighting each person by their contracted hours.",{"q":2797,"a":2865},"Pick a rule and document it. Counting them in that month's closing headcount and as a leaver in the same month is common; the reconciliation between opening, hires, leavers and closing then differs by exactly those people.","build a headcount and attrition report",null,{"breadcrumb":2869},[2870,2872,2875],{"name":2871,"item":735},"Home",{"name":2873,"item":2874},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":2876,"item":16},"Building Finance and Operations Reports in Excel with Python","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-headcount-and-attrition-report",{"title":2879,"description":2880},"Headcount and Attrition Report in Excel with Python (pandas)","Build an HR headcount and attrition report from hire and termination dates with pandas: month-end headcount, hires, leavers, monthly and rolling 12-month attrition rate, voluntary vs involuntary, tenure at exit, by department, Excel output.","build-a-headcount-and-attrition-report","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-headcount-and-attrition-report\u002Findex",[2884,2885],"report-layouts","pivots-and-summaries","how-to","G8DFxfQXZj6BB1emqamP5v5155B9b9wu2NDz4enF7mQ",[2889,2893],{"title":2890,"path":2891,"stem":2892,"children":-1},"Build a Foreign Exchange Revaluation Report","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-foreign-exchange-revaluation-report","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-foreign-exchange-revaluation-report\u002Findex",{"title":2894,"path":2895,"stem":2896,"children":-1},"Build a Lease Liability Schedule","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-lease-liability-schedule","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-lease-liability-schedule\u002Findex",[2898,2900],{"path":2899,"title":2876},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports",{"path":2901,"title":2873},"\u002Fautomating-reporting-workflows",[],[2904,2908,2911,2914,2917,2920],{"path":2905,"title":2906,"kind":2907},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards","Building Multi-Sheet Excel Dashboards","guide",{"path":2909,"title":2910,"kind":2886},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-data-dictionary-sheet-to-an-excel-report","Add a Data Dictionary Sheet to an Excel Report",{"path":2912,"title":2913,"kind":2886},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-kpi-summary-block-to-an-excel-dashboard","Add a KPI Summary Block to an Excel Dashboard",{"path":2915,"title":2916,"kind":2886},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-table-of-contents-sheet-with-hyperlinks-in-excel","Add a Table of Contents Sheet with Hyperlinks in Excel",{"path":2918,"title":2919,"kind":2886},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-slicer-style-filters-to-an-excel-dashboard","Add a Dropdown Filter That Drives an Excel Dashboard",{"path":2921,"title":2922,"kind":2886},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python","Add a Summary Sheet to an Excel Report with Python",1790972121570]