[{"data":1,"prerenderedAt":2448},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fround-timestamps-into-time-slots-with-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fround-timestamps-into-time-slots-with-pandas":2414,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fround-timestamps-into-time-slots-with-pandas":2423,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fround-timestamps-into-time-slots-with-pandas":2428,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fround-timestamps-into-time-slots-with-pandas":2429},{"id":4,"title":5,"body":6,"dateModified":2381,"datePublished":2381,"description":2382,"extension":2383,"faq":2384,"keyword":2393,"kind":2394,"meta":2395,"navigation":113,"path":2403,"seo":2404,"slug":2407,"stem":2408,"topics":2409,"type":2412,"__hash__":2413},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fround-timestamps-into-time-slots-with-pandas\u002Findex.md","Round Timestamps into Time Slots with pandas",{"type":7,"value":8,"toc":2367},"minimark",[9,42,47,75,81,85,286,326,485,489,611,614,618,843,858,1002,1006,1009,1272,1278,1519,1523,1530,1716,1734,1738,1749,2187,2200,2204,2225,2229,2274,2278,2299,2303,2312,2318,2324,2330,2334,2363],[10,11,12,13,17,18,17,21,24,25,17,28,31,32,35,36,41],"p",{},"Staffing questions are slot questions: how many calls arrive between 09:00 and 09:15, which hour of the week is busiest, how many orders land in each shift. The raw data has exact timestamps — ",[14,15,16],"code",{},"09:07:42",", ",[14,19,20],{},"09:14:59",[14,22,23],{},"09:15:00"," — and the report needs them grouped into slots of equal length. pandas rounds timestamps to any frequency with ",[14,26,27],{},"dt.floor",[14,29,30],{},"dt.ceil"," and ",[14,33,34],{},"dt.round",", and the rest is a group-by. Two details make the difference between a correct staffing table and a misleading one: slots with no events must appear as zeros, and slot boundaries must be handled consistently. This page covers both, plus heatmap tables and custom slots such as shifts. It belongs to ",[37,38,40],"a",{"href":39},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002F","Working with Dates and Times in Excel Data",".",[43,44,46],"h2",{"id":45},"prerequisites","Prerequisites",[48,49,54],"pre",{"className":50,"code":51,"language":52,"meta":53,"style":53},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,55,56],{"__ignoreMap":53},[57,58,61,65,69,72],"span",{"class":59,"line":60},"line",1,[57,62,64],{"class":63},"sMTad","pip",[57,66,68],{"class":67},"srMev"," install",[57,70,71],{"class":67}," pandas",[57,73,74],{"class":67}," openpyxl\n",[10,76,77,80],{},[14,78,79],{},"calls.xlsx"," has a few hundred support calls received between 08:00 and 18:00 over five weekdays, each with a duration in seconds. Three hand-placed calls sit on and around the 09:15 boundary.",[43,82,84],{"id":83},"step-1-floor-ceil-and-round","Step 1 — Floor, ceil and round",[48,86,90],{"className":87,"code":88,"language":89,"meta":53,"style":53},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ncalls = pd.read_excel(\"calls.xlsx\")\nsample = calls[calls[\"Received\"].dt.strftime(\"%Y-%m-%d %H:%M\").between(\"2026-09-28 09:07\", \"2026-09-28 09:15\")].copy()\nsample[\"floor 15min\"] = sample[\"Received\"].dt.floor(\"15min\")\nsample[\"ceil 15min\"] = sample[\"Received\"].dt.ceil(\"15min\")\nsample[\"round 15min\"] = sample[\"Received\"].dt.round(\"15min\")\nprint(sample[[\"Received\", \"floor 15min\", \"ceil 15min\", \"round 15min\"]].to_string(index=False))\n","python",[14,91,92,108,115,133,174,201,224,247],{"__ignoreMap":53},[57,93,94,98,102,105],{"class":59,"line":60},[57,95,97],{"class":96},"s-kum","import",[57,99,101],{"class":100},"skGVy"," pandas ",[57,103,104],{"class":96},"as",[57,106,107],{"class":100}," pd\n",[57,109,111],{"class":59,"line":110},2,[57,112,114],{"emptyLinePlaceholder":113},true,"\n",[57,116,118,121,124,127,130],{"class":59,"line":117},3,[57,119,120],{"class":100},"calls ",[57,122,123],{"class":96},"=",[57,125,126],{"class":100}," pd.read_excel(",[57,128,129],{"class":67},"\"calls.xlsx\"",[57,131,132],{"class":100},")\n",[57,134,136,139,141,144,147,150,153,157,160,163,166,168,171],{"class":59,"line":135},4,[57,137,138],{"class":100},"sample ",[57,140,123],{"class":96},[57,142,143],{"class":100}," calls[calls[",[57,145,146],{"class":67},"\"Received\"",[57,148,149],{"class":100},"].dt.strftime(",[57,151,152],{"class":67},"\"%Y-%m-",[57,154,156],{"class":155},"sSjpA","%d",[57,158,159],{"class":67}," %H:%M\"",[57,161,162],{"class":100},").between(",[57,164,165],{"class":67},"\"2026-09-28 09:07\"",[57,167,17],{"class":100},[57,169,170],{"class":67},"\"2026-09-28 09:15\"",[57,172,173],{"class":100},")].copy()\n",[57,175,177,180,183,186,188,191,193,196,199],{"class":59,"line":176},5,[57,178,179],{"class":100},"sample[",[57,181,182],{"class":67},"\"floor 15min\"",[57,184,185],{"class":100},"] ",[57,187,123],{"class":96},[57,189,190],{"class":100}," sample[",[57,192,146],{"class":67},[57,194,195],{"class":100},"].dt.floor(",[57,197,198],{"class":67},"\"15min\"",[57,200,132],{"class":100},[57,202,204,206,209,211,213,215,217,220,222],{"class":59,"line":203},6,[57,205,179],{"class":100},[57,207,208],{"class":67},"\"ceil 15min\"",[57,210,185],{"class":100},[57,212,123],{"class":96},[57,214,190],{"class":100},[57,216,146],{"class":67},[57,218,219],{"class":100},"].dt.ceil(",[57,221,198],{"class":67},[57,223,132],{"class":100},[57,225,227,229,232,234,236,238,240,243,245],{"class":59,"line":226},7,[57,228,179],{"class":100},[57,230,231],{"class":67},"\"round 15min\"",[57,233,185],{"class":100},[57,235,123],{"class":96},[57,237,190],{"class":100},[57,239,146],{"class":67},[57,241,242],{"class":100},"].dt.round(",[57,244,198],{"class":67},[57,246,132],{"class":100},[57,248,250,254,257,259,261,263,265,267,269,271,274,278,280,283],{"class":59,"line":249},8,[57,251,253],{"class":252},"sP0c6","print",[57,255,256],{"class":100},"(sample[[",[57,258,146],{"class":67},[57,260,17],{"class":100},[57,262,182],{"class":67},[57,264,17],{"class":100},[57,266,208],{"class":67},[57,268,17],{"class":100},[57,270,231],{"class":67},[57,272,273],{"class":100},"]].to_string(",[57,275,277],{"class":276},"sa561","index",[57,279,123],{"class":96},[57,281,282],{"class":252},"False",[57,284,285],{"class":100},"))\n",[10,287,288,289,292,293,297,298,31,300,302,303,305,306,309,310,313,314,17,317,17,320,17,323,41],{},"For time slots, ",[14,290,291],{},"floor"," is almost always the right choice: it labels each event with the ",[294,295,296],"em",{},"start"," of the slot it falls in, so ",[14,299,16],{},[14,301,20],{}," belong to the 09:00 slot and ",[14,304,23],{}," starts the next one. ",[14,307,308],{},"ceil"," labels with the slot's end — useful when a report says \"calls up to 09:15\" — and ",[14,311,312],{},"round"," sends each event to the nearest boundary, which splits a slot's events between two labels and is rarely what a staffing report means. The frequency string can be any multiple: ",[14,315,316],{},"\"5min\"",[14,318,319],{},"\"30min\"",[14,321,322],{},"\"h\"",[14,324,325],{},"\"4h\"",[327,328,336,337,336,341,336,345,336,353,336,360,336,370,336,381,336,388,336,393,336,397,336,401,336,407,336,414,336,419,336,423,336,425,336,427,336,430,336,435,336,438,336,440,336,442,336,444,336,449,336,455,336,459,336,462,336,464,336,466,336,469,336,474,336,478,336,481,336,483],"svg",{"viewBox":329,"role":330,"ariaLabelledBy":331,"xmlns":334,"style":335},"4 0 752 279","img",[332,333],"ts-floor-t","ts-floor-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  ",[338,339,340],"title",{"id":332},"Where three calls land with floor(\"15min\")",[342,343,344],"desc",{"id":333},"A timeline from 09:00 to 09:30. Calls at 09:07:42 and 09:14:59 are labelled with the 09:00 slot. A call at exactly 09:15:00 starts the 09:15 slot.",[346,347],"rect",{"x":348,"y":349,"width":350,"height":351,"fill":352},"4","0","752","279","#ffffff",[59,354],{"x1":355,"y1":356,"x2":357,"y2":356,"stroke":358,"style":359},"76.0","139.5","684.0","var(--muted,#5b6780)","stroke-width:2.5px",[346,361],{"x":362,"y":363,"width":364,"height":365,"rx":366,"fill":367,"stroke":368,"style":369},"20.0","16.0","136.0","91.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[371,372,374],"g",{"transform":373},"translate(76.0,29.0) scale(1.000)",[375,376],"path",{"d":377,"fill":378,"stroke":379,"style":380},"M12 3a9 9 0 1 0 .01 0z M12 7v5l3.5 2","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[382,383,387],"text",{"x":384,"y":385,"style":386},"88.0","74.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","09:00",[382,389,392],{"x":384,"y":390,"style":391},"93.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","slot start",[59,394],{"x1":384,"y1":395,"x2":384,"y2":356,"stroke":358,"style":396},"107.5","stroke-width:1.5px",[398,399],"circle",{"cx":384,"cy":356,"r":400,"fill":358,"stroke":352,"style":369},"7.0",[346,402],{"x":403,"y":404,"width":364,"height":365,"rx":366,"fill":405,"stroke":406,"style":369},"166.0","171.5","#f0f4ff","var(--brand,#5b5cf0)",[371,408,410],{"transform":409},"translate(222.0,184.5) scale(1.000)",[375,411],{"d":412,"fill":378,"stroke":413,"style":380},"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(--brand-strong,#4338ca)",[382,415,16],{"x":416,"y":417,"style":418},"234.0","230.0","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[382,420,422],{"x":416,"y":421,"style":391},"248.5","→ 09:00 slot",[59,424],{"x1":416,"y1":356,"x2":416,"y2":404,"stroke":358,"style":396},[398,426],{"cx":416,"cy":356,"r":400,"fill":406,"stroke":352,"style":369},[346,428],{"x":429,"y":363,"width":364,"height":365,"rx":366,"fill":405,"stroke":406,"style":369},"312.0",[371,431,433],{"transform":432},"translate(368.0,29.0) scale(1.000)",[375,434],{"d":412,"fill":378,"stroke":413,"style":380},[382,436,20],{"x":437,"y":385,"style":418},"380.0",[382,439,422],{"x":437,"y":390,"style":391},[59,441],{"x1":437,"y1":395,"x2":437,"y2":356,"stroke":358,"style":396},[398,443],{"cx":437,"cy":356,"r":400,"fill":406,"stroke":352,"style":369},[346,445],{"x":446,"y":404,"width":364,"height":365,"rx":366,"fill":447,"stroke":448,"style":369},"458.0","#d9f4f1","var(--teal,#0f9488)",[371,450,452],{"transform":451},"translate(514.0,184.5) scale(1.000)",[375,453],{"d":412,"fill":378,"stroke":454,"style":380},"var(--teal-ink,#0b6157)",[382,456,23],{"x":457,"y":417,"style":458},"526.0","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[382,460,461],{"x":457,"y":421,"style":391},"→ 09:15 slot",[59,463],{"x1":457,"y1":356,"x2":457,"y2":404,"stroke":358,"style":396},[398,465],{"cx":457,"cy":356,"r":400,"fill":448,"stroke":352,"style":369},[346,467],{"x":468,"y":363,"width":364,"height":365,"rx":366,"fill":367,"stroke":368,"style":369},"604.0",[371,470,472],{"transform":471},"translate(660.0,29.0) scale(1.000)",[375,473],{"d":377,"fill":378,"stroke":379,"style":380},[382,475,477],{"x":476,"y":385,"style":386},"672.0","09:30",[382,479,480],{"x":476,"y":390,"style":391},"next boundary",[59,482],{"x1":476,"y1":395,"x2":476,"y2":356,"stroke":358,"style":396},[398,484],{"cx":476,"cy":356,"r":400,"fill":358,"stroke":352,"style":369},[43,486,488],{"id":487},"step-2-count-and-sum-per-slot","Step 2 — Count and sum per slot",[48,490,492],{"className":87,"code":491,"language":89,"meta":53,"style":53},"calls[\"Slot\"] = calls[\"Received\"].dt.floor(\"15min\")\nper_slot = calls.groupby(\"Slot\").agg(Calls=(\"Received\", \"count\"), Minutes=(\"DurationSec\", lambda s: s.sum() \u002F 60))\nprint(per_slot.head(8).round(1))\nprint(\"slots with at least one call:\", len(per_slot))\n",[14,493,494,517,576,594],{"__ignoreMap":53},[57,495,496,499,502,504,506,509,511,513,515],{"class":59,"line":60},[57,497,498],{"class":100},"calls[",[57,500,501],{"class":67},"\"Slot\"",[57,503,185],{"class":100},[57,505,123],{"class":96},[57,507,508],{"class":100}," calls[",[57,510,146],{"class":67},[57,512,195],{"class":100},[57,514,198],{"class":67},[57,516,132],{"class":100},[57,518,519,522,524,527,529,532,535,537,540,542,544,547,550,553,555,557,560,562,565,568,571,574],{"class":59,"line":110},[57,520,521],{"class":100},"per_slot ",[57,523,123],{"class":96},[57,525,526],{"class":100}," calls.groupby(",[57,528,501],{"class":67},[57,530,531],{"class":100},").agg(",[57,533,534],{"class":276},"Calls",[57,536,123],{"class":96},[57,538,539],{"class":100},"(",[57,541,146],{"class":67},[57,543,17],{"class":100},[57,545,546],{"class":67},"\"count\"",[57,548,549],{"class":100},"), ",[57,551,552],{"class":276},"Minutes",[57,554,123],{"class":96},[57,556,539],{"class":100},[57,558,559],{"class":67},"\"DurationSec\"",[57,561,17],{"class":100},[57,563,564],{"class":96},"lambda",[57,566,567],{"class":100}," s: s.sum() ",[57,569,570],{"class":96},"\u002F",[57,572,573],{"class":252}," 60",[57,575,285],{"class":100},[57,577,578,580,583,586,589,592],{"class":59,"line":117},[57,579,253],{"class":252},[57,581,582],{"class":100},"(per_slot.head(",[57,584,585],{"class":252},"8",[57,587,588],{"class":100},").round(",[57,590,591],{"class":252},"1",[57,593,285],{"class":100},[57,595,596,598,600,603,605,608],{"class":59,"line":135},[57,597,253],{"class":252},[57,599,539],{"class":100},[57,601,602],{"class":67},"\"slots with at least one call:\"",[57,604,17],{"class":100},[57,606,607],{"class":252},"len",[57,609,610],{"class":100},"(per_slot))\n",[10,612,613],{},"The group-by gives call counts and handled minutes per 15-minute slot. But it only has rows for slots in which something happened: a quiet 15 minutes simply does not appear. A chart of this table draws a line straight over the gap, and an average per slot ignores the quiet ones and overstates the load. The fix is to build the full grid of slots first.",[43,615,617],{"id":616},"step-3-include-empty-slots","Step 3 — Include empty slots",[48,619,621],{"className":87,"code":620,"language":89,"meta":53,"style":53},"days = calls[\"Received\"].dt.normalize().unique()\ngrid = pd.DatetimeIndex([])\nfor day in days:\n    grid = grid.append(pd.date_range(day + pd.Timedelta(hours=8), day + pd.Timedelta(hours=17, minutes=45), freq=\"15min\"))\nfull = per_slot.reindex(grid, fill_value=0)\nfull.index.name = \"Slot\"\nprint(len(full), \"slots in opening hours;\", int((full[\"Calls\"] == 0).sum()), \"had no calls\")\nprint(\"average calls per slot:\", round(per_slot[\"Calls\"].mean(), 2), \"(busy slots only) vs\",\n      round(full[\"Calls\"].mean(), 2), \"(all opening-hour slots)\")\n",[14,622,623,637,647,661,719,738,748,789,821],{"__ignoreMap":53},[57,624,625,628,630,632,634],{"class":59,"line":60},[57,626,627],{"class":100},"days ",[57,629,123],{"class":96},[57,631,508],{"class":100},[57,633,146],{"class":67},[57,635,636],{"class":100},"].dt.normalize().unique()\n",[57,638,639,642,644],{"class":59,"line":110},[57,640,641],{"class":100},"grid ",[57,643,123],{"class":96},[57,645,646],{"class":100}," pd.DatetimeIndex([])\n",[57,648,649,652,655,658],{"class":59,"line":117},[57,650,651],{"class":96},"for",[57,653,654],{"class":100}," day ",[57,656,657],{"class":96},"in",[57,659,660],{"class":100}," days:\n",[57,662,663,666,668,671,674,677,680,682,684,687,689,691,693,695,698,700,703,705,708,710,713,715,717],{"class":59,"line":135},[57,664,665],{"class":100},"    grid ",[57,667,123],{"class":96},[57,669,670],{"class":100}," grid.append(pd.date_range(day ",[57,672,673],{"class":96},"+",[57,675,676],{"class":100}," pd.Timedelta(",[57,678,679],{"class":276},"hours",[57,681,123],{"class":96},[57,683,585],{"class":252},[57,685,686],{"class":100},"), day ",[57,688,673],{"class":96},[57,690,676],{"class":100},[57,692,679],{"class":276},[57,694,123],{"class":96},[57,696,697],{"class":252},"17",[57,699,17],{"class":100},[57,701,702],{"class":276},"minutes",[57,704,123],{"class":96},[57,706,707],{"class":252},"45",[57,709,549],{"class":100},[57,711,712],{"class":276},"freq",[57,714,123],{"class":96},[57,716,198],{"class":67},[57,718,285],{"class":100},[57,720,721,724,726,729,732,734,736],{"class":59,"line":176},[57,722,723],{"class":100},"full ",[57,725,123],{"class":96},[57,727,728],{"class":100}," per_slot.reindex(grid, ",[57,730,731],{"class":276},"fill_value",[57,733,123],{"class":96},[57,735,349],{"class":252},[57,737,132],{"class":100},[57,739,740,743,745],{"class":59,"line":203},[57,741,742],{"class":100},"full.index.name ",[57,744,123],{"class":96},[57,746,747],{"class":67}," \"Slot\"\n",[57,749,750,752,754,756,759,762,764,767,770,773,775,778,781,784,787],{"class":59,"line":226},[57,751,253],{"class":252},[57,753,539],{"class":100},[57,755,607],{"class":252},[57,757,758],{"class":100},"(full), ",[57,760,761],{"class":67},"\"slots in opening hours;\"",[57,763,17],{"class":100},[57,765,766],{"class":252},"int",[57,768,769],{"class":100},"((full[",[57,771,772],{"class":67},"\"Calls\"",[57,774,185],{"class":100},[57,776,777],{"class":96},"==",[57,779,780],{"class":252}," 0",[57,782,783],{"class":100},").sum()), ",[57,785,786],{"class":67},"\"had no calls\"",[57,788,132],{"class":100},[57,790,791,793,795,798,800,802,805,807,810,813,815,818],{"class":59,"line":249},[57,792,253],{"class":252},[57,794,539],{"class":100},[57,796,797],{"class":67},"\"average calls per slot:\"",[57,799,17],{"class":100},[57,801,312],{"class":252},[57,803,804],{"class":100},"(per_slot[",[57,806,772],{"class":67},[57,808,809],{"class":100},"].mean(), ",[57,811,812],{"class":252},"2",[57,814,549],{"class":100},[57,816,817],{"class":67},"\"(busy slots only) vs\"",[57,819,820],{"class":100},",\n",[57,822,824,827,830,832,834,836,838,841],{"class":59,"line":823},9,[57,825,826],{"class":252},"      round",[57,828,829],{"class":100},"(full[",[57,831,772],{"class":67},[57,833,809],{"class":100},[57,835,812],{"class":252},[57,837,549],{"class":100},[57,839,840],{"class":67},"\"(all opening-hour slots)\"",[57,842,132],{"class":100},[10,844,845,846,849,850,853,854,41],{},"The grid has every 15-minute slot from 08:00 to 17:45 on each day — 40 per day — and ",[14,847,848],{},"reindex(..., fill_value=0)"," adds the missing ones with zero calls. Building the grid per day keeps it to opening hours; a single ",[14,851,852],{},"date_range"," across the week would include the nights. The two averages show why this matters for staffing: the busy-slots-only figure is higher, because it never counts a quiet quarter-hour. Filling gaps in daily series follows the same pattern, as in ",[37,855,857],{"href":856},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffill-missing-dates-in-a-time-series-with-pandas\u002F","Fill Missing Dates in a Time Series from Excel with pandas",[327,859,336,865,336,868,336,871,336,875,336,881,336,886,336,890,336,893,336,896,336,899,336,902,336,906,336,909,336,912,336,914,336,920,336,924,336,928,336,932,336,935,336,939,336,943,336,947,336,951,336,954,336,960,336,964,336,969,336,974,336,979,336,982,336,986,336,989,336,994,336,997],{"viewBox":860,"role":330,"ariaLabelledBy":861,"xmlns":334,"style":864},"4 0 748 278",[862,863],"ts-slots-t","ts-slots-d","width:100%;max-width:748px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[338,866,867],{"id":862},"Calls per hour on Monday, with empty hours included",[342,869,870],{"id":863},"A column chart of calls per hour from 08:00 to 17:00 on Monday 28 September, after reindexing to the full grid so every opening hour appears, including any hour without calls.",[346,872],{"x":348,"y":349,"width":873,"height":874,"fill":352},"748","278",[59,876],{"x1":877,"y1":878,"x2":879,"y2":878,"stroke":368,"style":880},"39.5","214.0","736.0","stroke-width:1px",[382,882,349],{"x":883,"y":884,"style":885},"23.7","218.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle",[59,887],{"x1":877,"y1":888,"x2":879,"y2":888,"stroke":368,"style":889},"166.5","stroke-width:1px;stroke-dasharray:5 4",[382,891,812],{"x":883,"y":892,"style":885},"170.5",[59,894],{"x1":877,"y1":895,"x2":879,"y2":895,"stroke":368,"style":889},"119.0",[382,897,348],{"x":883,"y":898,"style":885},"123.0",[59,900],{"x1":877,"y1":901,"x2":879,"y2":901,"stroke":368,"style":889},"71.5",[382,903,905],{"x":883,"y":904,"style":885},"75.5","6",[59,907],{"x1":877,"y1":908,"x2":879,"y2":908,"stroke":368,"style":889},"24.0",[382,910,585],{"x":883,"y":911,"style":885},"28.0",[59,913],{"x1":877,"y1":878,"x2":879,"y2":878,"stroke":358,"style":396},[382,915,919],{"x":916,"y":917,"style":918},"74.3","232.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","08",[382,921,923],{"x":922,"y":917,"style":918},"143.9","09",[382,925,927],{"x":926,"y":917,"style":918},"213.6","10",[382,929,931],{"x":930,"y":917,"style":918},"283.2","11",[382,933,366],{"x":934,"y":917,"style":918},"352.9",[382,936,938],{"x":937,"y":917,"style":918},"422.6","13",[382,940,942],{"x":941,"y":917,"style":918},"492.2","14",[382,944,946],{"x":945,"y":917,"style":918},"561.9","15",[382,948,950],{"x":949,"y":917,"style":918},"631.5","16",[382,952,697],{"x":953,"y":917,"style":918},"701.2",[346,955],{"x":956,"y":895,"width":957,"height":958,"rx":959,"fill":405,"stroke":406,"style":396},"56.3","36.0","95.0","3",[346,961],{"x":962,"y":908,"width":957,"height":963,"rx":959,"fill":405,"stroke":406,"style":396},"125.9","190.0",[346,965],{"x":966,"y":967,"width":957,"height":968,"rx":959,"fill":405,"stroke":406,"style":396},"195.6","142.8","71.2",[346,970],{"x":971,"y":972,"width":957,"height":973,"rx":959,"fill":405,"stroke":406,"style":396},"265.2","47.8","166.2",[346,975],{"x":976,"y":977,"width":957,"height":978,"rx":959,"fill":405,"stroke":406,"style":396},"334.9","95.2","118.8",[346,980],{"x":981,"y":967,"width":957,"height":968,"rx":959,"fill":405,"stroke":406,"style":396},"404.6",[346,983],{"x":984,"y":901,"width":957,"height":985,"rx":959,"fill":405,"stroke":406,"style":396},"474.2","142.5",[346,987],{"x":988,"y":908,"width":957,"height":963,"rx":959,"fill":405,"stroke":406,"style":396},"543.9",[346,990],{"x":991,"y":992,"width":957,"height":993,"rx":959,"fill":405,"stroke":406,"style":396},"613.5","190.2","23.8",[346,995],{"x":996,"y":977,"width":957,"height":978,"rx":959,"fill":405,"stroke":406,"style":396},"683.2",[382,998,1001],{"x":437,"y":999,"style":1000},"258.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Monday's column of the heatmap table in Step 4; the 16:00 hour had a single call.",[43,1003,1005],{"id":1004},"step-4-a-weekday-by-hour-heatmap-table","Step 4 — A weekday-by-hour heatmap table",[10,1007,1008],{},"The classic staffing view is a grid of weekdays against hours. Floor to the hour, then pivot:",[48,1010,1012],{"className":87,"code":1011,"language":89,"meta":53,"style":53},"calls[\"Weekday\"] = calls[\"Received\"].dt.day_name().str[:3]\ncalls[\"Hour\"] = calls[\"Received\"].dt.hour\nheat = calls.pivot_table(index=\"Hour\", columns=\"Weekday\", values=\"Received\", aggfunc=\"count\", fill_value=0)\nheat = heat.reindex(index=range(8, 18), columns=[\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\"], fill_value=0)\nprint(heat)\nwith pd.ExcelWriter(\"call_heatmap.xlsx\", engine=\"xlsxwriter\") as xw:\n    heat.to_excel(xw, sheet_name=\"Heatmap\")\n    xw.sheets[\"Heatmap\"].conditional_format(1, 1, len(heat), len(heat.columns),\n                                            {\"type\": \"3_color_scale\"})\n",[14,1013,1014,1037,1055,1108,1176,1183,1212,1227,1255],{"__ignoreMap":53},[57,1015,1016,1018,1021,1023,1025,1027,1029,1032,1034],{"class":59,"line":60},[57,1017,498],{"class":100},[57,1019,1020],{"class":67},"\"Weekday\"",[57,1022,185],{"class":100},[57,1024,123],{"class":96},[57,1026,508],{"class":100},[57,1028,146],{"class":67},[57,1030,1031],{"class":100},"].dt.day_name().str[:",[57,1033,959],{"class":252},[57,1035,1036],{"class":100},"]\n",[57,1038,1039,1041,1044,1046,1048,1050,1052],{"class":59,"line":110},[57,1040,498],{"class":100},[57,1042,1043],{"class":67},"\"Hour\"",[57,1045,185],{"class":100},[57,1047,123],{"class":96},[57,1049,508],{"class":100},[57,1051,146],{"class":67},[57,1053,1054],{"class":100},"].dt.hour\n",[57,1056,1057,1060,1062,1065,1067,1069,1071,1073,1076,1078,1080,1082,1085,1087,1089,1091,1094,1096,1098,1100,1102,1104,1106],{"class":59,"line":117},[57,1058,1059],{"class":100},"heat ",[57,1061,123],{"class":96},[57,1063,1064],{"class":100}," calls.pivot_table(",[57,1066,277],{"class":276},[57,1068,123],{"class":96},[57,1070,1043],{"class":67},[57,1072,17],{"class":100},[57,1074,1075],{"class":276},"columns",[57,1077,123],{"class":96},[57,1079,1020],{"class":67},[57,1081,17],{"class":100},[57,1083,1084],{"class":276},"values",[57,1086,123],{"class":96},[57,1088,146],{"class":67},[57,1090,17],{"class":100},[57,1092,1093],{"class":276},"aggfunc",[57,1095,123],{"class":96},[57,1097,546],{"class":67},[57,1099,17],{"class":100},[57,1101,731],{"class":276},[57,1103,123],{"class":96},[57,1105,349],{"class":252},[57,1107,132],{"class":100},[57,1109,1110,1112,1114,1117,1119,1121,1124,1126,1128,1130,1133,1135,1137,1139,1142,1145,1147,1150,1152,1155,1157,1160,1162,1165,1168,1170,1172,1174],{"class":59,"line":135},[57,1111,1059],{"class":100},[57,1113,123],{"class":96},[57,1115,1116],{"class":100}," heat.reindex(",[57,1118,277],{"class":276},[57,1120,123],{"class":96},[57,1122,1123],{"class":252},"range",[57,1125,539],{"class":100},[57,1127,585],{"class":252},[57,1129,17],{"class":100},[57,1131,1132],{"class":252},"18",[57,1134,549],{"class":100},[57,1136,1075],{"class":276},[57,1138,123],{"class":96},[57,1140,1141],{"class":100},"[",[57,1143,1144],{"class":67},"\"Mon\"",[57,1146,17],{"class":100},[57,1148,1149],{"class":67},"\"Tue\"",[57,1151,17],{"class":100},[57,1153,1154],{"class":67},"\"Wed\"",[57,1156,17],{"class":100},[57,1158,1159],{"class":67},"\"Thu\"",[57,1161,17],{"class":100},[57,1163,1164],{"class":67},"\"Fri\"",[57,1166,1167],{"class":100},"], ",[57,1169,731],{"class":276},[57,1171,123],{"class":96},[57,1173,349],{"class":252},[57,1175,132],{"class":100},[57,1177,1178,1180],{"class":59,"line":176},[57,1179,253],{"class":252},[57,1181,1182],{"class":100},"(heat)\n",[57,1184,1185,1188,1191,1194,1196,1199,1201,1204,1207,1209],{"class":59,"line":203},[57,1186,1187],{"class":96},"with",[57,1189,1190],{"class":100}," pd.ExcelWriter(",[57,1192,1193],{"class":67},"\"call_heatmap.xlsx\"",[57,1195,17],{"class":100},[57,1197,1198],{"class":276},"engine",[57,1200,123],{"class":96},[57,1202,1203],{"class":67},"\"xlsxwriter\"",[57,1205,1206],{"class":100},") ",[57,1208,104],{"class":96},[57,1210,1211],{"class":100}," xw:\n",[57,1213,1214,1217,1220,1222,1225],{"class":59,"line":226},[57,1215,1216],{"class":100},"    heat.to_excel(xw, ",[57,1218,1219],{"class":276},"sheet_name",[57,1221,123],{"class":96},[57,1223,1224],{"class":67},"\"Heatmap\"",[57,1226,132],{"class":100},[57,1228,1229,1232,1234,1237,1239,1241,1243,1245,1247,1250,1252],{"class":59,"line":249},[57,1230,1231],{"class":100},"    xw.sheets[",[57,1233,1224],{"class":67},[57,1235,1236],{"class":100},"].conditional_format(",[57,1238,591],{"class":252},[57,1240,17],{"class":100},[57,1242,591],{"class":252},[57,1244,17],{"class":100},[57,1246,607],{"class":252},[57,1248,1249],{"class":100},"(heat), ",[57,1251,607],{"class":252},[57,1253,1254],{"class":100},"(heat.columns),\n",[57,1256,1257,1260,1263,1266,1269],{"class":59,"line":823},[57,1258,1259],{"class":100},"                                            {",[57,1261,1262],{"class":67},"\"type\"",[57,1264,1265],{"class":100},": ",[57,1267,1268],{"class":67},"\"3_color_scale\"",[57,1270,1271],{"class":100},"})\n",[10,1273,1274,1277],{},[14,1275,1276],{},"reindex"," on both axes guarantees all opening hours and all five weekdays appear in a fixed order, even if one had no calls.",[327,1279,336,1285,336,1288,336,1291,336,1296,336,1300,336,1304,336,1310,336,1314,336,1318,336,1322,336,1326,336,1330,336,1334,336,1338,336,1342,336,1346,336,1350,336,1353,336,1357,336,1359,336,1363,336,1365,336,1368,336,1370,336,1373,336,1375,336,1378,336,1380,336,1383,336,1385,336,1388,336,1391,336,1394,336,1396,336,1399,336,1401,336,1403,336,1405,336,1408,336,1410,336,1412,336,1414,336,1416,336,1418,336,1420,336,1423,336,1426,336,1428,336,1430,336,1432,336,1434,336,1436,336,1438,336,1440,336,1442,336,1446,336,1449,336,1451,336,1454,336,1457,336,1460,336,1462,336,1464,336,1466,336,1469,336,1471,336,1473,336,1475,336,1477,336,1481,336,1484,336,1486,336,1489,336,1492,336,1495,336,1497,336,1499,336,1501,336,1503,336,1505,336,1507,336,1509,336,1511,336,1513,336,1515,336,1517],{"viewBox":1280,"role":330,"ariaLabelledBy":1281,"xmlns":334,"style":1284},"204 0 351 196",[1282,1283],"ts-heat-t","ts-heat-d","width:100%;max-width:351px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[338,1286,1287],{"id":1282},"Part of the weekday-by-hour table",[342,1289,1290],{"id":1283},"Rows for 09:00, 15:00, 16:00 and 17:00 against Monday to Friday. The busiest cells are Thursday at 15:00 with 10 calls and Thursday and Friday afternoons; Monday at 16:00 has a single call.",[346,1292],{"x":1293,"y":349,"width":1294,"height":1295,"fill":352},"204","351","196",[346,1297],{"x":1298,"y":363,"width":1299,"height":908,"rx":349,"fill":367,"stroke":368,"style":880},"220.7","32.0",[346,1301],{"x":1302,"y":363,"width":1303,"height":908,"rx":349,"fill":367,"stroke":368,"style":880},"252.7","51.6",[382,1305,1309],{"x":1306,"y":1307,"style":1308},"278.5","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[346,1311],{"x":1312,"y":363,"width":1313,"height":908,"rx":349,"fill":367,"stroke":368,"style":880},"304.3","49.0",[382,1315,1317],{"x":1316,"y":1307,"style":1308},"328.8","B",[346,1319],{"x":1320,"y":363,"width":1321,"height":908,"rx":349,"fill":367,"stroke":368,"style":880},"353.3","45.8",[382,1323,1325],{"x":1324,"y":1307,"style":1308},"376.2","C",[346,1327],{"x":1328,"y":363,"width":1329,"height":908,"rx":349,"fill":367,"stroke":368,"style":880},"399.1","50.1",[382,1331,1333],{"x":1332,"y":1307,"style":1308},"424.2","D",[346,1335],{"x":1336,"y":363,"width":1337,"height":908,"rx":349,"fill":367,"stroke":368,"style":880},"449.2","46.1",[382,1339,1341],{"x":1340,"y":1307,"style":1308},"472.3","E",[346,1343],{"x":1344,"y":363,"width":1345,"height":908,"rx":349,"fill":367,"stroke":368,"style":880},"495.3","44.0",[382,1347,1349],{"x":1348,"y":1307,"style":1308},"517.3","F",[346,1351],{"x":1298,"y":1352,"width":1299,"height":911,"rx":349,"fill":367,"stroke":368,"style":880},"40.0",[382,1354,591],{"x":1355,"y":1356,"style":1308},"236.7","59.0",[346,1358],{"x":1302,"y":1352,"width":1303,"height":911,"rx":349,"fill":405,"stroke":406,"style":396},[382,1360,1362],{"x":1306,"y":1356,"style":1361},"font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Hour",[346,1364],{"x":1312,"y":1352,"width":1313,"height":911,"rx":349,"fill":405,"stroke":406,"style":396},[382,1366,1367],{"x":1316,"y":1356,"style":1361},"Mon",[346,1369],{"x":1320,"y":1352,"width":1321,"height":911,"rx":349,"fill":405,"stroke":406,"style":396},[382,1371,1372],{"x":1324,"y":1356,"style":1361},"Tue",[346,1374],{"x":1328,"y":1352,"width":1329,"height":911,"rx":349,"fill":405,"stroke":406,"style":396},[382,1376,1377],{"x":1332,"y":1356,"style":1361},"Wed",[346,1379],{"x":1336,"y":1352,"width":1337,"height":911,"rx":349,"fill":405,"stroke":406,"style":396},[382,1381,1382],{"x":1340,"y":1356,"style":1361},"Thu",[346,1384],{"x":1344,"y":1352,"width":1345,"height":911,"rx":349,"fill":405,"stroke":406,"style":396},[382,1386,1387],{"x":1348,"y":1356,"style":1361},"Fri",[346,1389],{"x":1298,"y":1390,"width":1299,"height":911,"rx":349,"fill":367,"stroke":368,"style":880},"68.0",[382,1392,812],{"x":1355,"y":1393,"style":1308},"87.0",[346,1395],{"x":1302,"y":1390,"width":1303,"height":911,"rx":349,"fill":367,"stroke":368,"style":396},[382,1397,923],{"x":1306,"y":1393,"style":1398},"font-size:11px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[346,1400],{"x":1312,"y":1390,"width":1313,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1402,585],{"x":1316,"y":1393,"style":391},[346,1404],{"x":1320,"y":1390,"width":1321,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1406,1407],{"x":1324,"y":1393,"style":391},"7",[346,1409],{"x":1328,"y":1390,"width":1329,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1411,1407],{"x":1332,"y":1393,"style":391},[346,1413],{"x":1336,"y":1390,"width":1337,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1415,905],{"x":1340,"y":1393,"style":391},[346,1417],{"x":1344,"y":1390,"width":1345,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1419,959],{"x":1348,"y":1393,"style":391},[346,1421],{"x":1298,"y":1422,"width":1299,"height":911,"rx":349,"fill":367,"stroke":368,"style":880},"96.0",[382,1424,959],{"x":1355,"y":1425,"style":1308},"115.0",[346,1427],{"x":1302,"y":1422,"width":1303,"height":911,"rx":349,"fill":367,"stroke":368,"style":396},[382,1429,946],{"x":1306,"y":1425,"style":1398},[346,1431],{"x":1312,"y":1422,"width":1313,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1433,585],{"x":1316,"y":1425,"style":391},[346,1435],{"x":1320,"y":1422,"width":1321,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1437,905],{"x":1324,"y":1425,"style":391},[346,1439],{"x":1328,"y":1422,"width":1329,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1441,348],{"x":1332,"y":1425,"style":391},[346,1443],{"x":1336,"y":1422,"width":1337,"height":911,"rx":349,"fill":1444,"stroke":1445,"style":396},"#fce9e9","var(--danger,#dc2626)",[382,1447,927],{"x":1340,"y":1425,"style":1448},"font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle",[346,1450],{"x":1344,"y":1422,"width":1345,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1452,1453],{"x":1348,"y":1425,"style":391},"5",[346,1455],{"x":1298,"y":1456,"width":1299,"height":911,"rx":349,"fill":367,"stroke":368,"style":880},"124.0",[382,1458,348],{"x":1355,"y":1459,"style":1308},"143.0",[346,1461],{"x":1302,"y":1456,"width":1303,"height":911,"rx":349,"fill":367,"stroke":368,"style":396},[382,1463,950],{"x":1306,"y":1459,"style":1398},[346,1465],{"x":1312,"y":1456,"width":1313,"height":911,"rx":349,"fill":447,"stroke":448,"style":396},[382,1467,591],{"x":1316,"y":1459,"style":1468},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[346,1470],{"x":1320,"y":1456,"width":1321,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1472,959],{"x":1324,"y":1459,"style":391},[346,1474],{"x":1328,"y":1456,"width":1329,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1476,959],{"x":1332,"y":1459,"style":391},[346,1478],{"x":1336,"y":1456,"width":1337,"height":911,"rx":349,"fill":1479,"stroke":1480,"style":396},"#fdefd8","var(--gold,#b4740a)",[382,1482,585],{"x":1340,"y":1459,"style":1483},"font-size:11px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[346,1485],{"x":1344,"y":1456,"width":1345,"height":911,"rx":349,"fill":1479,"stroke":1480,"style":396},[382,1487,1488],{"x":1348,"y":1459,"style":1483},"9",[346,1490],{"x":1298,"y":1491,"width":1299,"height":911,"rx":349,"fill":367,"stroke":368,"style":880},"152.0",[382,1493,1453],{"x":1355,"y":1494,"style":1308},"171.0",[346,1496],{"x":1302,"y":1491,"width":1303,"height":911,"rx":349,"fill":367,"stroke":368,"style":396},[382,1498,697],{"x":1306,"y":1494,"style":1398},[346,1500],{"x":1312,"y":1491,"width":1313,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1502,1453],{"x":1316,"y":1494,"style":391},[346,1504],{"x":1320,"y":1491,"width":1321,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1506,1407],{"x":1324,"y":1494,"style":391},[346,1508],{"x":1328,"y":1491,"width":1329,"height":911,"rx":349,"fill":1479,"stroke":1480,"style":396},[382,1510,1488],{"x":1332,"y":1494,"style":1483},[346,1512],{"x":1336,"y":1491,"width":1337,"height":911,"rx":349,"fill":352,"stroke":368,"style":880},[382,1514,348],{"x":1340,"y":1494,"style":391},[346,1516],{"x":1344,"y":1491,"width":1345,"height":911,"rx":349,"fill":1479,"stroke":1480,"style":396},[382,1518,1488],{"x":1348,"y":1494,"style":1483},[43,1520,1522],{"id":1521},"step-5-custom-slots-shifts-and-uneven-buckets","Step 5 — Custom slots: shifts and uneven buckets",[10,1524,1525,1526,1529],{},"Not every slot has a fixed length. Shifts — early 08:00–11:00, midday 11:00–14:00, late 14:00–18:00 — are uneven, and ",[14,1527,1528],{},"pd.cut"," on the time of day assigns them:",[48,1531,1533],{"className":87,"code":1532,"language":89,"meta":53,"style":53},"minutes = calls[\"Received\"].dt.hour * 60 + calls[\"Received\"].dt.minute\ncalls[\"Shift\"] = pd.cut(minutes, bins=[8 * 60, 11 * 60, 14 * 60, 18 * 60],\n                        labels=[\"Early\", \"Midday\", \"Late\"], right=False)\nprint(calls.groupby(\"Shift\", observed=False).agg(Calls=(\"Received\", \"count\"),\n                                                  AvgMinutes=(\"DurationSec\", lambda s: s.mean() \u002F 60)).round(1))\n",[14,1534,1535,1564,1619,1652,1687],{"__ignoreMap":53},[57,1536,1537,1540,1542,1544,1546,1549,1552,1554,1557,1559,1561],{"class":59,"line":60},[57,1538,1539],{"class":100},"minutes ",[57,1541,123],{"class":96},[57,1543,508],{"class":100},[57,1545,146],{"class":67},[57,1547,1548],{"class":100},"].dt.hour ",[57,1550,1551],{"class":96},"*",[57,1553,573],{"class":252},[57,1555,1556],{"class":96}," +",[57,1558,508],{"class":100},[57,1560,146],{"class":67},[57,1562,1563],{"class":100},"].dt.minute\n",[57,1565,1566,1568,1571,1573,1575,1578,1581,1583,1585,1587,1590,1592,1594,1596,1598,1600,1602,1604,1606,1608,1610,1612,1614,1616],{"class":59,"line":110},[57,1567,498],{"class":100},[57,1569,1570],{"class":67},"\"Shift\"",[57,1572,185],{"class":100},[57,1574,123],{"class":96},[57,1576,1577],{"class":100}," pd.cut(minutes, ",[57,1579,1580],{"class":276},"bins",[57,1582,123],{"class":96},[57,1584,1141],{"class":100},[57,1586,585],{"class":252},[57,1588,1589],{"class":96}," *",[57,1591,573],{"class":252},[57,1593,17],{"class":100},[57,1595,931],{"class":252},[57,1597,1589],{"class":96},[57,1599,573],{"class":252},[57,1601,17],{"class":100},[57,1603,942],{"class":252},[57,1605,1589],{"class":96},[57,1607,573],{"class":252},[57,1609,17],{"class":100},[57,1611,1132],{"class":252},[57,1613,1589],{"class":96},[57,1615,573],{"class":252},[57,1617,1618],{"class":100},"],\n",[57,1620,1621,1624,1626,1628,1631,1633,1636,1638,1641,1643,1646,1648,1650],{"class":59,"line":117},[57,1622,1623],{"class":276},"                        labels",[57,1625,123],{"class":96},[57,1627,1141],{"class":100},[57,1629,1630],{"class":67},"\"Early\"",[57,1632,17],{"class":100},[57,1634,1635],{"class":67},"\"Midday\"",[57,1637,17],{"class":100},[57,1639,1640],{"class":67},"\"Late\"",[57,1642,1167],{"class":100},[57,1644,1645],{"class":276},"right",[57,1647,123],{"class":96},[57,1649,282],{"class":252},[57,1651,132],{"class":100},[57,1653,1654,1656,1659,1661,1663,1666,1668,1670,1672,1674,1676,1678,1680,1682,1684],{"class":59,"line":135},[57,1655,253],{"class":252},[57,1657,1658],{"class":100},"(calls.groupby(",[57,1660,1570],{"class":67},[57,1662,17],{"class":100},[57,1664,1665],{"class":276},"observed",[57,1667,123],{"class":96},[57,1669,282],{"class":252},[57,1671,531],{"class":100},[57,1673,534],{"class":276},[57,1675,123],{"class":96},[57,1677,539],{"class":100},[57,1679,146],{"class":67},[57,1681,17],{"class":100},[57,1683,546],{"class":67},[57,1685,1686],{"class":100},"),\n",[57,1688,1689,1692,1694,1696,1698,1700,1702,1705,1707,1709,1712,1714],{"class":59,"line":176},[57,1690,1691],{"class":276},"                                                  AvgMinutes",[57,1693,123],{"class":96},[57,1695,539],{"class":100},[57,1697,559],{"class":67},[57,1699,17],{"class":100},[57,1701,564],{"class":96},[57,1703,1704],{"class":100}," s: s.mean() ",[57,1706,570],{"class":96},[57,1708,573],{"class":252},[57,1710,1711],{"class":100},")).round(",[57,1713,591],{"class":252},[57,1715,285],{"class":100},[10,1717,1718,1719,1722,1723,1725,1726,1729,1730,41],{},"Converting the time to minutes since midnight makes the bin edges readable, and ",[14,1720,1721],{},"right=False"," makes each bin include its start and exclude its end — the same convention as ",[14,1724,291],{},", so a call at exactly 11:00 belongs to Midday. ",[14,1727,1728],{},"observed=False"," keeps a shift in the output even if it had no calls. Binning numbers in general is covered in ",[37,1731,1733],{"href":1732},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut\u002F","Bin Values into Ranges with pandas cut",[43,1735,1737],{"id":1736},"step-6-excels-floor-and-its-boundary-problem","Step 6 — Excel's FLOOR and its boundary problem",[10,1739,1740,1741,1744,1745,1748],{},"In a spreadsheet, ",[14,1742,1743],{},"=FLOOR(A2, TIME(0,15,0))"," floors a date-time to 15 minutes. Spreadsheets store times as fractions of a day in binary floating point, and a quarter of an hour (1\u002F96 of a day) is not exactly representable, so a value ",[294,1746,1747],{},"on"," a boundary can come out a hair below it and fall into the previous slot — unless the implementation compensates. Compare with pandas on the boundary calls:",[48,1750,1752],{"className":87,"code":1751,"language":89,"meta":53,"style":53},"import subprocess\nfrom openpyxl import Workbook, load_workbook\n\nwb = Workbook()\nws = wb.active\nboundary = pd.to_datetime([\"2026-09-28 09:14:59\", \"2026-09-28 09:15:00\", \"2026-09-28 14:45:00\", \"2026-09-28 17:30:00\"])\nfor i, t in enumerate(boundary, start=1):\n    ws[f\"A{i}\"] = t.to_pydatetime()\n    ws[f\"B{i}\"] = f\"=FLOOR(A{i},TIME(0,15,0))\"\n    ws[f\"C{i}\"] = f\"=FLOOR(A{i}+TIME(0,0,0.5),TIME(0,15,0))\"\n    for col in \"ABC\":\n        ws[f\"{col}{i}\"].number_format = \"hh:mm:ss\"\nwb.save(\"floor_check.xlsx\")\nsubprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"xlsx\", \"--outdir\", \"recalc\", \"floor_check.xlsx\"],\n               check=True, capture_output=True)\nres = load_workbook(\"recalc\u002Ffloor_check.xlsx\", data_only=True).active\nfor i, t in enumerate(boundary, start=1):\n    print(t.time(), \"| sheet FLOOR:\", res[f\"B{i}\"].value, \"| with ½-second nudge:\", res[f\"C{i}\"].value,\n          \"| pandas:\", t.floor(\"15min\").time())\n",[14,1753,1754,1761,1774,1778,1788,1798,1829,1853,1883,1919,1954,1971,2003,2014,2054,2076,2102,2123,2173],{"__ignoreMap":53},[57,1755,1756,1758],{"class":59,"line":60},[57,1757,97],{"class":96},[57,1759,1760],{"class":100}," subprocess\n",[57,1762,1763,1766,1769,1771],{"class":59,"line":110},[57,1764,1765],{"class":96},"from",[57,1767,1768],{"class":100}," openpyxl ",[57,1770,97],{"class":96},[57,1772,1773],{"class":100}," Workbook, load_workbook\n",[57,1775,1776],{"class":59,"line":117},[57,1777,114],{"emptyLinePlaceholder":113},[57,1779,1780,1783,1785],{"class":59,"line":135},[57,1781,1782],{"class":100},"wb ",[57,1784,123],{"class":96},[57,1786,1787],{"class":100}," Workbook()\n",[57,1789,1790,1793,1795],{"class":59,"line":176},[57,1791,1792],{"class":100},"ws ",[57,1794,123],{"class":96},[57,1796,1797],{"class":100}," wb.active\n",[57,1799,1800,1803,1805,1808,1811,1813,1816,1818,1821,1823,1826],{"class":59,"line":203},[57,1801,1802],{"class":100},"boundary ",[57,1804,123],{"class":96},[57,1806,1807],{"class":100}," pd.to_datetime([",[57,1809,1810],{"class":67},"\"2026-09-28 09:14:59\"",[57,1812,17],{"class":100},[57,1814,1815],{"class":67},"\"2026-09-28 09:15:00\"",[57,1817,17],{"class":100},[57,1819,1820],{"class":67},"\"2026-09-28 14:45:00\"",[57,1822,17],{"class":100},[57,1824,1825],{"class":67},"\"2026-09-28 17:30:00\"",[57,1827,1828],{"class":100},"])\n",[57,1830,1831,1833,1836,1838,1841,1844,1846,1848,1850],{"class":59,"line":226},[57,1832,651],{"class":96},[57,1834,1835],{"class":100}," i, t ",[57,1837,657],{"class":96},[57,1839,1840],{"class":252}," enumerate",[57,1842,1843],{"class":100},"(boundary, ",[57,1845,296],{"class":276},[57,1847,123],{"class":96},[57,1849,591],{"class":252},[57,1851,1852],{"class":100},"):\n",[57,1854,1855,1858,1861,1864,1867,1870,1873,1876,1878,1880],{"class":59,"line":249},[57,1856,1857],{"class":100},"    ws[",[57,1859,1860],{"class":96},"f",[57,1862,1863],{"class":67},"\"A",[57,1865,1866],{"class":155},"{",[57,1868,1869],{"class":100},"i",[57,1871,1872],{"class":155},"}",[57,1874,1875],{"class":67},"\"",[57,1877,185],{"class":100},[57,1879,123],{"class":96},[57,1881,1882],{"class":100}," t.to_pydatetime()\n",[57,1884,1885,1887,1889,1892,1894,1896,1898,1900,1902,1904,1907,1910,1912,1914,1916],{"class":59,"line":823},[57,1886,1857],{"class":100},[57,1888,1860],{"class":96},[57,1890,1891],{"class":67},"\"B",[57,1893,1866],{"class":155},[57,1895,1869],{"class":100},[57,1897,1872],{"class":155},[57,1899,1875],{"class":67},[57,1901,185],{"class":100},[57,1903,123],{"class":96},[57,1905,1906],{"class":96}," f",[57,1908,1909],{"class":67},"\"=FLOOR(A",[57,1911,1866],{"class":155},[57,1913,1869],{"class":100},[57,1915,1872],{"class":155},[57,1917,1918],{"class":67},",TIME(0,15,0))\"\n",[57,1920,1922,1924,1926,1929,1931,1933,1935,1937,1939,1941,1943,1945,1947,1949,1951],{"class":59,"line":1921},10,[57,1923,1857],{"class":100},[57,1925,1860],{"class":96},[57,1927,1928],{"class":67},"\"C",[57,1930,1866],{"class":155},[57,1932,1869],{"class":100},[57,1934,1872],{"class":155},[57,1936,1875],{"class":67},[57,1938,185],{"class":100},[57,1940,123],{"class":96},[57,1942,1906],{"class":96},[57,1944,1909],{"class":67},[57,1946,1866],{"class":155},[57,1948,1869],{"class":100},[57,1950,1872],{"class":155},[57,1952,1953],{"class":67},"+TIME(0,0,0.5),TIME(0,15,0))\"\n",[57,1955,1957,1960,1963,1965,1968],{"class":59,"line":1956},11,[57,1958,1959],{"class":96},"    for",[57,1961,1962],{"class":100}," col ",[57,1964,657],{"class":96},[57,1966,1967],{"class":67}," \"ABC\"",[57,1969,1970],{"class":100},":\n",[57,1972,1974,1977,1979,1981,1983,1986,1989,1991,1993,1995,1998,2000],{"class":59,"line":1973},12,[57,1975,1976],{"class":100},"        ws[",[57,1978,1860],{"class":96},[57,1980,1875],{"class":67},[57,1982,1866],{"class":155},[57,1984,1985],{"class":100},"col",[57,1987,1988],{"class":155},"}{",[57,1990,1869],{"class":100},[57,1992,1872],{"class":155},[57,1994,1875],{"class":67},[57,1996,1997],{"class":100},"].number_format ",[57,1999,123],{"class":96},[57,2001,2002],{"class":67}," \"hh:mm:ss\"\n",[57,2004,2006,2009,2012],{"class":59,"line":2005},13,[57,2007,2008],{"class":100},"wb.save(",[57,2010,2011],{"class":67},"\"floor_check.xlsx\"",[57,2013,132],{"class":100},[57,2015,2017,2020,2023,2025,2028,2030,2033,2035,2038,2040,2043,2045,2048,2050,2052],{"class":59,"line":2016},14,[57,2018,2019],{"class":100},"subprocess.run([",[57,2021,2022],{"class":67},"\"soffice\"",[57,2024,17],{"class":100},[57,2026,2027],{"class":67},"\"--headless\"",[57,2029,17],{"class":100},[57,2031,2032],{"class":67},"\"--convert-to\"",[57,2034,17],{"class":100},[57,2036,2037],{"class":67},"\"xlsx\"",[57,2039,17],{"class":100},[57,2041,2042],{"class":67},"\"--outdir\"",[57,2044,17],{"class":100},[57,2046,2047],{"class":67},"\"recalc\"",[57,2049,17],{"class":100},[57,2051,2011],{"class":67},[57,2053,1618],{"class":100},[57,2055,2057,2060,2062,2065,2067,2070,2072,2074],{"class":59,"line":2056},15,[57,2058,2059],{"class":276},"               check",[57,2061,123],{"class":96},[57,2063,2064],{"class":252},"True",[57,2066,17],{"class":100},[57,2068,2069],{"class":276},"capture_output",[57,2071,123],{"class":96},[57,2073,2064],{"class":252},[57,2075,132],{"class":100},[57,2077,2079,2082,2084,2087,2090,2092,2095,2097,2099],{"class":59,"line":2078},16,[57,2080,2081],{"class":100},"res ",[57,2083,123],{"class":96},[57,2085,2086],{"class":100}," load_workbook(",[57,2088,2089],{"class":67},"\"recalc\u002Ffloor_check.xlsx\"",[57,2091,17],{"class":100},[57,2093,2094],{"class":276},"data_only",[57,2096,123],{"class":96},[57,2098,2064],{"class":252},[57,2100,2101],{"class":100},").active\n",[57,2103,2105,2107,2109,2111,2113,2115,2117,2119,2121],{"class":59,"line":2104},17,[57,2106,651],{"class":96},[57,2108,1835],{"class":100},[57,2110,657],{"class":96},[57,2112,1840],{"class":252},[57,2114,1843],{"class":100},[57,2116,296],{"class":276},[57,2118,123],{"class":96},[57,2120,591],{"class":252},[57,2122,1852],{"class":100},[57,2124,2126,2129,2132,2135,2138,2140,2142,2144,2146,2148,2150,2153,2156,2158,2160,2162,2164,2166,2168,2170],{"class":59,"line":2125},18,[57,2127,2128],{"class":252},"    print",[57,2130,2131],{"class":100},"(t.time(), ",[57,2133,2134],{"class":67},"\"| sheet FLOOR:\"",[57,2136,2137],{"class":100},", res[",[57,2139,1860],{"class":96},[57,2141,1891],{"class":67},[57,2143,1866],{"class":155},[57,2145,1869],{"class":100},[57,2147,1872],{"class":155},[57,2149,1875],{"class":67},[57,2151,2152],{"class":100},"].value, ",[57,2154,2155],{"class":67},"\"| with ½-second nudge:\"",[57,2157,2137],{"class":100},[57,2159,1860],{"class":96},[57,2161,1928],{"class":67},[57,2163,1866],{"class":155},[57,2165,1869],{"class":100},[57,2167,1872],{"class":155},[57,2169,1875],{"class":67},[57,2171,2172],{"class":100},"].value,\n",[57,2174,2176,2179,2182,2184],{"class":59,"line":2175},19,[57,2177,2178],{"class":67},"          \"| pandas:\"",[57,2180,2181],{"class":100},", t.floor(",[57,2183,198],{"class":67},[57,2185,2186],{"class":100},").time())\n",[10,2188,2189,2190,2193,2194,2196,2197,2199],{},"On these four boundary values LibreOffice's ",[14,2191,2192],{},"FLOOR"," agrees with pandas — its implementation rounds away tiny floating-point errors before flooring. Do not assume every spreadsheet does: run the same check in the Excel version your readers use, on real boundary values from your data, and if any row of the plain ",[14,2195,2192],{}," column disagrees with pandas, add a fraction of a second before flooring, as the third column does. pandas stores timestamps as integers, so its ",[14,2198,291],{}," has no floating-point edge case at all — one more reason to do the bucketing in Python and write the slot labels as values.",[43,2201,2203],{"id":2202},"performance-notes","Performance notes",[10,2205,2206,17,2208,31,2210,2212,2213,2216,2217,2220,2221,2224],{},[14,2207,27],{},[14,2209,308],{},[14,2211,312],{}," are vectorised integer operations and run over millions of timestamps in milliseconds; the group-by and pivot are equally quick. Building the slot grid with a Python loop over days is fine for weeks or months of data; for years, build it in one go with ",[14,2214,2215],{},"pd.date_range"," over the whole period at slot frequency and filter to opening hours with ",[14,2218,2219],{},"between_time(\"08:00\", \"17:45\")",". The lambda used to sum minutes runs once per slot and is the slowest piece — summing seconds with ",[14,2222,2223],{},"\"sum\""," and dividing afterwards is faster on large data.",[43,2226,2228],{"id":2227},"common-pitfalls","Common pitfalls",[2230,2231,2232,2242,2248,2254,2268],"ul",{},[2233,2234,2235,2239,2240,41],"li",{},[2236,2237,2238],"strong",{},"Using round for slots."," It splits each slot's events between two labels; use ",[14,2241,291],{},[2233,2243,2244,2247],{},[2236,2245,2246],{},"Missing empty slots."," Group-by results skip quiet slots; reindex to a full grid before charting or averaging.",[2233,2249,2250,2253],{},[2236,2251,2252],{},"A grid that spans the night."," Build the grid per day within opening hours.",[2233,2255,2256,2259,2260,2262,2263,2265,2266,41],{},[2236,2257,2258],{},"Inconsistent bin edges."," Use ",[14,2261,1721],{}," in ",[14,2264,1528],{}," so edges behave like ",[14,2267,291],{},[2233,2269,2270,2273],{},[2236,2271,2272],{},"Time zones."," Bucket in the zone where the work happens; a UTC 08:00 slot is 09:00 in London in summer.",[43,2275,2277],{"id":2276},"conclusion","Conclusion",[10,2279,2280,2281,2284,2285,17,2287,2289,2290,2292,2293,2295,2296,2298],{},"Label each timestamp with the start of its slot using ",[14,2282,2283],{},"dt.floor(\"15min\")"," (or ",[14,2286,322],{},[14,2288,319],{},", …), group and aggregate, and reindex to a full grid of opening-hour slots so quiet periods count as zeros. Pivot weekday against hour for a heatmap, use ",[14,2291,1528],{}," with ",[14,2294,1721],{}," for uneven slots such as shifts, and prefer doing the bucketing in pandas over Excel's ",[14,2297,2192],{},", whose floating-point times can misplace events that fall exactly on a boundary.",[43,2300,2302],{"id":2301},"frequently-asked-questions","Frequently asked questions",[10,2304,2305,2308,2309,2311],{},[2236,2306,2307],{},"How do I round timestamps down to 15 minutes in pandas?","\nUse df",[57,2310,146],{},".dt.floor(\"15min\"). Each timestamp is replaced by the start of its 15-minute slot, so 09:14:59 becomes 09:00 and 09:15:00 stays 09:15.",[10,2313,2314,2317],{},[2236,2315,2316],{},"How do I count events per time slot including empty slots?","\nGroup by the floored timestamp and count, then reindex the result to a complete pd.date_range of slots with fill_value=0, so slots without events appear with a count of zero.",[10,2319,2320,2323],{},[2236,2321,2322],{},"What is the difference between floor, ceil and round for timestamps?","\nfloor moves each timestamp to the start of its slot, ceil to the end, and round to the nearest boundary. For counting events per slot, floor is the usual choice.",[10,2325,2326,2329],{},[2236,2327,2328],{},"Can a spreadsheet FLOOR put a time in the previous slot?","\nSpreadsheets store times as binary fractions of a day, and slot lengths such as 15 minutes are not exactly representable, so a value exactly on a boundary can come out a tiny bit smaller than the boundary unless the implementation compensates. Test boundary values in your spreadsheet; adding a fraction of a second before flooring is a common safeguard. pandas uses integer timestamps and is not affected.",[43,2331,2333],{"id":2332},"related","Related",[2230,2335,2336,2342,2349,2356],{},[2233,2337,2338,2339,2341],{},"Up one level: ",[37,2340,40],{"href":39}," — dates and times in spreadsheet data.",[2233,2343,2344,2348],{},[37,2345,2347],{"href":2346},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-working-hours-between-two-timestamps\u002F","Calculate Working Hours Between Two Timestamps"," — durations inside opening hours.",[2233,2350,2351,2355],{},[37,2352,2354],{"href":2353},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-rows-by-month-and-quarter-with-pandas\u002F","Group Excel Rows by Month and Quarter with pandas"," — the same idea at calendar scale.",[2233,2357,2358,2362],{},[37,2359,2361],{"href":2360},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-support-ticket-sla-report\u002F","Build a Support Ticket SLA Report"," — slot counts in an operations report.",[2364,2365,2366],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}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}",{"title":53,"searchDepth":110,"depth":110,"links":2368},[2369,2370,2371,2372,2373,2374,2375,2376,2377,2378,2379,2380],{"id":45,"depth":110,"text":46},{"id":83,"depth":110,"text":84},{"id":487,"depth":110,"text":488},{"id":616,"depth":110,"text":617},{"id":1004,"depth":110,"text":1005},{"id":1521,"depth":110,"text":1522},{"id":1736,"depth":110,"text":1737},{"id":2202,"depth":110,"text":2203},{"id":2227,"depth":110,"text":2228},{"id":2276,"depth":110,"text":2277},{"id":2301,"depth":110,"text":2302},{"id":2332,"depth":110,"text":2333},"2026-10-02","Bucket call-centre calls, orders or sensor readings into 15-minute, hourly or shift slots: floor, ceil and round with dt accessors, count and sum per slot, show empty slots as zero with a full slot grid, build a weekday-by-hour heatmap table, assign custom slots such as shifts, and compare with Excel's FLOOR formula and its floating-point edge cases.","md",[2385,2387,2389,2391],{"q":2307,"a":2386},"Use df[\"Received\"].dt.floor(\"15min\"). Each timestamp is replaced by the start of its 15-minute slot, so 09:14:59 becomes 09:00 and 09:15:00 stays 09:15.",{"q":2316,"a":2388},"Group by the floored timestamp and count, then reindex the result to a complete pd.date_range of slots with fill_value=0, so slots without events appear with a count of zero.",{"q":2322,"a":2390},"floor moves each timestamp to the start of its slot, ceil to the end, and round to the nearest boundary. For counting events per slot, floor is the usual choice.",{"q":2328,"a":2392},"Spreadsheets store times as binary fractions of a day, and slot lengths such as 15 minutes are not exactly representable, so a value exactly on a boundary can come out a tiny bit smaller than the boundary unless the implementation compensates. Test boundary values in your spreadsheet; adding a fraction of a second before flooring is a common safeguard. pandas uses integer timestamps and is not affected.","round timestamps into time slots with pandas",null,{"breadcrumb":2396},[2397,2399,2402],{"name":2398,"item":570},"Home",{"name":2400,"item":2401},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":40,"item":39},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fround-timestamps-into-time-slots-with-pandas",{"title":2405,"description":2406},"Round Timestamps into 15-Minute or Hourly Slots with pandas","Group Excel timestamps into time slots: dt.floor(\"15min\"), ceil and round, counts per slot, a complete slot grid with zeros, weekday × hour heatmap tables, shift buckets with pd.cut, and Excel FLOOR(A2, TIME(0,15,0)) compared.","round-timestamps-into-time-slots-with-pandas","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fround-timestamps-into-time-slots-with-pandas\u002Findex",[2410,2411],"dates-and-times","pivots-and-summaries","how-to","Fvp2YfQxXpggE8nrhy8BTCFtg33jOZmzdpCblaAmBAI",[2415,2419],{"title":2416,"path":2417,"stem":2418,"children":-1},"Resample Irregular Readings to Regular Intervals","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fresample-irregular-readings-to-regular-intervals","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fresample-irregular-readings-to-regular-intervals\u002Findex",{"title":2420,"path":2421,"stem":2422,"children":-1},"Working with Large Excel Files in Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python","advanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Findex",[2424,2426],{"path":2425,"title":2400},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2427,"title":40},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data",[],[2430,2433,2436,2439,2442,2445],{"path":2431,"title":2432,"kind":2412},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl","Highlight Overdue Dates in Excel with openpyxl",{"path":2434,"title":2435,"kind":2412},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-weekends-and-holidays-in-a-date-column","Highlight Weekends and Holidays in a Date Column",{"path":2437,"title":2438,"kind":2412},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fkeep-the-latest-record-per-key-with-pandas","Keep the Latest Record per Key When Deduplicating with pandas",{"path":2440,"title":2441,"kind":2412},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-moving-averages-with-pandas-rolling","Calculate Moving Averages with pandas rolling",{"path":2443,"title":2444,"kind":2412},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas","EOMONTH and EDATE Equivalents in pandas",{"path":2446,"title":2447,"kind":2412},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fyearfrac-and-days360-equivalents-in-python","YEARFRAC and DAYS360 Equivalents in Python",1790972106562]