[{"data":1,"prerenderedAt":2117},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel":2083,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel":2092,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel":2097,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel":2098},{"id":4,"title":5,"body":6,"dateModified":2050,"datePublished":2050,"description":2051,"extension":2052,"faq":2053,"keyword":2062,"kind":2063,"meta":2064,"navigation":296,"path":2072,"seo":2073,"slug":2076,"stem":2077,"topics":2078,"type":2081,"__hash__":2082},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel\u002Findex.md","Goal Seek in Python Instead of Excel",{"type":7,"value":8,"toc":2038},"minimark",[9,29,34,65,72,253,257,268,533,541,545,548,695,701,705,716,906,922,1064,1068,1071,1371,1378,1382,1392,1772,1780,1901,1905,1959,1963,1973,1977,1983,1989,1995,2001,2005,2034],[10,11,12,13,17,18,22,23,28],"p",{},"Goal Seek is Excel's \"work backwards\" button: set this cell to that value by changing that other cell. What price gives a 40,000 profit? What interest rate makes the payment 450 a month? It works one question at a time, through a dialog, and leaves no record of what was asked. In Python the same thing is root finding — find the input where ",[14,15,16],"em",{},"result minus target"," equals zero — and ",[19,20,21],"code",{},"scipy.optimize"," does it in one line, for one question or ten thousand. This page, part of ",[24,25,27],"a",{"href":26},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",", shows the pattern on three typical questions and then applies it to an Excel model directly.",[30,31,33],"h2",{"id":32},"prerequisites","Prerequisites",[35,36,41],"pre",{"className":37,"code":38,"language":39,"meta":40,"style":40},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install scipy pandas openpyxl\n","bash","",[19,42,43],{"__ignoreMap":40},[44,45,48,52,56,59,62],"span",{"class":46,"line":47},"line",1,[44,49,51],{"class":50},"sMTad","pip",[44,53,55],{"class":54},"srMev"," install",[44,57,58],{"class":54}," scipy",[44,60,61],{"class":54}," pandas",[44,63,64],{"class":54}," openpyxl\n",[10,66,67,68,71],{},"The first examples use a small product P&L written as a Python function: revenue is units times price, variable cost is units times unit cost, fixed costs of 60,000 come off, and 25% tax is paid on any profit. The same model exists as a workbook, ",[19,69,70],{},"pricing_model.xlsx",", for the last step.",[73,74,82,83,82,87,82,91,82,99,82,108,82,115,82,120,82,124,82,131,82,136,82,140,82,144,82,147,82,151,82,154,82,158,82,161,82,167,82,171,82,175,82,179,82,183,82,187,82,191,82,195,82,201,82,207,82,210,82,213,82,216,82,219,82,222,82,225,82,228,82,231,82,233,82,235,82,237,82,239,82,241,82,243,82,245,82,247],"svg",{"viewBox":75,"role":76,"ariaLabelledBy":77,"xmlns":80,"style":81},"4 0 748 286","img",[78,79],"gs-curve-t","gs-curve-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  ",[84,85,86],"title",{"id":78},"Goal seek finds where the curve crosses the target",[88,89,90],"desc",{"id":79},"Line chart of net profit against price from 21 to 28. Net profit rises steadily from about 4 thousand to about 56 thousand and crosses the 40 thousand target between 25 and 26.",[92,93],"rect",{"x":94,"y":95,"width":96,"height":97,"fill":98},"4","0","748","286","#ffffff",[92,100],{"x":101,"y":102,"width":103,"height":103,"rx":104,"fill":105,"stroke":106,"style":107},"64.0","18.0","14.0","3","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[109,110,114],"text",{"x":111,"y":112,"style":113},"84.0","29.5","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:start","Net profit (€k)",[92,116],{"x":117,"y":102,"width":103,"height":103,"rx":104,"fill":118,"stroke":119,"style":107},"199.0","#fee8f2","var(--accent,#f43f8f)",[109,121,123],{"x":122,"y":112,"style":113},"219.0","Target 40k",[46,125],{"x1":126,"y1":127,"x2":128,"y2":127,"stroke":129,"style":130},"46.7","222.0","736.0","var(--line,#cdd5e6)","stroke-width:1px",[109,132,95],{"x":133,"y":134,"style":135},"27.4","226.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle",[46,137],{"x1":126,"y1":138,"x2":128,"y2":138,"stroke":129,"style":139},"165.3","stroke-width:1px;stroke-dasharray:5 4",[109,141,143],{"x":133,"y":142,"style":135},"169.3","20",[46,145],{"x1":126,"y1":146,"x2":128,"y2":146,"stroke":129,"style":139},"108.7",[109,148,150],{"x":133,"y":149,"style":135},"112.7","40",[46,152],{"x1":126,"y1":153,"x2":128,"y2":153,"stroke":129,"style":139},"52.0",[109,155,157],{"x":133,"y":156,"style":135},"56.0","60",[46,159],{"x1":126,"y1":127,"x2":128,"y2":127,"stroke":160,"style":107},"var(--muted,#5b6780)",[109,162,166],{"x":163,"y":164,"style":165},"89.8","240.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","21",[109,168,170],{"x":169,"y":164,"style":165},"176.0","22",[109,172,174],{"x":173,"y":164,"style":165},"262.1","23",[109,176,178],{"x":177,"y":164,"style":165},"348.3","24",[109,180,182],{"x":181,"y":164,"style":165},"434.5","25",[109,184,186],{"x":185,"y":164,"style":165},"520.6","26",[109,188,190],{"x":189,"y":164,"style":165},"606.8","27",[109,192,194],{"x":193,"y":164,"style":165},"692.9","28",[196,197],"path",{"d":198,"fill":199,"stroke":106,"style":200},"M89.8,211.4 L176.0,190.1 L262.1,168.9 L348.3,147.6 L434.5,126.4 L520.6,105.1 L606.8,83.9 L692.9,62.6","none","stroke-width:2.5px;stroke-linejoin:round;stroke-linecap:round",[202,203],"circle",{"cx":163,"cy":204,"r":205,"fill":98,"stroke":106,"style":206},"211.4","4.0","stroke-width:2px",[202,208],{"cx":169,"cy":209,"r":205,"fill":98,"stroke":106,"style":206},"190.1",[202,211],{"cx":173,"cy":212,"r":205,"fill":98,"stroke":106,"style":206},"168.9",[202,214],{"cx":177,"cy":215,"r":205,"fill":98,"stroke":106,"style":206},"147.6",[202,217],{"cx":181,"cy":218,"r":205,"fill":98,"stroke":106,"style":206},"126.4",[202,220],{"cx":185,"cy":221,"r":205,"fill":98,"stroke":106,"style":206},"105.1",[202,223],{"cx":189,"cy":224,"r":205,"fill":98,"stroke":106,"style":206},"83.9",[202,226],{"cx":193,"cy":227,"r":205,"fill":98,"stroke":106,"style":206},"62.6",[196,229],{"d":230,"fill":199,"stroke":119,"style":200},"M89.8,108.7 L176.0,108.7 L262.1,108.7 L348.3,108.7 L434.5,108.7 L520.6,108.7 L606.8,108.7 L692.9,108.7",[202,232],{"cx":163,"cy":146,"r":205,"fill":98,"stroke":119,"style":206},[202,234],{"cx":169,"cy":146,"r":205,"fill":98,"stroke":119,"style":206},[202,236],{"cx":173,"cy":146,"r":205,"fill":98,"stroke":119,"style":206},[202,238],{"cx":177,"cy":146,"r":205,"fill":98,"stroke":119,"style":206},[202,240],{"cx":181,"cy":146,"r":205,"fill":98,"stroke":119,"style":206},[202,242],{"cx":185,"cy":146,"r":205,"fill":98,"stroke":119,"style":206},[202,244],{"cx":189,"cy":146,"r":205,"fill":98,"stroke":119,"style":206},[202,246],{"cx":193,"cy":146,"r":205,"fill":98,"stroke":119,"style":206},[109,248,252],{"x":249,"y":250,"style":251},"380.0","266.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Once the model is in profit, each extra euro of price adds 7,500 of net profit after tax.",[30,254,256],{"id":255},"step-1-solve-for-one-input-with-brentq","Step 1 — Solve for one input with brentq",[10,258,259,260,263,264,267],{},"Write the model as a function of the input you want to change, subtract the target, and ask ",[19,261,262],{},"brentq"," for the zero. It needs a ",[14,265,266],{},"bracket",": two input values where the result is on opposite sides of the target:",[35,269,273],{"className":270,"code":271,"language":272,"meta":40,"style":40},"language-python shiki shiki-themes github-light github-dark-high-contrast","from scipy.optimize import brentq\n\ndef net_profit(price, units=10_000, unit_cost=14.5, fixed=60_000, tax=0.25):\n    operating = units * (price - unit_cost) - fixed\n    return operating - max(0.0, operating * tax)\n\ntarget = 40_000\nprice = brentq(lambda p: net_profit(p) - target, 15, 50, xtol=1e-9)\nprint(f\"price for {target:,}: {price:.4f}\")\nprint(f\"check: net profit at that price = {net_profit(price):,.2f}\")\n","python",[19,274,275,291,298,345,373,401,406,417,462,507],{"__ignoreMap":40},[44,276,277,281,285,288],{"class":46,"line":47},[44,278,280],{"class":279},"s-kum","from",[44,282,284],{"class":283},"skGVy"," scipy.optimize ",[44,286,287],{"class":279},"import",[44,289,290],{"class":283}," brentq\n",[44,292,294],{"class":46,"line":293},2,[44,295,297],{"emptyLinePlaceholder":296},true,"\n",[44,299,301,304,308,311,314,318,321,323,326,329,331,334,337,339,342],{"class":46,"line":300},3,[44,302,303],{"class":279},"def",[44,305,307],{"class":306},"s_Opv"," net_profit",[44,309,310],{"class":283},"(price, units",[44,312,313],{"class":279},"=",[44,315,317],{"class":316},"sP0c6","10_000",[44,319,320],{"class":283},", unit_cost",[44,322,313],{"class":279},[44,324,325],{"class":316},"14.5",[44,327,328],{"class":283},", fixed",[44,330,313],{"class":279},[44,332,333],{"class":316},"60_000",[44,335,336],{"class":283},", tax",[44,338,313],{"class":279},[44,340,341],{"class":316},"0.25",[44,343,344],{"class":283},"):\n",[44,346,348,351,353,356,359,362,365,368,370],{"class":46,"line":347},4,[44,349,350],{"class":283},"    operating ",[44,352,313],{"class":279},[44,354,355],{"class":283}," units ",[44,357,358],{"class":279},"*",[44,360,361],{"class":283}," (price ",[44,363,364],{"class":279},"-",[44,366,367],{"class":283}," unit_cost) ",[44,369,364],{"class":279},[44,371,372],{"class":283}," fixed\n",[44,374,376,379,382,384,387,390,393,396,398],{"class":46,"line":375},5,[44,377,378],{"class":279},"    return",[44,380,381],{"class":283}," operating ",[44,383,364],{"class":279},[44,385,386],{"class":316}," max",[44,388,389],{"class":283},"(",[44,391,392],{"class":316},"0.0",[44,394,395],{"class":283},", operating ",[44,397,358],{"class":279},[44,399,400],{"class":283}," tax)\n",[44,402,404],{"class":46,"line":403},6,[44,405,297],{"emptyLinePlaceholder":296},[44,407,409,412,414],{"class":46,"line":408},7,[44,410,411],{"class":283},"target ",[44,413,313],{"class":279},[44,415,416],{"class":316}," 40_000\n",[44,418,420,423,425,428,431,434,436,439,442,445,448,450,454,456,459],{"class":46,"line":419},8,[44,421,422],{"class":283},"price ",[44,424,313],{"class":279},[44,426,427],{"class":283}," brentq(",[44,429,430],{"class":279},"lambda",[44,432,433],{"class":283}," p: net_profit(p) ",[44,435,364],{"class":279},[44,437,438],{"class":283}," target, ",[44,440,441],{"class":316},"15",[44,443,444],{"class":283},", ",[44,446,447],{"class":316},"50",[44,449,444],{"class":283},[44,451,453],{"class":452},"sa561","xtol",[44,455,313],{"class":279},[44,457,458],{"class":316},"1e-9",[44,460,461],{"class":283},")\n",[44,463,465,468,470,473,476,480,483,486,489,492,494,497,500,502,505],{"class":46,"line":464},9,[44,466,467],{"class":316},"print",[44,469,389],{"class":283},[44,471,472],{"class":279},"f",[44,474,475],{"class":54},"\"price for ",[44,477,479],{"class":478},"sSjpA","{",[44,481,482],{"class":283},"target",[44,484,485],{"class":279},":,",[44,487,488],{"class":478},"}",[44,490,491],{"class":54},": ",[44,493,479],{"class":478},[44,495,496],{"class":283},"price",[44,498,499],{"class":279},":.4f",[44,501,488],{"class":478},[44,503,504],{"class":54},"\"",[44,506,461],{"class":283},[44,508,510,512,514,516,519,521,524,527,529,531],{"class":46,"line":509},10,[44,511,467],{"class":316},[44,513,389],{"class":283},[44,515,472],{"class":279},[44,517,518],{"class":54},"\"check: net profit at that price = ",[44,520,479],{"class":478},[44,522,523],{"class":283},"net_profit(price)",[44,525,526],{"class":279},":,.2f",[44,528,488],{"class":478},[44,530,504],{"class":54},[44,532,461],{"class":283},[10,534,535,537,538,540],{},[19,536,262],{}," combines bisection with faster interpolation steps. As long as the function is continuous and the bracket really contains a sign change, it is guaranteed to find the answer, and it does so in a handful of evaluations. The result — about 25.83 — is the price at which this model earns exactly 40,000; rounding it to a real price is a business decision, which is why the check line matters. Excel's Goal Seek stops at a fixed tolerance and quietly accepts \"close\"; here ",[19,539,453],{}," says how close you need.",[30,542,544],{"id":543},"step-2-when-there-is-no-answer","Step 2 — When there is no answer",[10,546,547],{},"Goal Seek's \"may not have found a solution\" message is easy to click away. In Python, a bracket with no sign change raises an error, which is exactly what an automated job should do:",[35,549,551],{"className":270,"code":550,"language":272,"meta":40,"style":40},"try:\n    brentq(lambda p: net_profit(p, units=1_000) - target, 15, 50)\nexcept ValueError as err:\n    print(\"no solution in range:\", err)\n\nimport numpy as np\nprices = np.linspace(15, 50, 8)\nprint([round(net_profit(p, units=1_000)) for p in prices])\n",[19,552,553,561,594,608,621,625,638,661],{"__ignoreMap":40},[44,554,555,558],{"class":46,"line":47},[44,556,557],{"class":279},"try",[44,559,560],{"class":283},":\n",[44,562,563,566,568,571,574,576,579,582,584,586,588,590,592],{"class":46,"line":293},[44,564,565],{"class":283},"    brentq(",[44,567,430],{"class":279},[44,569,570],{"class":283}," p: net_profit(p, ",[44,572,573],{"class":452},"units",[44,575,313],{"class":279},[44,577,578],{"class":316},"1_000",[44,580,581],{"class":283},") ",[44,583,364],{"class":279},[44,585,438],{"class":283},[44,587,441],{"class":316},[44,589,444],{"class":283},[44,591,447],{"class":316},[44,593,461],{"class":283},[44,595,596,599,602,605],{"class":46,"line":300},[44,597,598],{"class":279},"except",[44,600,601],{"class":316}," ValueError",[44,603,604],{"class":279}," as",[44,606,607],{"class":283}," err:\n",[44,609,610,613,615,618],{"class":46,"line":347},[44,611,612],{"class":316},"    print",[44,614,389],{"class":283},[44,616,617],{"class":54},"\"no solution in range:\"",[44,619,620],{"class":283},", err)\n",[44,622,623],{"class":46,"line":375},[44,624,297],{"emptyLinePlaceholder":296},[44,626,627,629,632,635],{"class":46,"line":403},[44,628,287],{"class":279},[44,630,631],{"class":283}," numpy ",[44,633,634],{"class":279},"as",[44,636,637],{"class":283}," np\n",[44,639,640,643,645,648,650,652,654,656,659],{"class":46,"line":408},[44,641,642],{"class":283},"prices ",[44,644,313],{"class":279},[44,646,647],{"class":283}," np.linspace(",[44,649,441],{"class":316},[44,651,444],{"class":283},[44,653,447],{"class":316},[44,655,444],{"class":283},[44,657,658],{"class":316},"8",[44,660,461],{"class":283},[44,662,663,665,668,671,674,676,678,680,683,686,689,692],{"class":46,"line":419},[44,664,467],{"class":316},[44,666,667],{"class":283},"([",[44,669,670],{"class":316},"round",[44,672,673],{"class":283},"(net_profit(p, ",[44,675,573],{"class":452},[44,677,313],{"class":279},[44,679,578],{"class":316},[44,681,682],{"class":283},")) ",[44,684,685],{"class":279},"for",[44,687,688],{"class":283}," p ",[44,690,691],{"class":279},"in",[44,693,694],{"class":283}," prices])\n",[10,696,697,698,700],{},"With only 1,000 units sold, even a price of 50 does not reach the target, so the function never crosses zero in the range, and ",[19,699,262],{}," says so. Tabulating the function over the range, as the second part does, is the quickest way to pick a sensible bracket — or to see that the target is unreachable.",[30,702,704],{"id":703},"step-3-the-rate-behind-a-payment-excels-rate","Step 3 — The rate behind a payment (Excel's RATE)",[10,706,707,708,711,712,715],{},"Some Excel functions ",[14,709,710],{},"are"," goal seeks. ",[19,713,714],{},"RATE"," finds the interest rate that makes a loan's payments match a given amount; there is no formula for it, only a search. The payment formula itself is simple, so solve it for the rate:",[35,717,719],{"className":270,"code":718,"language":272,"meta":40,"style":40},"def payment(rate, n, principal):\n    return principal * rate \u002F (1 - (1 + rate) ** -n) if rate else principal \u002F n\n\nloan, months, pay = 20_000, 48, 480\nmonthly = brentq(lambda r: payment(r, months, loan) - pay, 1e-9, 0.1)\nprint(f\"monthly rate {monthly:.6%}, annual {monthly * 12:.4%}\")\nprint(f\"payment check: {payment(monthly, months, loan):.4f}\")\n",[19,720,721,731,788,792,812,840,882],{"__ignoreMap":40},[44,722,723,725,728],{"class":46,"line":47},[44,724,303],{"class":279},[44,726,727],{"class":306}," payment",[44,729,730],{"class":283},"(rate, n, principal):\n",[44,732,733,735,738,740,743,746,749,752,755,757,759,762,765,768,770,773,776,778,781,783,785],{"class":46,"line":293},[44,734,378],{"class":279},[44,736,737],{"class":283}," principal ",[44,739,358],{"class":279},[44,741,742],{"class":283}," rate ",[44,744,745],{"class":279},"\u002F",[44,747,748],{"class":283}," (",[44,750,751],{"class":316},"1",[44,753,754],{"class":279}," -",[44,756,748],{"class":283},[44,758,751],{"class":316},[44,760,761],{"class":279}," +",[44,763,764],{"class":283}," rate) ",[44,766,767],{"class":279},"**",[44,769,754],{"class":279},[44,771,772],{"class":283},"n) ",[44,774,775],{"class":279},"if",[44,777,742],{"class":283},[44,779,780],{"class":279},"else",[44,782,737],{"class":283},[44,784,745],{"class":279},[44,786,787],{"class":283}," n\n",[44,789,790],{"class":46,"line":300},[44,791,297],{"emptyLinePlaceholder":296},[44,793,794,797,799,802,804,807,809],{"class":46,"line":347},[44,795,796],{"class":283},"loan, months, pay ",[44,798,313],{"class":279},[44,800,801],{"class":316}," 20_000",[44,803,444],{"class":283},[44,805,806],{"class":316},"48",[44,808,444],{"class":283},[44,810,811],{"class":316},"480\n",[44,813,814,817,819,821,823,826,828,831,833,835,838],{"class":46,"line":375},[44,815,816],{"class":283},"monthly ",[44,818,313],{"class":279},[44,820,427],{"class":283},[44,822,430],{"class":279},[44,824,825],{"class":283}," r: payment(r, months, loan) ",[44,827,364],{"class":279},[44,829,830],{"class":283}," pay, ",[44,832,458],{"class":316},[44,834,444],{"class":283},[44,836,837],{"class":316},"0.1",[44,839,461],{"class":283},[44,841,842,844,846,848,851,853,856,859,861,864,866,868,870,873,876,878,880],{"class":46,"line":403},[44,843,467],{"class":316},[44,845,389],{"class":283},[44,847,472],{"class":279},[44,849,850],{"class":54},"\"monthly rate ",[44,852,479],{"class":478},[44,854,855],{"class":283},"monthly",[44,857,858],{"class":279},":.6%",[44,860,488],{"class":478},[44,862,863],{"class":54},", annual ",[44,865,479],{"class":478},[44,867,816],{"class":283},[44,869,358],{"class":279},[44,871,872],{"class":316}," 12",[44,874,875],{"class":279},":.4%",[44,877,488],{"class":478},[44,879,504],{"class":54},[44,881,461],{"class":283},[44,883,884,886,888,890,893,895,898,900,902,904],{"class":46,"line":408},[44,885,467],{"class":316},[44,887,389],{"class":283},[44,889,472],{"class":279},[44,891,892],{"class":54},"\"payment check: ",[44,894,479],{"class":478},[44,896,897],{"class":283},"payment(monthly, months, loan)",[44,899,499],{"class":279},[44,901,488],{"class":478},[44,903,504],{"class":54},[44,905,461],{"class":283},[10,907,908,909,912,913,916,917,921],{},"A 20,000 loan repaid at 480 a month over four years carries a rate of about 0.59% a month — roughly 7.1% a year, nominal. The same structure answers \"how many months at this payment?\" (",[19,910,911],{},"NPER",") by solving for ",[19,914,915],{},"n"," instead. ",[24,918,920],{"href":919},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fpmt-npv-and-irr-in-python\u002F","PMT, NPV, IRR and XIRR Excel Functions in Python"," covers the functions that do have closed forms.",[73,923,82,929,82,932,82,935,82,939,82,947,82,956,82,962,82,967,82,973,82,981,82,985,82,991,82,995,82,1000,82,1004,82,1009,82,1014,82,1021,82,1026,82,1030,82,1034,82,1040,82,1043,82,1048,82,1051,82,1056,82,1059],{"viewBox":924,"role":76,"ariaLabelledBy":925,"xmlns":80,"style":928},"4 0 752 233",[926,927],"gs-compare-t","gs-compare-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[84,930,931],{"id":926},"Goal Seek in Excel and root finding in Python",[88,933,934],{"id":927},"Two panels comparing Excel's Goal Seek dialog with scipy root finding, on repeatability, scale and failure handling.",[92,936],{"x":94,"y":95,"width":937,"height":938,"fill":98},"752","233",[92,940],{"x":941,"y":942,"width":943,"height":944,"rx":945,"fill":946,"stroke":129,"style":206},"20.0","16.0","351.0","201.0","14","#e7ebef",[948,949,951],"g",{"transform":950},"translate(182.5,29.0) scale(1.083)",[196,952],{"d":953,"fill":199,"stroke":954,"style":955},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--text,#172033)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[109,957,961],{"x":958,"y":959,"style":960},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Excel Goal Seek",[46,963],{"x1":964,"y1":965,"x2":966,"y2":965,"stroke":129,"style":130},"36.0","91.0","355.0",[109,968,972],{"x":969,"y":970,"style":971},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","one question per dialog",[948,974,976],{"transform":975},"translate(38.5,99.5) scale(0.625)",[196,977],{"d":978,"fill":199,"stroke":979,"style":980},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold,#b4740a)","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[109,982,984],{"x":969,"y":983,"style":971},"137.0","overwrites the input cell",[948,986,988],{"transform":987},"translate(38.5,125.5) scale(0.625)",[196,989],{"d":990,"fill":199,"stroke":119,"style":980},"M6 6l12 12 M18 6L6 18",[109,992,994],{"x":969,"y":993,"style":971},"163.0","no record of what was asked",[948,996,998],{"transform":997},"translate(38.5,151.5) scale(0.625)",[196,999],{"d":990,"fill":199,"stroke":119,"style":980},[109,1001,1003],{"x":969,"y":1002,"style":971},"189.0","fixed tolerance",[948,1005,1007],{"transform":1006},"translate(38.5,177.5) scale(0.625)",[196,1008],{"d":978,"fill":199,"stroke":979,"style":980},[92,1010],{"x":1011,"y":942,"width":943,"height":944,"rx":945,"fill":1012,"stroke":1013,"style":206},"389.0","#d9f4f1","var(--teal,#0f9488)",[948,1015,1017],{"transform":1016},"translate(551.5,29.0) scale(1.083)",[196,1018],{"d":1019,"fill":199,"stroke":1020,"style":955},"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(--teal-ink,#0b6157)",[109,1022,1025],{"x":1023,"y":959,"style":1024},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","scipy brentq \u002F root_scalar",[46,1027],{"x1":1028,"y1":965,"x2":1029,"y2":965,"stroke":1013,"style":130},"405.0","724.0",[109,1031,1033],{"x":1032,"y":970,"style":971},"429.0","one call per question, in a loop",[948,1035,1037],{"transform":1036},"translate(407.5,99.5) scale(0.625)",[196,1038],{"d":1039,"fill":199,"stroke":1013,"style":980},"M5 12.5l4.5 4.5L19 7",[109,1041,1042],{"x":1032,"y":983,"style":971},"inputs untouched, answer returned",[948,1044,1046],{"transform":1045},"translate(407.5,125.5) scale(0.625)",[196,1047],{"d":1039,"fill":199,"stroke":1013,"style":980},[109,1049,1050],{"x":1032,"y":993,"style":971},"the script is the record",[948,1052,1054],{"transform":1053},"translate(407.5,151.5) scale(0.625)",[196,1055],{"d":1039,"fill":199,"stroke":1013,"style":980},[109,1057,1058],{"x":1032,"y":1002,"style":971},"raises when there is no answer",[948,1060,1062],{"transform":1061},"translate(407.5,177.5) scale(0.625)",[196,1063],{"d":1039,"fill":199,"stroke":1013,"style":980},[30,1065,1067],{"id":1066},"step-4-goal-seek-every-row-of-a-table","Step 4 — Goal seek every row of a table",[10,1069,1070],{},"Goal Seek works on one cell. A list of products, each with its own volume and costs, needs one answer per row — the price that gives each product a 30% net margin, say. Wrap the solve in a function and apply it:",[35,1072,1074],{"className":270,"code":1073,"language":272,"meta":40,"style":40},"import pandas as pd\n\nproducts = pd.DataFrame({\n    \"product\": [\"Basic\", \"Plus\", \"Pro\", \"Niche\"],\n    \"units\": [12_000, 6_000, 2_500, 300],\n    \"unit_cost\": [9.0, 16.0, 38.0, 55.0],\n    \"fixed\": [40_000, 35_000, 30_000, 20_000],\n})\n\ndef price_for_margin(row, margin=0.30):\n    f = lambda p: net_profit(p, row.units, row.unit_cost, row.fixed) - margin * row.units * p\n    try:\n        return brentq(f, row.unit_cost, row.unit_cost * 20)\n    except ValueError:\n        return float(\"nan\")\n\nproducts[\"price_for_30pct\"] = products.apply(price_for_margin, axis=1).round(2)\nprint(products.to_string(index=False))\n",[19,1075,1076,1088,1092,1102,1131,1158,1184,1211,1216,1220,1237,1266,1274,1290,1300,1315,1320,1352],{"__ignoreMap":40},[44,1077,1078,1080,1083,1085],{"class":46,"line":47},[44,1079,287],{"class":279},[44,1081,1082],{"class":283}," pandas ",[44,1084,634],{"class":279},[44,1086,1087],{"class":283}," pd\n",[44,1089,1090],{"class":46,"line":293},[44,1091,297],{"emptyLinePlaceholder":296},[44,1093,1094,1097,1099],{"class":46,"line":300},[44,1095,1096],{"class":283},"products ",[44,1098,313],{"class":279},[44,1100,1101],{"class":283}," pd.DataFrame({\n",[44,1103,1104,1107,1110,1113,1115,1118,1120,1123,1125,1128],{"class":46,"line":347},[44,1105,1106],{"class":54},"    \"product\"",[44,1108,1109],{"class":283},": [",[44,1111,1112],{"class":54},"\"Basic\"",[44,1114,444],{"class":283},[44,1116,1117],{"class":54},"\"Plus\"",[44,1119,444],{"class":283},[44,1121,1122],{"class":54},"\"Pro\"",[44,1124,444],{"class":283},[44,1126,1127],{"class":54},"\"Niche\"",[44,1129,1130],{"class":283},"],\n",[44,1132,1133,1136,1138,1141,1143,1146,1148,1151,1153,1156],{"class":46,"line":375},[44,1134,1135],{"class":54},"    \"units\"",[44,1137,1109],{"class":283},[44,1139,1140],{"class":316},"12_000",[44,1142,444],{"class":283},[44,1144,1145],{"class":316},"6_000",[44,1147,444],{"class":283},[44,1149,1150],{"class":316},"2_500",[44,1152,444],{"class":283},[44,1154,1155],{"class":316},"300",[44,1157,1130],{"class":283},[44,1159,1160,1163,1165,1168,1170,1172,1174,1177,1179,1182],{"class":46,"line":403},[44,1161,1162],{"class":54},"    \"unit_cost\"",[44,1164,1109],{"class":283},[44,1166,1167],{"class":316},"9.0",[44,1169,444],{"class":283},[44,1171,942],{"class":316},[44,1173,444],{"class":283},[44,1175,1176],{"class":316},"38.0",[44,1178,444],{"class":283},[44,1180,1181],{"class":316},"55.0",[44,1183,1130],{"class":283},[44,1185,1186,1189,1191,1194,1196,1199,1201,1204,1206,1209],{"class":46,"line":408},[44,1187,1188],{"class":54},"    \"fixed\"",[44,1190,1109],{"class":283},[44,1192,1193],{"class":316},"40_000",[44,1195,444],{"class":283},[44,1197,1198],{"class":316},"35_000",[44,1200,444],{"class":283},[44,1202,1203],{"class":316},"30_000",[44,1205,444],{"class":283},[44,1207,1208],{"class":316},"20_000",[44,1210,1130],{"class":283},[44,1212,1213],{"class":46,"line":419},[44,1214,1215],{"class":283},"})\n",[44,1217,1218],{"class":46,"line":464},[44,1219,297],{"emptyLinePlaceholder":296},[44,1221,1222,1224,1227,1230,1232,1235],{"class":46,"line":509},[44,1223,303],{"class":279},[44,1225,1226],{"class":306}," price_for_margin",[44,1228,1229],{"class":283},"(row, margin",[44,1231,313],{"class":279},[44,1233,1234],{"class":316},"0.30",[44,1236,344],{"class":283},[44,1238,1240,1243,1245,1248,1251,1253,1256,1258,1261,1263],{"class":46,"line":1239},11,[44,1241,1242],{"class":283},"    f ",[44,1244,313],{"class":279},[44,1246,1247],{"class":279}," lambda",[44,1249,1250],{"class":283}," p: net_profit(p, row.units, row.unit_cost, row.fixed) ",[44,1252,364],{"class":279},[44,1254,1255],{"class":283}," margin ",[44,1257,358],{"class":279},[44,1259,1260],{"class":283}," row.units ",[44,1262,358],{"class":279},[44,1264,1265],{"class":283}," p\n",[44,1267,1269,1272],{"class":46,"line":1268},12,[44,1270,1271],{"class":279},"    try",[44,1273,560],{"class":283},[44,1275,1277,1280,1283,1285,1288],{"class":46,"line":1276},13,[44,1278,1279],{"class":279},"        return",[44,1281,1282],{"class":283}," brentq(f, row.unit_cost, row.unit_cost ",[44,1284,358],{"class":279},[44,1286,1287],{"class":316}," 20",[44,1289,461],{"class":283},[44,1291,1293,1296,1298],{"class":46,"line":1292},14,[44,1294,1295],{"class":279},"    except",[44,1297,601],{"class":316},[44,1299,560],{"class":283},[44,1301,1303,1305,1308,1310,1313],{"class":46,"line":1302},15,[44,1304,1279],{"class":279},[44,1306,1307],{"class":316}," float",[44,1309,389],{"class":283},[44,1311,1312],{"class":54},"\"nan\"",[44,1314,461],{"class":283},[44,1316,1318],{"class":46,"line":1317},16,[44,1319,297],{"emptyLinePlaceholder":296},[44,1321,1323,1326,1329,1332,1334,1337,1340,1342,1344,1347,1350],{"class":46,"line":1322},17,[44,1324,1325],{"class":283},"products[",[44,1327,1328],{"class":54},"\"price_for_30pct\"",[44,1330,1331],{"class":283},"] ",[44,1333,313],{"class":279},[44,1335,1336],{"class":283}," products.apply(price_for_margin, ",[44,1338,1339],{"class":452},"axis",[44,1341,313],{"class":279},[44,1343,751],{"class":316},[44,1345,1346],{"class":283},").round(",[44,1348,1349],{"class":316},"2",[44,1351,461],{"class":283},[44,1353,1355,1357,1360,1363,1365,1368],{"class":46,"line":1354},18,[44,1356,467],{"class":316},[44,1358,1359],{"class":283},"(products.to_string(",[44,1361,1362],{"class":452},"index",[44,1364,313],{"class":279},[44,1366,1367],{"class":316},"False",[44,1369,1370],{"class":283},"))\n",[10,1372,1373,1374,1377],{},"The target here depends on the input itself — 30% of revenue, which grows with price — so it goes inside the function rather than being a fixed number. Rows with no solution come back as ",[19,1375,1376],{},"NaN"," instead of stopping the whole table; with 300 units and 20,000 of fixed costs, the niche product needs a price of about 203 — steep, but it exists. Seeing that number next to the others is often the real point of the exercise. For thousands of rows this is fast enough; each solve takes microseconds.",[30,1379,1381],{"id":1380},"step-5-goal-seek-the-excel-model-itself","Step 5 — Goal seek the Excel model itself",[10,1383,1384,1385,1388,1389,1391],{},"When the model lives in a workbook and must stay there, the function being solved can ",[14,1386,1387],{},"be"," the workbook: write the input, recalculate, read the output. openpyxl cannot calculate, so each evaluation goes through headless LibreOffice. That is slow — around a second per evaluation — which is exactly why ",[19,1390,262],{},"'s small number of steps matters:",[35,1393,1395],{"className":270,"code":1394,"language":272,"meta":40,"style":40},"import subprocess\nimport tempfile\nfrom pathlib import Path\nfrom openpyxl import load_workbook\n\ncalls = []\n\ndef model_net_profit(price):\n    with tempfile.TemporaryDirectory() as tmp:\n        wb = load_workbook(\"pricing_model.xlsx\")\n        wb[\"Inputs\"][\"B3\"] = price\n        src = Path(tmp) \u002F \"run.xlsx\"\n        wb.save(src)\n        subprocess.run([\"soffice\", f\"-env:UserInstallation=file:\u002F\u002F{tmp}\u002Fp\", \"--headless\",\n                        \"--convert-to\", \"xlsx\", \"--outdir\", f\"{tmp}\u002Fout\", str(src)],\n                       check=True, capture_output=True, timeout=120)\n        value = load_workbook(Path(tmp) \u002F \"out\" \u002F \"run.xlsx\", data_only=True)[\"Model\"][\"B7\"].value\n    calls.append(price)\n    return value\n\nprice_xl = brentq(lambda p: model_net_profit(p) - target, 20, 35, xtol=1e-4)\nprint(f\"price from the workbook: {price_xl:.4f} after {len(calls)} recalculations\")\n",[19,1396,1397,1404,1411,1423,1435,1439,1449,1453,1463,1476,1491,1512,1527,1532,1565,1603,1634,1678,1683,1691,1696,1733],{"__ignoreMap":40},[44,1398,1399,1401],{"class":46,"line":47},[44,1400,287],{"class":279},[44,1402,1403],{"class":283}," subprocess\n",[44,1405,1406,1408],{"class":46,"line":293},[44,1407,287],{"class":279},[44,1409,1410],{"class":283}," tempfile\n",[44,1412,1413,1415,1418,1420],{"class":46,"line":300},[44,1414,280],{"class":279},[44,1416,1417],{"class":283}," pathlib ",[44,1419,287],{"class":279},[44,1421,1422],{"class":283}," Path\n",[44,1424,1425,1427,1430,1432],{"class":46,"line":347},[44,1426,280],{"class":279},[44,1428,1429],{"class":283}," openpyxl ",[44,1431,287],{"class":279},[44,1433,1434],{"class":283}," load_workbook\n",[44,1436,1437],{"class":46,"line":375},[44,1438,297],{"emptyLinePlaceholder":296},[44,1440,1441,1444,1446],{"class":46,"line":403},[44,1442,1443],{"class":283},"calls ",[44,1445,313],{"class":279},[44,1447,1448],{"class":283}," []\n",[44,1450,1451],{"class":46,"line":408},[44,1452,297],{"emptyLinePlaceholder":296},[44,1454,1455,1457,1460],{"class":46,"line":419},[44,1456,303],{"class":279},[44,1458,1459],{"class":306}," model_net_profit",[44,1461,1462],{"class":283},"(price):\n",[44,1464,1465,1468,1471,1473],{"class":46,"line":464},[44,1466,1467],{"class":279},"    with",[44,1469,1470],{"class":283}," tempfile.TemporaryDirectory() ",[44,1472,634],{"class":279},[44,1474,1475],{"class":283}," tmp:\n",[44,1477,1478,1481,1483,1486,1489],{"class":46,"line":509},[44,1479,1480],{"class":283},"        wb ",[44,1482,313],{"class":279},[44,1484,1485],{"class":283}," load_workbook(",[44,1487,1488],{"class":54},"\"pricing_model.xlsx\"",[44,1490,461],{"class":283},[44,1492,1493,1496,1499,1502,1505,1507,1509],{"class":46,"line":1239},[44,1494,1495],{"class":283},"        wb[",[44,1497,1498],{"class":54},"\"Inputs\"",[44,1500,1501],{"class":283},"][",[44,1503,1504],{"class":54},"\"B3\"",[44,1506,1331],{"class":283},[44,1508,313],{"class":279},[44,1510,1511],{"class":283}," price\n",[44,1513,1514,1517,1519,1522,1524],{"class":46,"line":1268},[44,1515,1516],{"class":283},"        src ",[44,1518,313],{"class":279},[44,1520,1521],{"class":283}," Path(tmp) ",[44,1523,745],{"class":279},[44,1525,1526],{"class":54}," \"run.xlsx\"\n",[44,1528,1529],{"class":46,"line":1276},[44,1530,1531],{"class":283},"        wb.save(src)\n",[44,1533,1534,1537,1540,1542,1544,1547,1549,1552,1554,1557,1559,1562],{"class":46,"line":1292},[44,1535,1536],{"class":283},"        subprocess.run([",[44,1538,1539],{"class":54},"\"soffice\"",[44,1541,444],{"class":283},[44,1543,472],{"class":279},[44,1545,1546],{"class":54},"\"-env:UserInstallation=file:\u002F\u002F",[44,1548,479],{"class":478},[44,1550,1551],{"class":283},"tmp",[44,1553,488],{"class":478},[44,1555,1556],{"class":54},"\u002Fp\"",[44,1558,444],{"class":283},[44,1560,1561],{"class":54},"\"--headless\"",[44,1563,1564],{"class":283},",\n",[44,1566,1567,1570,1572,1575,1577,1580,1582,1584,1586,1588,1590,1592,1595,1597,1600],{"class":46,"line":1302},[44,1568,1569],{"class":54},"                        \"--convert-to\"",[44,1571,444],{"class":283},[44,1573,1574],{"class":54},"\"xlsx\"",[44,1576,444],{"class":283},[44,1578,1579],{"class":54},"\"--outdir\"",[44,1581,444],{"class":283},[44,1583,472],{"class":279},[44,1585,504],{"class":54},[44,1587,479],{"class":478},[44,1589,1551],{"class":283},[44,1591,488],{"class":478},[44,1593,1594],{"class":54},"\u002Fout\"",[44,1596,444],{"class":283},[44,1598,1599],{"class":316},"str",[44,1601,1602],{"class":283},"(src)],\n",[44,1604,1605,1608,1610,1613,1615,1618,1620,1622,1624,1627,1629,1632],{"class":46,"line":1317},[44,1606,1607],{"class":452},"                       check",[44,1609,313],{"class":279},[44,1611,1612],{"class":316},"True",[44,1614,444],{"class":283},[44,1616,1617],{"class":452},"capture_output",[44,1619,313],{"class":279},[44,1621,1612],{"class":316},[44,1623,444],{"class":283},[44,1625,1626],{"class":452},"timeout",[44,1628,313],{"class":279},[44,1630,1631],{"class":316},"120",[44,1633,461],{"class":283},[44,1635,1636,1639,1641,1644,1646,1649,1652,1655,1657,1660,1662,1664,1667,1670,1672,1675],{"class":46,"line":1322},[44,1637,1638],{"class":283},"        value ",[44,1640,313],{"class":279},[44,1642,1643],{"class":283}," load_workbook(Path(tmp) ",[44,1645,745],{"class":279},[44,1647,1648],{"class":54}," \"out\"",[44,1650,1651],{"class":279}," \u002F",[44,1653,1654],{"class":54}," \"run.xlsx\"",[44,1656,444],{"class":283},[44,1658,1659],{"class":452},"data_only",[44,1661,313],{"class":279},[44,1663,1612],{"class":316},[44,1665,1666],{"class":283},")[",[44,1668,1669],{"class":54},"\"Model\"",[44,1671,1501],{"class":283},[44,1673,1674],{"class":54},"\"B7\"",[44,1676,1677],{"class":283},"].value\n",[44,1679,1680],{"class":46,"line":1354},[44,1681,1682],{"class":283},"    calls.append(price)\n",[44,1684,1686,1688],{"class":46,"line":1685},19,[44,1687,378],{"class":279},[44,1689,1690],{"class":283}," value\n",[44,1692,1694],{"class":46,"line":1693},20,[44,1695,297],{"emptyLinePlaceholder":296},[44,1697,1699,1702,1704,1706,1708,1711,1713,1715,1717,1719,1722,1724,1726,1728,1731],{"class":46,"line":1698},21,[44,1700,1701],{"class":283},"price_xl ",[44,1703,313],{"class":279},[44,1705,427],{"class":283},[44,1707,430],{"class":279},[44,1709,1710],{"class":283}," p: model_net_profit(p) ",[44,1712,364],{"class":279},[44,1714,438],{"class":283},[44,1716,143],{"class":316},[44,1718,444],{"class":283},[44,1720,1721],{"class":316},"35",[44,1723,444],{"class":283},[44,1725,453],{"class":452},[44,1727,313],{"class":279},[44,1729,1730],{"class":316},"1e-4",[44,1732,461],{"class":283},[44,1734,1736,1738,1740,1742,1745,1747,1750,1752,1754,1757,1759,1762,1765,1767,1770],{"class":46,"line":1735},22,[44,1737,467],{"class":316},[44,1739,389],{"class":283},[44,1741,472],{"class":279},[44,1743,1744],{"class":54},"\"price from the workbook: ",[44,1746,479],{"class":478},[44,1748,1749],{"class":283},"price_xl",[44,1751,499],{"class":279},[44,1753,488],{"class":478},[44,1755,1756],{"class":54}," after ",[44,1758,479],{"class":478},[44,1760,1761],{"class":316},"len",[44,1763,1764],{"class":283},"(calls)",[44,1766,488],{"class":478},[44,1768,1769],{"class":54}," recalculations\"",[44,1771,461],{"class":283},[10,1773,1774,1775,1779],{},"The answer agrees with the Python version to four decimal places, and it took only six recalculations — the same order of magnitude as Excel's own Goal Seek, but scripted and repeatable. On Windows with Excel, the same function can write and read the cells through xlwings, with Excel recalculating in memory, which is much faster per step. ",[24,1776,1778],{"href":1777},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Frun-what-if-scenarios-on-an-excel-model-with-python\u002F","Run What-If Scenarios on an Excel Model with Python"," uses the same write–recalculate–read loop for scenario tables.",[73,1781,82,1786,82,1789,82,1792,82,1795,82,1800,82,1805,82,1808,82,1812,82,1815,82,1819,82,1823,82,1825,82,1827,82,1832,82,1837,82,1845,82,1851,82,1856,82,1859,82,1864,82,1869,82,1873,82,1876,82,1881,82,1885,82,1890,82,1893,82,1897],{"viewBox":1782,"role":76,"ariaLabelledBy":1783,"xmlns":80,"style":928},"4 0 752 292",[1784,1785],"gs-seq-t","gs-seq-d",[84,1787,1788],{"id":1784},"One goal-seek step against the workbook",[88,1790,1791],{"id":1785},"A sequence diagram. Python's brentq proposes a price, openpyxl writes it into a copy of the model, LibreOffice recalculates, openpyxl reads net profit, and brentq chooses the next price.",[92,1793],{"x":94,"y":95,"width":937,"height":1794,"fill":98},"292",[92,1796],{"x":941,"y":942,"width":1797,"height":1798,"rx":1799,"fill":105,"stroke":106,"style":206},"180.0","48.0","12",[109,1801,262],{"x":1802,"y":1803,"style":1804},"110.0","44.5","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[92,1806],{"x":1807,"y":942,"width":1797,"height":1798,"rx":1799,"fill":946,"stroke":129,"style":206},"290.0",[109,1809,1811],{"x":249,"y":1803,"style":1810},"font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","openpyxl",[92,1813],{"x":1814,"y":942,"width":1797,"height":1798,"rx":1799,"fill":946,"stroke":129,"style":206},"560.0",[109,1816,1818],{"x":1817,"y":1803,"style":1810},"650.0","LibreOffice",[46,1820],{"x1":1802,"y1":101,"x2":1802,"y2":1821,"stroke":129,"style":1822},"276.0","stroke-width:1.5px;stroke-dasharray:5 4",[46,1824],{"x1":249,"y1":101,"x2":249,"y2":1821,"stroke":129,"style":1822},[46,1826],{"x1":1817,"y1":101,"x2":1817,"y2":1821,"stroke":129,"style":1822},[46,1828],{"x1":1829,"y1":1830,"x2":1831,"y2":1830,"stroke":106,"style":206},"115.0","104.0","371.0",[1833,1834],"polygon",{"points":1835,"fill":1836},"375.0,104.0 366.0,109.0 366.0,99.0","#5b5cf0",[92,1838],{"x":1839,"y":1840,"width":1841,"height":1842,"rx":1843,"fill":98,"stroke":129,"style":1844},"161.1","75.0","167.8","21.0","10","stroke-width:1.2px",[109,1846,1850],{"x":1847,"y":1848,"style":1849},"245.0","89.5","font-size:11.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1. write price to Inputs!B3",[46,1852],{"x1":1853,"y1":1854,"x2":1855,"y2":1854,"stroke":106,"style":206},"385.0","152.0","641.0",[1833,1857],{"points":1858,"fill":1836},"645.0,152.0 636.0,157.0 636.0,147.0",[92,1860],{"x":1861,"y":1862,"width":1863,"height":1842,"rx":1843,"fill":98,"stroke":129,"style":1844},"438.6","123.0","152.8",[109,1865,1868],{"x":1866,"y":1867,"style":1849},"515.0","137.5","2. recalculate the copy",[46,1870],{"x1":1871,"y1":1872,"x2":1011,"y2":1872,"stroke":106,"style":206},"645.0","200.0",[1833,1874],{"points":1875,"fill":1836},"385.0,200.0 394.0,195.0 394.0,205.0",[92,1877],{"x":1878,"y":1879,"width":1880,"height":1842,"rx":1843,"fill":98,"stroke":129,"style":1844},"434.0","171.0","162.0",[109,1882,1884],{"x":1866,"y":1883,"style":1849},"185.5","3. saved file with results",[46,1886],{"x1":1887,"y1":1888,"x2":1889,"y2":1888,"stroke":106,"style":206},"375.0","248.0","119.0",[1833,1891],{"points":1892,"fill":1836},"115.0,248.0 124.0,243.0 124.0,253.0",[92,1894],{"x":1895,"y":122,"width":1896,"height":1842,"rx":1843,"fill":98,"stroke":129,"style":1844},"166.4","157.2",[109,1898,1900],{"x":1847,"y":1899,"style":1849},"233.5","4. Model!B7 = net profit",[30,1902,1904],{"id":1903},"common-pitfalls","Common pitfalls",[1906,1907,1908,1919,1935,1941,1953],"ul",{},[1909,1910,1911,1915,1916,1918],"li",{},[1912,1913,1914],"strong",{},"A bracket without a sign change."," ",[19,1917,262],{}," needs the result on both sides of the target; tabulate the function first.",[1909,1920,1921,1915,1924,1927,1928,1931,1932,1934],{},[1912,1922,1923],{},"Discontinuous models.",[19,1925,1926],{},"IF","s, ",[19,1929,1930],{},"MAX(0, …)",", rounding and lookup tables make steps in the curve. ",[19,1933,262],{}," still finds a crossing if one exists, but a stepped model can jump over the target entirely.",[1909,1936,1937,1940],{},[1912,1938,1939],{},"Several answers."," A curve that crosses the target twice has two answers, and the bracket decides which you get. Narrow it deliberately.",[1909,1942,1943,1915,1946,1948,1949,1952],{},[1912,1944,1945],{},"Tolerance on the wrong axis.",[19,1947,453],{}," is how precisely the ",[14,1950,1951],{},"input"," is found; check the output against the target afterwards, as the examples do.",[1909,1954,1955,1958],{},[1912,1956,1957],{},"Solving by recalculating Excel for every row."," Translate the logic to Python when you need thousands of answers; keep the workbook route for one-off questions on a model you must not duplicate.",[30,1960,1962],{"id":1961},"conclusion","Conclusion",[10,1964,1965,1966,1969,1970,1972],{},"Goal Seek is root finding: write the result as a function of the input, subtract the target, and pass it to ",[19,1967,1968],{},"scipy.optimize.brentq"," with a bracket that contains a sign change. Check the answer, let failures raise, solve RATE-style questions the same way, and apply the solver per row for tables. When the model must stay in Excel, make the function write, recalculate and read the workbook — ",[19,1971,262],{}," needs few enough steps for that to be practical.",[30,1974,1976],{"id":1975},"frequently-asked-questions","Frequently asked questions",[10,1978,1979,1982],{},[1912,1980,1981],{},"What is the Python equivalent of Excel Goal Seek?","\nRoot finding with scipy.optimize: brentq(lambda x: model(x) - target, low, high) returns the input at which the model equals the target, provided the result is below the target at one end of the range and above it at the other.",[10,1984,1985,1988],{},[1912,1986,1987],{},"Why does brentq say f(a) and f(b) must have different signs?","\nThe range you gave does not contain a crossing: the model is on the same side of the target at both ends. Evaluate the model over a wider range to find a bracket, or accept that the target cannot be reached.",[10,1990,1991,1994],{},[1912,1992,1993],{},"How do I calculate Excel's RATE function in Python?","\nRATE has no closed form, so solve for it: write the payment formula as a function of the rate and use brentq to find the rate at which the payment equals the known amount.",[10,1996,1997,2000],{},[1912,1998,1999],{},"Can Python goal seek an Excel workbook directly?","\nYes. Make the function write the input with openpyxl, recalculate the workbook with headless LibreOffice or with Excel through xlwings, and read the output. brentq usually needs only a handful to a dozen or so evaluations.",[30,2002,2004],{"id":2003},"related","Related",[1906,2006,2007,2013,2020,2027],{},[1909,2008,2009,2010,2012],{},"Up one level: ",[24,2011,27],{"href":26}," — Excel functions and features translated to Python.",[1909,2014,2015,2019],{},[24,2016,2018],{"href":2017},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Freplace-excel-solver-with-python\u002F","Replace Excel Solver with Python"," — optimising with constraints instead of hitting one target.",[1909,2021,2022,2026],{},[24,2023,2025],{"href":2024},"\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"," — fitting lines to data.",[1909,2028,2029,2033],{},[24,2030,2032],{"href":2031},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-loan-amortization-schedule-in-excel\u002F","Build a Loan Amortization Schedule in Excel"," — the payment formula in a full schedule.",[2035,2036,2037],"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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":40,"searchDepth":293,"depth":293,"links":2039},[2040,2041,2042,2043,2044,2045,2046,2047,2048,2049],{"id":32,"depth":293,"text":33},{"id":255,"depth":293,"text":256},{"id":543,"depth":293,"text":544},{"id":703,"depth":293,"text":704},{"id":1066,"depth":293,"text":1067},{"id":1380,"depth":293,"text":1381},{"id":1903,"depth":293,"text":1904},{"id":1961,"depth":293,"text":1962},{"id":1975,"depth":293,"text":1976},{"id":2003,"depth":293,"text":2004},"2026-09-29","Replace Excel's Goal Seek with scipy.optimize: find the price that hits a profit target, the interest rate behind a loan payment, a break-even value for every row of a DataFrame, and even goal-seek an Excel model itself by recalculating it in LibreOffice — with brackets, tolerances and failure checks.","md",[2054,2056,2058,2060],{"q":1981,"a":2055},"Root finding with scipy.optimize: brentq(lambda x: model(x) - target, low, high) returns the input at which the model equals the target, provided the result is below the target at one end of the range and above it at the other.",{"q":1987,"a":2057},"The range you gave does not contain a crossing: the model is on the same side of the target at both ends. Evaluate the model over a wider range to find a bracket, or accept that the target cannot be reached.",{"q":1993,"a":2059},"RATE has no closed form, so solve for it: write the payment formula as a function of the rate and use brentq to find the rate at which the payment equals the known amount.",{"q":1999,"a":2061},"Yes. Make the function write the input with openpyxl, recalculate the workbook with headless LibreOffice or with Excel through xlwings, and read the output. brentq usually needs only a handful to a dozen or so evaluations.","goal seek in python instead of excel",null,{"breadcrumb":2065},[2066,2068,2071],{"name":2067,"item":745},"Home",{"name":2069,"item":2070},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":27,"item":26},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel",{"title":2074,"description":2075},"Excel Goal Seek Equivalent in Python with scipy brentq","Goal Seek in Python: scipy.optimize.brentq and root_scalar on a function, the loan rate behind a payment (RATE), row-by-row solving in pandas, and goal-seeking an Excel workbook through headless LibreOffice recalculation.","goal-seek-in-python-instead-of-excel","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel\u002Findex",[2079,2080],"formula-equivalents","excel-formulas","how-to","r5kbcjpCmNeiuQIhCW2tN9xN6wrbtBnPFZb_UosdASk",[2084,2088],{"title":2085,"path":2086,"stem":2087,"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",{"title":2089,"path":2090,"stem":2091,"children":-1},"Excel GROUPBY and PIVOTBY Function Equivalents in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgroupby-and-pivotby-function-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgroupby-and-pivotby-function-equivalents-in-pandas\u002Findex",[2093,2095],{"path":2094,"title":27},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":2096,"title":2069},"\u002Fadvanced-data-transformation-and-cleaning",[],[2099,2102,2105,2108,2111,2114],{"path":2100,"title":2101,"kind":2081},"\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":2103,"title":2104,"kind":2081},"\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":2106,"title":2107,"kind":2081},"\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":2109,"title":2110,"kind":2081},"\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":2112,"title":2113,"kind":2081},"\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":2115,"title":2116,"kind":2081},"\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",1790972088225]