[{"data":1,"prerenderedAt":1912},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python":1878,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python":1887,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python":1892,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python":1893},{"id":4,"title":5,"body":6,"dateModified":1844,"datePublished":1844,"description":1845,"extension":1846,"faq":1847,"keyword":1856,"kind":1857,"meta":1858,"navigation":322,"path":1866,"seo":1867,"slug":1870,"stem":1871,"topics":1872,"type":1876,"__hash__":1877},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python\u002Findex.md","FORECAST, TREND, SLOPE and RSQ Equivalents in Python",{"type":7,"value":8,"toc":1829},"minimark",[9,44,223,228,258,264,268,281,556,580,584,592,676,693,697,704,834,839,955,959,970,1061,1064,1068,1071,1282,1285,1345,1349,1352,1646,1669,1673,1680,1684,1719,1723,1732,1736,1764,1768,1774,1780,1786,1792,1796,1825],[10,11,12,13,17,18,21,22,25,26,29,30,33,34,37,38,43],"p",{},"A trend line is the first forecast most reports make: sales are growing by about seven units a month, so next quarter will look like this. In Excel it is ",[14,15,16],"code",{},"SLOPE"," and ",[14,19,20],{},"INTERCEPT",", ",[14,23,24],{},"TREND"," to extend a column of fitted values, ",[14,27,28],{},"FORECAST.LINEAR"," for a single projected point, and ",[14,31,32],{},"RSQ"," or ",[14,35,36],{},"CORREL"," to say how well the line fits. All of them are ordinary least-squares regression on one variable, which NumPy computes in one call. This page, part of ",[39,40,42],"a",{"href":41},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",", reproduces each function — the results below match the same formulas recalculated in LibreOffice to many decimal places — and then applies them per product, with dates, and with the caution a straight-line forecast deserves.",[45,46,54,55,54,59,54,63,54,71,54,79,54,85,54,89,54,93,54,96,54,100,54,103,54,107,54,110,54,114,54,118,54,124,54,128,54,132,54,136,54,140,54,144,54,148,54,152,54,156,54,160,54,164,54,171,54,177,54,180,54,183,54,186,54,188,54,191,54,194,54,197,54,200,54,203,54,206,54,211,54,218],"svg",{"viewBox":47,"role":48,"ariaLabelledBy":49,"xmlns":52,"style":53},"4 0 748 222","img",[50,51],"fc-chart-t","fc-chart-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:748px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[56,57,58],"title",{"id":50},"The fitted widget trend and a three-month projection",[60,61,62],"desc",{"id":51},"A line chart of the least-squares trend fitted to eight months of widget sales, which rose from 120 to 175. The fitted line runs from 120.3 in January to 172.2 in August and is extended as a projection to 179.6, 187.0 and 194.4 for September to November.",[64,65],"rect",{"x":66,"y":67,"width":68,"height":69,"fill":70},"4","0","748","222","#ffffff",[72,73],"line",{"x1":74,"y1":75,"x2":76,"y2":75,"stroke":77,"style":78},"53.6","184.0","736.0","var(--line,#cdd5e6)","stroke-width:1px",[80,81,67],"text",{"x":82,"y":83,"style":84},"30.8","188.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle",[72,86],{"x1":74,"y1":87,"x2":76,"y2":87,"stroke":77,"style":88},"144.0","stroke-width:1px;stroke-dasharray:5 4",[80,90,92],{"x":82,"y":91,"style":84},"148.0","50",[72,94],{"x1":74,"y1":95,"x2":76,"y2":95,"stroke":77,"style":88},"104.0",[80,97,99],{"x":82,"y":98,"style":84},"108.0","100",[72,101],{"x1":74,"y1":102,"x2":76,"y2":102,"stroke":77,"style":88},"64.0",[80,104,106],{"x":82,"y":105,"style":84},"68.0","150",[72,108],{"x1":74,"y1":109,"x2":76,"y2":109,"stroke":77,"style":88},"24.0",[80,111,113],{"x":82,"y":112,"style":84},"28.0","200",[72,115],{"x1":74,"y1":75,"x2":76,"y2":75,"stroke":116,"style":117},"var(--muted,#5b6780)","stroke-width:1.5px",[80,119,123],{"x":120,"y":121,"style":122},"84.6","202.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Jan",[80,125,127],{"x":126,"y":121,"style":122},"146.7","Feb",[80,129,131],{"x":130,"y":121,"style":122},"208.7","Mar",[80,133,135],{"x":134,"y":121,"style":122},"270.7","Apr",[80,137,139],{"x":138,"y":121,"style":122},"332.8","May",[80,141,143],{"x":142,"y":121,"style":122},"394.8","Jun",[80,145,147],{"x":146,"y":121,"style":122},"456.8","Jul",[80,149,151],{"x":150,"y":121,"style":122},"518.9","Aug",[80,153,155],{"x":154,"y":121,"style":122},"580.9","Sep",[80,157,159],{"x":158,"y":121,"style":122},"642.9","Oct",[80,161,163],{"x":162,"y":121,"style":122},"705.0","Nov",[165,166],"path",{"d":167,"fill":168,"stroke":169,"style":170},"M84.6,87.8 L146.7,81.8 L208.7,75.9 L270.7,70.0 L332.8,64.0 L394.8,58.1 L456.8,52.2 L518.9,46.2 L580.9,40.3 L642.9,34.4 L705.0,28.5","none","var(--brand,#5b5cf0)","stroke-width:2.5px;stroke-linejoin:round;stroke-linecap:round",[172,173],"circle",{"cx":120,"cy":174,"r":175,"fill":70,"stroke":169,"style":176},"87.8","4.0","stroke-width:2px",[172,178],{"cx":126,"cy":179,"r":175,"fill":70,"stroke":169,"style":176},"81.8",[172,181],{"cx":130,"cy":182,"r":175,"fill":70,"stroke":169,"style":176},"75.9",[172,184],{"cx":134,"cy":185,"r":175,"fill":70,"stroke":169,"style":176},"70.0",[172,187],{"cx":138,"cy":102,"r":175,"fill":70,"stroke":169,"style":176},[172,189],{"cx":142,"cy":190,"r":175,"fill":70,"stroke":169,"style":176},"58.1",[172,192],{"cx":146,"cy":193,"r":175,"fill":70,"stroke":169,"style":176},"52.2",[172,195],{"cx":150,"cy":196,"r":175,"fill":70,"stroke":169,"style":176},"46.2",[172,198],{"cx":154,"cy":199,"r":175,"fill":70,"stroke":169,"style":176},"40.3",[172,201],{"cx":158,"cy":202,"r":175,"fill":70,"stroke":169,"style":176},"34.4",[172,204],{"cx":162,"cy":205,"r":175,"fill":70,"stroke":169,"style":176},"28.5",[72,207],{"x1":154,"y1":208,"x2":154,"y2":75,"stroke":209,"style":210},"20.0","var(--accent,#f43f8f)","stroke-width:1.5px;stroke-dasharray:5 4",[64,212],{"x":213,"y":214,"width":215,"height":208,"rx":216,"fill":217,"stroke":209,"style":78},"544.2","8.0","73.5","10","#fee8f2",[80,219,222],{"x":154,"y":220,"style":221},"22.0","font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","projection",[224,225,227],"h2",{"id":226},"prerequisites","Prerequisites",[229,230,235],"pre",{"className":231,"code":232,"language":233,"meta":234,"style":234},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install numpy pandas openpyxl\n","bash","",[14,236,237],{"__ignoreMap":234},[238,239,241,245,249,252,255],"span",{"class":72,"line":240},1,[238,242,244],{"class":243},"sMTad","pip",[238,246,248],{"class":247},"srMev"," install",[238,250,251],{"class":247}," numpy",[238,253,254],{"class":247}," pandas",[238,256,257],{"class":247}," openpyxl\n",[10,259,260,263],{},[14,261,262],{},"monthly.xlsx"," holds eight months of sales for two products: Widgets, growing steadily, and Gadgets, nearly flat with noise.",[224,265,267],{"id":266},"step-1-slope-intercept-rsq-and-correl","Step 1 — SLOPE, INTERCEPT, RSQ and CORREL",[10,269,270,273,274,277,278,280],{},[14,271,272],{},"numpy.polyfit(x, y, 1)"," fits a straight line and returns its coefficients, slope first. ",[14,275,276],{},"numpy.corrcoef"," gives the correlation, and its square is ",[14,279,32],{},":",[229,282,286],{"className":283,"code":284,"language":285,"meta":234,"style":234},"language-python shiki shiki-themes github-light github-dark-high-contrast","import numpy as np\nimport pandas as pd\n\nmonthly = pd.read_excel(\"monthly.xlsx\")\nw = monthly[monthly[\"product\"] == \"Widgets\"].reset_index(drop=True)\nx = np.arange(1, len(w) + 1)            # period numbers 1..8\ny = w[\"sales\"].to_numpy(dtype=float)\n\nslope, intercept = np.polyfit(x, y, 1)\nr = np.corrcoef(x, y)[0, 1]\nprint(f\"SLOPE {slope:.6f}  INTERCEPT {intercept:.6f}  CORREL {r:.6f}  RSQ {r**2:.6f}\")\n","python",[14,287,288,304,317,324,342,380,415,442,447,462,482],{"__ignoreMap":234},[238,289,290,294,298,301],{"class":72,"line":240},[238,291,293],{"class":292},"s-kum","import",[238,295,297],{"class":296},"skGVy"," numpy ",[238,299,300],{"class":292},"as",[238,302,303],{"class":296}," np\n",[238,305,307,309,312,314],{"class":72,"line":306},2,[238,308,293],{"class":292},[238,310,311],{"class":296}," pandas ",[238,313,300],{"class":292},[238,315,316],{"class":296}," pd\n",[238,318,320],{"class":72,"line":319},3,[238,321,323],{"emptyLinePlaceholder":322},true,"\n",[238,325,327,330,333,336,339],{"class":72,"line":326},4,[238,328,329],{"class":296},"monthly ",[238,331,332],{"class":292},"=",[238,334,335],{"class":296}," pd.read_excel(",[238,337,338],{"class":247},"\"monthly.xlsx\"",[238,340,341],{"class":296},")\n",[238,343,345,348,350,353,356,359,362,365,368,372,374,378],{"class":72,"line":344},5,[238,346,347],{"class":296},"w ",[238,349,332],{"class":292},[238,351,352],{"class":296}," monthly[monthly[",[238,354,355],{"class":247},"\"product\"",[238,357,358],{"class":296},"] ",[238,360,361],{"class":292},"==",[238,363,364],{"class":247}," \"Widgets\"",[238,366,367],{"class":296},"].reset_index(",[238,369,371],{"class":370},"sa561","drop",[238,373,332],{"class":292},[238,375,377],{"class":376},"sP0c6","True",[238,379,341],{"class":296},[238,381,383,386,388,391,394,396,399,402,405,408,411],{"class":72,"line":382},6,[238,384,385],{"class":296},"x ",[238,387,332],{"class":292},[238,389,390],{"class":296}," np.arange(",[238,392,393],{"class":376},"1",[238,395,21],{"class":296},[238,397,398],{"class":376},"len",[238,400,401],{"class":296},"(w) ",[238,403,404],{"class":292},"+",[238,406,407],{"class":376}," 1",[238,409,410],{"class":296},")            ",[238,412,414],{"class":413},"s-wDw","# period numbers 1..8\n",[238,416,418,421,423,426,429,432,435,437,440],{"class":72,"line":417},7,[238,419,420],{"class":296},"y ",[238,422,332],{"class":292},[238,424,425],{"class":296}," w[",[238,427,428],{"class":247},"\"sales\"",[238,430,431],{"class":296},"].to_numpy(",[238,433,434],{"class":370},"dtype",[238,436,332],{"class":292},[238,438,439],{"class":376},"float",[238,441,341],{"class":296},[238,443,445],{"class":72,"line":444},8,[238,446,323],{"emptyLinePlaceholder":322},[238,448,450,453,455,458,460],{"class":72,"line":449},9,[238,451,452],{"class":296},"slope, intercept ",[238,454,332],{"class":292},[238,456,457],{"class":296}," np.polyfit(x, y, ",[238,459,393],{"class":376},[238,461,341],{"class":296},[238,463,465,468,470,473,475,477,479],{"class":72,"line":464},10,[238,466,467],{"class":296},"r ",[238,469,332],{"class":292},[238,471,472],{"class":296}," np.corrcoef(x, y)[",[238,474,67],{"class":376},[238,476,21],{"class":296},[238,478,393],{"class":376},[238,480,481],{"class":296},"]\n",[238,483,485,488,491,494,497,501,504,507,510,513,515,518,520,522,525,527,530,532,534,537,539,541,544,547,549,551,554],{"class":72,"line":484},11,[238,486,487],{"class":376},"print",[238,489,490],{"class":296},"(",[238,492,493],{"class":292},"f",[238,495,496],{"class":247},"\"SLOPE ",[238,498,500],{"class":499},"sSjpA","{",[238,502,503],{"class":296},"slope",[238,505,506],{"class":292},":.6f",[238,508,509],{"class":499},"}",[238,511,512],{"class":247},"  INTERCEPT ",[238,514,500],{"class":499},[238,516,517],{"class":296},"intercept",[238,519,506],{"class":292},[238,521,509],{"class":499},[238,523,524],{"class":247},"  CORREL ",[238,526,500],{"class":499},[238,528,529],{"class":296},"r",[238,531,506],{"class":292},[238,533,509],{"class":499},[238,535,536],{"class":247},"  RSQ ",[238,538,500],{"class":499},[238,540,529],{"class":296},[238,542,543],{"class":292},"**",[238,545,546],{"class":376},"2",[238,548,506],{"class":292},[238,550,509],{"class":499},[238,552,553],{"class":247},"\"",[238,555,341],{"class":296},[10,557,558,559,21,562,21,565,17,568,571,572,575,576,579],{},"The slope is 7.404762 units per month and the intercept 112.928571, with a correlation of 0.982 and an R² of 0.965 — identical to ",[14,560,561],{},"=SLOPE(B2:B9, A2:A9)",[14,563,564],{},"=INTERCEPT(...)",[14,566,567],{},"=CORREL(...)",[14,569,570],{},"=RSQ(...)"," over the same numbers. Note the argument order: Excel's regression functions take the y values first (",[14,573,574],{},"SLOPE(known_ys, known_xs)","), while ",[14,577,578],{},"polyfit"," takes x first. Swapping them in Excel gives the regression of x on y, a different line — a mistake worth checking for when porting a workbook.",[224,581,583],{"id":582},"step-2-trend-and-forecastlinear","Step 2 — TREND and FORECAST.LINEAR",[10,585,586,588,589,591],{},[14,587,24],{}," returns fitted values for known or new x values, and ",[14,590,28],{}," a single projected value. Both are the fitted line evaluated at x:",[229,593,595],{"className":283,"code":594,"language":285,"meta":234,"style":234},"fitted = np.polyval([slope, intercept], x)\nfuture_x = np.array([9, 10, 11])\nprojection = slope * future_x + intercept\nprint(np.round(fitted, 1).tolist())\nprint(np.round(projection, 2).tolist())\n",[14,596,597,607,632,653,665],{"__ignoreMap":234},[238,598,599,602,604],{"class":72,"line":240},[238,600,601],{"class":296},"fitted ",[238,603,332],{"class":292},[238,605,606],{"class":296}," np.polyval([slope, intercept], x)\n",[238,608,609,612,614,617,620,622,624,626,629],{"class":72,"line":306},[238,610,611],{"class":296},"future_x ",[238,613,332],{"class":292},[238,615,616],{"class":296}," np.array([",[238,618,619],{"class":376},"9",[238,621,21],{"class":296},[238,623,216],{"class":376},[238,625,21],{"class":296},[238,627,628],{"class":376},"11",[238,630,631],{"class":296},"])\n",[238,633,634,637,639,642,645,648,650],{"class":72,"line":319},[238,635,636],{"class":296},"projection ",[238,638,332],{"class":292},[238,640,641],{"class":296}," slope ",[238,643,644],{"class":292},"*",[238,646,647],{"class":296}," future_x ",[238,649,404],{"class":292},[238,651,652],{"class":296}," intercept\n",[238,654,655,657,660,662],{"class":72,"line":326},[238,656,487],{"class":376},[238,658,659],{"class":296},"(np.round(fitted, ",[238,661,393],{"class":376},[238,663,664],{"class":296},").tolist())\n",[238,666,667,669,672,674],{"class":72,"line":344},[238,668,487],{"class":376},[238,670,671],{"class":296},"(np.round(projection, ",[238,673,546],{"class":376},[238,675,664],{"class":296},[10,677,678,679,17,682,685,686,689,690,692],{},"The projection for period 9 is 179.57 and for period 10 is 186.98 — ",[14,680,681],{},"=TREND(B2:B9, A2:A9, 9)",[14,683,684],{},"=FORECAST.LINEAR(10, B2:B9, A2:A9)"," give the same figures. The older ",[14,687,688],{},"FORECAST"," function is identical to ",[14,691,28],{},"; Excel kept it for compatibility.",[224,694,696],{"id":695},"step-3-how-far-off-is-the-line-steyx","Step 3 — How far off is the line? STEYX",[10,698,699,700,703],{},"A projection without an error estimate invites false precision. Excel's ",[14,701,702],{},"STEYX"," is the standard error of the fitted values — the typical distance of the actual points from the line — computed from the residuals with n − 2 degrees of freedom:",[229,705,707],{"className":283,"code":706,"language":285,"meta":234,"style":234},"residuals = y - fitted\nsteyx = np.sqrt((residuals ** 2).sum() \u002F (len(y) - 2))\nprint(round(steyx, 6))\nprint(\"roughly:\", [f\"{p:.0f} ± {2 * steyx:.0f}\" for p in projection])\n",[14,708,709,725,761,778],{"__ignoreMap":234},[238,710,711,714,716,719,722],{"class":72,"line":240},[238,712,713],{"class":296},"residuals ",[238,715,332],{"class":292},[238,717,718],{"class":296}," y ",[238,720,721],{"class":292},"-",[238,723,724],{"class":296}," fitted\n",[238,726,727,730,732,735,737,740,743,746,749,751,754,756,758],{"class":72,"line":306},[238,728,729],{"class":296},"steyx ",[238,731,332],{"class":292},[238,733,734],{"class":296}," np.sqrt((residuals ",[238,736,543],{"class":292},[238,738,739],{"class":376}," 2",[238,741,742],{"class":296},").sum() ",[238,744,745],{"class":292},"\u002F",[238,747,748],{"class":296}," (",[238,750,398],{"class":376},[238,752,753],{"class":296},"(y) ",[238,755,721],{"class":292},[238,757,739],{"class":376},[238,759,760],{"class":296},"))\n",[238,762,763,765,767,770,773,776],{"class":72,"line":319},[238,764,487],{"class":376},[238,766,490],{"class":296},[238,768,769],{"class":376},"round",[238,771,772],{"class":296},"(steyx, ",[238,774,775],{"class":376},"6",[238,777,760],{"class":296},[238,779,780,782,784,787,790,792,794,796,798,801,803,806,808,810,813,816,818,820,822,825,828,831],{"class":72,"line":326},[238,781,487],{"class":376},[238,783,490],{"class":296},[238,785,786],{"class":247},"\"roughly:\"",[238,788,789],{"class":296},", [",[238,791,493],{"class":292},[238,793,553],{"class":247},[238,795,500],{"class":499},[238,797,10],{"class":296},[238,799,800],{"class":292},":.0f",[238,802,509],{"class":499},[238,804,805],{"class":247}," ± ",[238,807,500],{"class":499},[238,809,546],{"class":376},[238,811,812],{"class":292}," *",[238,814,815],{"class":296}," steyx",[238,817,800],{"class":292},[238,819,509],{"class":499},[238,821,553],{"class":247},[238,823,824],{"class":292}," for",[238,826,827],{"class":296}," p ",[238,829,830],{"class":292},"in",[238,832,833],{"class":296}," projection])\n",[10,835,836,838],{},[14,837,702],{}," is 3.755, matching the spreadsheet. A rough band of two standard errors around each projection — about ±8 units here — is a far more honest thing to show than a single number, though it understates the uncertainty of points far beyond the data, where the slope's own error grows.",[45,840,54,846,54,849,54,852,54,856,54,861,54,867,54,873,54,878,54,884,54,886,54,890,54,893,54,897,54,899,54,903,54,906,54,910,54,912,54,916,54,919,54,923,54,925,54,929,54,932,54,936,54,938,54,942,54,945,54,949,54,951],{"viewBox":841,"role":48,"ariaLabelledBy":842,"xmlns":52,"style":845},"4 0 752 276",[843,844],"fc-map-t","fc-map-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[56,847,848],{"id":843},"Excel regression functions and their NumPy equivalents",[60,850,851],{"id":844},"A translation table. SLOPE and INTERCEPT come from numpy polyfit with degree 1, with x and y in the opposite order to Excel. CORREL comes from numpy corrcoef, and RSQ is its square. TREND and FORECAST.LINEAR evaluate the fitted line with polyval at new x values. STEYX is the square root of the sum of squared residuals divided by n minus 2. FORECAST.ETS has no NumPy equivalent.",[64,853],{"x":66,"y":67,"width":854,"height":855,"fill":70},"752","276",[64,857],{"x":208,"y":858,"width":859,"height":860,"rx":67,"fill":70,"stroke":77,"style":78},"16.0","268.9","40.0",[64,862],{"x":863,"y":858,"width":864,"height":860,"rx":67,"fill":865,"stroke":169,"style":866},"288.9","451.1","#f0f4ff","stroke-width:1.2px",[80,868,872],{"x":869,"y":870,"style":871},"514.5","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Python",[64,874],{"x":208,"y":875,"width":859,"height":876,"rx":67,"fill":877,"stroke":77,"style":78},"56.0","34.0","#e7ebef",[80,879,883],{"x":880,"y":881,"style":882},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","=SLOPE(ys, xs) \u002F =INTERCEPT(ys, xs)",[64,885],{"x":863,"y":875,"width":864,"height":876,"rx":67,"fill":70,"stroke":77,"style":78},[80,887,889],{"x":869,"y":888,"style":122},"77.0","np.polyfit(xs, ys, 1)",[64,891],{"x":208,"y":892,"width":859,"height":876,"rx":67,"fill":877,"stroke":77,"style":78},"90.0",[80,894,896],{"x":880,"y":895,"style":882},"112.0","=CORREL(xs, ys) \u002F =RSQ(ys, xs)",[64,898],{"x":863,"y":892,"width":864,"height":876,"rx":67,"fill":70,"stroke":77,"style":78},[80,900,902],{"x":869,"y":901,"style":122},"111.0","np.corrcoef(xs, ys)[0, 1], squared",[64,904],{"x":208,"y":905,"width":859,"height":876,"rx":67,"fill":877,"stroke":77,"style":78},"124.0",[80,907,909],{"x":880,"y":908,"style":882},"146.0","=TREND(ys, xs, new_xs)",[64,911],{"x":863,"y":905,"width":864,"height":876,"rx":67,"fill":70,"stroke":77,"style":78},[80,913,915],{"x":869,"y":914,"style":122},"145.0","np.polyval(coef, new_xs)",[64,917],{"x":208,"y":918,"width":859,"height":876,"rx":67,"fill":877,"stroke":77,"style":78},"158.0",[80,920,922],{"x":880,"y":921,"style":882},"180.0","=FORECAST.LINEAR(x, ys, xs)",[64,924],{"x":863,"y":918,"width":864,"height":876,"rx":67,"fill":70,"stroke":77,"style":78},[80,926,928],{"x":869,"y":927,"style":122},"179.0","slope * x + intercept",[64,930],{"x":208,"y":931,"width":859,"height":876,"rx":67,"fill":877,"stroke":77,"style":78},"192.0",[80,933,935],{"x":880,"y":934,"style":882},"214.0","=STEYX(ys, xs)",[64,937],{"x":863,"y":931,"width":864,"height":876,"rx":67,"fill":70,"stroke":77,"style":78},[80,939,941],{"x":869,"y":940,"style":122},"213.0","sqrt(sum(residuals²) \u002F (n − 2))",[64,943],{"x":208,"y":944,"width":859,"height":876,"rx":67,"fill":877,"stroke":77,"style":78},"226.0",[80,946,948],{"x":880,"y":947,"style":882},"248.0","=FORECAST.ETS(…)",[64,950],{"x":863,"y":944,"width":864,"height":876,"rx":67,"fill":70,"stroke":77,"style":78},[80,952,954],{"x":869,"y":953,"style":122},"247.0","no NumPy equivalent",[224,956,958],{"id":957},"step-4-dates-as-the-x-axis","Step 4 — Dates as the x axis",[10,960,961,962,964,965,969],{},"When a sheet uses the date column as x — ",[14,963,561],{}," with real dates in column A — Excel regresses on date serial numbers, so the slope is a change ",[966,967,968],"em",{},"per day",". Python needs numbers too; convert the dates to days since a start date to get the same per-day slope, or keep period numbers for a per-month one:",[229,971,973],{"className":283,"code":972,"language":285,"meta":234,"style":234},"days = (w[\"month\"] - w[\"month\"].iloc[0]).dt.days.to_numpy()\nper_day, _ = np.polyfit(days, y, 1)\nprint(f\"{per_day:.4f} per day  ~ {per_day * 30.44:.2f} per month\")\n",[14,974,975,1004,1018],{"__ignoreMap":234},[238,976,977,980,982,985,988,990,992,994,996,999,1001],{"class":72,"line":240},[238,978,979],{"class":296},"days ",[238,981,332],{"class":292},[238,983,984],{"class":296}," (w[",[238,986,987],{"class":247},"\"month\"",[238,989,358],{"class":296},[238,991,721],{"class":292},[238,993,425],{"class":296},[238,995,987],{"class":247},[238,997,998],{"class":296},"].iloc[",[238,1000,67],{"class":376},[238,1002,1003],{"class":296},"]).dt.days.to_numpy()\n",[238,1005,1006,1009,1011,1014,1016],{"class":72,"line":306},[238,1007,1008],{"class":296},"per_day, _ ",[238,1010,332],{"class":292},[238,1012,1013],{"class":296}," np.polyfit(days, y, ",[238,1015,393],{"class":376},[238,1017,341],{"class":296},[238,1019,1020,1022,1024,1026,1028,1030,1033,1036,1038,1041,1043,1046,1048,1051,1054,1056,1059],{"class":72,"line":319},[238,1021,487],{"class":376},[238,1023,490],{"class":296},[238,1025,493],{"class":292},[238,1027,553],{"class":247},[238,1029,500],{"class":499},[238,1031,1032],{"class":296},"per_day",[238,1034,1035],{"class":292},":.4f",[238,1037,509],{"class":499},[238,1039,1040],{"class":247}," per day  ~ ",[238,1042,500],{"class":499},[238,1044,1045],{"class":296},"per_day ",[238,1047,644],{"class":292},[238,1049,1050],{"class":376}," 30.44",[238,1052,1053],{"class":292},":.2f",[238,1055,509],{"class":499},[238,1057,1058],{"class":247}," per month\"",[238,1060,341],{"class":296},[10,1062,1063],{},"Months have different lengths, so the per-day slope converted to a month is close to, but not exactly, the period-number slope of 7.40. Choose the x axis that matches the question: period numbers treat every month as one step, which is what monthly reports usually mean; days are right for irregularly spaced observations. Either way, state the unit next to the slope.",[224,1065,1067],{"id":1066},"step-5-a-trend-for-every-product","Step 5 — A trend for every product",[10,1069,1070],{},"The same fit applied per group gives each product its own slope and fit quality — a table Excel would need a block of formulas per product to build:",[229,1072,1074],{"className":283,"code":1073,"language":285,"meta":234,"style":234},"def trend_stats(group):\n    xs = np.arange(1, len(group) + 1)\n    ys = group[\"sales\"].to_numpy(dtype=float)\n    m, c = np.polyfit(xs, ys, 1)\n    r2 = np.corrcoef(xs, ys)[0, 1] ** 2\n    return pd.Series({\"slope_per_month\": round(m, 2), \"r2\": round(r2, 3), \"next_month\": round(m * (len(ys) + 1) + c, 1)})\n\ntrends = monthly.sort_values(\"month\").groupby(\"product\")[[\"sales\"]].apply(trend_stats)\nprint(trends)\n",[14,1075,1076,1088,1112,1134,1148,1171,1246,1250,1275],{"__ignoreMap":234},[238,1077,1078,1081,1085],{"class":72,"line":240},[238,1079,1080],{"class":292},"def",[238,1082,1084],{"class":1083},"s_Opv"," trend_stats",[238,1086,1087],{"class":296},"(group):\n",[238,1089,1090,1093,1095,1097,1099,1101,1103,1106,1108,1110],{"class":72,"line":306},[238,1091,1092],{"class":296},"    xs ",[238,1094,332],{"class":292},[238,1096,390],{"class":296},[238,1098,393],{"class":376},[238,1100,21],{"class":296},[238,1102,398],{"class":376},[238,1104,1105],{"class":296},"(group) ",[238,1107,404],{"class":292},[238,1109,407],{"class":376},[238,1111,341],{"class":296},[238,1113,1114,1117,1119,1122,1124,1126,1128,1130,1132],{"class":72,"line":319},[238,1115,1116],{"class":296},"    ys ",[238,1118,332],{"class":292},[238,1120,1121],{"class":296}," group[",[238,1123,428],{"class":247},[238,1125,431],{"class":296},[238,1127,434],{"class":370},[238,1129,332],{"class":292},[238,1131,439],{"class":376},[238,1133,341],{"class":296},[238,1135,1136,1139,1141,1144,1146],{"class":72,"line":326},[238,1137,1138],{"class":296},"    m, c ",[238,1140,332],{"class":292},[238,1142,1143],{"class":296}," np.polyfit(xs, ys, ",[238,1145,393],{"class":376},[238,1147,341],{"class":296},[238,1149,1150,1153,1155,1158,1160,1162,1164,1166,1168],{"class":72,"line":344},[238,1151,1152],{"class":296},"    r2 ",[238,1154,332],{"class":292},[238,1156,1157],{"class":296}," np.corrcoef(xs, ys)[",[238,1159,67],{"class":376},[238,1161,21],{"class":296},[238,1163,393],{"class":376},[238,1165,358],{"class":296},[238,1167,543],{"class":292},[238,1169,1170],{"class":376}," 2\n",[238,1172,1173,1176,1179,1182,1185,1187,1190,1192,1195,1198,1200,1202,1205,1208,1210,1213,1215,1217,1220,1222,1224,1226,1229,1231,1233,1236,1238,1241,1243],{"class":72,"line":382},[238,1174,1175],{"class":292},"    return",[238,1177,1178],{"class":296}," pd.Series({",[238,1180,1181],{"class":247},"\"slope_per_month\"",[238,1183,1184],{"class":296},": ",[238,1186,769],{"class":376},[238,1188,1189],{"class":296},"(m, ",[238,1191,546],{"class":376},[238,1193,1194],{"class":296},"), ",[238,1196,1197],{"class":247},"\"r2\"",[238,1199,1184],{"class":296},[238,1201,769],{"class":376},[238,1203,1204],{"class":296},"(r2, ",[238,1206,1207],{"class":376},"3",[238,1209,1194],{"class":296},[238,1211,1212],{"class":247},"\"next_month\"",[238,1214,1184],{"class":296},[238,1216,769],{"class":376},[238,1218,1219],{"class":296},"(m ",[238,1221,644],{"class":292},[238,1223,748],{"class":296},[238,1225,398],{"class":376},[238,1227,1228],{"class":296},"(ys) ",[238,1230,404],{"class":292},[238,1232,407],{"class":376},[238,1234,1235],{"class":296},") ",[238,1237,404],{"class":292},[238,1239,1240],{"class":296}," c, ",[238,1242,393],{"class":376},[238,1244,1245],{"class":296},")})\n",[238,1247,1248],{"class":72,"line":417},[238,1249,323],{"emptyLinePlaceholder":322},[238,1251,1252,1255,1257,1260,1262,1265,1267,1270,1272],{"class":72,"line":444},[238,1253,1254],{"class":296},"trends ",[238,1256,332],{"class":292},[238,1258,1259],{"class":296}," monthly.sort_values(",[238,1261,987],{"class":247},[238,1263,1264],{"class":296},").groupby(",[238,1266,355],{"class":247},[238,1268,1269],{"class":296},")[[",[238,1271,428],{"class":247},[238,1273,1274],{"class":296},"]].apply(trend_stats)\n",[238,1276,1277,1279],{"class":72,"line":449},[238,1278,487],{"class":376},[238,1280,1281],{"class":296},"(trends)\n",[10,1283,1284],{},"Widgets grow by 7.40 a month with an R² of 0.96 — a trend worth stating. Gadgets grow by 1.70 a month with an R² of 0.82 on a small, noisy base; the slope is real but the projection is less reliable. Reporting R² next to each slope stops readers treating every line as equally trustworthy, and a minimum — say 0.7 — can decide whether a projection is shown at all. Sort by month before fitting: the period numbers assume rows are in time order.",[45,1286,54,1292,54,1295,54,1298,54,1302,54,1308,54,1313,54,1319,54,1323,54,1328,54,1331,54,1335,54,1340],{"viewBox":1287,"role":48,"ariaLabelledBy":1288,"xmlns":52,"style":1291},"4 0 744 132",[1289,1290],"fc-products-t","fc-products-d","width:100%;max-width:744px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[56,1293,1294],{"id":1289},"Monthly trend per product with its fit quality",[60,1296,1297],{"id":1290},"Bars comparing the fitted monthly slope for two products. Widgets grow by 7.40 units a month with R squared of 0.96. Gadgets grow by 1.70 units a month with a weaker fit of 0.82. The projection for next month is 179.6 for Widgets and 92.8 for Gadgets.",[64,1299],{"x":66,"y":67,"width":1300,"height":1301,"fill":70},"744","132",[80,1303,1307],{"x":1304,"y":1305,"style":1306},"380.0","31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Slope per month (R² in the label)",[64,1309],{"x":208,"y":1310,"width":1311,"height":112,"rx":1312,"fill":877,"stroke":77,"style":78},"48.0","144.7","7",[80,1314,1318],{"x":1315,"y":1316,"style":1317},"92.3","66.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Widgets (R² 0.96)",[64,1320],{"x":1321,"y":1310,"width":1322,"height":112,"rx":1312,"fill":865,"stroke":169,"style":117},"174.7","461.8",[80,1324,1327],{"x":1325,"y":1316,"style":1326},"644.5","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start","+7.40 \u002F month",[64,1329],{"x":208,"y":1330,"width":1311,"height":112,"rx":1312,"fill":877,"stroke":77,"style":78},"88.0",[80,1332,1334],{"x":1315,"y":1333,"style":1317},"106.5","Gadgets (R² 0.82)",[64,1336],{"x":1321,"y":1330,"width":1337,"height":112,"rx":1312,"fill":1338,"stroke":1339,"style":117},"106.1","#d9f4f1","var(--teal,#0f9488)",[80,1341,1344],{"x":1342,"y":1333,"style":1343},"288.8","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","+1.70 \u002F month",[224,1346,1348],{"id":1347},"put-the-formulas-in-the-workbook","Put the formulas in the workbook",[10,1350,1351],{},"When readers will add next month's figure themselves, write the trend as Excel formulas next to the data instead of static numbers, so the slope and projection update with the sheet:",[229,1353,1355],{"className":283,"code":1354,"language":285,"meta":234,"style":234},"from openpyxl import load_workbook\n\nw.assign(period=x)[[\"period\", \"month\", \"sales\"]].to_excel(\"widgets_trend.xlsx\", index=False)\nwb = load_workbook(\"widgets_trend.xlsx\")\nws = wb.active\nn = ws.max_row\nws[\"E1\"], ws[\"F1\"] = \"slope per month\", f\"=SLOPE(C2:C{n},A2:A{n})\"\nws[\"E2\"], ws[\"F2\"] = \"R squared\", f\"=RSQ(C2:C{n},A2:A{n})\"\nws[\"E3\"], ws[\"F3\"] = \"next month\", f\"=_xlfn.FORECAST.LINEAR(MAX(A2:A{n})+1,C2:C{n},A2:A{n})\"\nwb.save(\"widgets_trend.xlsx\")\nprint([ws[f\"F{i}\"].value for i in (1, 2, 3)])\n",[14,1356,1357,1370,1374,1416,1430,1440,1450,1497,1539,1590,1599],{"__ignoreMap":234},[238,1358,1359,1362,1365,1367],{"class":72,"line":240},[238,1360,1361],{"class":292},"from",[238,1363,1364],{"class":296}," openpyxl ",[238,1366,293],{"class":292},[238,1368,1369],{"class":296}," load_workbook\n",[238,1371,1372],{"class":72,"line":306},[238,1373,323],{"emptyLinePlaceholder":322},[238,1375,1376,1379,1382,1384,1387,1390,1392,1394,1396,1398,1401,1404,1406,1409,1411,1414],{"class":72,"line":319},[238,1377,1378],{"class":296},"w.assign(",[238,1380,1381],{"class":370},"period",[238,1383,332],{"class":292},[238,1385,1386],{"class":296},"x)[[",[238,1388,1389],{"class":247},"\"period\"",[238,1391,21],{"class":296},[238,1393,987],{"class":247},[238,1395,21],{"class":296},[238,1397,428],{"class":247},[238,1399,1400],{"class":296},"]].to_excel(",[238,1402,1403],{"class":247},"\"widgets_trend.xlsx\"",[238,1405,21],{"class":296},[238,1407,1408],{"class":370},"index",[238,1410,332],{"class":292},[238,1412,1413],{"class":376},"False",[238,1415,341],{"class":296},[238,1417,1418,1421,1423,1426,1428],{"class":72,"line":326},[238,1419,1420],{"class":296},"wb ",[238,1422,332],{"class":292},[238,1424,1425],{"class":296}," load_workbook(",[238,1427,1403],{"class":247},[238,1429,341],{"class":296},[238,1431,1432,1435,1437],{"class":72,"line":344},[238,1433,1434],{"class":296},"ws ",[238,1436,332],{"class":292},[238,1438,1439],{"class":296}," wb.active\n",[238,1441,1442,1445,1447],{"class":72,"line":382},[238,1443,1444],{"class":296},"n ",[238,1446,332],{"class":292},[238,1448,1449],{"class":296}," ws.max_row\n",[238,1451,1452,1455,1458,1461,1464,1466,1468,1471,1473,1475,1478,1480,1483,1485,1488,1490,1492,1494],{"class":72,"line":417},[238,1453,1454],{"class":296},"ws[",[238,1456,1457],{"class":247},"\"E1\"",[238,1459,1460],{"class":296},"], ws[",[238,1462,1463],{"class":247},"\"F1\"",[238,1465,358],{"class":296},[238,1467,332],{"class":292},[238,1469,1470],{"class":247}," \"slope per month\"",[238,1472,21],{"class":296},[238,1474,493],{"class":292},[238,1476,1477],{"class":247},"\"=SLOPE(C2:C",[238,1479,500],{"class":499},[238,1481,1482],{"class":296},"n",[238,1484,509],{"class":499},[238,1486,1487],{"class":247},",A2:A",[238,1489,500],{"class":499},[238,1491,1482],{"class":296},[238,1493,509],{"class":499},[238,1495,1496],{"class":247},")\"\n",[238,1498,1499,1501,1504,1506,1509,1511,1513,1516,1518,1520,1523,1525,1527,1529,1531,1533,1535,1537],{"class":72,"line":444},[238,1500,1454],{"class":296},[238,1502,1503],{"class":247},"\"E2\"",[238,1505,1460],{"class":296},[238,1507,1508],{"class":247},"\"F2\"",[238,1510,358],{"class":296},[238,1512,332],{"class":292},[238,1514,1515],{"class":247}," \"R squared\"",[238,1517,21],{"class":296},[238,1519,493],{"class":292},[238,1521,1522],{"class":247},"\"=RSQ(C2:C",[238,1524,500],{"class":499},[238,1526,1482],{"class":296},[238,1528,509],{"class":499},[238,1530,1487],{"class":247},[238,1532,500],{"class":499},[238,1534,1482],{"class":296},[238,1536,509],{"class":499},[238,1538,1496],{"class":247},[238,1540,1541,1543,1546,1548,1551,1553,1555,1558,1560,1562,1565,1567,1569,1571,1574,1576,1578,1580,1582,1584,1586,1588],{"class":72,"line":449},[238,1542,1454],{"class":296},[238,1544,1545],{"class":247},"\"E3\"",[238,1547,1460],{"class":296},[238,1549,1550],{"class":247},"\"F3\"",[238,1552,358],{"class":296},[238,1554,332],{"class":292},[238,1556,1557],{"class":247}," \"next month\"",[238,1559,21],{"class":296},[238,1561,493],{"class":292},[238,1563,1564],{"class":247},"\"=_xlfn.FORECAST.LINEAR(MAX(A2:A",[238,1566,500],{"class":499},[238,1568,1482],{"class":296},[238,1570,509],{"class":499},[238,1572,1573],{"class":247},")+1,C2:C",[238,1575,500],{"class":499},[238,1577,1482],{"class":296},[238,1579,509],{"class":499},[238,1581,1487],{"class":247},[238,1583,500],{"class":499},[238,1585,1482],{"class":296},[238,1587,509],{"class":499},[238,1589,1496],{"class":247},[238,1591,1592,1595,1597],{"class":72,"line":464},[238,1593,1594],{"class":296},"wb.save(",[238,1596,1403],{"class":247},[238,1598,341],{"class":296},[238,1600,1601,1603,1606,1608,1611,1613,1616,1618,1620,1623,1626,1629,1631,1633,1635,1637,1639,1641,1643],{"class":72,"line":484},[238,1602,487],{"class":376},[238,1604,1605],{"class":296},"([ws[",[238,1607,493],{"class":292},[238,1609,1610],{"class":247},"\"F",[238,1612,500],{"class":499},[238,1614,1615],{"class":296},"i",[238,1617,509],{"class":499},[238,1619,553],{"class":247},[238,1621,1622],{"class":296},"].value ",[238,1624,1625],{"class":292},"for",[238,1627,1628],{"class":296}," i ",[238,1630,830],{"class":292},[238,1632,748],{"class":296},[238,1634,393],{"class":376},[238,1636,21],{"class":296},[238,1638,546],{"class":376},[238,1640,21],{"class":296},[238,1642,1207],{"class":376},[238,1644,1645],{"class":296},")])\n",[10,1647,1648,1650,1651,1654,1655,1658,1659,1663,1664,17,1666,1668],{},[14,1649,28],{}," is one of the newer functions that must be stored with the ",[14,1652,1653],{},"_xlfn."," prefix, or Excel shows ",[14,1656,1657],{},"#NAME?","; ",[39,1660,1662],{"href":1661},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-name-error-for-new-excel-functions-written-by-python\u002F","Fix #NAME? in Formulas Written from Python"," explains why. ",[14,1665,16],{},[14,1667,32],{}," are old enough to need no prefix. Recalculated, the three cells give the same 7.40, 0.965 and 179.6 as the Python code.",[224,1670,1672],{"id":1671},"step-6-where-a-straight-line-stops-being-a-forecast","Step 6 — Where a straight line stops being a forecast",[10,1674,1675,1676,1679],{},"A linear trend assumes the next months continue the recent pattern at a constant rate. That is a reasonable short-term summary and a poor long-term forecast: it cannot see seasonality, saturation or a change in direction, and extrapolating twelve months from eight months of data mostly measures optimism. Excel's ",[14,1677,1678],{},"FORECAST.ETS"," handles seasonality with exponential smoothing; Python libraries such as statsmodels offer similar methods, but their algorithms and defaults differ, so do not expect them to reproduce Excel's numbers. For reports, a sensible rule is to project only a few periods ahead, show the error band, and label the figures as a trend, not a forecast.",[224,1681,1683],{"id":1682},"common-pitfalls","Common pitfalls",[1685,1686,1687,1701,1707,1713],"ul",{},[1688,1689,1690,1694,1695,1658,1697,1700],"li",{},[1691,1692,1693],"strong",{},"Excel's y-first argument order."," ",[14,1696,574],{},[14,1698,1699],{},"polyfit(x, y, 1)",". Swapping them fits a different line.",[1688,1702,1703,1706],{},[1691,1704,1705],{},"Unsorted rows."," Period numbers assume time order; sort by date first.",[1688,1708,1709,1712],{},[1691,1710,1711],{},"Dates used directly."," Convert to period numbers or days; know the unit of the slope.",[1688,1714,1715,1718],{},[1691,1716,1717],{},"Long extrapolations."," Project a few periods, with an error band, and revisit the fit every time new data arrives.",[224,1720,1722],{"id":1721},"performance-notes","Performance notes",[10,1724,1725,1727,1728,1731],{},[14,1726,578],{}," on a few dozen points takes microseconds; fitting thousands of product series with ",[14,1729,1730],{},"groupby().apply"," takes a second or two. For very many series, the slope and intercept can be computed in closed form from grouped sums of x, y, x² and xy, which vectorises completely.",[224,1733,1735],{"id":1734},"conclusion","Conclusion",[10,1737,1738,1741,1742,17,1744,1746,1747,1750,1751,1753,1754,1756,1757,17,1759,1658,1761,1763],{},[14,1739,1740],{},"np.polyfit(x, y, 1)"," gives Excel's ",[14,1743,16],{},[14,1745,20],{}," (note the reversed argument order), ",[14,1748,1749],{},"np.corrcoef"," gives ",[14,1752,36],{}," and, squared, ",[14,1755,32],{},", and evaluating the line reproduces ",[14,1758,24],{},[14,1760,28],{},[14,1762,702],{}," comes from the residuals. Use period numbers or days since a start date as x, fit per group to compare products, report R² and an error band beside every slope, and keep straight-line projections short.",[224,1765,1767],{"id":1766},"frequently-asked-questions","Frequently asked questions",[10,1769,1770,1773],{},[1691,1771,1772],{},"What is the Python equivalent of SLOPE and INTERCEPT?","\nnumpy.polyfit(x, y, 1) returns the slope and intercept of the least-squares line, the same values SLOPE(y, x) and INTERCEPT(y, x) return. Note Excel puts the y values first; polyfit takes x first.",[10,1775,1776,1779],{},[1691,1777,1778],{},"How do I reproduce FORECAST.LINEAR or TREND?","\nFit the line with polyfit and evaluate it at the new x: slope * x_new + intercept. numpy.polyval(coefficients, x_new) does the same for several points, like TREND with new x values.",[10,1781,1782,1785],{},[1691,1783,1784],{},"Can I use dates as the x values?","\nYes, but convert them to numbers first — Excel uses date serial numbers, so SLOPE over dates is a change per day. In Python, use a period number (1, 2, 3 …) for a change per month, or days since a start date to match Excel's per-day slope.",[10,1787,1788,1791],{},[1691,1789,1790],{},"Does NumPy reproduce FORECAST.ETS?","\nNo. FORECAST.ETS uses Excel's implementation of exponential triple smoothing with automatic seasonality detection. Python libraries such as statsmodels offer exponential smoothing, but their parameters and results differ, so do not expect matching numbers.",[224,1793,1795],{"id":1794},"related","Related",[1685,1797,1798,1804,1811,1818],{},[1688,1799,1800,1801,1803],{},"Up one level: ",[39,1802,42],{"href":41}," — every function, translated.",[1688,1805,1806,1810],{},[39,1807,1809],{"href":1808},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fadd-a-trendline-to-an-excel-chart-with-python\u002F","Add a Trendline to an Excel Chart with Python"," — letting the chart draw the same line.",[1688,1812,1813,1817],{},[39,1814,1816],{"href":1815},"\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 assuming a straight line.",[1688,1819,1820,1824],{},[39,1821,1823],{"href":1822},"\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"," — growth as a comparison rather than a fit.",[1826,1827,1828],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}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}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":234,"searchDepth":306,"depth":306,"links":1830},[1831,1832,1833,1834,1835,1836,1837,1838,1839,1840,1841,1842,1843],{"id":226,"depth":306,"text":227},{"id":266,"depth":306,"text":267},{"id":582,"depth":306,"text":583},{"id":695,"depth":306,"text":696},{"id":957,"depth":306,"text":958},{"id":1066,"depth":306,"text":1067},{"id":1347,"depth":306,"text":1348},{"id":1671,"depth":306,"text":1672},{"id":1682,"depth":306,"text":1683},{"id":1721,"depth":306,"text":1722},{"id":1734,"depth":306,"text":1735},{"id":1766,"depth":306,"text":1767},{"id":1794,"depth":306,"text":1795},"2026-09-25","Reproduce Excel's linear trend functions — SLOPE, INTERCEPT, RSQ, CORREL, STEYX, TREND and FORECAST.LINEAR — with NumPy, matching the spreadsheet to many decimal places. Fit a trend per product, use dates as the x-axis, project the next periods, and know where a straight line stops being a forecast.","md",[1848,1850,1852,1854],{"q":1772,"a":1849},"numpy.polyfit(x, y, 1) returns the slope and intercept of the least-squares line, the same values SLOPE(y, x) and INTERCEPT(y, x) return. Note Excel puts the y values first; polyfit takes x first.",{"q":1778,"a":1851},"Fit the line with polyfit and evaluate it at the new x: slope * x_new + intercept. numpy.polyval(coefficients, x_new) does the same for several points, like TREND with new x values.",{"q":1784,"a":1853},"Yes, but convert them to numbers first — Excel uses date serial numbers, so SLOPE over dates is a change per day. In Python, use a period number (1, 2, 3 …) for a change per month, or days since a start date to match Excel's per-day slope.",{"q":1790,"a":1855},"No. FORECAST.ETS uses Excel's implementation of exponential triple smoothing with automatic seasonality detection. Python libraries such as statsmodels offer exponential smoothing, but their parameters and results differ, so do not expect matching numbers.","forecast trend slope linear regression excel functions in python",null,{"breadcrumb":1859},[1860,1862,1865],{"name":1861,"item":745},"Home",{"name":1863,"item":1864},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":42,"item":41},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python",{"title":1868,"description":1869},"Excel FORECAST.LINEAR, TREND and SLOPE in Python (NumPy polyfit)","Excel linear regression functions in Python: numpy polyfit for SLOPE and INTERCEPT, corrcoef for CORREL and RSQ, STEYX, TREND and FORECAST.LINEAR, trends per group, dates as x values, and the limits of linear forecasts.","forecast-trend-and-slope-equivalents-in-python","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python\u002Findex",[1873,1874,1875],"formula-equivalents","pivots-and-summaries","pandas","how-to","-DaXYXj0oA2nz8hxjQm5XauAqKSTekQVxSmxOTOuO5Y",[1879,1883],{"title":1880,"path":1881,"stem":1882,"children":-1},"FIND, SEARCH and SUBSTITUTE Equivalents in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffind-search-and-substitute-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffind-search-and-substitute-equivalents-in-pandas\u002Findex",{"title":1884,"path":1885,"stem":1886,"children":-1},"FV, PV, RATE and NPER Equivalents in Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffv-pv-rate-and-nper-equivalents-in-python","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffv-pv-rate-and-nper-equivalents-in-python\u002Findex",[1888,1890],{"path":1889,"title":42},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":1891,"title":1863},"\u002Fadvanced-data-transformation-and-cleaning",[],[1894,1897,1900,1903,1906,1909],{"path":1895,"title":1896,"kind":1876},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fvlookup-equivalent-in-pandas-for-excel-files","The VLOOKUP Equivalent in pandas for Excel Files",{"path":1898,"title":1899,"kind":1876},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fextract-numbers-and-codes-from-text-with-pandas","Extract Numbers and Codes from Text with pandas",{"path":1901,"title":1902,"kind":1876},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-full-names-into-first-and-last-names-with-pandas","Split Full Names into First and Last Names with pandas",{"path":1904,"title":1905,"kind":1876},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut","Bin Values into Ranges Like Excel FREQUENCY with pd.cut",{"path":1907,"title":1908,"kind":1876},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-percent-of-total-per-group-in-pandas","Calculate Percent of Total per Group in pandas",{"path":1910,"title":1911,"kind":1876},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcount-distinct-values-per-group-in-pandas","Count Distinct Values per Group in pandas",1790972088174]