[{"data":1,"prerenderedAt":1769},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fforecast-a-time-series-with-python-in-excel":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fforecast-a-time-series-with-python-in-excel":1740,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fforecast-a-time-series-with-python-in-excel":1748,"children:\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fforecast-a-time-series-with-python-in-excel":1753,"related:\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fforecast-a-time-series-with-python-in-excel":1754},{"id":4,"title":5,"body":6,"dateModified":1707,"datePublished":1707,"description":1708,"extension":1709,"faq":1710,"keyword":1719,"kind":1720,"meta":1721,"navigation":278,"path":1729,"seo":1730,"slug":1733,"stem":1734,"topics":1735,"type":1738,"__hash__":1739},"docs\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fforecast-a-time-series-with-python-in-excel\u002Findex.md","Forecast a Time Series with Python in Excel",{"type":7,"value":8,"toc":1693},"minimark",[9,35,40,70,74,81,222,248,252,255,352,366,517,521,524,692,713,717,744,942,946,952,1139,1151,1155,1162,1303,1306,1435,1438,1442,1445,1562,1565,1569,1612,1616,1628,1632,1638,1644,1650,1656,1660,1689],[10,11,12,13,17,18,21,22,24,25,28,29,34],"p",{},"Excel has had a forecasting function for years — ",[14,15,16],"code",{},"FORECAST.ETS"," — but it is a black box with few options, and charts of its output take manual work. Python in Excel puts pandas and statsmodels inside the workbook itself: a ",[14,19,20],{},"=PY()"," cell can read a table of monthly sales, fit a seasonal model, and return next year's forecast as a range that other formulas and charts can use. The code runs in Microsoft's cloud, so nothing needs installing, and the workbook stays a normal file that colleagues open in Excel. This page walks through a monthly sales forecast step by step. Because ",[14,23,20],{}," cells only run inside Excel, the snippets here were tested outside it with a small stand-in for the ",[14,26,27],{},"xl()"," function, which is shown first. It belongs to ",[30,31,33],"a",{"href":32},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002F","Using Python in Excel",".",[36,37,39],"h2",{"id":38},"prerequisites","Prerequisites",[10,41,42,43,46,47,50,51,54,55,58,59,62,63,66,67,69],{},"Python in Excel is available in Microsoft 365 (Excel for Windows, Mac and the web) on supported plans. In the workbook, put the history in an Excel table named ",[14,44,45],{},"SalesHistory"," with two columns, ",[14,48,49],{},"Month"," (real dates, first day of each month) and ",[14,52,53],{},"Sales",". The sample data covers 36 months from January 2023 to December 2025 with a rising trend and a clear seasonal pattern. Outside Excel, the code below runs with ",[14,56,57],{},"pandas",", ",[14,60,61],{},"statsmodels"," and ",[14,64,65],{},"matplotlib"," and the stand-in ",[14,68,27],{}," function defined above.",[36,71,73],{"id":72},"step-1-read-the-table-and-set-a-monthly-index","Step 1 — Read the table and set a monthly index",[10,75,76,77,80],{},"In a new cell, type ",[14,78,79],{},"=PY("," to enter Python mode, then:",[82,83,88],"pre",{"className":84,"code":85,"language":86,"meta":87,"style":87},"language-python shiki shiki-themes github-light github-dark-high-contrast","df = xl(\"SalesHistory[#All]\", headers=True)\nhistory = (df.assign(Month=pd.to_datetime(df[\"Month\"]))\n             .set_index(\"Month\")[\"Sales\"]\n             .asfreq(\"MS\"))\nprint(history.tail(3))\nprint(\"months:\", len(history), \"| gaps:\", int(history.isna().sum()))\n","python","",[14,89,90,125,149,166,178,192],{"__ignoreMap":87},[91,92,95,99,103,106,110,112,116,118,122],"span",{"class":93,"line":94},"line",1,[91,96,98],{"class":97},"skGVy","df ",[91,100,102],{"class":101},"s-kum","=",[91,104,105],{"class":97}," xl(",[91,107,109],{"class":108},"srMev","\"SalesHistory[#All]\"",[91,111,58],{"class":97},[91,113,115],{"class":114},"sa561","headers",[91,117,102],{"class":101},[91,119,121],{"class":120},"sP0c6","True",[91,123,124],{"class":97},")\n",[91,126,128,131,133,136,138,140,143,146],{"class":93,"line":127},2,[91,129,130],{"class":97},"history ",[91,132,102],{"class":101},[91,134,135],{"class":97}," (df.assign(",[91,137,49],{"class":114},[91,139,102],{"class":101},[91,141,142],{"class":97},"pd.to_datetime(df[",[91,144,145],{"class":108},"\"Month\"",[91,147,148],{"class":97},"]))\n",[91,150,152,155,157,160,163],{"class":93,"line":151},3,[91,153,154],{"class":97},"             .set_index(",[91,156,145],{"class":108},[91,158,159],{"class":97},")[",[91,161,162],{"class":108},"\"Sales\"",[91,164,165],{"class":97},"]\n",[91,167,169,172,175],{"class":93,"line":168},4,[91,170,171],{"class":97},"             .asfreq(",[91,173,174],{"class":108},"\"MS\"",[91,176,177],{"class":97},"))\n",[91,179,181,184,187,190],{"class":93,"line":180},5,[91,182,183],{"class":120},"print",[91,185,186],{"class":97},"(history.tail(",[91,188,189],{"class":120},"3",[91,191,177],{"class":97},[91,193,195,197,200,203,205,208,211,214,216,219],{"class":93,"line":194},6,[91,196,183],{"class":120},[91,198,199],{"class":97},"(",[91,201,202],{"class":108},"\"months:\"",[91,204,58],{"class":97},[91,206,207],{"class":120},"len",[91,209,210],{"class":97},"(history), ",[91,212,213],{"class":108},"\"| gaps:\"",[91,215,58],{"class":97},[91,217,218],{"class":120},"int",[91,220,221],{"class":97},"(history.isna().sum()))\n",[10,223,224,227,228,232,233,62,236,239,240,243,244,247],{},[14,225,226],{},"xl(\"SalesHistory[#All]\", headers=True)"," returns the table, header row included, as a DataFrame — the Python in Excel equivalent of reading a sheet with pandas, described in ",[30,229,231],{"href":230},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Freference-cells-ranges-and-tables-with-xl-in-python-in-excel\u002F","Reference Cells, Ranges and Tables with xl() in Python in Excel",". Forecasting models need a regular time index, so the month column becomes a ",[14,234,235],{},"DatetimeIndex",[14,237,238],{},"asfreq(\"MS\")"," declares it monthly (month start). If a month were missing, ",[14,241,242],{},"asfreq"," would insert it as ",[14,245,246],{},"NaN"," — and the gap count makes that visible before the model silently misreads the seasonality.",[36,249,251],{"id":250},"step-2-fit-a-seasonal-model","Step 2 — Fit a seasonal model",[10,253,254],{},"Holt-Winters exponential smoothing handles a trend and a repeating yearly pattern, which describes most monthly business series:",[82,256,258],{"className":84,"code":257,"language":86,"meta":87,"style":87},"from statsmodels.tsa.holtwinters import ExponentialSmoothing\n\nmodel = ExponentialSmoothing(history, trend=\"add\", seasonal=\"add\", seasonal_periods=12).fit()\nforecast = model.forecast(12).round(0)\nprint(forecast.head(6))\n",[14,259,260,274,280,320,340],{"__ignoreMap":87},[91,261,262,265,268,271],{"class":93,"line":94},[91,263,264],{"class":101},"from",[91,266,267],{"class":97}," statsmodels.tsa.holtwinters ",[91,269,270],{"class":101},"import",[91,272,273],{"class":97}," ExponentialSmoothing\n",[91,275,276],{"class":93,"line":127},[91,277,279],{"emptyLinePlaceholder":278},true,"\n",[91,281,282,285,287,290,293,295,298,300,303,305,307,309,312,314,317],{"class":93,"line":151},[91,283,284],{"class":97},"model ",[91,286,102],{"class":101},[91,288,289],{"class":97}," ExponentialSmoothing(history, ",[91,291,292],{"class":114},"trend",[91,294,102],{"class":101},[91,296,297],{"class":108},"\"add\"",[91,299,58],{"class":97},[91,301,302],{"class":114},"seasonal",[91,304,102],{"class":101},[91,306,297],{"class":108},[91,308,58],{"class":97},[91,310,311],{"class":114},"seasonal_periods",[91,313,102],{"class":101},[91,315,316],{"class":120},"12",[91,318,319],{"class":97},").fit()\n",[91,321,322,325,327,330,332,335,338],{"class":93,"line":168},[91,323,324],{"class":97},"forecast ",[91,326,102],{"class":101},[91,328,329],{"class":97}," model.forecast(",[91,331,316],{"class":120},[91,333,334],{"class":97},").round(",[91,336,337],{"class":120},"0",[91,339,124],{"class":97},[91,341,342,344,347,350],{"class":93,"line":180},[91,343,183],{"class":120},[91,345,346],{"class":97},"(forecast.head(",[91,348,349],{"class":120},"6",[91,351,177],{"class":97},[10,353,354,357,358,361,362,365],{},[14,355,356],{},"trend=\"add\""," lets the level grow by a roughly constant amount each month; ",[14,359,360],{},"seasonal=\"add\""," adds a fixed pattern that repeats every 12 months. The model needs at least two full seasons of history — 24 months — to estimate the pattern; three years, as here, is more comfortable. Use ",[14,363,364],{},"seasonal=\"mul\""," when the seasonal swing grows with the level (December being 30% above average rather than 200 units above). The fitted model is a Python object; the forecast is a pandas Series with the next twelve month-start dates as its index.",[367,368,376,377,376,381,376,385,376,392,376,401,376,412,376,419,376,424,376,430,376,435,376,439,376,446,376,452,376,456,376,460,376,464,376,467,376,470,376,476,376,480,376,484,376,487,376,491,376,494,376,499,376,506,376,511,376,514],"svg",{"viewBox":369,"role":370,"ariaLabelledBy":371,"xmlns":374,"style":375},"4 0 752 142","img",[372,373],"pie-flow-t","pie-flow-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  ",[378,379,380],"title",{"id":372},"From an Excel table to a spilled forecast",[382,383,384],"desc",{"id":373},"The SalesHistory table is read with xl() into a DataFrame, given a monthly index, fitted with a Holt-Winters model, and the forecast DataFrame is returned to the grid as an Excel range that spills below the PY cell.",[386,387],"rect",{"x":388,"y":337,"width":389,"height":390,"fill":391},"4","752","142","#ffffff",[386,393],{"x":394,"y":395,"width":396,"height":397,"rx":316,"fill":398,"stroke":399,"style":400},"20.0","16.0","153.0","110.5","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[402,403,405],"g",{"transform":404},"translate(84.5,37.8) scale(1.000)",[406,407],"path",{"d":408,"fill":409,"stroke":410,"style":411},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[413,414,418],"text",{"x":415,"y":416,"style":417},"96.5","84.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","SalesHistory table",[413,420,423],{"x":415,"y":421,"style":422},"103.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Month · Sales",[93,425],{"x1":426,"y1":427,"x2":428,"y2":427,"stroke":429,"style":400},"178.0","71.2","200.0","var(--brand,#5b5cf0)",[431,432],"polygon",{"points":433,"fill":434},"204.0,71.2 195.0,76.2 195.0,66.2","#5b5cf0",[386,436],{"x":437,"y":395,"width":396,"height":397,"rx":316,"fill":438,"stroke":429,"style":400},"209.0","#f0f4ff",[402,440,442],{"transform":441},"translate(273.5,29.0) scale(1.000)",[406,443],{"d":444,"fill":409,"stroke":445,"style":411},"M12 3c-4 0-4 1.6-4 3v2h4.2v1H6c-2 0-3 1.4-3 4s1 4 3 4h2v-2.6c0-1.6 1.3-2.9 2.9-2.9h4.2c1.3 0 2.4-1.1 2.4-2.4V6c0-1.6-1.8-3-5.5-3z M10 5.5h.01 M12 21c4 0 4-1.6 4-3v-2h-4.2v-1H18c2 0 3-1.4 3-4s-1-4-3-4h-2v2.6c0 1.6-1.3 2.9-2.9 2.9H8.9c-1.3 0-2.4 1.1-2.4 2.4V18c0 1.6 1.8 3 5.5 3z M14 18.5h.01","var(--brand-strong,#4338ca)",[413,447,451],{"x":448,"y":449,"style":450},"285.5","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","xl(…,",[413,453,455],{"x":448,"y":454,"style":450},"93.0","headers=True)",[413,457,459],{"x":448,"y":458,"style":422},"112.5","DataFrame",[93,461],{"x1":462,"y1":427,"x2":463,"y2":427,"stroke":429,"style":400},"367.0","389.0",[431,465],{"points":466,"fill":434},"393.0,71.2 384.0,76.2 384.0,66.2",[386,468],{"x":469,"y":395,"width":396,"height":397,"rx":316,"fill":438,"stroke":429,"style":400},"398.0",[402,471,473],{"transform":472},"translate(462.5,29.0) scale(1.000)",[406,474],{"d":475,"fill":409,"stroke":445,"style":411},"M4 20h16 M4 20V4 M6 16l4-5 4 3 6-7",[413,477,479],{"x":478,"y":449,"style":450},"474.5","Holt-Winters fit",[413,481,483],{"x":478,"y":482,"style":422},"95.0","trend + 12-month",[413,485,486],{"x":478,"y":458,"style":422},"season",[93,488],{"x1":489,"y1":427,"x2":490,"y2":427,"stroke":429,"style":400},"556.0","578.0",[431,492],{"points":493,"fill":434},"582.0,71.2 573.0,76.2 573.0,66.2",[386,495],{"x":496,"y":395,"width":396,"height":397,"rx":316,"fill":497,"stroke":498,"style":400},"587.0","#d9f4f1","var(--teal,#0f9488)",[402,500,502],{"transform":501},"translate(651.5,29.0) scale(1.000)",[406,503],{"d":504,"fill":409,"stroke":505,"style":411},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--teal-ink,#0b6157)",[413,507,510],{"x":508,"y":449,"style":509},"663.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Spilled range",[413,512,513],{"x":508,"y":482,"style":422},"Month · Forecast · Low",[413,515,516],{"x":508,"y":458,"style":422},"· High",[36,518,520],{"id":519},"step-3-add-a-simple-uncertainty-band","Step 3 — Add a simple uncertainty band",[10,522,523],{},"A forecast without any sense of its error invites false precision. A pragmatic band uses the spread of the model's in-sample errors:",[82,525,527],{"className":84,"code":526,"language":86,"meta":87,"style":87},"residual_sd = (history - model.fittedvalues).std()\nband = 1.96 * residual_sd * np.sqrt(np.arange(1, 13) \u002F 3).clip(min=1)\nresult = pd.DataFrame({\n    \"Month\": forecast.index,\n    \"Forecast\": forecast.values,\n    \"Low\": (forecast.values - band).round(0),\n    \"High\": (forecast.values + band).round(0),\n})\nprint(result.head(3).to_string(index=False))\nresult\n",[14,528,529,545,596,606,614,622,640,657,663,686],{"__ignoreMap":87},[91,530,531,534,536,539,542],{"class":93,"line":94},[91,532,533],{"class":97},"residual_sd ",[91,535,102],{"class":101},[91,537,538],{"class":97}," (history ",[91,540,541],{"class":101},"-",[91,543,544],{"class":97}," model.fittedvalues).std()\n",[91,546,547,550,552,555,558,561,564,567,570,572,575,578,581,584,587,590,592,594],{"class":93,"line":127},[91,548,549],{"class":97},"band ",[91,551,102],{"class":101},[91,553,554],{"class":120}," 1.96",[91,556,557],{"class":101}," *",[91,559,560],{"class":97}," residual_sd ",[91,562,563],{"class":101},"*",[91,565,566],{"class":97}," np.sqrt(np.arange(",[91,568,569],{"class":120},"1",[91,571,58],{"class":97},[91,573,574],{"class":120},"13",[91,576,577],{"class":97},") ",[91,579,580],{"class":101},"\u002F",[91,582,583],{"class":120}," 3",[91,585,586],{"class":97},").clip(",[91,588,589],{"class":114},"min",[91,591,102],{"class":101},[91,593,569],{"class":120},[91,595,124],{"class":97},[91,597,598,601,603],{"class":93,"line":151},[91,599,600],{"class":97},"result ",[91,602,102],{"class":101},[91,604,605],{"class":97}," pd.DataFrame({\n",[91,607,608,611],{"class":93,"line":168},[91,609,610],{"class":108},"    \"Month\"",[91,612,613],{"class":97},": forecast.index,\n",[91,615,616,619],{"class":93,"line":180},[91,617,618],{"class":108},"    \"Forecast\"",[91,620,621],{"class":97},": forecast.values,\n",[91,623,624,627,630,632,635,637],{"class":93,"line":194},[91,625,626],{"class":108},"    \"Low\"",[91,628,629],{"class":97},": (forecast.values ",[91,631,541],{"class":101},[91,633,634],{"class":97}," band).round(",[91,636,337],{"class":120},[91,638,639],{"class":97},"),\n",[91,641,643,646,648,651,653,655],{"class":93,"line":642},7,[91,644,645],{"class":108},"    \"High\"",[91,647,629],{"class":97},[91,649,650],{"class":101},"+",[91,652,634],{"class":97},[91,654,337],{"class":120},[91,656,639],{"class":97},[91,658,660],{"class":93,"line":659},8,[91,661,662],{"class":97},"})\n",[91,664,666,668,671,673,676,679,681,684],{"class":93,"line":665},9,[91,667,183],{"class":120},[91,669,670],{"class":97},"(result.head(",[91,672,189],{"class":120},[91,674,675],{"class":97},").to_string(",[91,677,678],{"class":114},"index",[91,680,102],{"class":101},[91,682,683],{"class":120},"False",[91,685,177],{"class":97},[91,687,689],{"class":93,"line":688},10,[91,690,691],{"class":97},"result\n",[10,693,694,695,698,699,702,703,705,706,709,710,712],{},"The band starts at about ±2 residual standard deviations and widens for months further out, a rough but honest stand-in for a full prediction interval. For formal intervals, statsmodels' state-space ",[14,696,697],{},"ETSModel"," provides ",[14,700,701],{},"get_prediction(...).summary_frame()",". The ",[14,704,183],{}," output goes to the cell's diagnostics pane in Excel; the last line, a bare ",[14,707,708],{},"result",", is what the ",[14,711,20],{}," cell returns: Python in Excel uses the value of the cell's final expression.",[36,714,716],{"id":715},"step-4-return-the-forecast-to-the-grid","Step 4 — Return the forecast to the grid",[10,718,719,720,722,723,727,728,731,732,735,736,739,740,34],{},"A ",[14,721,20],{}," cell can return its result in two forms, switched with the output menu next to the formula bar (or Ctrl+Alt+Shift+M): as a ",[724,725,726],"strong",{},"Python object",", shown as a card that other Python cells can reference, or as an ",[724,729,730],{},"Excel value",", which spills the DataFrame into the grid as an ordinary range. Choose Excel value here: the twelve forecast rows spill below the cell with their headers, and regular formulas can refer to them — ",[14,733,734],{},"=SUM(...)"," over the forecast column gives next year's total, and a chart can plot actuals and forecast together. Dates come back as real Excel dates; give the column a date format such as ",[14,737,738],{},"mmm yyyy",". Returning values and objects is covered in ",[30,741,743],{"href":742},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Freturn-dataframes-and-values-from-python-in-excel-cells\u002F","Return DataFrames and Values from Python in Excel Cells",[367,745,376,751,376,754,376,757,376,762,376,768,376,772,376,778,376,782,376,786,376,790,376,794,376,798,376,802,376,806,376,810,376,813,376,816,376,818,376,821,376,823,376,826,376,828,376,831,376,834,376,838,376,840,376,844,376,846,376,850,376,852,376,855,376,857,376,860,376,863,376,866,376,868,376,871,376,873,376,876,376,878,376,881,376,883,376,886,376,889,376,892,376,894,376,897,376,899,376,902,376,904,376,907,376,909,376,912,376,915,376,919,376,921,376,924,376,926,376,928,376,930,376,932,376,934,376,936],{"viewBox":746,"role":370,"ariaLabelledBy":747,"xmlns":374,"style":750},"53 0 653 222",[748,749],"pie-spill-t","pie-spill-d","width:100%;max-width:653px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[378,752,753],{"id":748},"The forecast spilled into the sheet",[382,755,756],{"id":749},"A spilled range starting at the PY cell with columns Month, Forecast, Low and High. Each of the twelve rows from January 2026 holds a forecast and a band that widens slowly.",[386,758],{"x":759,"y":337,"width":760,"height":761,"fill":391},"53","653","222",[386,763],{"x":764,"y":395,"width":765,"height":766,"rx":337,"fill":398,"stroke":399,"style":767},"240.1","32.0","24.0","stroke-width:1px",[386,769],{"x":770,"y":395,"width":771,"height":766,"rx":337,"fill":398,"stroke":399,"style":767},"272.1","76.3",[413,773,777],{"x":774,"y":775,"style":776},"310.2","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[386,779],{"x":780,"y":395,"width":781,"height":766,"rx":337,"fill":398,"stroke":399,"style":767},"348.3","71.4",[413,783,785],{"x":784,"y":775,"style":776},"384.0","B",[386,787],{"x":788,"y":395,"width":789,"height":766,"rx":337,"fill":398,"stroke":399,"style":767},"419.7","50.0",[413,791,793],{"x":792,"y":775,"style":776},"444.7","C",[386,795],{"x":796,"y":395,"width":797,"height":766,"rx":337,"fill":398,"stroke":399,"style":767},"469.7","50.2",[413,799,801],{"x":800,"y":775,"style":776},"494.8","D",[386,803],{"x":764,"y":804,"width":765,"height":805,"rx":337,"fill":398,"stroke":399,"style":767},"40.0","28.0",[413,807,569],{"x":808,"y":809,"style":776},"256.1","59.0",[386,811],{"x":770,"y":804,"width":771,"height":805,"rx":337,"fill":438,"stroke":429,"style":812},"stroke-width:1.5px",[413,814,49],{"x":774,"y":809,"style":815},"font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[386,817],{"x":780,"y":804,"width":781,"height":805,"rx":337,"fill":438,"stroke":429,"style":812},[413,819,820],{"x":784,"y":809,"style":815},"Forecast",[386,822],{"x":788,"y":804,"width":789,"height":805,"rx":337,"fill":438,"stroke":429,"style":812},[413,824,825],{"x":792,"y":809,"style":815},"Low",[386,827],{"x":796,"y":804,"width":797,"height":805,"rx":337,"fill":438,"stroke":429,"style":812},[413,829,830],{"x":800,"y":809,"style":815},"High",[386,832],{"x":764,"y":833,"width":765,"height":805,"rx":337,"fill":398,"stroke":399,"style":767},"68.0",[413,835,837],{"x":808,"y":836,"style":776},"87.0","2",[386,839],{"x":770,"y":833,"width":771,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,841,843],{"x":774,"y":836,"style":842},"font-size:10.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Jan 2026",[386,845],{"x":780,"y":833,"width":781,"height":805,"rx":337,"fill":497,"stroke":498,"style":812},[413,847,849],{"x":784,"y":836,"style":848},"font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","1487",[386,851],{"x":788,"y":833,"width":789,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,853,854],{"x":792,"y":836,"style":842},"1425",[386,856],{"x":796,"y":833,"width":797,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,858,859],{"x":800,"y":836,"style":842},"1549",[386,861],{"x":764,"y":862,"width":765,"height":805,"rx":337,"fill":398,"stroke":399,"style":767},"96.0",[413,864,189],{"x":808,"y":865,"style":776},"115.0",[386,867],{"x":770,"y":862,"width":771,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,869,870],{"x":774,"y":865,"style":842},"Feb 2026",[386,872],{"x":780,"y":862,"width":781,"height":805,"rx":337,"fill":497,"stroke":498,"style":812},[413,874,875],{"x":784,"y":865,"style":848},"1627",[386,877],{"x":788,"y":862,"width":789,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,879,880],{"x":792,"y":865,"style":842},"1565",[386,882],{"x":796,"y":862,"width":797,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,884,885],{"x":800,"y":865,"style":842},"1689",[386,887],{"x":764,"y":888,"width":765,"height":805,"rx":337,"fill":398,"stroke":399,"style":767},"124.0",[413,890,388],{"x":808,"y":891,"style":776},"143.0",[386,893],{"x":770,"y":888,"width":771,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,895,896],{"x":774,"y":891,"style":842},"Mar 2026",[386,898],{"x":780,"y":888,"width":781,"height":805,"rx":337,"fill":497,"stroke":498,"style":812},[413,900,901],{"x":784,"y":891,"style":848},"1727",[386,903],{"x":788,"y":888,"width":789,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,905,906],{"x":792,"y":891,"style":842},"1665",[386,908],{"x":796,"y":888,"width":797,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,910,911],{"x":800,"y":891,"style":842},"1789",[386,913],{"x":764,"y":914,"width":765,"height":805,"rx":337,"fill":398,"stroke":399,"style":767},"152.0",[413,916,918],{"x":808,"y":917,"style":776},"171.0","5",[386,920],{"x":770,"y":914,"width":771,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,922,923],{"x":774,"y":917,"style":842},"…",[386,925],{"x":780,"y":914,"width":781,"height":805,"rx":337,"fill":497,"stroke":498,"style":812},[413,927,923],{"x":784,"y":917,"style":848},[386,929],{"x":788,"y":914,"width":789,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,931,923],{"x":792,"y":917,"style":842},[386,933],{"x":796,"y":914,"width":797,"height":805,"rx":337,"fill":391,"stroke":399,"style":767},[413,935,923],{"x":800,"y":917,"style":842},[413,937,941],{"x":938,"y":939,"style":940},"380.0","202.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","The first forecast values from the sample data; the band widths depend on the fitted model's errors.",[36,943,945],{"id":944},"step-5-chart-actuals-and-forecast","Step 5 — Chart actuals and forecast",[10,947,948,949,951],{},"A matplotlib figure returned from a ",[14,950,20],{}," cell appears as an image you can place over the grid:",[82,953,955],{"className":84,"code":954,"language":86,"meta":87,"style":87},"import matplotlib.pyplot as plt\n\nfig, ax = plt.subplots(figsize=(7, 3))\nax.plot(history.index, history.values, label=\"Actual\", color=\"#5B5CF0\")\nax.plot(result[\"Month\"], result[\"Forecast\"], label=\"Forecast\", color=\"#0F9488\")\nax.fill_between(result[\"Month\"], result[\"Low\"], result[\"High\"], color=\"#0F9488\", alpha=0.15, label=\"Band\")\nax.set_title(\"Monthly sales and 12-month forecast\")\nax.legend(loc=\"upper left\")\nfig.tight_layout()\nfig\n",[14,956,957,970,974,1000,1025,1058,1104,1114,1129,1134],{"__ignoreMap":87},[91,958,959,961,964,967],{"class":93,"line":94},[91,960,270],{"class":101},[91,962,963],{"class":97}," matplotlib.pyplot ",[91,965,966],{"class":101},"as",[91,968,969],{"class":97}," plt\n",[91,971,972],{"class":93,"line":127},[91,973,279],{"emptyLinePlaceholder":278},[91,975,976,979,981,984,987,989,991,994,996,998],{"class":93,"line":151},[91,977,978],{"class":97},"fig, ax ",[91,980,102],{"class":101},[91,982,983],{"class":97}," plt.subplots(",[91,985,986],{"class":114},"figsize",[91,988,102],{"class":101},[91,990,199],{"class":97},[91,992,993],{"class":120},"7",[91,995,58],{"class":97},[91,997,189],{"class":120},[91,999,177],{"class":97},[91,1001,1002,1005,1008,1010,1013,1015,1018,1020,1023],{"class":93,"line":168},[91,1003,1004],{"class":97},"ax.plot(history.index, history.values, ",[91,1006,1007],{"class":114},"label",[91,1009,102],{"class":101},[91,1011,1012],{"class":108},"\"Actual\"",[91,1014,58],{"class":97},[91,1016,1017],{"class":114},"color",[91,1019,102],{"class":101},[91,1021,1022],{"class":108},"\"#5B5CF0\"",[91,1024,124],{"class":97},[91,1026,1027,1030,1032,1035,1038,1041,1043,1045,1047,1049,1051,1053,1056],{"class":93,"line":180},[91,1028,1029],{"class":97},"ax.plot(result[",[91,1031,145],{"class":108},[91,1033,1034],{"class":97},"], result[",[91,1036,1037],{"class":108},"\"Forecast\"",[91,1039,1040],{"class":97},"], ",[91,1042,1007],{"class":114},[91,1044,102],{"class":101},[91,1046,1037],{"class":108},[91,1048,58],{"class":97},[91,1050,1017],{"class":114},[91,1052,102],{"class":101},[91,1054,1055],{"class":108},"\"#0F9488\"",[91,1057,124],{"class":97},[91,1059,1060,1063,1065,1067,1070,1072,1075,1077,1079,1081,1083,1085,1088,1090,1093,1095,1097,1099,1102],{"class":93,"line":194},[91,1061,1062],{"class":97},"ax.fill_between(result[",[91,1064,145],{"class":108},[91,1066,1034],{"class":97},[91,1068,1069],{"class":108},"\"Low\"",[91,1071,1034],{"class":97},[91,1073,1074],{"class":108},"\"High\"",[91,1076,1040],{"class":97},[91,1078,1017],{"class":114},[91,1080,102],{"class":101},[91,1082,1055],{"class":108},[91,1084,58],{"class":97},[91,1086,1087],{"class":114},"alpha",[91,1089,102],{"class":101},[91,1091,1092],{"class":120},"0.15",[91,1094,58],{"class":97},[91,1096,1007],{"class":114},[91,1098,102],{"class":101},[91,1100,1101],{"class":108},"\"Band\"",[91,1103,124],{"class":97},[91,1105,1106,1109,1112],{"class":93,"line":642},[91,1107,1108],{"class":97},"ax.set_title(",[91,1110,1111],{"class":108},"\"Monthly sales and 12-month forecast\"",[91,1113,124],{"class":97},[91,1115,1116,1119,1122,1124,1127],{"class":93,"line":659},[91,1117,1118],{"class":97},"ax.legend(",[91,1120,1121],{"class":114},"loc",[91,1123,102],{"class":101},[91,1125,1126],{"class":108},"\"upper left\"",[91,1128,124],{"class":97},[91,1130,1131],{"class":93,"line":665},[91,1132,1133],{"class":97},"fig.tight_layout()\n",[91,1135,1136],{"class":93,"line":688},[91,1137,1138],{"class":97},"fig\n",[10,1140,1141,1142,1145,1146,1150],{},"Returning ",[14,1143,1144],{},"fig"," as the last expression displays the chart; in Excel the image can be resized and moved like any picture, and it refreshes when the cell recalculates. The general technique is in ",[30,1147,1149],{"href":1148},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fplot-charts-with-matplotlib-in-python-in-excel\u002F","Plot Charts with matplotlib in Python in Excel",". If the people reading the workbook prefer native Excel charts, chart the spilled range from Step 4 instead.",[36,1152,1154],{"id":1153},"step-6-compare-with-forecastets","Step 6 — Compare with FORECAST.ETS",[10,1156,1157,1158,1161],{},"Excel's own ",[14,1159,1160],{},"FORECAST.ETS(target_date, values, timeline, seasonality)"," also uses exponential smoothing and needs no Python at all. It is a reasonable baseline and a good cross-check:",[82,1163,1165],{"className":84,"code":1164,"language":86,"meta":87,"style":87},"check = pd.DataFrame({\n    \"Month\": result[\"Month\"].dt.strftime(\"%b %Y\"),\n    \"Python forecast\": result[\"Forecast\"],\n})\ncheck[\"Excel formula to compare\"] = [\n    f\"=FORECAST.ETS(DATE({d.year},{d.month},1),SalesHistory[Sales],SalesHistory[Month],12)\"\n    for d in result[\"Month\"]\n]\nprint(check.head(3).to_string(index=False))\n",[14,1166,1167,1176,1200,1212,1216,1232,1262,1280,1284],{"__ignoreMap":87},[91,1168,1169,1172,1174],{"class":93,"line":94},[91,1170,1171],{"class":97},"check ",[91,1173,102],{"class":101},[91,1175,605],{"class":97},[91,1177,1178,1180,1183,1185,1188,1191,1195,1198],{"class":93,"line":127},[91,1179,610],{"class":108},[91,1181,1182],{"class":97},": result[",[91,1184,145],{"class":108},[91,1186,1187],{"class":97},"].dt.strftime(",[91,1189,1190],{"class":108},"\"",[91,1192,1194],{"class":1193},"sSjpA","%b",[91,1196,1197],{"class":108}," %Y\"",[91,1199,639],{"class":97},[91,1201,1202,1205,1207,1209],{"class":93,"line":151},[91,1203,1204],{"class":108},"    \"Python forecast\"",[91,1206,1182],{"class":97},[91,1208,1037],{"class":108},[91,1210,1211],{"class":97},"],\n",[91,1213,1214],{"class":93,"line":168},[91,1215,662],{"class":97},[91,1217,1218,1221,1224,1227,1229],{"class":93,"line":180},[91,1219,1220],{"class":97},"check[",[91,1222,1223],{"class":108},"\"Excel formula to compare\"",[91,1225,1226],{"class":97},"] ",[91,1228,102],{"class":101},[91,1230,1231],{"class":97}," [\n",[91,1233,1234,1237,1240,1243,1246,1249,1252,1254,1257,1259],{"class":93,"line":194},[91,1235,1236],{"class":101},"    f",[91,1238,1239],{"class":108},"\"=FORECAST.ETS(DATE(",[91,1241,1242],{"class":1193},"{",[91,1244,1245],{"class":97},"d.year",[91,1247,1248],{"class":1193},"}",[91,1250,1251],{"class":108},",",[91,1253,1242],{"class":1193},[91,1255,1256],{"class":97},"d.month",[91,1258,1248],{"class":1193},[91,1260,1261],{"class":108},",1),SalesHistory[Sales],SalesHistory[Month],12)\"\n",[91,1263,1264,1267,1270,1273,1276,1278],{"class":93,"line":642},[91,1265,1266],{"class":101},"    for",[91,1268,1269],{"class":97}," d ",[91,1271,1272],{"class":101},"in",[91,1274,1275],{"class":97}," result[",[91,1277,145],{"class":108},[91,1279,165],{"class":97},[91,1281,1282],{"class":93,"line":659},[91,1283,165],{"class":97},[91,1285,1286,1288,1291,1293,1295,1297,1299,1301],{"class":93,"line":665},[91,1287,183],{"class":120},[91,1289,1290],{"class":97},"(check.head(",[91,1292,189],{"class":120},[91,1294,675],{"class":97},[91,1296,678],{"class":114},[91,1298,102],{"class":101},[91,1300,683],{"class":120},[91,1302,177],{"class":97},[10,1304,1305],{},"Put those formulas next to the spilled forecast: the two will be close but not identical, because Excel's implementation (AAA exponential smoothing with its own parameter estimation) differs in detail from statsmodels' Holt-Winters fit. Large differences usually point to a data problem — a gap, an outlier, a month typed as text — that one method tolerates and the other does not.",[367,1307,376,1312,376,1315,376,1318,376,1321,376,1326,376,1332,376,1337,376,1342,376,1348,376,1355,376,1359,376,1364,376,1368,376,1375,376,1379,376,1384,376,1386,376,1391,376,1396,376,1400,376,1404,376,1409,376,1412,376,1417,376,1420,376,1425,376,1428],{"viewBox":1308,"role":370,"ariaLabelledBy":1309,"xmlns":374,"style":375},"4 0 752 233",[1310,1311],"pie-compare-t","pie-compare-d",[378,1313,1314],{"id":1310},"FORECAST.ETS or a Python model",[382,1316,1317],{"id":1311},"FORECAST.ETS needs no Python and recalculates instantly but offers few options. A statsmodels model in Python in Excel lets you choose additive or multiplicative seasonality, inspect parameters, add bands and charts, but runs in the cloud and recalculates more slowly.",[386,1319],{"x":388,"y":337,"width":389,"height":1320,"fill":391},"233",[386,1322],{"x":394,"y":395,"width":1323,"height":1324,"rx":1325,"fill":438,"stroke":429,"style":400},"351.0","201.0","14",[402,1327,1329],{"transform":1328},"translate(182.5,29.0) scale(1.083)",[406,1330],{"d":504,"fill":409,"stroke":445,"style":1331},"stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[413,1333,16],{"x":1334,"y":1335,"style":1336},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[93,1338],{"x1":1339,"y1":1340,"x2":1341,"y2":1340,"stroke":429,"style":767},"36.0","91.0","355.0",[413,1343,1347],{"x":1344,"y":1345,"style":1346},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","plain formula, works offline",[402,1349,1351],{"transform":1350},"translate(38.5,99.5) scale(0.625)",[406,1352],{"d":1353,"fill":409,"stroke":498,"style":1354},"M5 12.5l4.5 4.5L19 7","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[413,1356,1358],{"x":1344,"y":1357,"style":1346},"137.0","instant recalculation",[402,1360,1362],{"transform":1361},"translate(38.5,125.5) scale(0.625)",[406,1363],{"d":1353,"fill":409,"stroke":498,"style":1354},[413,1365,1367],{"x":1344,"y":1366,"style":1346},"163.0","one model, few options",[402,1369,1371],{"transform":1370},"translate(38.5,151.5) scale(0.625)",[406,1372],{"d":1373,"fill":409,"stroke":1374,"style":1354},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[413,1376,1378],{"x":1344,"y":1377,"style":1346},"189.0","no parameters to inspect",[402,1380,1382],{"transform":1381},"translate(38.5,177.5) scale(0.625)",[406,1383],{"d":1373,"fill":409,"stroke":1374,"style":1354},[386,1385],{"x":463,"y":395,"width":1323,"height":1324,"rx":1325,"fill":497,"stroke":498,"style":400},[402,1387,1389],{"transform":1388},"translate(551.5,29.0) scale(1.083)",[406,1390],{"d":444,"fill":409,"stroke":505,"style":1331},[413,1392,1395],{"x":1393,"y":1335,"style":1394},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Python in Excel",[93,1397],{"x1":1398,"y1":1340,"x2":1399,"y2":1340,"stroke":498,"style":767},"405.0","724.0",[413,1401,1403],{"x":1402,"y":1345,"style":1346},"429.0","choose the model and seasonality",[402,1405,1407],{"transform":1406},"translate(407.5,99.5) scale(0.625)",[406,1408],{"d":1353,"fill":409,"stroke":498,"style":1354},[413,1410,1411],{"x":1402,"y":1357,"style":1346},"inspect fitted parameters",[402,1413,1415],{"transform":1414},"translate(407.5,125.5) scale(0.625)",[406,1416],{"d":1353,"fill":409,"stroke":498,"style":1354},[413,1418,1419],{"x":1402,"y":1366,"style":1346},"bands, charts, extra variables",[402,1421,1423],{"transform":1422},"translate(407.5,151.5) scale(0.625)",[406,1424],{"d":1353,"fill":409,"stroke":498,"style":1354},[413,1426,1427],{"x":1402,"y":1377,"style":1346},"cloud execution, slower recalc",[402,1429,1431],{"transform":1430},"translate(407.5,177.5) scale(0.625)",[406,1432],{"d":1433,"fill":409,"stroke":1434,"style":1354},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold,#b4740a)",[10,1436,1437],{},"Python earns its place when you need what the formula cannot do: choosing the model, inspecting its parameters, multiplicative seasonality, or adding explanatory variables.",[36,1439,1441],{"id":1440},"step-7-test-the-method-on-a-year-you-already-know","Step 7 — Test the method on a year you already know",[10,1443,1444],{},"Before trusting next year's numbers, check how the same method would have done on the last year of history: fit on the first 24 months, forecast the next 12, and compare with what actually happened:",[82,1446,1448],{"className":84,"code":1447,"language":86,"meta":87,"style":87},"train, test = history[:24], history[24:]\nbacktest = ExponentialSmoothing(train, trend=\"add\", seasonal=\"add\", seasonal_periods=12).fit().forecast(12)\nmape = ((backtest - test).abs() \u002F test).mean() * 100\nprint(f\"average absolute error on 2025: {mape:.1f}%\")\n",[14,1449,1450,1471,1510,1535],{"__ignoreMap":87},[91,1451,1452,1455,1457,1460,1463,1466,1468],{"class":93,"line":94},[91,1453,1454],{"class":97},"train, test ",[91,1456,102],{"class":101},[91,1458,1459],{"class":97}," history[:",[91,1461,1462],{"class":120},"24",[91,1464,1465],{"class":97},"], history[",[91,1467,1462],{"class":120},[91,1469,1470],{"class":97},":]\n",[91,1472,1473,1476,1478,1481,1483,1485,1487,1489,1491,1493,1495,1497,1499,1501,1503,1506,1508],{"class":93,"line":127},[91,1474,1475],{"class":97},"backtest ",[91,1477,102],{"class":101},[91,1479,1480],{"class":97}," ExponentialSmoothing(train, ",[91,1482,292],{"class":114},[91,1484,102],{"class":101},[91,1486,297],{"class":108},[91,1488,58],{"class":97},[91,1490,302],{"class":114},[91,1492,102],{"class":101},[91,1494,297],{"class":108},[91,1496,58],{"class":97},[91,1498,311],{"class":114},[91,1500,102],{"class":101},[91,1502,316],{"class":120},[91,1504,1505],{"class":97},").fit().forecast(",[91,1507,316],{"class":120},[91,1509,124],{"class":97},[91,1511,1512,1515,1517,1520,1522,1525,1527,1530,1532],{"class":93,"line":151},[91,1513,1514],{"class":97},"mape ",[91,1516,102],{"class":101},[91,1518,1519],{"class":97}," ((backtest ",[91,1521,541],{"class":101},[91,1523,1524],{"class":97}," test).abs() ",[91,1526,580],{"class":101},[91,1528,1529],{"class":97}," test).mean() ",[91,1531,563],{"class":101},[91,1533,1534],{"class":120}," 100\n",[91,1536,1537,1539,1541,1544,1547,1549,1552,1555,1557,1560],{"class":93,"line":168},[91,1538,183],{"class":120},[91,1540,199],{"class":97},[91,1542,1543],{"class":101},"f",[91,1545,1546],{"class":108},"\"average absolute error on 2025: ",[91,1548,1242],{"class":1193},[91,1550,1551],{"class":97},"mape",[91,1553,1554],{"class":101},":.1f",[91,1556,1248],{"class":1193},[91,1558,1559],{"class":108},"%\"",[91,1561,124],{"class":97},[10,1563,1564],{},"On the sample data the method misses each 2025 month by a few percent on average — a figure worth stating next to the forecast, because it tells readers how much weight the numbers can bear. If the error is large, try multiplicative seasonality or a simpler model and keep whichever does best on the held-out year.",[36,1566,1568],{"id":1567},"common-pitfalls","Common pitfalls",[1570,1571,1572,1581,1587,1593,1602],"ul",{},[1573,1574,1575,1578,1579,34],"li",{},[724,1576,1577],{},"Irregular dates."," Months stored as text or with gaps break the seasonal model; set a monthly index and check for ",[14,1580,246],{},[1573,1582,1583,1586],{},[724,1584,1585],{},"Too little history."," A 12-month season needs at least 24 months of data.",[1573,1588,1589,1592],{},[724,1590,1591],{},"Returning a Python object."," Switch the cell to Excel value output to spill the forecast into the grid.",[1573,1594,1595,1598,1599,1601],{},[724,1596,1597],{},"Recalculation surprises."," ",[14,1600,20],{}," cells recalculate with the workbook; a long-running fit makes editing slow, so consider manual calculation for heavy models.",[1573,1603,1604,1607,1608,34],{},[724,1605,1606],{},"Expecting local resources."," Code runs in Microsoft's cloud with a fixed set of packages and no access to local files or the internet, as listed in ",[30,1609,1611],{"href":1610},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Ffix-python-in-excel-errors\u002F","Fix Python in Excel Errors",[36,1613,1615],{"id":1614},"conclusion","Conclusion",[10,1617,1618,1619,1621,1622,1624,1625,1627],{},"Python in Excel turns a sales table into a forecast without leaving the workbook: read it with ",[14,1620,27],{},", set a monthly ",[14,1623,235],{},", fit a Holt-Winters model with trend and seasonality, add an uncertainty band, and return a DataFrame as an Excel value so it spills into the grid for formulas and charts. Plot actuals and forecast with matplotlib if you like, and keep ",[14,1626,16],{}," alongside as a quick cross-check.",[36,1629,1631],{"id":1630},"frequently-asked-questions","Frequently asked questions",[10,1633,1634,1637],{},[724,1635,1636],{},"Can I forecast in Excel with Python?","\nYes. In Microsoft 365, a =PY() cell can read a table with xl(), fit a model with statsmodels such as ExponentialSmoothing, and return the forecast as a DataFrame that spills into the grid.",[10,1639,1640,1643],{},[724,1641,1642],{},"How do I return a forecast DataFrame to Excel cells from Python in Excel?","\nMake the DataFrame the last expression in the =PY() cell and switch the cell's output from Python object to Excel value. The DataFrame then spills into the sheet as a range with headers.",[10,1645,1646,1649],{},[724,1647,1648],{},"How much history does a seasonal forecast need?","\nAt least two full seasons — 24 months for monthly data with a yearly pattern — so the model can estimate the seasonal effect; more history gives more stable estimates.",[10,1651,1652,1655],{},[724,1653,1654],{},"How does Python's forecast compare with FORECAST.ETS?","\nBoth use exponential smoothing, so results are usually close but not identical, because the implementations estimate parameters differently. Python lets you choose the model type, inspect parameters and use multiplicative seasonality.",[36,1657,1659],{"id":1658},"related","Related",[1570,1661,1662,1668,1675,1682],{},[1573,1663,1664,1665,1667],{},"Up one level: ",[30,1666,33],{"href":32}," — Python inside Microsoft 365 Excel.",[1573,1669,1670,1674],{},[30,1671,1673],{"href":1672},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Frun-a-regression-with-python-in-excel\u002F","Run a Regression with Python in Excel"," — statsmodels for explanatory models.",[1573,1676,1677,1681],{},[30,1678,1680],{"href":1679},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python\u002F","FORECAST, TREND and SLOPE Equivalents in Python"," — linear forecasting functions.",[1573,1683,1684,1688],{},[30,1685,1687],{"href":1686},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-moving-averages-with-pandas-rolling\u002F","Calculate Moving Averages with pandas rolling"," — smoothing without a model.",[1690,1691,1692],"style",{},"html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":87,"searchDepth":127,"depth":127,"links":1694},[1695,1696,1697,1698,1699,1700,1701,1702,1703,1704,1705,1706],{"id":38,"depth":127,"text":39},{"id":72,"depth":127,"text":73},{"id":250,"depth":127,"text":251},{"id":519,"depth":127,"text":520},{"id":715,"depth":127,"text":716},{"id":944,"depth":127,"text":945},{"id":1153,"depth":127,"text":1154},{"id":1440,"depth":127,"text":1441},{"id":1567,"depth":127,"text":1568},{"id":1614,"depth":127,"text":1615},{"id":1630,"depth":127,"text":1631},{"id":1658,"depth":127,"text":1659},"2026-10-02","Use Python in Excel to forecast monthly sales from a table in the workbook: pull the history with xl(), give it a proper monthly date index, fit a Holt-Winters model with statsmodels, return the forecast and a simple prediction band as a spilled range, chart it with matplotlib, and know the limits — cloud execution, recalculation and FORECAST.ETS as a formula-only alternative.","md",[1711,1713,1715,1717],{"q":1636,"a":1712},"Yes. In Microsoft 365, a =PY() cell can read a table with xl(), fit a model with statsmodels such as ExponentialSmoothing, and return the forecast as a DataFrame that spills into the grid.",{"q":1642,"a":1714},"Make the DataFrame the last expression in the =PY() cell and switch the cell's output from Python object to Excel value. The DataFrame then spills into the sheet as a range with headers.",{"q":1648,"a":1716},"At least two full seasons — 24 months for monthly data with a yearly pattern — so the model can estimate the seasonal effect; more history gives more stable estimates.",{"q":1654,"a":1718},"Both use exponential smoothing, so results are usually close but not identical, because the implementations estimate parameters differently. Python lets you choose the model type, inspect parameters and use multiplicative seasonality.","forecast a time series with python in excel",null,{"breadcrumb":1722},[1723,1725,1728],{"name":1724,"item":580},"Home",{"name":1726,"item":1727},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":33,"item":32},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fforecast-a-time-series-with-python-in-excel",{"title":1731,"description":1732},"Forecast a Time Series with Python in Excel (statsmodels)","Forecast monthly sales inside Excel with =PY(): read a table with xl(), set a monthly DatetimeIndex, Holt-Winters ExponentialSmoothing, return the forecast as an Excel range, plot actuals and forecast, and compare with FORECAST.ETS.","forecast-a-time-series-with-python-in-excel","getting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fforecast-a-time-series-with-python-in-excel\u002Findex",[1736,1737],"python-in-excel","dates-and-times","how-to","XyWGuY-69_7PfecqLvFSCw4gz_86DQFS7rThYae7Z_M",[1741,1744],{"title":1611,"path":1742,"stem":1743,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Ffix-python-in-excel-errors","getting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Ffix-python-in-excel-errors\u002Findex",{"title":1745,"path":1746,"stem":1747,"children":-1},"Get External Data into Python in Excel with Power Query","\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fget-external-data-into-python-in-excel","getting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fget-external-data-into-python-in-excel\u002Findex",[1749,1751],{"path":1750,"title":1726},"\u002Fgetting-started-with-python-excel-automation",{"path":1752,"title":33},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel",[],[1755,1758,1760,1761,1763,1767],{"path":1756,"title":1757,"kind":1738},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fclean-data-with-pandas-in-python-in-excel","Clean Data with pandas in Python in Excel",{"path":1742,"title":1611,"kind":1759},"fix",{"path":1746,"title":1745,"kind":1738},{"path":1762,"title":1149,"kind":1738},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fplot-charts-with-matplotlib-in-python-in-excel",{"path":1764,"title":1765,"kind":1766},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Fpython-in-excel-vs-openpyxl-and-xlwings","Python in Excel vs openpyxl and xlwings","compare",{"path":1768,"title":231,"kind":1738},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-python-in-excel\u002Freference-cells-ranges-and-tables-with-xl-in-python-in-excel",1790972075077]