[{"data":1,"prerenderedAt":2036},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-data-by-fiscal-year-and-iso-week":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-data-by-fiscal-year-and-iso-week":2004,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-data-by-fiscal-year-and-iso-week":2012,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-data-by-fiscal-year-and-iso-week":2017,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-data-by-fiscal-year-and-iso-week":2018},{"id":4,"title":5,"body":6,"dateModified":1970,"datePublished":1970,"description":1971,"extension":1972,"faq":1973,"keyword":1982,"kind":1983,"meta":1984,"navigation":256,"path":1993,"seo":1994,"slug":1996,"stem":1997,"topics":1998,"type":2002,"__hash__":2003},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-data-by-fiscal-year-and-iso-week\u002Findex.md","Group Excel Data by Fiscal Year and ISO Week with pandas",{"type":7,"value":8,"toc":1956},"minimark",[9,24,192,197,225,228,440,444,447,670,677,681,684,765,775,887,891,901,1041,1056,1060,1070,1193,1196,1200,1206,1343,1353,1491,1495,1498,1725,1731,1735,1738,1848,1851,1855,1882,1886,1893,1897,1903,1909,1915,1921,1925,1952],[10,11,12,13,18,19,23],"p",{},"Managers rarely think in calendar years. The business year may run from April to March, reports are compared \"week 23 against week 23 last year\", and a finance team numbers its months as periods 1 to 12 starting in the fiscal year's first month. An Excel export has only dates. Adding the fiscal and weekly keys correctly is easy to get almost right — and the \"almost\" shows up exactly at year ends, where a few days land in the wrong year and a weekly comparison quietly compares different weeks. This guide, part of ",[14,15,17],"a",{"href":16},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002F","Working with Dates and Times in Excel Data",", builds those keys explicitly. Month and calendar-quarter grouping is covered in ",[14,20,22],{"href":21},"\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",".",[25,26,34,35,34,39,34,43,34,51,34,59,34,69,34,80,34,87,34,92,34,96,34,100,34,106,34,112,34,118,34,122,34,124,34,126,34,131,34,137,34,142,34,145,34,147,34,149,34,154,34,161,34,166,34,169,34,171,34,173,34,176,34,181,34,185,34,188,34,190],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"4 0 752 279","img",[30,31],"fy-timeline-t","fy-timeline-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  ",[36,37,38],"title",{"id":30},"One fiscal year from April to March",[40,41,42],"desc",{"id":31},"With a fiscal year starting in April, 1 April 2026 opens period 1 of FY2026\u002F27, 1 October 2026 starts period 7, 1 January 2027 is period 10 and 31 March 2027 closes period 12, after which 1 April 2027 starts FY2027\u002F28.",[44,45],"rect",{"x":46,"y":47,"width":48,"height":49,"fill":50},"4","0","752","279","#ffffff",[52,53],"line",{"x1":54,"y1":55,"x2":56,"y2":55,"stroke":57,"style":58},"76.0","139.5","684.0","var(--muted,#5b6780)","stroke-width:2.5px",[44,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"20.0","16.0","136.0","91.5","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[70,71,73],"g",{"transform":72},"translate(76.0,29.0) scale(1.000)",[74,75],"path",{"d":76,"fill":77,"stroke":78,"style":79},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[81,82,86],"text",{"x":83,"y":84,"style":85},"88.0","74.5","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","1 Apr 2026",[81,88,91],{"x":83,"y":89,"style":90},"93.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","P1 · FY2026\u002F27",[52,93],{"x1":83,"y1":94,"x2":83,"y2":55,"stroke":57,"style":95},"107.5","stroke-width:1.5px",[97,98],"circle",{"cx":83,"cy":55,"r":99,"fill":67,"stroke":50,"style":68},"7.0",[44,101],{"x":102,"y":103,"width":63,"height":64,"rx":65,"fill":104,"stroke":105,"style":68},"166.0","171.5","#e7ebef","var(--line,#cdd5e6)",[70,107,109],{"transform":108},"translate(222.0,184.5) scale(1.000)",[74,110],{"d":76,"fill":77,"stroke":111,"style":79},"var(--text,#172033)",[81,113,117],{"x":114,"y":115,"style":116},"234.0","230.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1 Oct 2026",[81,119,121],{"x":114,"y":120,"style":90},"248.5","P7",[52,123],{"x1":114,"y1":55,"x2":114,"y2":103,"stroke":57,"style":95},[97,125],{"cx":114,"cy":55,"r":99,"fill":57,"stroke":50,"style":68},[44,127],{"x":128,"y":62,"width":63,"height":64,"rx":65,"fill":129,"stroke":130,"style":68},"312.0","#fdefd8","var(--gold,#b4740a)",[70,132,134],{"transform":133},"translate(368.0,29.0) scale(1.000)",[74,135],{"d":76,"fill":77,"stroke":136,"style":79},"var(--gold-ink,#7a4e06)",[81,138,141],{"x":139,"y":84,"style":140},"380.0","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","1 Jan 2027",[81,143,144],{"x":139,"y":89,"style":90},"P10, same FY",[52,146],{"x1":139,"y1":94,"x2":139,"y2":55,"stroke":57,"style":95},[97,148],{"cx":139,"cy":55,"r":99,"fill":130,"stroke":50,"style":68},[44,150],{"x":151,"y":103,"width":63,"height":64,"rx":65,"fill":152,"stroke":153,"style":68},"458.0","#d9f4f1","var(--teal,#0f9488)",[70,155,157],{"transform":156},"translate(514.0,184.5) scale(1.000)",[74,158],{"d":159,"fill":77,"stroke":160,"style":79},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[81,162,165],{"x":163,"y":115,"style":164},"526.0","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","31 Mar 2027",[81,167,168],{"x":163,"y":120,"style":90},"P12, year end",[52,170],{"x1":163,"y1":55,"x2":163,"y2":103,"stroke":57,"style":95},[97,172],{"cx":163,"cy":55,"r":99,"fill":153,"stroke":50,"style":68},[44,174],{"x":175,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"604.0",[70,177,179],{"transform":178},"translate(660.0,29.0) scale(1.000)",[74,180],{"d":76,"fill":77,"stroke":78,"style":79},[81,182,184],{"x":183,"y":84,"style":85},"672.0","1 Apr 2027",[81,186,187],{"x":183,"y":89,"style":90},"P1 · FY2027\u002F28",[52,189],{"x1":183,"y1":94,"x2":183,"y2":55,"stroke":57,"style":95},[97,191],{"cx":183,"cy":55,"r":99,"fill":67,"stroke":50,"style":68},[193,194,196],"h2",{"id":195},"prerequisites","Prerequisites",[198,199,204],"pre",{"className":200,"code":201,"language":202,"meta":203,"style":203},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[205,206,207],"code",{"__ignoreMap":203},[208,209,211,215,219,222],"span",{"class":52,"line":210},1,[208,212,214],{"class":213},"sMTad","pip",[208,216,218],{"class":217},"srMev"," install",[208,220,221],{"class":217}," pandas",[208,223,224],{"class":217}," openpyxl\n",[10,226,227],{},"Sales dates chosen to sit on the awkward boundaries — around New Year and around the April fiscal start:",[198,229,233],{"className":230,"code":231,"language":232,"meta":203,"style":203},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.DataFrame({\n    \"date\": pd.to_datetime([\"2025-12-29\", \"2026-01-01\", \"2026-01-04\", \"2026-01-05\", \"2026-03-31\",\n                            \"2026-04-01\", \"2026-12-31\", \"2027-01-01\", \"2027-01-03\", \"2027-01-04\"]),\n    \"amount\": [120, 80, 45, 200, 310, 95, 60, 75, 30, 150],\n})\nsales.to_excel(\"sales.xlsx\", index=False)\ndf = pd.read_excel(\"sales.xlsx\")\n","python",[205,234,235,251,258,270,306,335,396,402,425],{"__ignoreMap":203},[208,236,237,241,245,248],{"class":52,"line":210},[208,238,240],{"class":239},"s-kum","import",[208,242,244],{"class":243},"skGVy"," pandas ",[208,246,247],{"class":239},"as",[208,249,250],{"class":243}," pd\n",[208,252,254],{"class":52,"line":253},2,[208,255,257],{"emptyLinePlaceholder":256},true,"\n",[208,259,261,264,267],{"class":52,"line":260},3,[208,262,263],{"class":243},"sales ",[208,265,266],{"class":239},"=",[208,268,269],{"class":243}," pd.DataFrame({\n",[208,271,273,276,279,282,285,288,290,293,295,298,300,303],{"class":52,"line":272},4,[208,274,275],{"class":217},"    \"date\"",[208,277,278],{"class":243},": pd.to_datetime([",[208,280,281],{"class":217},"\"2025-12-29\"",[208,283,284],{"class":243},", ",[208,286,287],{"class":217},"\"2026-01-01\"",[208,289,284],{"class":243},[208,291,292],{"class":217},"\"2026-01-04\"",[208,294,284],{"class":243},[208,296,297],{"class":217},"\"2026-01-05\"",[208,299,284],{"class":243},[208,301,302],{"class":217},"\"2026-03-31\"",[208,304,305],{"class":243},",\n",[208,307,309,312,314,317,319,322,324,327,329,332],{"class":52,"line":308},5,[208,310,311],{"class":217},"                            \"2026-04-01\"",[208,313,284],{"class":243},[208,315,316],{"class":217},"\"2026-12-31\"",[208,318,284],{"class":243},[208,320,321],{"class":217},"\"2027-01-01\"",[208,323,284],{"class":243},[208,325,326],{"class":217},"\"2027-01-03\"",[208,328,284],{"class":243},[208,330,331],{"class":217},"\"2027-01-04\"",[208,333,334],{"class":243},"]),\n",[208,336,338,341,344,348,350,353,355,358,360,363,365,368,370,373,375,378,380,383,385,388,390,393],{"class":52,"line":337},6,[208,339,340],{"class":217},"    \"amount\"",[208,342,343],{"class":243},": [",[208,345,347],{"class":346},"sP0c6","120",[208,349,284],{"class":243},[208,351,352],{"class":346},"80",[208,354,284],{"class":243},[208,356,357],{"class":346},"45",[208,359,284],{"class":243},[208,361,362],{"class":346},"200",[208,364,284],{"class":243},[208,366,367],{"class":346},"310",[208,369,284],{"class":243},[208,371,372],{"class":346},"95",[208,374,284],{"class":243},[208,376,377],{"class":346},"60",[208,379,284],{"class":243},[208,381,382],{"class":346},"75",[208,384,284],{"class":243},[208,386,387],{"class":346},"30",[208,389,284],{"class":243},[208,391,392],{"class":346},"150",[208,394,395],{"class":243},"],\n",[208,397,399],{"class":52,"line":398},7,[208,400,401],{"class":243},"})\n",[208,403,405,408,411,413,417,419,422],{"class":52,"line":404},8,[208,406,407],{"class":243},"sales.to_excel(",[208,409,410],{"class":217},"\"sales.xlsx\"",[208,412,284],{"class":243},[208,414,416],{"class":415},"sa561","index",[208,418,266],{"class":239},[208,420,421],{"class":346},"False",[208,423,424],{"class":243},")\n",[208,426,428,431,433,436,438],{"class":52,"line":427},9,[208,429,430],{"class":243},"df ",[208,432,266],{"class":239},[208,434,435],{"class":243}," pd.read_excel(",[208,437,410],{"class":217},[208,439,424],{"class":243},[193,441,443],{"id":442},"step-1-fiscal-year-and-fiscal-period-from-a-start-month","Step 1 — Fiscal year and fiscal period from a start month",[10,445,446],{},"A fiscal year that starts in April runs from 1 April to 31 March and is usually named after the year it ends in, or with both years. Two lines of arithmetic cover any start month:",[198,448,450],{"className":230,"code":449,"language":232,"meta":203,"style":203},"FY_START = 4    # April\n\nfy_end = df[\"date\"].dt.year + (df[\"date\"].dt.month >= FY_START).astype(int)\ndf[\"fiscal_year\"] = fy_end\ndf[\"fy_label\"] = \"FY\" + (fy_end - 1).astype(str) + \"\u002F\" + (fy_end % 100).astype(str).str.zfill(2)\ndf[\"fiscal_period\"] = (df[\"date\"].dt.month - FY_START) % 12 + 1\nprint(df[[\"date\", \"fy_label\", \"fiscal_period\"]].astype(str).values.tolist()[3:7])\n# [['2026-01-05', 'FY2025\u002F26', '10'], ['2026-03-31', 'FY2025\u002F26', '12'],\n#  ['2026-04-01', 'FY2026\u002F27', '1'], ['2026-12-31', 'FY2026\u002F27', '9']]\n",[205,451,452,467,471,512,528,589,622,660,665],{"__ignoreMap":203},[208,453,454,457,460,463],{"class":52,"line":210},[208,455,456],{"class":346},"FY_START",[208,458,459],{"class":239}," =",[208,461,462],{"class":346}," 4",[208,464,466],{"class":465},"s-wDw","    # April\n",[208,468,469],{"class":52,"line":253},[208,470,257],{"emptyLinePlaceholder":256},[208,472,473,476,478,481,484,487,490,493,495,498,501,504,507,510],{"class":52,"line":260},[208,474,475],{"class":243},"fy_end ",[208,477,266],{"class":239},[208,479,480],{"class":243}," df[",[208,482,483],{"class":217},"\"date\"",[208,485,486],{"class":243},"].dt.year ",[208,488,489],{"class":239},"+",[208,491,492],{"class":243}," (df[",[208,494,483],{"class":217},[208,496,497],{"class":243},"].dt.month ",[208,499,500],{"class":239},">=",[208,502,503],{"class":346}," FY_START",[208,505,506],{"class":243},").astype(",[208,508,509],{"class":346},"int",[208,511,424],{"class":243},[208,513,514,517,520,523,525],{"class":52,"line":272},[208,515,516],{"class":243},"df[",[208,518,519],{"class":217},"\"fiscal_year\"",[208,521,522],{"class":243},"] ",[208,524,266],{"class":239},[208,526,527],{"class":243}," fy_end\n",[208,529,530,532,535,537,539,542,545,548,551,554,556,559,562,564,567,569,571,574,577,579,581,584,587],{"class":52,"line":308},[208,531,516],{"class":243},[208,533,534],{"class":217},"\"fy_label\"",[208,536,522],{"class":243},[208,538,266],{"class":239},[208,540,541],{"class":217}," \"FY\"",[208,543,544],{"class":239}," +",[208,546,547],{"class":243}," (fy_end ",[208,549,550],{"class":239},"-",[208,552,553],{"class":346}," 1",[208,555,506],{"class":243},[208,557,558],{"class":346},"str",[208,560,561],{"class":243},") ",[208,563,489],{"class":239},[208,565,566],{"class":217}," \"\u002F\"",[208,568,544],{"class":239},[208,570,547],{"class":243},[208,572,573],{"class":239},"%",[208,575,576],{"class":346}," 100",[208,578,506],{"class":243},[208,580,558],{"class":346},[208,582,583],{"class":243},").str.zfill(",[208,585,586],{"class":346},"2",[208,588,424],{"class":243},[208,590,591,593,596,598,600,602,604,606,608,610,612,614,617,619],{"class":52,"line":337},[208,592,516],{"class":243},[208,594,595],{"class":217},"\"fiscal_period\"",[208,597,522],{"class":243},[208,599,266],{"class":239},[208,601,492],{"class":243},[208,603,483],{"class":217},[208,605,497],{"class":243},[208,607,550],{"class":239},[208,609,503],{"class":346},[208,611,561],{"class":243},[208,613,573],{"class":239},[208,615,616],{"class":346}," 12",[208,618,544],{"class":239},[208,620,621],{"class":346}," 1\n",[208,623,624,627,630,632,634,636,638,640,643,645,648,651,654,657],{"class":52,"line":398},[208,625,626],{"class":346},"print",[208,628,629],{"class":243},"(df[[",[208,631,483],{"class":217},[208,633,284],{"class":243},[208,635,534],{"class":217},[208,637,284],{"class":243},[208,639,595],{"class":217},[208,641,642],{"class":243},"]].astype(",[208,644,558],{"class":346},[208,646,647],{"class":243},").values.tolist()[",[208,649,650],{"class":346},"3",[208,652,653],{"class":243},":",[208,655,656],{"class":346},"7",[208,658,659],{"class":243},"])\n",[208,661,662],{"class":52,"line":404},[208,663,664],{"class":465},"# [['2026-01-05', 'FY2025\u002F26', '10'], ['2026-03-31', 'FY2025\u002F26', '12'],\n",[208,666,667],{"class":52,"line":427},[208,668,669],{"class":465},"#  ['2026-04-01', 'FY2026\u002F27', '1'], ['2026-12-31', 'FY2026\u002F27', '9']]\n",[10,671,672,673,676],{},"31 March 2026 is period 12 of FY2025\u002F26 and 1 April 2026 period 1 of FY2026\u002F27. The modulo keeps the period formula valid for any start month, including January, where it reduces to the calendar month. Store the numeric ",[205,674,675],{},"fiscal_year"," for sorting and filtering, and the label only for display.",[193,678,680],{"id":679},"step-2-check-against-pandas-anchored-periods","Step 2 — Check against pandas' anchored periods",[10,682,683],{},"pandas can express the same fiscal year with an anchored period, named by the month the year ends in:",[198,685,687],{"className":230,"code":686,"language":232,"meta":203,"style":203},"fq = df[\"date\"].dt.to_period(\"Q-MAR\")\nprint(fq.astype(str).tolist()[4:6])                           # ['2026Q4', '2027Q1']\nassert (fq.dt.qyear == df[\"fiscal_year\"]).all()\ndf[\"fiscal_quarter\"] = fq.dt.quarter\n",[205,688,689,708,733,751],{"__ignoreMap":203},[208,690,691,694,696,698,700,703,706],{"class":52,"line":210},[208,692,693],{"class":243},"fq ",[208,695,266],{"class":239},[208,697,480],{"class":243},[208,699,483],{"class":217},[208,701,702],{"class":243},"].dt.to_period(",[208,704,705],{"class":217},"\"Q-MAR\"",[208,707,424],{"class":243},[208,709,710,712,715,717,720,722,724,727,730],{"class":52,"line":253},[208,711,626],{"class":346},[208,713,714],{"class":243},"(fq.astype(",[208,716,558],{"class":346},[208,718,719],{"class":243},").tolist()[",[208,721,46],{"class":346},[208,723,653],{"class":243},[208,725,726],{"class":346},"6",[208,728,729],{"class":243},"])                           ",[208,731,732],{"class":465},"# ['2026Q4', '2027Q1']\n",[208,734,735,738,741,744,746,748],{"class":52,"line":260},[208,736,737],{"class":239},"assert",[208,739,740],{"class":243}," (fq.dt.qyear ",[208,742,743],{"class":239},"==",[208,745,480],{"class":243},[208,747,519],{"class":217},[208,749,750],{"class":243},"]).all()\n",[208,752,753,755,758,760,762],{"class":52,"line":272},[208,754,516],{"class":243},[208,756,757],{"class":217},"\"fiscal_quarter\"",[208,759,522],{"class":243},[208,761,266],{"class":239},[208,763,764],{"class":243}," fq.dt.quarter\n",[10,766,767,770,771,774],{},[205,768,769],{},"qyear"," is the fiscal year in which each quarter falls, and it agrees with the arithmetic above. The period form is handy for quarters; the explicit columns are easier to read, to explain to finance colleagues and to write back to Excel, where a ",[205,772,773],{},"Period"," object would become text anyway.",[25,776,34,781,34,784,34,787,34,790,34,795,34,800,34,806,34,809,34,813,34,817,34,823,34,825,34,830,34,832,34,835,34,838,34,842,34,844,34,848,34,850,34,852,34,855,34,859,34,861,34,865,34,867,34,870,34,873,34,877,34,879,34,883,34,885],{"viewBox":777,"role":28,"ariaLabelledBy":778,"xmlns":32,"style":33},"4 0 752 208",[779,780],"fy-keys-t","fy-keys-d",[36,782,783],{"id":779},"The keys computed for four boundary dates",[40,785,786],{"id":780},"31 March 2026 is fiscal period 12 of FY2025\u002F26 and ISO week 14 of 2026. 1 April 2026 is period 1 of FY2026\u002F27, still ISO week 14. 31 December 2026 is ISO week 53 of 2026. 1 January 2027 is also ISO week 53 of ISO year 2026, although its calendar year is 2027.",[44,788],{"x":46,"y":47,"width":48,"height":789,"fill":50},"208",[44,791],{"x":61,"y":62,"width":792,"height":793,"rx":47,"fill":50,"stroke":105,"style":794},"135.5","40.0","stroke-width:1px",[44,796],{"x":797,"y":62,"width":798,"height":793,"rx":47,"fill":66,"stroke":67,"style":799},"155.5","292.3","stroke-width:1.2px",[81,801,805],{"x":802,"y":803,"style":804},"301.6","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Fiscal",[44,807],{"x":808,"y":62,"width":798,"height":793,"rx":47,"fill":66,"stroke":67,"style":799},"447.7",[81,810,812],{"x":811,"y":803,"style":804},"593.9","ISO year-week",[44,814],{"x":61,"y":815,"width":792,"height":816,"rx":47,"fill":104,"stroke":105,"style":794},"56.0","34.0",[81,818,822],{"x":819,"y":820,"style":821},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","Tue 31 Mar 2026",[44,824],{"x":797,"y":815,"width":798,"height":816,"rx":47,"fill":50,"stroke":105,"style":794},[81,826,829],{"x":802,"y":827,"style":828},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","FY2025\u002F26 P12",[44,831],{"x":808,"y":815,"width":798,"height":816,"rx":47,"fill":50,"stroke":105,"style":794},[81,833,834],{"x":811,"y":827,"style":828},"2026-W14",[44,836],{"x":61,"y":837,"width":792,"height":816,"rx":47,"fill":104,"stroke":105,"style":794},"90.0",[81,839,841],{"x":819,"y":840,"style":821},"112.0","Wed 1 Apr 2026",[44,843],{"x":797,"y":837,"width":798,"height":816,"rx":47,"fill":50,"stroke":105,"style":794},[81,845,847],{"x":802,"y":846,"style":828},"111.0","FY2026\u002F27 P1",[44,849],{"x":808,"y":837,"width":798,"height":816,"rx":47,"fill":50,"stroke":105,"style":794},[81,851,834],{"x":811,"y":846,"style":828},[44,853],{"x":61,"y":854,"width":792,"height":816,"rx":47,"fill":104,"stroke":105,"style":794},"124.0",[81,856,858],{"x":819,"y":857,"style":821},"146.0","Thu 31 Dec 2026",[44,860],{"x":797,"y":854,"width":798,"height":816,"rx":47,"fill":50,"stroke":105,"style":794},[81,862,864],{"x":802,"y":863,"style":828},"145.0","FY2026\u002F27 P9",[44,866],{"x":808,"y":854,"width":798,"height":816,"rx":47,"fill":50,"stroke":105,"style":794},[81,868,869],{"x":811,"y":863,"style":828},"2026-W53",[44,871],{"x":61,"y":872,"width":792,"height":816,"rx":47,"fill":104,"stroke":105,"style":794},"158.0",[81,874,876],{"x":819,"y":875,"style":821},"180.0","Fri 1 Jan 2027",[44,878],{"x":797,"y":872,"width":798,"height":816,"rx":47,"fill":50,"stroke":105,"style":794},[81,880,882],{"x":802,"y":881,"style":828},"179.0","FY2026\u002F27 P10",[44,884],{"x":808,"y":872,"width":798,"height":816,"rx":47,"fill":50,"stroke":105,"style":794},[81,886,869],{"x":811,"y":881,"style":828},[193,888,890],{"id":889},"step-3-iso-weeks-and-the-iso-year","Step 3 — ISO weeks and the ISO year",[10,892,893,896,897,900],{},[205,894,895],{},"dt.isocalendar()"," returns the ISO year, week and weekday, and matches Excel's ",[205,898,899],{},"ISOWEEKNUM",". The ISO year is the part people forget:",[198,902,904],{"className":230,"code":903,"language":232,"meta":203,"style":203},"iso = df[\"date\"].dt.isocalendar()\ndf[\"iso_year\"], df[\"iso_week\"] = iso[\"year\"], iso[\"week\"]\ndf[\"year_week\"] = df[\"date\"].dt.strftime(\"%G-W%V\")\nprint(df[[\"date\", \"year_week\"]].astype(str).values.tolist()[:2])\n# [['2025-12-29', '2026-W01'], ['2026-01-01', '2026-W01']]\nprint(df.loc[df[\"date\"] == \"2027-01-01\", [\"iso_year\", \"iso_week\"]].values.tolist())   # [[2026, 53]]\n",[205,905,906,920,952,982,1005,1010],{"__ignoreMap":203},[208,907,908,911,913,915,917],{"class":52,"line":210},[208,909,910],{"class":243},"iso ",[208,912,266],{"class":239},[208,914,480],{"class":243},[208,916,483],{"class":217},[208,918,919],{"class":243},"].dt.isocalendar()\n",[208,921,922,924,927,930,933,935,937,940,943,946,949],{"class":52,"line":253},[208,923,516],{"class":243},[208,925,926],{"class":217},"\"iso_year\"",[208,928,929],{"class":243},"], df[",[208,931,932],{"class":217},"\"iso_week\"",[208,934,522],{"class":243},[208,936,266],{"class":239},[208,938,939],{"class":243}," iso[",[208,941,942],{"class":217},"\"year\"",[208,944,945],{"class":243},"], iso[",[208,947,948],{"class":217},"\"week\"",[208,950,951],{"class":243},"]\n",[208,953,954,956,959,961,963,965,967,970,973,977,980],{"class":52,"line":260},[208,955,516],{"class":243},[208,957,958],{"class":217},"\"year_week\"",[208,960,522],{"class":243},[208,962,266],{"class":239},[208,964,480],{"class":243},[208,966,483],{"class":217},[208,968,969],{"class":243},"].dt.strftime(",[208,971,972],{"class":217},"\"",[208,974,976],{"class":975},"sSjpA","%G",[208,978,979],{"class":217},"-W%V\"",[208,981,424],{"class":243},[208,983,984,986,988,990,992,994,996,998,1001,1003],{"class":52,"line":272},[208,985,626],{"class":346},[208,987,629],{"class":243},[208,989,483],{"class":217},[208,991,284],{"class":243},[208,993,958],{"class":217},[208,995,642],{"class":243},[208,997,558],{"class":346},[208,999,1000],{"class":243},").values.tolist()[:",[208,1002,586],{"class":346},[208,1004,659],{"class":243},[208,1006,1007],{"class":52,"line":308},[208,1008,1009],{"class":465},"# [['2025-12-29', '2026-W01'], ['2026-01-01', '2026-W01']]\n",[208,1011,1012,1014,1017,1019,1021,1023,1026,1029,1031,1033,1035,1038],{"class":52,"line":337},[208,1013,626],{"class":346},[208,1015,1016],{"class":243},"(df.loc[df[",[208,1018,483],{"class":217},[208,1020,522],{"class":243},[208,1022,743],{"class":239},[208,1024,1025],{"class":217}," \"2027-01-01\"",[208,1027,1028],{"class":243},", [",[208,1030,926],{"class":217},[208,1032,284],{"class":243},[208,1034,932],{"class":217},[208,1036,1037],{"class":243},"]].values.tolist())   ",[208,1039,1040],{"class":465},"# [[2026, 53]]\n",[10,1042,1043,1044,1047,1048,1051,1052,1055],{},"29 December 2025 belongs to ISO week 1 of 2026, and 1 January 2027 to week 53 of 2026 — 2026 is one of the years with 53 ISO weeks. Grouping by calendar year and ISO week would put 1 January 2027 into \"2027 week 53\", a week that does not exist. Always pair ",[205,1045,1046],{},"iso_week"," with ",[205,1049,1050],{},"iso_year","; the ",[205,1053,1054],{},"%G-W%V"," label does that in one sortable string.",[193,1057,1059],{"id":1058},"step-4-matching-excels-weeknum","Step 4 — Matching Excel's WEEKNUM",[10,1061,1062,1063,1066,1067,1069],{},"Many workbooks use ",[205,1064,1065],{},"WEEKNUM(date)"," rather than ",[205,1068,899],{},". With its default return type, Excel documents a different rule: weeks start on Sunday and week 1 is the week that contains 1 January, so the first and last weeks of a year are often partial. Reproduce it when a report must match the workbook's numbering:",[198,1071,1073],{"className":230,"code":1072,"language":232,"meta":203,"style":203},"jan1 = pd.to_datetime(df[\"date\"].dt.year.astype(str) + \"-01-01\")\noffset = (jan1.dt.dayofweek + 1) % 7                    # days from the Sunday on\u002Fbefore 1 January\ndf[\"weeknum_excel\"] = ((df[\"date\"] - jan1).dt.days + offset) \u002F\u002F 7 + 1\nprint(df[[\"date\", \"iso_week\", \"weeknum_excel\"]].astype(str).values.tolist()[:4])\n# [['2025-12-29', '1', '53'], ['2026-01-01', '1', '1'], ['2026-01-04', '1', '2'], ['2026-01-05', '2', '2']]\n",[205,1074,1075,1101,1125,1162,1188],{"__ignoreMap":203},[208,1076,1077,1080,1082,1085,1087,1090,1092,1094,1096,1099],{"class":52,"line":210},[208,1078,1079],{"class":243},"jan1 ",[208,1081,266],{"class":239},[208,1083,1084],{"class":243}," pd.to_datetime(df[",[208,1086,483],{"class":217},[208,1088,1089],{"class":243},"].dt.year.astype(",[208,1091,558],{"class":346},[208,1093,561],{"class":243},[208,1095,489],{"class":239},[208,1097,1098],{"class":217}," \"-01-01\"",[208,1100,424],{"class":243},[208,1102,1103,1106,1108,1111,1113,1115,1117,1119,1122],{"class":52,"line":253},[208,1104,1105],{"class":243},"offset ",[208,1107,266],{"class":239},[208,1109,1110],{"class":243}," (jan1.dt.dayofweek ",[208,1112,489],{"class":239},[208,1114,553],{"class":346},[208,1116,561],{"class":243},[208,1118,573],{"class":239},[208,1120,1121],{"class":346}," 7",[208,1123,1124],{"class":465},"                    # days from the Sunday on\u002Fbefore 1 January\n",[208,1126,1127,1129,1132,1134,1136,1139,1141,1143,1145,1148,1150,1153,1156,1158,1160],{"class":52,"line":260},[208,1128,516],{"class":243},[208,1130,1131],{"class":217},"\"weeknum_excel\"",[208,1133,522],{"class":243},[208,1135,266],{"class":239},[208,1137,1138],{"class":243}," ((df[",[208,1140,483],{"class":217},[208,1142,522],{"class":243},[208,1144,550],{"class":239},[208,1146,1147],{"class":243}," jan1).dt.days ",[208,1149,489],{"class":239},[208,1151,1152],{"class":243}," offset) ",[208,1154,1155],{"class":239},"\u002F\u002F",[208,1157,1121],{"class":346},[208,1159,544],{"class":239},[208,1161,621],{"class":346},[208,1163,1164,1166,1168,1170,1172,1174,1176,1178,1180,1182,1184,1186],{"class":52,"line":272},[208,1165,626],{"class":346},[208,1167,629],{"class":243},[208,1169,483],{"class":217},[208,1171,284],{"class":243},[208,1173,932],{"class":217},[208,1175,284],{"class":243},[208,1177,1131],{"class":217},[208,1179,642],{"class":243},[208,1181,558],{"class":346},[208,1183,1000],{"class":243},[208,1185,46],{"class":346},[208,1187,659],{"class":243},[208,1189,1190],{"class":52,"line":308},[208,1191,1192],{"class":465},"# [['2025-12-29', '1', '53'], ['2026-01-01', '1', '1'], ['2026-01-04', '1', '2'], ['2026-01-05', '2', '2']]\n",[10,1194,1195],{},"The two schemes disagree on 29 December (ISO week 1 of 2026 versus week 53 of 2025) and on Sunday 4 January (ISO week 1 versus week 2). Neither is wrong; they are different definitions. Pick one per report, name the column after it, and never compare a week number from one scheme with the other.",[193,1197,1199],{"id":1198},"step-5-weekly-totals-with-no-missing-weeks","Step 5 — Weekly totals with no missing weeks",[10,1201,1202,1205],{},[205,1203,1204],{},"groupby"," produces only weeks that have rows. A weekly report needs every week in the range, with zero for quiet weeks, so that charts and \"same week last year\" comparisons line up:",[198,1207,1209],{"className":230,"code":1208,"language":232,"meta":203,"style":203},"weekly = df.groupby(\"year_week\")[\"amount\"].sum()\nstart, end = df[\"date\"].min(), df[\"date\"].max()\nmondays = pd.date_range(start - pd.Timedelta(days=start.dayofweek), end, freq=\"W-MON\")\nall_weeks = mondays.strftime(\"%G-W%V\")\nweekly = weekly.reindex(all_weeks, fill_value=0)\nprint(len(weekly), weekly.head(3).to_dict())\n# 54 {'2026-W01': 245, '2026-W02': 200, '2026-W03': 0}\n",[205,1210,1211,1232,1251,1284,1302,1320,1338],{"__ignoreMap":203},[208,1212,1213,1216,1218,1221,1223,1226,1229],{"class":52,"line":210},[208,1214,1215],{"class":243},"weekly ",[208,1217,266],{"class":239},[208,1219,1220],{"class":243}," df.groupby(",[208,1222,958],{"class":217},[208,1224,1225],{"class":243},")[",[208,1227,1228],{"class":217},"\"amount\"",[208,1230,1231],{"class":243},"].sum()\n",[208,1233,1234,1237,1239,1241,1243,1246,1248],{"class":52,"line":253},[208,1235,1236],{"class":243},"start, end ",[208,1238,266],{"class":239},[208,1240,480],{"class":243},[208,1242,483],{"class":217},[208,1244,1245],{"class":243},"].min(), df[",[208,1247,483],{"class":217},[208,1249,1250],{"class":243},"].max()\n",[208,1252,1253,1256,1258,1261,1263,1266,1269,1271,1274,1277,1279,1282],{"class":52,"line":260},[208,1254,1255],{"class":243},"mondays ",[208,1257,266],{"class":239},[208,1259,1260],{"class":243}," pd.date_range(start ",[208,1262,550],{"class":239},[208,1264,1265],{"class":243}," pd.Timedelta(",[208,1267,1268],{"class":415},"days",[208,1270,266],{"class":239},[208,1272,1273],{"class":243},"start.dayofweek), end, ",[208,1275,1276],{"class":415},"freq",[208,1278,266],{"class":239},[208,1280,1281],{"class":217},"\"W-MON\"",[208,1283,424],{"class":243},[208,1285,1286,1289,1291,1294,1296,1298,1300],{"class":52,"line":272},[208,1287,1288],{"class":243},"all_weeks ",[208,1290,266],{"class":239},[208,1292,1293],{"class":243}," mondays.strftime(",[208,1295,972],{"class":217},[208,1297,976],{"class":975},[208,1299,979],{"class":217},[208,1301,424],{"class":243},[208,1303,1304,1306,1308,1311,1314,1316,1318],{"class":52,"line":308},[208,1305,1215],{"class":243},[208,1307,266],{"class":239},[208,1309,1310],{"class":243}," weekly.reindex(all_weeks, ",[208,1312,1313],{"class":415},"fill_value",[208,1315,266],{"class":239},[208,1317,47],{"class":346},[208,1319,424],{"class":243},[208,1321,1322,1324,1327,1330,1333,1335],{"class":52,"line":337},[208,1323,626],{"class":346},[208,1325,1326],{"class":243},"(",[208,1328,1329],{"class":346},"len",[208,1331,1332],{"class":243},"(weekly), weekly.head(",[208,1334,650],{"class":346},[208,1336,1337],{"class":243},").to_dict())\n",[208,1339,1340],{"class":52,"line":398},[208,1341,1342],{"class":465},"# 54 {'2026-W01': 245, '2026-W02': 200, '2026-W03': 0}\n",[10,1344,1345,1346,1348,1349,1352],{},"Stepping through Mondays and formatting each with ",[205,1347,1054],{}," generates exactly the ISO weeks in the range, including week 53 in years that have one. Filled weeks with a zero are genuinely empty — no sales recorded — which is different from a week whose data has not arrived yet; if that distinction matters, fill with ",[205,1350,1351],{},"NaN"," and let the report show blanks.",[25,1354,34,1359,34,1362,34,1365,34,1368,34,1372,34,1377,34,1383,34,1387,34,1392,34,1397,34,1400,34,1406,34,1411,34,1415,34,1419,34,1423,34,1426,34,1429,34,1435,34,1439,34,1442,34,1446,34,1449,34,1452,34,1458,34,1462,34,1465,34,1469,34,1472,34,1475,34,1480,34,1485,34,1488],{"viewBox":1355,"role":28,"ariaLabelledBy":1356,"xmlns":32,"style":33},"4 0 752 142",[1357,1358],"fy-flow-t","fy-flow-d",[36,1360,1361],{"id":1357},"Adding period keys to an Excel export",[40,1363,1364],{"id":1358},"Read the dates, compute fiscal year, label and period from the start month, cross-check with an anchored quarter period, add ISO year and week together, reproduce Excel's WEEKNUM only if the workbook used it, then group and reindex so every week or period appears.",[44,1366],{"x":46,"y":47,"width":48,"height":1367,"fill":50},"142",[44,1369],{"x":61,"y":62,"width":1370,"height":1371,"rx":65,"fill":66,"stroke":67,"style":68},"115.2","110.5",[70,1373,1375],{"transform":1374},"translate(65.6,37.8) scale(1.000)",[74,1376],{"d":76,"fill":77,"stroke":78,"style":79},[81,1378,1382],{"x":1379,"y":1380,"style":1381},"77.6","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Read dates",[81,1384,1386],{"x":1379,"y":1385,"style":828},"103.8","real datetimes",[52,1388],{"x1":1389,"y1":1390,"x2":1391,"y2":1390,"stroke":67,"style":68},"140.2","71.2","162.2",[1393,1394],"polygon",{"points":1395,"fill":1396},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[44,1398],{"x":1399,"y":62,"width":1370,"height":1371,"rx":65,"fill":66,"stroke":67,"style":68},"171.2",[70,1401,1403],{"transform":1402},"translate(216.8,29.0) scale(1.000)",[74,1404],{"d":1405,"fill":77,"stroke":78,"style":79},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16",[81,1407,1410],{"x":1408,"y":1409,"style":1381},"228.8","75.5","Fiscal keys",[81,1412,1414],{"x":1408,"y":1413,"style":828},"95.0","year, label,",[81,1416,1418],{"x":1408,"y":1417,"style":828},"112.5","period",[52,1420],{"x1":1421,"y1":1390,"x2":1422,"y2":1390,"stroke":67,"style":68},"291.4","313.4",[1393,1424],{"points":1425,"fill":1396},"317.4,71.2 308.4,76.2 308.4,66.2",[44,1427],{"x":1428,"y":62,"width":1370,"height":1371,"rx":65,"fill":152,"stroke":153,"style":68},"322.4",[70,1430,1432],{"transform":1431},"translate(368.0,37.8) scale(1.000)",[74,1433],{"d":1434,"fill":77,"stroke":160,"style":79},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5",[81,1436,1438],{"x":139,"y":1380,"style":1437},"font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","ISO keys",[81,1440,1441],{"x":139,"y":1385,"style":828},"iso_year + week",[52,1443],{"x1":1444,"y1":1390,"x2":1445,"y2":1390,"stroke":67,"style":68},"442.6","464.6",[1393,1447],{"points":1448,"fill":1396},"468.6,71.2 459.6,76.2 459.6,66.2",[44,1450],{"x":1451,"y":62,"width":1370,"height":1371,"rx":65,"fill":66,"stroke":67,"style":68},"473.6",[70,1453,1455],{"transform":1454},"translate(519.2,37.8) scale(1.000)",[74,1456],{"d":1457,"fill":77,"stroke":78,"style":79},"M4 20h16 M7 17v-5 M12 17V7 M17 17v-8",[81,1459,1461],{"x":1460,"y":1380,"style":1381},"531.2","Group",[81,1463,1464],{"x":1460,"y":1385,"style":828},"by the keys",[52,1466],{"x1":1467,"y1":1390,"x2":1468,"y2":1390,"stroke":67,"style":68},"593.8","615.8",[1393,1470],{"points":1471,"fill":1396},"619.8,71.2 610.8,76.2 610.8,66.2",[44,1473],{"x":1474,"y":62,"width":1370,"height":1371,"rx":65,"fill":129,"stroke":130,"style":68},"624.8",[70,1476,1478],{"transform":1477},"translate(670.4,29.0) scale(1.000)",[74,1479],{"d":159,"fill":77,"stroke":136,"style":79},[81,1481,1484],{"x":1482,"y":1409,"style":1483},"682.4","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Reindex",[81,1486,1487],{"x":1482,"y":1413,"style":828},"no missing",[81,1489,1490],{"x":1482,"y":1417,"style":828},"weeks",[193,1492,1494],{"id":1493},"step-6-same-week-last-year","Step 6 — Same week last year",[10,1496,1497],{},"\"Week 23 against week 23 last year\" is a join on the ISO week number with the ISO year shifted by one. Build it from the gap-free weekly table so that quiet weeks compare as zero rather than disappearing:",[198,1499,1501],{"className":230,"code":1500,"language":232,"meta":203,"style":203},"wk = weekly.rename(\"amount\").reset_index().rename(columns={\"index\": \"year_week\"})\nwk[[\"iso_year\", \"iso_week\"]] = wk[\"year_week\"].str.extract(r\"(\\d{4})-W(\\d{2})\").astype(int)\nprior = wk[[\"iso_year\", \"iso_week\", \"amount\"]].assign(iso_year=lambda t: t[\"iso_year\"] + 1)\nyoy = wk.merge(prior, on=[\"iso_year\", \"iso_week\"], how=\"left\", suffixes=(\"\", \"_last_year\"))\nprint(yoy.loc[yoy[\"iso_year\"] == 2027, [\"year_week\", \"amount\", \"amount_last_year\"]].values.tolist())\n# [['2027-W01', 150, 245.0]]\n",[205,1502,1503,1536,1592,1633,1688,1720],{"__ignoreMap":203},[208,1504,1505,1508,1510,1513,1515,1518,1521,1523,1526,1529,1532,1534],{"class":52,"line":210},[208,1506,1507],{"class":243},"wk ",[208,1509,266],{"class":239},[208,1511,1512],{"class":243}," weekly.rename(",[208,1514,1228],{"class":217},[208,1516,1517],{"class":243},").reset_index().rename(",[208,1519,1520],{"class":415},"columns",[208,1522,266],{"class":239},[208,1524,1525],{"class":243},"{",[208,1527,1528],{"class":217},"\"index\"",[208,1530,1531],{"class":243},": ",[208,1533,958],{"class":217},[208,1535,401],{"class":243},[208,1537,1538,1541,1543,1545,1547,1550,1552,1555,1557,1560,1563,1565,1568,1571,1574,1577,1579,1582,1584,1586,1588,1590],{"class":52,"line":253},[208,1539,1540],{"class":243},"wk[[",[208,1542,926],{"class":217},[208,1544,284],{"class":243},[208,1546,932],{"class":217},[208,1548,1549],{"class":243},"]] ",[208,1551,266],{"class":239},[208,1553,1554],{"class":243}," wk[",[208,1556,958],{"class":217},[208,1558,1559],{"class":243},"].str.extract(",[208,1561,1562],{"class":239},"r",[208,1564,972],{"class":217},[208,1566,1567],{"class":346},"(\\d",[208,1569,1570],{"class":239},"{4}",[208,1572,1573],{"class":346},")",[208,1575,1576],{"class":217},"-W",[208,1578,1567],{"class":346},[208,1580,1581],{"class":239},"{2}",[208,1583,1573],{"class":346},[208,1585,972],{"class":217},[208,1587,506],{"class":243},[208,1589,509],{"class":346},[208,1591,424],{"class":243},[208,1593,1594,1597,1599,1602,1604,1606,1608,1610,1612,1615,1617,1620,1623,1625,1627,1629,1631],{"class":52,"line":260},[208,1595,1596],{"class":243},"prior ",[208,1598,266],{"class":239},[208,1600,1601],{"class":243}," wk[[",[208,1603,926],{"class":217},[208,1605,284],{"class":243},[208,1607,932],{"class":217},[208,1609,284],{"class":243},[208,1611,1228],{"class":217},[208,1613,1614],{"class":243},"]].assign(",[208,1616,1050],{"class":415},[208,1618,1619],{"class":239},"=lambda",[208,1621,1622],{"class":243}," t: t[",[208,1624,926],{"class":217},[208,1626,522],{"class":243},[208,1628,489],{"class":239},[208,1630,553],{"class":346},[208,1632,424],{"class":243},[208,1634,1635,1638,1640,1643,1646,1648,1651,1653,1655,1657,1660,1663,1665,1668,1670,1673,1675,1677,1680,1682,1685],{"class":52,"line":272},[208,1636,1637],{"class":243},"yoy ",[208,1639,266],{"class":239},[208,1641,1642],{"class":243}," wk.merge(prior, ",[208,1644,1645],{"class":415},"on",[208,1647,266],{"class":239},[208,1649,1650],{"class":243},"[",[208,1652,926],{"class":217},[208,1654,284],{"class":243},[208,1656,932],{"class":217},[208,1658,1659],{"class":243},"], ",[208,1661,1662],{"class":415},"how",[208,1664,266],{"class":239},[208,1666,1667],{"class":217},"\"left\"",[208,1669,284],{"class":243},[208,1671,1672],{"class":415},"suffixes",[208,1674,266],{"class":239},[208,1676,1326],{"class":243},[208,1678,1679],{"class":217},"\"\"",[208,1681,284],{"class":243},[208,1683,1684],{"class":217},"\"_last_year\"",[208,1686,1687],{"class":243},"))\n",[208,1689,1690,1692,1695,1697,1699,1701,1704,1706,1708,1710,1712,1714,1717],{"class":52,"line":308},[208,1691,626],{"class":346},[208,1693,1694],{"class":243},"(yoy.loc[yoy[",[208,1696,926],{"class":217},[208,1698,522],{"class":243},[208,1700,743],{"class":239},[208,1702,1703],{"class":346}," 2027",[208,1705,1028],{"class":243},[208,1707,958],{"class":217},[208,1709,284],{"class":243},[208,1711,1228],{"class":217},[208,1713,284],{"class":243},[208,1715,1716],{"class":217},"\"amount_last_year\"",[208,1718,1719],{"class":243},"]].values.tolist())\n",[208,1721,1722],{"class":52,"line":337},[208,1723,1724],{"class":465},"# [['2027-W01', 150, 245.0]]\n",[10,1726,1727,1728,1730],{},"Week 1 of 2027 is compared with week 1 of 2026, even though they start on different calendar dates — which is the point of ISO weeks: each week has the same seven weekdays in both years. Week 53 has no partner in a 52-week year and gets ",[205,1729,1351],{},"; decide whether to compare it with week 52 or leave it out, and say which in the report.",[193,1732,1734],{"id":1733},"step-7-fiscal-summaries-written-back-to-excel","Step 7 — Fiscal summaries written back to Excel",[10,1736,1737],{},"With the keys as ordinary columns, a fiscal summary is a plain group-by, and the columns write to Excel without conversion:",[198,1739,1741],{"className":230,"code":1740,"language":232,"meta":203,"style":203},"summary = (df.groupby([\"fiscal_year\", \"fy_label\", \"fiscal_period\"], as_index=False)[\"amount\"].sum()\n             .sort_values([\"fiscal_year\", \"fiscal_period\"]))\nsummary.drop(columns=\"fiscal_year\").to_excel(\"fiscal_summary.xlsx\", index=False)\nprint(summary[[\"fy_label\", \"fiscal_period\", \"amount\"]].values.tolist())\n# [['FY2025\u002F26', 9, 120], ['FY2025\u002F26', 10, 325], ['FY2025\u002F26', 12, 310],\n#  ['FY2026\u002F27', 1, 95], ['FY2026\u002F27', 9, 60], ['FY2026\u002F27', 10, 255]]\n",[205,1742,1743,1778,1792,1819,1838,1843],{"__ignoreMap":203},[208,1744,1745,1748,1750,1753,1755,1757,1759,1761,1763,1765,1768,1770,1772,1774,1776],{"class":52,"line":210},[208,1746,1747],{"class":243},"summary ",[208,1749,266],{"class":239},[208,1751,1752],{"class":243}," (df.groupby([",[208,1754,519],{"class":217},[208,1756,284],{"class":243},[208,1758,534],{"class":217},[208,1760,284],{"class":243},[208,1762,595],{"class":217},[208,1764,1659],{"class":243},[208,1766,1767],{"class":415},"as_index",[208,1769,266],{"class":239},[208,1771,421],{"class":346},[208,1773,1225],{"class":243},[208,1775,1228],{"class":217},[208,1777,1231],{"class":243},[208,1779,1780,1783,1785,1787,1789],{"class":52,"line":253},[208,1781,1782],{"class":243},"             .sort_values([",[208,1784,519],{"class":217},[208,1786,284],{"class":243},[208,1788,595],{"class":217},[208,1790,1791],{"class":243},"]))\n",[208,1793,1794,1797,1799,1801,1803,1806,1809,1811,1813,1815,1817],{"class":52,"line":260},[208,1795,1796],{"class":243},"summary.drop(",[208,1798,1520],{"class":415},[208,1800,266],{"class":239},[208,1802,519],{"class":217},[208,1804,1805],{"class":243},").to_excel(",[208,1807,1808],{"class":217},"\"fiscal_summary.xlsx\"",[208,1810,284],{"class":243},[208,1812,416],{"class":415},[208,1814,266],{"class":239},[208,1816,421],{"class":346},[208,1818,424],{"class":243},[208,1820,1821,1823,1826,1828,1830,1832,1834,1836],{"class":52,"line":272},[208,1822,626],{"class":346},[208,1824,1825],{"class":243},"(summary[[",[208,1827,534],{"class":217},[208,1829,284],{"class":243},[208,1831,595],{"class":217},[208,1833,284],{"class":243},[208,1835,1228],{"class":217},[208,1837,1719],{"class":243},[208,1839,1840],{"class":52,"line":308},[208,1841,1842],{"class":465},"# [['FY2025\u002F26', 9, 120], ['FY2025\u002F26', 10, 325], ['FY2025\u002F26', 12, 310],\n",[208,1844,1845],{"class":52,"line":337},[208,1846,1847],{"class":465},"#  ['FY2026\u002F27', 1, 95], ['FY2026\u002F27', 9, 60], ['FY2026\u002F27', 10, 255]]\n",[10,1849,1850],{},"Sorting by the numeric year and period, not the label, keeps the order right even when a label format changes. Periods with no sales are missing here; reindex against the full list of periods, as in Step 5, when the report needs all twelve.",[193,1852,1854],{"id":1853},"common-pitfalls","Common pitfalls",[1856,1857,1858,1870,1876],"ul",{},[1859,1860,1861,1865,1866,1047,1868,23],"li",{},[1862,1863,1864],"strong",{},"Grouping ISO weeks by calendar year."," Around New Year, days move between years. Use ",[205,1867,1050],{},[205,1869,1046],{},[1859,1871,1872,1875],{},[1862,1873,1874],{},"Fiscal year naming."," Some organisations name a year by its start, others by its end. Agree the convention and put it in the column name or label.",[1859,1877,1878,1881],{},[1862,1879,1880],{},"Retail 4-4-5 calendars."," Fiscal months made of whole weeks cannot be derived from calendar months; keep a calendar table from the finance team and merge on date.",[193,1883,1885],{"id":1884},"conclusion","Conclusion",[10,1887,1888,1889,1892],{},"Fiscal and weekly keys are small calculations with sharp edges at year ends. Derive fiscal year and period from the start month with explicit arithmetic and check them against an anchored period, pair ISO weeks with the ISO year, reproduce Excel's ",[205,1890,1891],{},"WEEKNUM"," only when the workbook used it, and reindex weekly results so every week appears. Keep the keys as ordinary columns, and write them back to Excel as numbers and labels.",[193,1894,1896],{"id":1895},"frequently-asked-questions","Frequently asked questions",[10,1898,1899,1902],{},[1862,1900,1901],{},"How do I calculate the fiscal year for a date in pandas?","\nWith a fiscal year starting in month S, the fiscal year ending year is the calendar year plus one for months from S onwards. For an April start, 15 May 2026 belongs to the fiscal year ending in 2027, often labelled FY2026\u002F27.",[10,1904,1905,1908],{},[1862,1906,1907],{},"Why does 1 January show up in week 53 of the previous year?","\nISO weeks start on Monday, and week 1 is the week containing the year's first Thursday. Days before that Monday belong to the last ISO week of the previous year, so always group by the ISO year together with the ISO week, not the calendar year.",[10,1910,1911,1914],{},[1862,1912,1913],{},"Is pandas' isocalendar the same as Excel's ISOWEEKNUM?","\nYes. dt.isocalendar().week gives the same numbers as ISOWEEKNUM. Excel's WEEKNUM with its default return type is different — weeks start on Sunday and week 1 is the week containing 1 January — so match the function your workbook used.",[10,1916,1917,1920],{},[1862,1918,1919],{},"Why does my weekly report skip a week?","\ngroupby only creates groups for weeks that have rows. Build the full list of weeks for the range and reindex the result against it, filling missing weeks with zero.",[193,1922,1924],{"id":1923},"related","Related",[1856,1926,1927,1933,1938,1945],{},[1859,1928,1929,1930,1932],{},"Up one level: ",[14,1931,17],{"href":16}," — dates and times from Excel.",[1859,1934,1935,1937],{},[14,1936,22],{"href":21}," — calendar periods.",[1859,1939,1940,1944],{},[14,1941,1943],{"href":1942},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas\u002F","EOMONTH and EDATE Equivalents in pandas"," — month arithmetic.",[1859,1946,1947,1951],{},[14,1948,1950],{"href":1949},"\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"," — comparing periods.",[1953,1954,1955],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":203,"searchDepth":253,"depth":253,"links":1957},[1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968,1969],{"id":195,"depth":253,"text":196},{"id":442,"depth":253,"text":443},{"id":679,"depth":253,"text":680},{"id":889,"depth":253,"text":890},{"id":1058,"depth":253,"text":1059},{"id":1198,"depth":253,"text":1199},{"id":1493,"depth":253,"text":1494},{"id":1733,"depth":253,"text":1734},{"id":1853,"depth":253,"text":1854},{"id":1884,"depth":253,"text":1885},{"id":1895,"depth":253,"text":1896},{"id":1923,"depth":253,"text":1924},"2026-09-23","Assign every Excel row to a fiscal year, fiscal period and ISO week with pandas — any fiscal start month, labels such as FY2026\u002F27, period numbers 1–12, ISO years that differ from calendar years around New Year, Excel's WEEKNUM rule, and complete weekly tables with no missing weeks.","md",[1974,1976,1978,1980],{"q":1901,"a":1975},"With a fiscal year starting in month S, the fiscal year ending year is the calendar year plus one for months from S onwards. For an April start, 15 May 2026 belongs to the fiscal year ending in 2027, often labelled FY2026\u002F27.",{"q":1907,"a":1977},"ISO weeks start on Monday, and week 1 is the week containing the year's first Thursday. Days before that Monday belong to the last ISO week of the previous year, so always group by the ISO year together with the ISO week, not the calendar year.",{"q":1913,"a":1979},"Yes. dt.isocalendar().week gives the same numbers as ISOWEEKNUM. Excel's WEEKNUM with its default return type is different — weeks start on Sunday and week 1 is the week containing 1 January — so match the function your workbook used.",{"q":1919,"a":1981},"groupby only creates groups for weeks that have rows. Build the full list of weeks for the range and reindex the result against it, filling missing weeks with zero.","group excel data by fiscal year and iso week with pandas",null,{"breadcrumb":1985},[1986,1989,1992],{"name":1987,"item":1988},"Home","\u002F",{"name":1990,"item":1991},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":17,"item":16},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-data-by-fiscal-year-and-iso-week",{"title":5,"description":1995},"Add fiscal year, fiscal period and ISO week columns to Excel data in pandas — start-month arithmetic, FY labels, dt.isocalendar and the ISO year trap, Excel WEEKNUM system 1, 53-week years and gap-free weekly reports.","group-excel-data-by-fiscal-year-and-iso-week","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-data-by-fiscal-year-and-iso-week\u002Findex",[1999,2000,2001],"dates-and-times","pivots-and-summaries","pandas","how-to","TF0kLb3kVnSL2G83M6eMe7FPBKxM8iDT9OGLsn4MwIc",[2005,2009],{"title":2006,"path":2007,"stem":2008,"children":-1},"Flag Public Holidays in Excel Data with Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fflag-public-holidays-in-excel-data-with-python","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fflag-public-holidays-in-excel-data-with-python\u002Findex",{"title":22,"path":2010,"stem":2011,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-rows-by-month-and-quarter-with-pandas","advanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fgroup-excel-rows-by-month-and-quarter-with-pandas\u002Findex",[2013,2015],{"path":2014,"title":1990},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2016,"title":17},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data",[],[2019,2022,2025,2028,2031,2033],{"path":2020,"title":2021,"kind":2002},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-overdue-dates-with-openpyxl","Highlight Overdue Dates in Excel with openpyxl",{"path":2023,"title":2024,"kind":2002},"\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":2026,"title":2027,"kind":2002},"\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":2029,"title":2030,"kind":2002},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-moving-averages-with-pandas-rolling","Calculate Moving Averages with pandas rolling",{"path":2032,"title":1943,"kind":2002},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas",{"path":2034,"title":2035,"kind":2002},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fyearfrac-and-days360-equivalents-in-python","YEARFRAC and DAYS360 Equivalents in Python",1790972106398]