[{"data":1,"prerenderedAt":2383},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-month-to-date-and-year-to-date-totals-with-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-month-to-date-and-year-to-date-totals-with-pandas":2349,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-month-to-date-and-year-to-date-totals-with-pandas":2358,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-month-to-date-and-year-to-date-totals-with-pandas":2363,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-month-to-date-and-year-to-date-totals-with-pandas":2364},{"id":4,"title":5,"body":6,"dateModified":2316,"datePublished":2316,"description":2317,"extension":2318,"faq":2319,"keyword":2328,"kind":2329,"meta":2330,"navigation":104,"path":2338,"seo":2339,"slug":2342,"stem":2343,"topics":2344,"type":2347,"__hash__":2348},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-month-to-date-and-year-to-date-totals-with-pandas\u002Findex.md","Calculate Month-to-Date and Year-to-Date Totals with pandas",{"type":7,"value":8,"toc":2302},"minimark",[9,33,38,66,72,76,395,409,564,568,867,878,995,999,1002,1145,1152,1156,1159,1430,1445,1449,1452,1662,1665,1669,1961,1964,2161,2165,2172,2176,2215,2219,2237,2241,2247,2253,2259,2265,2269,2298],[10,11,12,13,17,18,22,23,26,27,32],"p",{},"\"How are we doing this month?\" is a month-to-date question: sales from the first of the month up to today. Management packs add quarter-to-date and year-to-date, and every one of those figures is compared with the same period last year — which must stop at the ",[14,15,16],"em",{},"same day"," last year, not at the end of last year's month, or this month always looks bad. In Excel these are ",[19,20,21],"code",{},"SUMIFS"," formulas with date bounds that someone has to keep correct. In pandas they are boolean masks on a date column, plus ",[19,24,25],{},"groupby(...).cumsum()"," when the report needs a running column. This page builds both, and the comparison that people most often get wrong. It belongs to ",[28,29,31],"a",{"href":30},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002F","Working with Dates and Times in Excel Data",".",[34,35,37],"h2",{"id":36},"prerequisites","Prerequisites",[39,40,45],"pre",{"className":41,"code":42,"language":43,"meta":44,"style":44},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[19,46,47],{"__ignoreMap":44},[48,49,52,56,60,63],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sMTad","pip",[48,57,59],{"class":58},"srMev"," install",[48,61,62],{"class":58}," pandas",[48,64,65],{"class":58}," openpyxl\n",[10,67,68,71],{},[19,69,70],{},"daily_sales_2y.xlsx"," has daily sales from 1 January 2025 to 2 October 2026, with no rows for Sundays — a realistic gap that matters for the comparisons below. The examples report as of 2 October 2026.",[34,73,75],{"id":74},"step-1-mtd-qtd-and-ytd-as-of-a-date","Step 1 — MTD, QTD and YTD as of a date",[39,77,81],{"className":78,"code":79,"language":80,"meta":44,"style":44},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.read_excel(\"daily_sales_2y.xlsx\", parse_dates=[\"Date\"])\nas_of = pd.Timestamp(\"2026-10-02\")\n\ndef period_to_date(df, as_of, freq):\n    start = as_of.to_period(freq).start_time\n    mask = (df[\"Date\"] >= start) & (df[\"Date\"] \u003C= as_of)\n    return df.loc[mask, \"Sales\"].sum(), start\n\nfor label, freq in ((\"MTD\", \"M\"), (\"QTD\", \"Q\"), (\"YTD\", \"Y\")):\n    total, start = period_to_date(sales, as_of, freq)\n    print(f\"{label}: {total:>12,.0f}  ({start:%d %b %Y} – {as_of:%d %b %Y})\")\n","python",[19,82,83,99,106,139,156,161,174,185,222,237,242,289,300],{"__ignoreMap":44},[48,84,85,89,93,96],{"class":50,"line":51},[48,86,88],{"class":87},"s-kum","import",[48,90,92],{"class":91},"skGVy"," pandas ",[48,94,95],{"class":87},"as",[48,97,98],{"class":91}," pd\n",[48,100,102],{"class":50,"line":101},2,[48,103,105],{"emptyLinePlaceholder":104},true,"\n",[48,107,109,112,115,118,121,124,128,130,133,136],{"class":50,"line":108},3,[48,110,111],{"class":91},"sales ",[48,113,114],{"class":87},"=",[48,116,117],{"class":91}," pd.read_excel(",[48,119,120],{"class":58},"\"daily_sales_2y.xlsx\"",[48,122,123],{"class":91},", ",[48,125,127],{"class":126},"sa561","parse_dates",[48,129,114],{"class":87},[48,131,132],{"class":91},"[",[48,134,135],{"class":58},"\"Date\"",[48,137,138],{"class":91},"])\n",[48,140,142,145,147,150,153],{"class":50,"line":141},4,[48,143,144],{"class":91},"as_of ",[48,146,114],{"class":87},[48,148,149],{"class":91}," pd.Timestamp(",[48,151,152],{"class":58},"\"2026-10-02\"",[48,154,155],{"class":91},")\n",[48,157,159],{"class":50,"line":158},5,[48,160,105],{"emptyLinePlaceholder":104},[48,162,164,167,171],{"class":50,"line":163},6,[48,165,166],{"class":87},"def",[48,168,170],{"class":169},"s_Opv"," period_to_date",[48,172,173],{"class":91},"(df, as_of, freq):\n",[48,175,177,180,182],{"class":50,"line":176},7,[48,178,179],{"class":91},"    start ",[48,181,114],{"class":87},[48,183,184],{"class":91}," as_of.to_period(freq).start_time\n",[48,186,188,191,193,196,198,201,204,207,210,212,214,216,219],{"class":50,"line":187},8,[48,189,190],{"class":91},"    mask ",[48,192,114],{"class":87},[48,194,195],{"class":91}," (df[",[48,197,135],{"class":58},[48,199,200],{"class":91},"] ",[48,202,203],{"class":87},">=",[48,205,206],{"class":91}," start) ",[48,208,209],{"class":87},"&",[48,211,195],{"class":91},[48,213,135],{"class":58},[48,215,200],{"class":91},[48,217,218],{"class":87},"\u003C=",[48,220,221],{"class":91}," as_of)\n",[48,223,225,228,231,234],{"class":50,"line":224},9,[48,226,227],{"class":87},"    return",[48,229,230],{"class":91}," df.loc[mask, ",[48,232,233],{"class":58},"\"Sales\"",[48,235,236],{"class":91},"].sum(), start\n",[48,238,240],{"class":50,"line":239},10,[48,241,105],{"emptyLinePlaceholder":104},[48,243,245,248,251,254,257,260,262,265,268,271,273,276,278,281,283,286],{"class":50,"line":244},11,[48,246,247],{"class":87},"for",[48,249,250],{"class":91}," label, freq ",[48,252,253],{"class":87},"in",[48,255,256],{"class":91}," ((",[48,258,259],{"class":58},"\"MTD\"",[48,261,123],{"class":91},[48,263,264],{"class":58},"\"M\"",[48,266,267],{"class":91},"), (",[48,269,270],{"class":58},"\"QTD\"",[48,272,123],{"class":91},[48,274,275],{"class":58},"\"Q\"",[48,277,267],{"class":91},[48,279,280],{"class":58},"\"YTD\"",[48,282,123],{"class":91},[48,284,285],{"class":58},"\"Y\"",[48,287,288],{"class":91},")):\n",[48,290,292,295,297],{"class":50,"line":291},12,[48,293,294],{"class":91},"    total, start ",[48,296,114],{"class":87},[48,298,299],{"class":91}," period_to_date(sales, as_of, freq)\n",[48,301,303,307,310,313,316,320,323,326,329,331,334,337,339,342,344,347,350,353,356,358,361,363,366,368,371,373,376,378,380,382,384,386,388,390,393],{"class":50,"line":302},13,[48,304,306],{"class":305},"sP0c6","    print",[48,308,309],{"class":91},"(",[48,311,312],{"class":87},"f",[48,314,315],{"class":58},"\"",[48,317,319],{"class":318},"sSjpA","{",[48,321,322],{"class":91},"label",[48,324,325],{"class":318},"}",[48,327,328],{"class":58},": ",[48,330,319],{"class":318},[48,332,333],{"class":91},"total",[48,335,336],{"class":87},":>12,.0f",[48,338,325],{"class":318},[48,340,341],{"class":58},"  (",[48,343,319],{"class":318},[48,345,346],{"class":91},"start",[48,348,349],{"class":87},":",[48,351,352],{"class":91},"%",[48,354,355],{"class":87},"d ",[48,357,352],{"class":91},[48,359,360],{"class":87},"b ",[48,362,352],{"class":91},[48,364,365],{"class":87},"Y",[48,367,325],{"class":318},[48,369,370],{"class":58}," – ",[48,372,319],{"class":318},[48,374,375],{"class":91},"as_of:",[48,377,352],{"class":87},[48,379,355],{"class":91},[48,381,352],{"class":87},[48,383,360],{"class":91},[48,385,352],{"class":87},[48,387,365],{"class":91},[48,389,325],{"class":318},[48,391,392],{"class":58},")\"",[48,394,155],{"class":91},[10,396,397,400,401,404,405,32],{},[19,398,399],{},"as_of.to_period(\"M\").start_time"," is the first day of the period containing the report date — 1 October for the month, 1 October for the quarter (Q4 starts in October), 1 January for the year. One function covers all three, and the printed date range makes the figure self-explanatory in a log or report. Pass the report date in explicitly rather than calling ",[19,402,403],{},"Timestamp.today()"," inside the function: reruns and backfills then produce the same numbers, as discussed in ",[28,406,408],{"href":407},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Fbackfill-excel-reports-for-past-dates\u002F","Backfill Excel Reports for Past Dates",[410,411,419,420,419,424,419,428,419,436,419,443,419,453,419,464,419,471,419,476,419,480,419,484,419,490,419,496,419,502,419,506,419,510,419,512,419,514,419,519,419,525,419,530,419,533,419,535,419,537,419,542,419,549,419,554,419,557,419,560,419,562],"svg",{"viewBox":412,"role":413,"ariaLabelledBy":414,"xmlns":417,"style":418},"4 0 752 313","img",[415,416],"ptd-windows-t","ptd-windows-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  ",[421,422,423],"title",{"id":415},"The three windows ending on the report date",[425,426,427],"desc",{"id":416},"For a report date of 2 October 2026, year-to-date runs from 1 January 2026, quarter-to-date from 1 October 2026 and month-to-date from 1 October 2026. Each window ends on 2 October.",[429,430],"rect",{"x":431,"y":432,"width":433,"height":434,"fill":435},"4","0","752","313","#ffffff",[50,437],{"x1":438,"y1":439,"x2":440,"y2":439,"stroke":441,"style":442},"94.0","156.5","666.0","var(--muted,#5b6780)","stroke-width:2.5px",[429,444],{"x":445,"y":446,"width":447,"height":448,"rx":449,"fill":450,"stroke":451,"style":452},"20.0","16.0","172.0","108.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[454,455,457],"g",{"transform":456},"translate(94.0,37.5) scale(1.000)",[458,459],"path",{"d":460,"fill":461,"stroke":462,"style":463},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[465,466,470],"text",{"x":467,"y":468,"style":469},"106.0","83.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1 Jan 2026",[465,472,475],{"x":467,"y":473,"style":474},"101.5","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","YTD starts",[50,477],{"x1":467,"y1":478,"x2":467,"y2":439,"stroke":441,"style":479},"124.5","stroke-width:1.5px",[481,482],"circle",{"cx":467,"cy":439,"r":483,"fill":441,"stroke":435,"style":452},"7.0",[429,485],{"x":486,"y":487,"width":447,"height":448,"rx":449,"fill":488,"stroke":489,"style":452},"202.7","188.5","#fdefd8","var(--gold,#b4740a)",[454,491,493],{"transform":492},"translate(276.7,201.5) scale(1.000)",[458,494],{"d":460,"fill":461,"stroke":495,"style":463},"var(--gold-ink,#7a4e06)",[465,497,501],{"x":498,"y":499,"style":500},"288.7","247.0","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","1 Apr 2026",[465,503,505],{"x":498,"y":504,"style":474},"265.5","fiscal YTD starts",[465,507,509],{"x":498,"y":508,"style":474},"282.5","(April year)",[50,511],{"x1":498,"y1":439,"x2":498,"y2":487,"stroke":441,"style":479},[481,513],{"cx":498,"cy":439,"r":483,"fill":489,"stroke":435,"style":452},[429,515],{"x":516,"y":446,"width":447,"height":448,"rx":449,"fill":517,"stroke":518,"style":452},"385.3","#f0f4ff","var(--brand,#5b5cf0)",[454,520,522],{"transform":521},"translate(459.3,37.5) scale(1.000)",[458,523],{"d":460,"fill":461,"stroke":524,"style":463},"var(--brand-strong,#4338ca)",[465,526,529],{"x":527,"y":468,"style":528},"471.3","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","1 Oct 2026",[465,531,532],{"x":527,"y":473,"style":474},"QTD and MTD start",[50,534],{"x1":527,"y1":478,"x2":527,"y2":439,"stroke":441,"style":479},[481,536],{"cx":527,"cy":439,"r":483,"fill":518,"stroke":435,"style":452},[429,538],{"x":539,"y":487,"width":447,"height":448,"rx":449,"fill":540,"stroke":541,"style":452},"568.0","#d9f4f1","var(--teal,#0f9488)",[454,543,545],{"transform":544},"translate(642.0,201.5) scale(1.000)",[458,546],{"d":547,"fill":461,"stroke":548,"style":463},"M12 3a9 9 0 1 0 .01 0z M12 7v5l3.5 2","var(--teal-ink,#0b6157)",[465,550,553],{"x":551,"y":499,"style":552},"654.0","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","2 Oct 2026",[465,555,556],{"x":551,"y":504,"style":474},"report date — all windows",[465,558,559],{"x":551,"y":508,"style":474},"end",[50,561],{"x1":551,"y1":439,"x2":551,"y2":487,"stroke":441,"style":479},[481,563],{"cx":551,"cy":439,"r":483,"fill":541,"stroke":435,"style":452},[34,565,567],{"id":566},"step-2-the-same-period-last-year-to-the-same-day","Step 2 — The same period last year, to the same day",[39,569,571],{"className":78,"code":570,"language":80,"meta":44,"style":44},"def same_period_last_year(df, as_of, freq):\n    ly_end = as_of - pd.DateOffset(years=1)\n    ly_start = ly_end.to_period(freq).start_time\n    mask = (df[\"Date\"] >= ly_start) & (df[\"Date\"] \u003C= ly_end)\n    return df.loc[mask, \"Sales\"].sum(), ly_start, ly_end\n\nrows = []\nfor label, freq in ((\"MTD\", \"M\"), (\"QTD\", \"Q\"), (\"YTD\", \"Y\")):\n    this, _ = period_to_date(sales, as_of, freq)\n    last, s, e = same_period_last_year(sales, as_of, freq)\n    rows.append({\"Measure\": label, \"This year\": this, \"Last year\": last,\n                 \"Change %\": (this \u002F last - 1) * 100, \"LY window\": f\"{s:%d %b} – {e:%d %b %Y}\"})\nsummary = pd.DataFrame(rows)\nprint(summary.round(1).to_string(index=False))\n",[19,572,573,582,608,618,648,659,663,673,707,716,726,749,832,842],{"__ignoreMap":44},[48,574,575,577,580],{"class":50,"line":51},[48,576,166],{"class":87},[48,578,579],{"class":169}," same_period_last_year",[48,581,173],{"class":91},[48,583,584,587,589,592,595,598,601,603,606],{"class":50,"line":101},[48,585,586],{"class":91},"    ly_end ",[48,588,114],{"class":87},[48,590,591],{"class":91}," as_of ",[48,593,594],{"class":87},"-",[48,596,597],{"class":91}," pd.DateOffset(",[48,599,600],{"class":126},"years",[48,602,114],{"class":87},[48,604,605],{"class":305},"1",[48,607,155],{"class":91},[48,609,610,613,615],{"class":50,"line":108},[48,611,612],{"class":91},"    ly_start ",[48,614,114],{"class":87},[48,616,617],{"class":91}," ly_end.to_period(freq).start_time\n",[48,619,620,622,624,626,628,630,632,635,637,639,641,643,645],{"class":50,"line":141},[48,621,190],{"class":91},[48,623,114],{"class":87},[48,625,195],{"class":91},[48,627,135],{"class":58},[48,629,200],{"class":91},[48,631,203],{"class":87},[48,633,634],{"class":91}," ly_start) ",[48,636,209],{"class":87},[48,638,195],{"class":91},[48,640,135],{"class":58},[48,642,200],{"class":91},[48,644,218],{"class":87},[48,646,647],{"class":91}," ly_end)\n",[48,649,650,652,654,656],{"class":50,"line":158},[48,651,227],{"class":87},[48,653,230],{"class":91},[48,655,233],{"class":58},[48,657,658],{"class":91},"].sum(), ly_start, ly_end\n",[48,660,661],{"class":50,"line":163},[48,662,105],{"emptyLinePlaceholder":104},[48,664,665,668,670],{"class":50,"line":176},[48,666,667],{"class":91},"rows ",[48,669,114],{"class":87},[48,671,672],{"class":91}," []\n",[48,674,675,677,679,681,683,685,687,689,691,693,695,697,699,701,703,705],{"class":50,"line":187},[48,676,247],{"class":87},[48,678,250],{"class":91},[48,680,253],{"class":87},[48,682,256],{"class":91},[48,684,259],{"class":58},[48,686,123],{"class":91},[48,688,264],{"class":58},[48,690,267],{"class":91},[48,692,270],{"class":58},[48,694,123],{"class":91},[48,696,275],{"class":58},[48,698,267],{"class":91},[48,700,280],{"class":58},[48,702,123],{"class":91},[48,704,285],{"class":58},[48,706,288],{"class":91},[48,708,709,712,714],{"class":50,"line":224},[48,710,711],{"class":91},"    this, _ ",[48,713,114],{"class":87},[48,715,299],{"class":91},[48,717,718,721,723],{"class":50,"line":239},[48,719,720],{"class":91},"    last, s, e ",[48,722,114],{"class":87},[48,724,725],{"class":91}," same_period_last_year(sales, as_of, freq)\n",[48,727,728,731,734,737,740,743,746],{"class":50,"line":244},[48,729,730],{"class":91},"    rows.append({",[48,732,733],{"class":58},"\"Measure\"",[48,735,736],{"class":91},": label, ",[48,738,739],{"class":58},"\"This year\"",[48,741,742],{"class":91},": this, ",[48,744,745],{"class":58},"\"Last year\"",[48,747,748],{"class":91},": last,\n",[48,750,751,754,757,760,763,765,768,771,774,777,779,782,784,786,788,790,793,795,797,799,801,804,806,808,810,813,815,817,819,821,823,825,827,829],{"class":50,"line":291},[48,752,753],{"class":58},"                 \"Change %\"",[48,755,756],{"class":91},": (this ",[48,758,759],{"class":87},"\u002F",[48,761,762],{"class":91}," last ",[48,764,594],{"class":87},[48,766,767],{"class":305}," 1",[48,769,770],{"class":91},") ",[48,772,773],{"class":87},"*",[48,775,776],{"class":305}," 100",[48,778,123],{"class":91},[48,780,781],{"class":58},"\"LY window\"",[48,783,328],{"class":91},[48,785,312],{"class":87},[48,787,315],{"class":58},[48,789,319],{"class":318},[48,791,792],{"class":91},"s",[48,794,349],{"class":87},[48,796,352],{"class":91},[48,798,355],{"class":87},[48,800,352],{"class":91},[48,802,803],{"class":87},"b",[48,805,325],{"class":318},[48,807,370],{"class":58},[48,809,319],{"class":318},[48,811,812],{"class":91},"e:",[48,814,352],{"class":87},[48,816,355],{"class":91},[48,818,352],{"class":87},[48,820,360],{"class":91},[48,822,352],{"class":87},[48,824,365],{"class":91},[48,826,325],{"class":318},[48,828,315],{"class":58},[48,830,831],{"class":91},"})\n",[48,833,834,837,839],{"class":50,"line":302},[48,835,836],{"class":91},"summary ",[48,838,114],{"class":87},[48,840,841],{"class":91}," pd.DataFrame(rows)\n",[48,843,845,848,851,853,856,859,861,864],{"class":50,"line":844},14,[48,846,847],{"class":305},"print",[48,849,850],{"class":91},"(summary.round(",[48,852,605],{"class":305},[48,854,855],{"class":91},").to_string(",[48,857,858],{"class":126},"index",[48,860,114],{"class":87},[48,862,863],{"class":305},"False",[48,865,866],{"class":91},"))\n",[10,868,869,870,873,874,877],{},"Last year's window ends on 2 October 2025, not 31 October: comparing two days of this October with all of last October would show a 90-plus percent drop every month on day two. ",[19,871,872],{},"pd.DateOffset(years=1)"," moves the date back one calendar year and handles month lengths; for a report run on 29 February it lands on 28 February, which is the usual convention. The comparison is like-for-like in calendar days but not necessarily in ",[14,875,876],{},"trading"," days: 1–2 October 2026 are a Thursday and Friday, 1–2 October 2025 a Wednesday and Thursday, and with no Sunday trading a window that contains an extra Sunday has one fewer trading day. Step 5 deals with that.",[410,879,419,884,419,887,419,890,419,893,419,900,419,908,419,914,419,920,419,926,419,933,419,937,419,942,419,946,419,951,419,954,419,960,419,965,419,969,419,973,419,978,419,981,419,986,419,989],{"viewBox":880,"role":413,"ariaLabelledBy":881,"xmlns":417,"style":418},"4 0 752 207",[882,883],"ptd-ly-t","ptd-ly-d",[421,885,886],{"id":882},"Last year's month-to-date — two ways to get it wrong",[425,888,889],{"id":883},"Comparing 1–2 October 2026 with all of October 2025 makes this month look 90 percent down. Comparing with 1–2 October 2025, the same days last year, gives a like-for-like change.",[429,891],{"x":431,"y":432,"width":433,"height":892,"fill":435},"207",[429,894],{"x":445,"y":446,"width":895,"height":896,"rx":897,"fill":898,"stroke":899,"style":452},"351.0","175.0","14","#fce9e9","var(--danger,#dc2626)",[454,901,903],{"transform":902},"translate(182.5,29.0) scale(1.083)",[458,904],{"d":905,"fill":461,"stroke":906,"style":907},"M6 6l12 12 M18 6L6 18","var(--accent-ink,#be185d)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[465,909,913],{"x":910,"y":911,"style":912},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Whole of last October",[50,915],{"x1":916,"y1":917,"x2":918,"y2":917,"stroke":899,"style":919},"36.0","91.0","355.0","stroke-width:1px",[465,921,925],{"x":922,"y":923,"style":924},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","2 days vs 31 days",[454,927,929],{"transform":928},"translate(38.5,99.5) scale(0.625)",[458,930],{"d":905,"fill":461,"stroke":931,"style":932},"var(--accent,#f43f8f)","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[465,934,936],{"x":922,"y":935,"style":924},"137.0","always looks like a collapse early in the month",[454,938,940],{"transform":939},"translate(38.5,125.5) scale(0.625)",[458,941],{"d":905,"fill":461,"stroke":931,"style":932},[465,943,945],{"x":922,"y":944,"style":924},"163.0","improves by itself as the month goes on",[454,947,949],{"transform":948},"translate(38.5,151.5) scale(0.625)",[458,950],{"d":905,"fill":461,"stroke":931,"style":932},[429,952],{"x":953,"y":446,"width":895,"height":896,"rx":897,"fill":540,"stroke":541,"style":452},"389.0",[454,955,957],{"transform":956},"translate(551.5,29.0) scale(1.083)",[458,958],{"d":959,"fill":461,"stroke":548,"style":907},"M5 12.5l4.5 4.5L19 7",[465,961,964],{"x":962,"y":911,"style":963},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","To the same day last year",[50,966],{"x1":967,"y1":917,"x2":968,"y2":917,"stroke":541,"style":919},"405.0","724.0",[465,970,972],{"x":971,"y":923,"style":924},"429.0","1–2 Oct 2026 vs 1–2 Oct 2025",[454,974,976],{"transform":975},"translate(407.5,99.5) scale(0.625)",[458,977],{"d":959,"fill":461,"stroke":541,"style":932},[465,979,980],{"x":971,"y":935,"style":924},"like-for-like calendar days",[454,982,984],{"transform":983},"translate(407.5,125.5) scale(0.625)",[458,985],{"d":959,"fill":461,"stroke":541,"style":932},[465,987,988],{"x":971,"y":944,"style":924},"trading days can still differ",[454,990,992],{"transform":991},"translate(407.5,151.5) scale(0.625)",[458,993],{"d":994,"fill":461,"stroke":489,"style":932},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[34,996,998],{"id":997},"step-3-running-period-to-date-columns","Step 3 — Running period-to-date columns",[10,1000,1001],{},"Daily reports often want a column that accumulates through the month and resets on the first — the running MTD next to each day's sales:",[39,1003,1005],{"className":78,"code":1004,"language":80,"meta":44,"style":44},"recent = sales[sales[\"Date\"] >= \"2026-09-01\"].copy()\nrecent[\"MTD\"] = recent.groupby(recent[\"Date\"].dt.to_period(\"M\"))[\"Sales\"].cumsum()\nrecent[\"YTD\"] = sales.groupby(sales[\"Date\"].dt.year)[\"Sales\"].cumsum().loc[recent.index]\nprint(recent.tail(5).to_string(index=False))\nprint(recent.loc[recent[\"Date\"].isin(pd.to_datetime([\"2026-09-30\", \"2026-10-01\"])), [\"Date\", \"Sales\", \"MTD\"]].to_string(index=False))\n",[19,1006,1007,1029,1058,1081,1101],{"__ignoreMap":44},[48,1008,1009,1012,1014,1017,1019,1021,1023,1026],{"class":50,"line":51},[48,1010,1011],{"class":91},"recent ",[48,1013,114],{"class":87},[48,1015,1016],{"class":91}," sales[sales[",[48,1018,135],{"class":58},[48,1020,200],{"class":91},[48,1022,203],{"class":87},[48,1024,1025],{"class":58}," \"2026-09-01\"",[48,1027,1028],{"class":91},"].copy()\n",[48,1030,1031,1034,1036,1038,1040,1043,1045,1048,1050,1053,1055],{"class":50,"line":101},[48,1032,1033],{"class":91},"recent[",[48,1035,259],{"class":58},[48,1037,200],{"class":91},[48,1039,114],{"class":87},[48,1041,1042],{"class":91}," recent.groupby(recent[",[48,1044,135],{"class":58},[48,1046,1047],{"class":91},"].dt.to_period(",[48,1049,264],{"class":58},[48,1051,1052],{"class":91},"))[",[48,1054,233],{"class":58},[48,1056,1057],{"class":91},"].cumsum()\n",[48,1059,1060,1062,1064,1066,1068,1071,1073,1076,1078],{"class":50,"line":108},[48,1061,1033],{"class":91},[48,1063,280],{"class":58},[48,1065,200],{"class":91},[48,1067,114],{"class":87},[48,1069,1070],{"class":91}," sales.groupby(sales[",[48,1072,135],{"class":58},[48,1074,1075],{"class":91},"].dt.year)[",[48,1077,233],{"class":58},[48,1079,1080],{"class":91},"].cumsum().loc[recent.index]\n",[48,1082,1083,1085,1088,1091,1093,1095,1097,1099],{"class":50,"line":141},[48,1084,847],{"class":305},[48,1086,1087],{"class":91},"(recent.tail(",[48,1089,1090],{"class":305},"5",[48,1092,855],{"class":91},[48,1094,858],{"class":126},[48,1096,114],{"class":87},[48,1098,863],{"class":305},[48,1100,866],{"class":91},[48,1102,1103,1105,1108,1110,1113,1116,1118,1121,1124,1126,1128,1130,1132,1134,1137,1139,1141,1143],{"class":50,"line":158},[48,1104,847],{"class":305},[48,1106,1107],{"class":91},"(recent.loc[recent[",[48,1109,135],{"class":58},[48,1111,1112],{"class":91},"].isin(pd.to_datetime([",[48,1114,1115],{"class":58},"\"2026-09-30\"",[48,1117,123],{"class":91},[48,1119,1120],{"class":58},"\"2026-10-01\"",[48,1122,1123],{"class":91},"])), [",[48,1125,135],{"class":58},[48,1127,123],{"class":91},[48,1129,233],{"class":58},[48,1131,123],{"class":91},[48,1133,259],{"class":58},[48,1135,1136],{"class":91},"]].to_string(",[48,1138,858],{"class":126},[48,1140,114],{"class":87},[48,1142,863],{"class":305},[48,1144,866],{"class":91},[10,1146,1147,1148,32],{},"Grouping by the month period and taking a cumulative sum gives a running total that restarts each month: 30 September carries the full September total, 1 October starts again from that day's sales. The YTD column is computed on the full table and then aligned to the recent rows by index, so it includes January to August. Running totals in general, including year-over-year growth, are in ",[28,1149,1151],{"href":1150},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frunning-totals-and-year-over-year-growth-in-pandas\u002F","Running Totals and Year-Over-Year Growth in pandas",[34,1153,1155],{"id":1154},"step-4-fiscal-year-to-date","Step 4 — Fiscal year-to-date",[10,1157,1158],{},"With a fiscal year starting in April, YTD starts on 1 April. Pandas periods can carry the fiscal year directly through an anchored frequency:",[39,1160,1162],{"className":78,"code":1161,"language":80,"meta":44,"style":44},"fiscal_year = sales[\"Date\"].dt.to_period(\"Q-MAR\").dt.qyear      # FY ends in March\nsales[\"FiscalYear\"] = \"FY\" + fiscal_year.astype(str).str[-2:]\nfy_label = sales.loc[sales[\"Date\"] == as_of, \"FiscalYear\"].iloc[0]\nfy_start = pd.Timestamp(year=as_of.year if as_of.month >= 4 else as_of.year - 1, month=4, day=1)\nfytd = sales.loc[(sales[\"Date\"] >= fy_start) & (sales[\"Date\"] \u003C= as_of), \"Sales\"].sum()\nprint(f\"{fy_label} to date ({fy_start:%d %b %Y} – {as_of:%d %b %Y}): {fytd:,.0f}\")\n",[19,1163,1164,1188,1223,1253,1311,1349],{"__ignoreMap":44},[48,1165,1166,1169,1171,1174,1176,1178,1181,1184],{"class":50,"line":51},[48,1167,1168],{"class":91},"fiscal_year ",[48,1170,114],{"class":87},[48,1172,1173],{"class":91}," sales[",[48,1175,135],{"class":58},[48,1177,1047],{"class":91},[48,1179,1180],{"class":58},"\"Q-MAR\"",[48,1182,1183],{"class":91},").dt.qyear      ",[48,1185,1187],{"class":1186},"s-wDw","# FY ends in March\n",[48,1189,1190,1193,1196,1198,1200,1203,1206,1209,1212,1215,1217,1220],{"class":50,"line":101},[48,1191,1192],{"class":91},"sales[",[48,1194,1195],{"class":58},"\"FiscalYear\"",[48,1197,200],{"class":91},[48,1199,114],{"class":87},[48,1201,1202],{"class":58}," \"FY\"",[48,1204,1205],{"class":87}," +",[48,1207,1208],{"class":91}," fiscal_year.astype(",[48,1210,1211],{"class":305},"str",[48,1213,1214],{"class":91},").str[",[48,1216,594],{"class":87},[48,1218,1219],{"class":305},"2",[48,1221,1222],{"class":91},":]\n",[48,1224,1225,1228,1230,1233,1235,1237,1240,1243,1245,1248,1250],{"class":50,"line":108},[48,1226,1227],{"class":91},"fy_label ",[48,1229,114],{"class":87},[48,1231,1232],{"class":91}," sales.loc[sales[",[48,1234,135],{"class":58},[48,1236,200],{"class":91},[48,1238,1239],{"class":87},"==",[48,1241,1242],{"class":91}," as_of, ",[48,1244,1195],{"class":58},[48,1246,1247],{"class":91},"].iloc[",[48,1249,432],{"class":305},[48,1251,1252],{"class":91},"]\n",[48,1254,1255,1258,1260,1262,1265,1267,1270,1273,1276,1278,1281,1284,1287,1289,1291,1293,1296,1298,1300,1302,1305,1307,1309],{"class":50,"line":141},[48,1256,1257],{"class":91},"fy_start ",[48,1259,114],{"class":87},[48,1261,149],{"class":91},[48,1263,1264],{"class":126},"year",[48,1266,114],{"class":87},[48,1268,1269],{"class":91},"as_of.year ",[48,1271,1272],{"class":87},"if",[48,1274,1275],{"class":91}," as_of.month ",[48,1277,203],{"class":87},[48,1279,1280],{"class":305}," 4",[48,1282,1283],{"class":87}," else",[48,1285,1286],{"class":91}," as_of.year ",[48,1288,594],{"class":87},[48,1290,767],{"class":305},[48,1292,123],{"class":91},[48,1294,1295],{"class":126},"month",[48,1297,114],{"class":87},[48,1299,431],{"class":305},[48,1301,123],{"class":91},[48,1303,1304],{"class":126},"day",[48,1306,114],{"class":87},[48,1308,605],{"class":305},[48,1310,155],{"class":91},[48,1312,1313,1316,1318,1321,1323,1325,1327,1330,1332,1335,1337,1339,1341,1344,1346],{"class":50,"line":158},[48,1314,1315],{"class":91},"fytd ",[48,1317,114],{"class":87},[48,1319,1320],{"class":91}," sales.loc[(sales[",[48,1322,135],{"class":58},[48,1324,200],{"class":91},[48,1326,203],{"class":87},[48,1328,1329],{"class":91}," fy_start) ",[48,1331,209],{"class":87},[48,1333,1334],{"class":91}," (sales[",[48,1336,135],{"class":58},[48,1338,200],{"class":91},[48,1340,218],{"class":87},[48,1342,1343],{"class":91}," as_of), ",[48,1345,233],{"class":58},[48,1347,1348],{"class":91},"].sum()\n",[48,1350,1351,1353,1355,1357,1359,1361,1364,1366,1369,1371,1374,1376,1378,1380,1382,1384,1386,1388,1390,1392,1394,1397,1399,1401,1403,1405,1407,1409,1411,1413,1416,1418,1421,1424,1426,1428],{"class":50,"line":163},[48,1352,847],{"class":305},[48,1354,309],{"class":91},[48,1356,312],{"class":87},[48,1358,315],{"class":58},[48,1360,319],{"class":318},[48,1362,1363],{"class":91},"fy_label",[48,1365,325],{"class":318},[48,1367,1368],{"class":58}," to date (",[48,1370,319],{"class":318},[48,1372,1373],{"class":91},"fy_start",[48,1375,349],{"class":87},[48,1377,352],{"class":91},[48,1379,355],{"class":87},[48,1381,352],{"class":91},[48,1383,360],{"class":87},[48,1385,352],{"class":91},[48,1387,365],{"class":87},[48,1389,325],{"class":318},[48,1391,370],{"class":58},[48,1393,319],{"class":318},[48,1395,1396],{"class":91},"as_of",[48,1398,349],{"class":87},[48,1400,352],{"class":91},[48,1402,355],{"class":87},[48,1404,352],{"class":91},[48,1406,360],{"class":87},[48,1408,352],{"class":91},[48,1410,365],{"class":87},[48,1412,325],{"class":318},[48,1414,1415],{"class":58},"): ",[48,1417,319],{"class":318},[48,1419,1420],{"class":91},"fytd",[48,1422,1423],{"class":87},":,.0f",[48,1425,325],{"class":318},[48,1427,315],{"class":58},[48,1429,155],{"class":91},[10,1431,1432,1435,1436,1439,1440,1444],{},[19,1433,1434],{},"to_period(\"Q-MAR\")"," creates quarters for a year ending in March, and ",[19,1437,1438],{},".qyear"," is the fiscal year each date belongs to — 2 October 2026 is in fiscal year 2027, labelled FY27 here. The same labels come out of the date table in ",[28,1441,1443],{"href":1442},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-date-table-for-excel-reports-with-pandas\u002F","Build a Date Table for Excel Reports with pandas",", which is the better home for fiscal logic when several reports share it.",[34,1446,1448],{"id":1447},"step-5-compare-per-trading-day-when-calendars-differ","Step 5 — Compare per trading day when calendars differ",[10,1450,1451],{},"When the number of trading days differs between the two windows, compare averages per trading day alongside the totals:",[39,1453,1455],{"className":78,"code":1454,"language":80,"meta":44,"style":44},"def window(df, start, end):\n    w = df[(df[\"Date\"] >= start) & (df[\"Date\"] \u003C= end)]\n    return w[\"Sales\"].sum(), w[\"Date\"].nunique()\n\nthis_total, this_days = window(sales, pd.Timestamp(\"2026-10-01\"), as_of)\nlast_total, last_days = window(sales, pd.Timestamp(\"2025-10-01\"), as_of - pd.DateOffset(years=1))\nprint(f\"this year: {this_total:,.0f} over {this_days} trading days = {this_total \u002F this_days:,.0f}\u002Fday\")\nprint(f\"last year: {last_total:,.0f} over {last_days} trading days = {last_total \u002F last_days:,.0f}\u002Fday\")\n",[19,1456,1457,1467,1498,1515,1519,1534,1561,1613],{"__ignoreMap":44},[48,1458,1459,1461,1464],{"class":50,"line":51},[48,1460,166],{"class":87},[48,1462,1463],{"class":169}," window",[48,1465,1466],{"class":91},"(df, start, end):\n",[48,1468,1469,1472,1474,1477,1479,1481,1483,1485,1487,1489,1491,1493,1495],{"class":50,"line":101},[48,1470,1471],{"class":91},"    w ",[48,1473,114],{"class":87},[48,1475,1476],{"class":91}," df[(df[",[48,1478,135],{"class":58},[48,1480,200],{"class":91},[48,1482,203],{"class":87},[48,1484,206],{"class":91},[48,1486,209],{"class":87},[48,1488,195],{"class":91},[48,1490,135],{"class":58},[48,1492,200],{"class":91},[48,1494,218],{"class":87},[48,1496,1497],{"class":91}," end)]\n",[48,1499,1500,1502,1505,1507,1510,1512],{"class":50,"line":108},[48,1501,227],{"class":87},[48,1503,1504],{"class":91}," w[",[48,1506,233],{"class":58},[48,1508,1509],{"class":91},"].sum(), w[",[48,1511,135],{"class":58},[48,1513,1514],{"class":91},"].nunique()\n",[48,1516,1517],{"class":50,"line":141},[48,1518,105],{"emptyLinePlaceholder":104},[48,1520,1521,1524,1526,1529,1531],{"class":50,"line":158},[48,1522,1523],{"class":91},"this_total, this_days ",[48,1525,114],{"class":87},[48,1527,1528],{"class":91}," window(sales, pd.Timestamp(",[48,1530,1120],{"class":58},[48,1532,1533],{"class":91},"), as_of)\n",[48,1535,1536,1539,1541,1543,1546,1549,1551,1553,1555,1557,1559],{"class":50,"line":163},[48,1537,1538],{"class":91},"last_total, last_days ",[48,1540,114],{"class":87},[48,1542,1528],{"class":91},[48,1544,1545],{"class":58},"\"2025-10-01\"",[48,1547,1548],{"class":91},"), as_of ",[48,1550,594],{"class":87},[48,1552,597],{"class":91},[48,1554,600],{"class":126},[48,1556,114],{"class":87},[48,1558,605],{"class":305},[48,1560,866],{"class":91},[48,1562,1563,1565,1567,1569,1572,1574,1577,1579,1581,1584,1586,1589,1591,1594,1596,1599,1601,1604,1606,1608,1611],{"class":50,"line":176},[48,1564,847],{"class":305},[48,1566,309],{"class":91},[48,1568,312],{"class":87},[48,1570,1571],{"class":58},"\"this year: ",[48,1573,319],{"class":318},[48,1575,1576],{"class":91},"this_total",[48,1578,1423],{"class":87},[48,1580,325],{"class":318},[48,1582,1583],{"class":58}," over ",[48,1585,319],{"class":318},[48,1587,1588],{"class":91},"this_days",[48,1590,325],{"class":318},[48,1592,1593],{"class":58}," trading days = ",[48,1595,319],{"class":318},[48,1597,1598],{"class":91},"this_total ",[48,1600,759],{"class":87},[48,1602,1603],{"class":91}," this_days",[48,1605,1423],{"class":87},[48,1607,325],{"class":318},[48,1609,1610],{"class":58},"\u002Fday\"",[48,1612,155],{"class":91},[48,1614,1615,1617,1619,1621,1624,1626,1629,1631,1633,1635,1637,1640,1642,1644,1646,1649,1651,1654,1656,1658,1660],{"class":50,"line":187},[48,1616,847],{"class":305},[48,1618,309],{"class":91},[48,1620,312],{"class":87},[48,1622,1623],{"class":58},"\"last year: ",[48,1625,319],{"class":318},[48,1627,1628],{"class":91},"last_total",[48,1630,1423],{"class":87},[48,1632,325],{"class":318},[48,1634,1583],{"class":58},[48,1636,319],{"class":318},[48,1638,1639],{"class":91},"last_days",[48,1641,325],{"class":318},[48,1643,1593],{"class":58},[48,1645,319],{"class":318},[48,1647,1648],{"class":91},"last_total ",[48,1650,759],{"class":87},[48,1652,1653],{"class":91}," last_days",[48,1655,1423],{"class":87},[48,1657,325],{"class":318},[48,1659,1610],{"class":58},[48,1661,155],{"class":91},[10,1663,1664],{},"Counting distinct dates with sales gives the trading days in each window. In this sample both windows have two trading days, so the totals and the per-day averages tell the same story — an 11% drop on a two-day base, which says more about two individual days than about the month. On a window that straddles a Sunday in one year and not in the other, the two measures diverge. The totals answer \"how much\", the per-day averages answer \"how well\", and showing both prevents an extra trading day from passing as growth. Early in a month, when windows are a few days long, the per-day figure is the more honest of the two.",[34,1666,1668],{"id":1667},"step-6-write-the-summary-sheet","Step 6 — Write the summary sheet",[39,1670,1672],{"className":78,"code":1671,"language":80,"meta":44,"style":44},"summary[\"This year\"] = summary[\"This year\"].round(0)\nsummary[\"Last year\"] = summary[\"Last year\"].round(0)\nsummary[\"Change %\"] = (summary[\"Change %\"] \u002F 100).round(4)\nwith pd.ExcelWriter(\"period_to_date.xlsx\", engine=\"xlsxwriter\") as xw:\n    summary.to_excel(xw, sheet_name=\"Summary\", index=False, startrow=2)\n    book, ws = xw.book, xw.sheets[\"Summary\"]\n    ws.write(0, 0, f\"Period-to-date sales as of {as_of:%d %B %Y}\", book.add_format({\"bold\": True, \"font_size\": 13}))\n    ws.set_column(\"B:C\", 14, book.add_format({\"num_format\": \"#,##0\"}))\n    ws.set_column(\"D:D\", 10, book.add_format({\"num_format\": \"+0.0%;-0.0%\"}))\n    ws.set_column(\"E:E\", 26)\nprint(summary.to_string(index=False))\n",[19,1673,1674,1697,1717,1746,1774,1806,1820,1885,1909,1932,1946],{"__ignoreMap":44},[48,1675,1676,1679,1681,1683,1685,1688,1690,1693,1695],{"class":50,"line":51},[48,1677,1678],{"class":91},"summary[",[48,1680,739],{"class":58},[48,1682,200],{"class":91},[48,1684,114],{"class":87},[48,1686,1687],{"class":91}," summary[",[48,1689,739],{"class":58},[48,1691,1692],{"class":91},"].round(",[48,1694,432],{"class":305},[48,1696,155],{"class":91},[48,1698,1699,1701,1703,1705,1707,1709,1711,1713,1715],{"class":50,"line":101},[48,1700,1678],{"class":91},[48,1702,745],{"class":58},[48,1704,200],{"class":91},[48,1706,114],{"class":87},[48,1708,1687],{"class":91},[48,1710,745],{"class":58},[48,1712,1692],{"class":91},[48,1714,432],{"class":305},[48,1716,155],{"class":91},[48,1718,1719,1721,1724,1726,1728,1731,1733,1735,1737,1739,1742,1744],{"class":50,"line":108},[48,1720,1678],{"class":91},[48,1722,1723],{"class":58},"\"Change %\"",[48,1725,200],{"class":91},[48,1727,114],{"class":87},[48,1729,1730],{"class":91}," (summary[",[48,1732,1723],{"class":58},[48,1734,200],{"class":91},[48,1736,759],{"class":87},[48,1738,776],{"class":305},[48,1740,1741],{"class":91},").round(",[48,1743,431],{"class":305},[48,1745,155],{"class":91},[48,1747,1748,1751,1754,1757,1759,1762,1764,1767,1769,1771],{"class":50,"line":141},[48,1749,1750],{"class":87},"with",[48,1752,1753],{"class":91}," pd.ExcelWriter(",[48,1755,1756],{"class":58},"\"period_to_date.xlsx\"",[48,1758,123],{"class":91},[48,1760,1761],{"class":126},"engine",[48,1763,114],{"class":87},[48,1765,1766],{"class":58},"\"xlsxwriter\"",[48,1768,770],{"class":91},[48,1770,95],{"class":87},[48,1772,1773],{"class":91}," xw:\n",[48,1775,1776,1779,1782,1784,1787,1789,1791,1793,1795,1797,1800,1802,1804],{"class":50,"line":158},[48,1777,1778],{"class":91},"    summary.to_excel(xw, ",[48,1780,1781],{"class":126},"sheet_name",[48,1783,114],{"class":87},[48,1785,1786],{"class":58},"\"Summary\"",[48,1788,123],{"class":91},[48,1790,858],{"class":126},[48,1792,114],{"class":87},[48,1794,863],{"class":305},[48,1796,123],{"class":91},[48,1798,1799],{"class":126},"startrow",[48,1801,114],{"class":87},[48,1803,1219],{"class":305},[48,1805,155],{"class":91},[48,1807,1808,1811,1813,1816,1818],{"class":50,"line":163},[48,1809,1810],{"class":91},"    book, ws ",[48,1812,114],{"class":87},[48,1814,1815],{"class":91}," xw.book, xw.sheets[",[48,1817,1786],{"class":58},[48,1819,1252],{"class":91},[48,1821,1822,1825,1827,1829,1831,1833,1835,1838,1840,1842,1844,1846,1848,1850,1853,1855,1857,1859,1861,1864,1867,1869,1872,1874,1877,1879,1882],{"class":50,"line":176},[48,1823,1824],{"class":91},"    ws.write(",[48,1826,432],{"class":305},[48,1828,123],{"class":91},[48,1830,432],{"class":305},[48,1832,123],{"class":91},[48,1834,312],{"class":87},[48,1836,1837],{"class":58},"\"Period-to-date sales as of ",[48,1839,319],{"class":318},[48,1841,1396],{"class":91},[48,1843,349],{"class":87},[48,1845,352],{"class":91},[48,1847,355],{"class":87},[48,1849,352],{"class":91},[48,1851,1852],{"class":87},"B ",[48,1854,352],{"class":91},[48,1856,365],{"class":87},[48,1858,325],{"class":318},[48,1860,315],{"class":58},[48,1862,1863],{"class":91},", book.add_format({",[48,1865,1866],{"class":58},"\"bold\"",[48,1868,328],{"class":91},[48,1870,1871],{"class":305},"True",[48,1873,123],{"class":91},[48,1875,1876],{"class":58},"\"font_size\"",[48,1878,328],{"class":91},[48,1880,1881],{"class":305},"13",[48,1883,1884],{"class":91},"}))\n",[48,1886,1887,1890,1893,1895,1897,1899,1902,1904,1907],{"class":50,"line":187},[48,1888,1889],{"class":91},"    ws.set_column(",[48,1891,1892],{"class":58},"\"B:C\"",[48,1894,123],{"class":91},[48,1896,897],{"class":305},[48,1898,1863],{"class":91},[48,1900,1901],{"class":58},"\"num_format\"",[48,1903,328],{"class":91},[48,1905,1906],{"class":58},"\"#,##0\"",[48,1908,1884],{"class":91},[48,1910,1911,1913,1916,1918,1921,1923,1925,1927,1930],{"class":50,"line":224},[48,1912,1889],{"class":91},[48,1914,1915],{"class":58},"\"D:D\"",[48,1917,123],{"class":91},[48,1919,1920],{"class":305},"10",[48,1922,1863],{"class":91},[48,1924,1901],{"class":58},[48,1926,328],{"class":91},[48,1928,1929],{"class":58},"\"+0.0%;-0.0%\"",[48,1931,1884],{"class":91},[48,1933,1934,1936,1939,1941,1944],{"class":50,"line":239},[48,1935,1889],{"class":91},[48,1937,1938],{"class":58},"\"E:E\"",[48,1940,123],{"class":91},[48,1942,1943],{"class":305},"26",[48,1945,155],{"class":91},[48,1947,1948,1950,1953,1955,1957,1959],{"class":50,"line":244},[48,1949,847],{"class":305},[48,1951,1952],{"class":91},"(summary.to_string(",[48,1954,858],{"class":126},[48,1956,114],{"class":87},[48,1958,863],{"class":305},[48,1960,866],{"class":91},[10,1962,1963],{},"The title states the report date, the windows are spelled out in the last column, and the change is formatted with an explicit sign. A reader can verify every number against the source without asking how it was computed — the best defence against \"these don't match my spreadsheet\".",[410,1965,419,1971,419,1974,419,1977,419,1982,419,1987,419,1991,419,1997,419,2001,419,2005,419,2009,419,2013,419,2017,419,2021,419,2025,419,2029,419,2033,419,2037,419,2039,419,2043,419,2045,419,2048,419,2050,419,2053,419,2055,419,2058,419,2060,419,2063,419,2066,419,2069,419,2071,419,2075,419,2077,419,2080,419,2082,419,2085,419,2087,419,2091,419,2093,419,2096,419,2099,419,2103,419,2105,419,2108,419,2110,419,2112,419,2114,419,2116,419,2118,419,2120,419,2122,419,2124,419,2127,419,2130,419,2132,419,2135,419,2137,419,2140,419,2142,419,2145,419,2147,419,2150,419,2152,419,2155],{"viewBox":1966,"role":413,"ariaLabelledBy":1967,"xmlns":417,"style":1970},"137 0 485 194",[1968,1969],"ptd-summary-t","ptd-summary-d","width:100%;max-width:485px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[421,1972,1973],{"id":1968},"The period-to-date summary sheet",[425,1975,1976],{"id":1969},"A summary sheet titled with the report date, listing MTD, QTD and YTD with this year's total, last year's total for the same window, the percentage change and the last-year window used.",[429,1978],{"x":1979,"y":432,"width":1980,"height":1981,"fill":435},"137","485","194",[429,1983],{"x":1984,"y":446,"width":1985,"height":1986,"rx":432,"fill":450,"stroke":451,"style":919},"153.6","32.0","24.0",[429,1988],{"x":1989,"y":446,"width":1990,"height":1986,"rx":432,"fill":450,"stroke":451,"style":919},"185.6","71.4",[465,1992,1996],{"x":1993,"y":1994,"style":1995},"221.2","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[429,1998],{"x":1999,"y":446,"width":2000,"height":1986,"rx":432,"fill":450,"stroke":451,"style":919},"256.9","74.1",[465,2002,2004],{"x":2003,"y":1994,"style":1995},"294.0","B",[429,2006],{"x":2007,"y":446,"width":2008,"height":1986,"rx":432,"fill":450,"stroke":451,"style":919},"331.0","73.6",[465,2010,2012],{"x":2011,"y":1994,"style":1995},"367.8","C",[429,2014],{"x":2015,"y":446,"width":2016,"height":1986,"rx":432,"fill":450,"stroke":451,"style":919},"404.6","65.8",[465,2018,2020],{"x":2019,"y":1994,"style":1995},"437.5","D",[429,2022],{"x":2023,"y":446,"width":2024,"height":1986,"rx":432,"fill":450,"stroke":451,"style":919},"470.4","136.0",[465,2026,2028],{"x":2027,"y":1994,"style":1995},"538.4","E",[429,2030],{"x":1984,"y":2031,"width":1985,"height":2032,"rx":432,"fill":450,"stroke":451,"style":919},"40.0","28.0",[465,2034,605],{"x":2035,"y":2036,"style":1995},"169.6","59.0",[429,2038],{"x":1989,"y":2031,"width":1990,"height":2032,"rx":432,"fill":517,"stroke":518,"style":479},[465,2040,2042],{"x":1993,"y":2036,"style":2041},"font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Measure",[429,2044],{"x":1999,"y":2031,"width":2000,"height":2032,"rx":432,"fill":517,"stroke":518,"style":479},[465,2046,2047],{"x":2003,"y":2036,"style":2041},"This year",[429,2049],{"x":2007,"y":2031,"width":2008,"height":2032,"rx":432,"fill":517,"stroke":518,"style":479},[465,2051,2052],{"x":2011,"y":2036,"style":2041},"Last year",[429,2054],{"x":2015,"y":2031,"width":2016,"height":2032,"rx":432,"fill":517,"stroke":518,"style":479},[465,2056,2057],{"x":2019,"y":2036,"style":2041},"Change",[429,2059],{"x":2023,"y":2031,"width":2024,"height":2032,"rx":432,"fill":517,"stroke":518,"style":479},[465,2061,2062],{"x":2027,"y":2036,"style":2041},"LY window",[429,2064],{"x":1984,"y":2065,"width":1985,"height":2032,"rx":432,"fill":450,"stroke":451,"style":919},"68.0",[465,2067,1219],{"x":2035,"y":2068,"style":1995},"87.0",[429,2070],{"x":1989,"y":2065,"width":1990,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2072,2074],{"x":1993,"y":2068,"style":2073},"font-size:10.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","MTD",[429,2076],{"x":1999,"y":2065,"width":2000,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2078,2079],{"x":2003,"y":2068,"style":2073},"2,231",[429,2081],{"x":2007,"y":2065,"width":2008,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2083,2084],{"x":2011,"y":2068,"style":2073},"2,510",[429,2086],{"x":2015,"y":2065,"width":2016,"height":2032,"rx":432,"fill":540,"stroke":541,"style":479},[465,2088,2090],{"x":2019,"y":2068,"style":2089},"font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","-11.1%",[429,2092],{"x":2023,"y":2065,"width":2024,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2094,2095],{"x":2027,"y":2068,"style":2073},"01 Oct – 02 Oct 2025",[429,2097],{"x":1984,"y":2098,"width":1985,"height":2032,"rx":432,"fill":450,"stroke":451,"style":919},"96.0",[465,2100,2102],{"x":2035,"y":2101,"style":1995},"115.0","3",[429,2104],{"x":1989,"y":2098,"width":1990,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2106,2107],{"x":1993,"y":2101,"style":2073},"QTD",[429,2109],{"x":1999,"y":2098,"width":2000,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2111,2079],{"x":2003,"y":2101,"style":2073},[429,2113],{"x":2007,"y":2098,"width":2008,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2115,2084],{"x":2011,"y":2101,"style":2073},[429,2117],{"x":2015,"y":2098,"width":2016,"height":2032,"rx":432,"fill":540,"stroke":541,"style":479},[465,2119,2090],{"x":2019,"y":2101,"style":2089},[429,2121],{"x":2023,"y":2098,"width":2024,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2123,2095],{"x":2027,"y":2101,"style":2073},[429,2125],{"x":1984,"y":2126,"width":1985,"height":2032,"rx":432,"fill":450,"stroke":451,"style":919},"124.0",[465,2128,431],{"x":2035,"y":2129,"style":1995},"143.0",[429,2131],{"x":1989,"y":2126,"width":1990,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2133,2134],{"x":1993,"y":2129,"style":2073},"YTD",[429,2136],{"x":1999,"y":2126,"width":2000,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2138,2139],{"x":2003,"y":2129,"style":2073},"280,862",[429,2141],{"x":2007,"y":2126,"width":2008,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2143,2144],{"x":2011,"y":2129,"style":2073},"287,787",[429,2146],{"x":2015,"y":2126,"width":2016,"height":2032,"rx":432,"fill":540,"stroke":541,"style":479},[465,2148,2149],{"x":2019,"y":2129,"style":2089},"-2.4%",[429,2151],{"x":2023,"y":2126,"width":2024,"height":2032,"rx":432,"fill":435,"stroke":451,"style":919},[465,2153,2154],{"x":2027,"y":2129,"style":2073},"01 Jan – 02 Oct 2025",[465,2156,2160],{"x":2157,"y":2158,"style":2159},"380.0","174.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Early in a month a few days decide the MTD change; YTD moves slowly.",[34,2162,2164],{"id":2163},"performance-notes","Performance notes",[10,2166,2167,2168,2171],{},"Each period-to-date figure is a boolean mask and a sum over the date column, which pandas evaluates in milliseconds even on millions of rows; computing a dozen measures by masking repeatedly is fine. If the table is very large and the report needs many windows, sort by date once and use ",[19,2169,2170],{},"searchsorted"," to find window boundaries, or aggregate to daily totals first — summing 600 daily rows is faster than masking 6 million transaction rows each time, and gives the same answer.",[34,2173,2175],{"id":2174},"common-pitfalls","Common pitfalls",[2177,2178,2179,2187,2193,2199,2209],"ul",{},[2180,2181,2182,2186],"li",{},[2183,2184,2185],"strong",{},"Comparing with last year's whole period."," End last year's window on the same day.",[2180,2188,2189,2192],{},[2183,2190,2191],{},"Using today's date inside the calculation."," Pass the report date in, so reruns are reproducible.",[2180,2194,2195,2198],{},[2183,2196,2197],{},"Incomplete data for the report date."," If today's sales arrive overnight, report up to yesterday.",[2180,2200,2201,2204,2205,2208],{},[2183,2202,2203],{},"Leap days."," ",[19,2206,2207],{},"DateOffset(years=1)"," maps 29 February to 28 February; be consistent about it.",[2180,2210,2211,2214],{},[2183,2212,2213],{},"Trading-day differences."," Show per-trading-day averages next to totals.",[34,2216,2218],{"id":2217},"conclusion","Conclusion",[10,2220,2221,2222,2225,2226,2228,2229,2232,2233,2236],{},"Period-to-date figures are sums over a window from the start of the period to the report date: compute the start with ",[19,2223,2224],{},"to_period(freq).start_time",", mask, and sum. Compare with last year by moving the report date back with ",[19,2227,2207],{}," and using the same-length window, not last year's full period. Use ",[19,2230,2231],{},"groupby(period).cumsum()"," for running columns that reset, ",[19,2234,2235],{},"Q-MAR"," periods for an April fiscal year, per-trading-day averages when calendars differ, and state the report date and windows in the output.",[34,2238,2240],{"id":2239},"frequently-asked-questions","Frequently asked questions",[10,2242,2243,2246],{},[2183,2244,2245],{},"How do I calculate month-to-date sales in pandas?","\nFind the first day of the month with as_of.to_period(\"M\").start_time, select rows with dates between that day and the report date, and sum the sales column.",[10,2248,2249,2252],{},[2183,2250,2251],{},"How do I compare MTD with the same period last year?","\nMove the report date back one year with as_of - pd.DateOffset(years=1), take the start of that month, and sum the window up to that same day. Do not compare with the whole of last year's month.",[10,2254,2255,2258],{},[2183,2256,2257],{},"How do I add a running MTD column that resets each month?","\nGroup by the month period of the date column and take a cumulative sum of the sales within each group; the total restarts on the first of every month.",[10,2260,2261,2264],{},[2183,2262,2263],{},"How do I calculate fiscal year-to-date for a year starting in April?","\nUse 1 April of the current fiscal year as the window start — the same calendar year if the report month is April or later, otherwise the previous year. to_period(\"Q-MAR\").dt.qyear gives the fiscal year number.",[34,2266,2268],{"id":2267},"related","Related",[2177,2270,2271,2277,2284,2291],{},[2180,2272,2273,2274,2276],{},"Up one level: ",[28,2275,31],{"href":30}," — date handling for reports.",[2180,2278,2279,2283],{},[28,2280,2282],{"href":2281},"\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"," — totals per full period.",[2180,2285,2286,2290],{},[28,2287,2289],{"href":2288},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumif-and-sumifs-equivalent-in-pandas\u002F","SUMIF and SUMIFS Equivalent in pandas"," — the conditional sums behind these figures.",[2180,2292,2293,2297],{},[28,2294,2296],{"href":2295},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-kpi-summary-block-to-an-excel-dashboard\u002F","Add a KPI Summary Block to an Excel Dashboard"," — presenting MTD and YTD in a dashboard.",[2299,2300,2301],"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_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}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":44,"searchDepth":101,"depth":101,"links":2303},[2304,2305,2306,2307,2308,2309,2310,2311,2312,2313,2314,2315],{"id":36,"depth":101,"text":37},{"id":74,"depth":101,"text":75},{"id":566,"depth":101,"text":567},{"id":997,"depth":101,"text":998},{"id":1154,"depth":101,"text":1155},{"id":1447,"depth":101,"text":1448},{"id":1667,"depth":101,"text":1668},{"id":2163,"depth":101,"text":2164},{"id":2174,"depth":101,"text":2175},{"id":2217,"depth":101,"text":2218},{"id":2239,"depth":101,"text":2240},{"id":2267,"depth":101,"text":2268},"2026-10-02","Compute MTD, QTD and YTD figures as of a report date, running period-to-date columns that reset each month or year, the same-period-last-year comparison that cuts last year at the same day, fiscal year-to-date with an April start, and a tidy summary table for an Excel report — including the leap-day and incomplete-data traps.","md",[2320,2322,2324,2326],{"q":2245,"a":2321},"Find the first day of the month with as_of.to_period(\"M\").start_time, select rows with dates between that day and the report date, and sum the sales column.",{"q":2251,"a":2323},"Move the report date back one year with as_of - pd.DateOffset(years=1), take the start of that month, and sum the window up to that same day. Do not compare with the whole of last year's month.",{"q":2257,"a":2325},"Group by the month period of the date column and take a cumulative sum of the sales within each group; the total restarts on the first of every month.",{"q":2263,"a":2327},"Use 1 April of the current fiscal year as the window start — the same calendar year if the report month is April or later, otherwise the previous year. to_period(\"Q-MAR\").dt.qyear gives the fiscal year number.","calculate month to date and year to date totals with pandas",null,{"breadcrumb":2331},[2332,2334,2337],{"name":2333,"item":759},"Home",{"name":2335,"item":2336},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":31,"item":30},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-month-to-date-and-year-to-date-totals-with-pandas",{"title":2340,"description":2341},"Month-to-Date (MTD) and Year-to-Date (YTD) Totals in pandas","Period-to-date totals in pandas: MTD, QTD and YTD as of a date, running groupby cumsum that resets by period, same period last year to the same day, fiscal YTD from April, leap day handling, and a summary sheet for Excel.","calculate-month-to-date-and-year-to-date-totals-with-pandas","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-month-to-date-and-year-to-date-totals-with-pandas\u002Findex",[2345,2346],"dates-and-times","pivots-and-summaries","how-to","hGBJXsJDz-lAxKz_iuXJpsjZOEw-GAjINznB1IC9MoI",[2350,2354],{"title":2351,"path":2352,"stem":2353,"children":-1},"Calculate Business Days Between Excel Dates with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-business-days-between-excel-dates-with-pandas","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-business-days-between-excel-dates-with-pandas\u002Findex",{"title":2355,"path":2356,"stem":2357,"children":-1},"Calculate the Nth Weekday of a Month","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-the-nth-weekday-of-a-month","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fcalculate-the-nth-weekday-of-a-month\u002Findex",[2359,2361],{"path":2360,"title":2335},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2362,"title":31},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data",[],[2365,2368,2371,2374,2377,2380],{"path":2366,"title":2367,"kind":2347},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl","Highlight Overdue Dates in Excel with openpyxl",{"path":2369,"title":2370,"kind":2347},"\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":2372,"title":2373,"kind":2347},"\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":2375,"title":2376,"kind":2347},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-moving-averages-with-pandas-rolling","Calculate Moving Averages with pandas rolling",{"path":2378,"title":2379,"kind":2347},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas","EOMONTH and EDATE Equivalents in pandas",{"path":2381,"title":2382,"kind":2347},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fyearfrac-and-days360-equivalents-in-python","YEARFRAC and DAYS360 Equivalents in Python",1790972101126]