[{"data":1,"prerenderedAt":2782},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffv-pv-rate-and-nper-equivalents-in-python":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffv-pv-rate-and-nper-equivalents-in-python":2749,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffv-pv-rate-and-nper-equivalents-in-python":2757,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffv-pv-rate-and-nper-equivalents-in-python":2762,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffv-pv-rate-and-nper-equivalents-in-python":2763},{"id":4,"title":5,"body":6,"dateModified":2717,"datePublished":2717,"description":2718,"extension":2719,"faq":2720,"keyword":2729,"kind":2730,"meta":2731,"navigation":308,"path":2739,"seo":2740,"slug":2743,"stem":2744,"topics":2745,"type":2747,"__hash__":2748},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffv-pv-rate-and-nper-equivalents-in-python\u002Findex.md","FV, PV, RATE and NPER Equivalents in Python",{"type":7,"value":8,"toc":2704},"minimark",[9,36,41,75,86,90,113,118,142,282,286,803,813,817,823,1113,1123,1254,1258,1263,1594,1602,1606,1613,2010,2019,2023,2458,2461,2567,2571,2614,2618,2628,2632,2649,2655,2661,2667,2671,2700],[10,11,12,13,17,18,21,22,25,26,29,30,35],"p",{},"Four spreadsheet functions answer most everyday finance questions. ",[14,15,16],"code",{},"FV",": how much will I have if I save 200 a month for ten years? ",[14,19,20],{},"PV",": what lump sum is a stream of payments worth today? ",[14,23,24],{},"NPER",": how many months to pay off a loan at a given instalment? ",[14,27,28],{},"RATE",": what interest rate does a \"pay 500 a month for four years on a 20,000 loan\" offer actually imply? They share one equation and one sign convention, and once that is clear, each is a few lines of Python — useful when a report outgrows the spreadsheet but the numbers must still match it exactly. This page builds the four functions, checks them against the spreadsheet, and applies them to whole DataFrame columns. It belongs to ",[31,32,34],"a",{"href":33},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",".",[37,38,40],"h2",{"id":39},"prerequisites","Prerequisites",[42,43,48],"pre",{"className":44,"code":45,"language":46,"meta":47,"style":47},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install numpy pandas scipy openpyxl\n","bash","",[14,49,50],{"__ignoreMap":47},[51,52,55,59,63,66,69,72],"span",{"class":53,"line":54},"line",1,[51,56,58],{"class":57},"sMTad","pip",[51,60,62],{"class":61},"srMev"," install",[51,64,65],{"class":61}," numpy",[51,67,68],{"class":61}," pandas",[51,70,71],{"class":61}," scipy",[51,73,74],{"class":61}," openpyxl\n",[10,76,77,78,81,82,85],{},"LibreOffice (",[14,79,80],{},"soffice",") recalculates the reference formulas in Step 6. The ",[14,83,84],{},"numpy-financial"," package implements the same functions; writing them out shows exactly what they assume, and avoids a dependency for four short formulas.",[37,87,89],{"id":88},"step-1-the-equation-and-the-sign-convention","Step 1 — The equation and the sign convention",[10,91,92,93,96,97,100,101,104,105,108,109,112],{},"All four functions solve one equation that balances the present value ",[14,94,95],{},"pv",", a regular payment ",[14,98,99],{},"pmt"," over ",[14,102,103],{},"nper"," periods at ",[14,106,107],{},"rate",", and a future value ",[14,110,111],{},"fv",":",[10,114,115],{},[14,116,117],{},"pv·(1+rate)^nper + pmt·(1 + rate·type)·((1+rate)^nper − 1)\u002Frate + fv = 0",[10,119,120,121,125,126,129,130,133,134,137,138,141],{},"The equation equals zero because cash ",[122,123,124],"em",{},"paid out"," and cash ",[122,127,128],{},"received"," have opposite signs. Saving 200 a month means ",[14,131,132],{},"pmt = -200"," (money leaves your account) and the resulting balance is positive (money you get back). Borrowing 20,000 means ",[14,135,136],{},"pv = +20000"," (received) and the instalments are negative. ",[14,139,140],{},"type"," is 0 when payments happen at the end of each period, 1 when they happen at the start. Getting the signs wrong is the most common reason a Python version \"disagrees\" with Excel.",[143,144,152,153,152,157,152,161,152,169,152,179,152,190,152,197,152,203,152,209,152,217,152,221,152,226,152,230,152,237,152,241,152,248,152,253,152,257,152,261,152,266,152,269,152,274,152,277],"svg",{"viewBox":145,"role":146,"ariaLabelledBy":147,"xmlns":150,"style":151},"4 0 752 207","img",[148,149],"tv-signs-t","tv-signs-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[154,155,156],"title",{"id":148},"Cash-flow signs in the time-value functions",[158,159,160],"desc",{"id":149},"Money you pay out is negative and money you receive is positive. In a savings plan the deposits are negative and the future value positive. In a loan the amount borrowed is positive and the instalments negative.",[162,163],"rect",{"x":164,"y":165,"width":166,"height":167,"fill":168},"4","0","752","207","#ffffff",[162,170],{"x":171,"y":172,"width":173,"height":174,"rx":175,"fill":176,"stroke":177,"style":178},"20.0","16.0","351.0","175.0","14","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[180,181,183],"g",{"transform":182},"translate(182.5,29.0) scale(1.083)",[184,185],"path",{"d":186,"fill":187,"stroke":188,"style":189},"M5 6c0-1.7 3.1-3 7-3s7 1.3 7 3-3.1 3-7 3-7-1.3-7-3z M5 6v12c0 1.7 3.1 3 7 3s7-1.3 7-3V6 M5 12c0 1.7 3.1 3 7 3s7-1.3 7-3","none","var(--brand-strong,#4338ca)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[191,192,196],"text",{"x":193,"y":194,"style":195},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Savings plan (FV)",[53,198],{"x1":199,"y1":200,"x2":201,"y2":200,"stroke":177,"style":202},"36.0","91.0","355.0","stroke-width:1px",[191,204,208],{"x":205,"y":206,"style":207},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","pv = −1,000 initial deposit",[180,210,212],{"transform":211},"translate(38.5,99.5) scale(0.625)",[184,213],{"d":214,"fill":187,"stroke":215,"style":216},"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",[191,218,220],{"x":205,"y":219,"style":207},"137.0","pmt = −200 per month",[180,222,224],{"transform":223},"translate(38.5,125.5) scale(0.625)",[184,225],{"d":214,"fill":187,"stroke":215,"style":216},[191,227,229],{"x":205,"y":228,"style":207},"163.0","result fv = +32,703",[180,231,233],{"transform":232},"translate(38.5,151.5) scale(0.625)",[184,234],{"d":235,"fill":187,"stroke":236,"style":216},"M5 12.5l4.5 4.5L19 7","var(--teal,#0f9488)",[162,238],{"x":239,"y":172,"width":173,"height":174,"rx":175,"fill":240,"stroke":236,"style":178},"389.0","#d9f4f1",[180,242,244],{"transform":243},"translate(551.5,29.0) scale(1.083)",[184,245],{"d":246,"fill":187,"stroke":247,"style":189},"M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8z M4 21c0-4 3.6-7 8-7s8 3 8 7","var(--teal-ink,#0b6157)",[191,249,252],{"x":250,"y":194,"style":251},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Loan (NPER, RATE)",[53,254],{"x1":255,"y1":200,"x2":256,"y2":200,"stroke":236,"style":202},"405.0","724.0",[191,258,260],{"x":259,"y":206,"style":207},"429.0","pv = +20,000 borrowed",[180,262,264],{"transform":263},"translate(407.5,99.5) scale(0.625)",[184,265],{"d":235,"fill":187,"stroke":236,"style":216},[191,267,268],{"x":259,"y":219,"style":207},"pmt = −500 per month",[180,270,272],{"transform":271},"translate(407.5,125.5) scale(0.625)",[184,273],{"d":214,"fill":187,"stroke":215,"style":216},[191,275,276],{"x":259,"y":228,"style":207},"fv = 0 when repaid",[180,278,280],{"transform":279},"translate(407.5,151.5) scale(0.625)",[184,281],{"d":214,"fill":187,"stroke":215,"style":216},[37,283,285],{"id":284},"step-2-fv-and-pv","Step 2 — FV and PV",[42,287,291],{"className":288,"code":289,"language":290,"meta":47,"style":47},"language-python shiki shiki-themes github-light github-dark-high-contrast","import math\n\ndef fv(rate, nper, pmt, pv=0.0, type=0):\n    if rate == 0:\n        return -(pv + pmt * nper)\n    growth = (1 + rate) ** nper\n    return -(pv * growth + pmt * (1 + rate * type) * (growth - 1) \u002F rate)\n\ndef pv(rate, nper, pmt, fv=0.0, type=0):\n    if rate == 0:\n        return -(fv + pmt * nper)\n    growth = (1 + rate) ** nper\n    return -(fv + pmt * (1 + rate * type) * (growth - 1) \u002F rate) \u002F growth\n\nprint(f\"Save 200\u002Fmonth for 10 years at 5%, starting with 1,000: {fv(0.05\u002F12, 120, -200, -1000):,.2f}\")\nprint(f\"  ...paying at the start of each month instead:        {fv(0.05\u002F12, 120, -200, -1000, type=1):,.2f}\")\nprint(f\"Lump sum worth 450\u002Fmonth for 5 years at 6%:            {pv(0.06\u002F12, 60, -450):,.2f}\")\nprint(f\"No interest, ten payments of 100:                       {fv(0, 10, -100):,.2f}\")\n","python",[14,292,293,303,310,340,358,382,408,464,469,492,505,523,542,590,595,660,717,763],{"__ignoreMap":47},[51,294,295,299],{"class":53,"line":54},[51,296,298],{"class":297},"s-kum","import",[51,300,302],{"class":301},"skGVy"," math\n",[51,304,306],{"class":53,"line":305},2,[51,307,309],{"emptyLinePlaceholder":308},true,"\n",[51,311,313,316,320,323,326,330,333,335,337],{"class":53,"line":312},3,[51,314,315],{"class":297},"def",[51,317,319],{"class":318},"s_Opv"," fv",[51,321,322],{"class":301},"(rate, nper, pmt, pv",[51,324,325],{"class":297},"=",[51,327,329],{"class":328},"sP0c6","0.0",[51,331,332],{"class":301},", type",[51,334,325],{"class":297},[51,336,165],{"class":328},[51,338,339],{"class":301},"):\n",[51,341,343,346,349,352,355],{"class":53,"line":342},4,[51,344,345],{"class":297},"    if",[51,347,348],{"class":301}," rate ",[51,350,351],{"class":297},"==",[51,353,354],{"class":328}," 0",[51,356,357],{"class":301},":\n",[51,359,361,364,367,370,373,376,379],{"class":53,"line":360},5,[51,362,363],{"class":297},"        return",[51,365,366],{"class":297}," -",[51,368,369],{"class":301},"(pv ",[51,371,372],{"class":297},"+",[51,374,375],{"class":301}," pmt ",[51,377,378],{"class":297},"*",[51,380,381],{"class":301}," nper)\n",[51,383,385,388,390,393,396,399,402,405],{"class":53,"line":384},6,[51,386,387],{"class":301},"    growth ",[51,389,325],{"class":297},[51,391,392],{"class":301}," (",[51,394,395],{"class":328},"1",[51,397,398],{"class":297}," +",[51,400,401],{"class":301}," rate) ",[51,403,404],{"class":297},"**",[51,406,407],{"class":301}," nper\n",[51,409,411,414,416,418,420,423,425,427,429,431,433,435,437,439,442,445,447,450,453,456,458,461],{"class":53,"line":410},7,[51,412,413],{"class":297},"    return",[51,415,366],{"class":297},[51,417,369],{"class":301},[51,419,378],{"class":297},[51,421,422],{"class":301}," growth ",[51,424,372],{"class":297},[51,426,375],{"class":301},[51,428,378],{"class":297},[51,430,392],{"class":301},[51,432,395],{"class":328},[51,434,398],{"class":297},[51,436,348],{"class":301},[51,438,378],{"class":297},[51,440,441],{"class":328}," type",[51,443,444],{"class":301},") ",[51,446,378],{"class":297},[51,448,449],{"class":301}," (growth ",[51,451,452],{"class":297},"-",[51,454,455],{"class":328}," 1",[51,457,444],{"class":301},[51,459,460],{"class":297},"\u002F",[51,462,463],{"class":301}," rate)\n",[51,465,467],{"class":53,"line":466},8,[51,468,309],{"emptyLinePlaceholder":308},[51,470,472,474,477,480,482,484,486,488,490],{"class":53,"line":471},9,[51,473,315],{"class":297},[51,475,476],{"class":318}," pv",[51,478,479],{"class":301},"(rate, nper, pmt, fv",[51,481,325],{"class":297},[51,483,329],{"class":328},[51,485,332],{"class":301},[51,487,325],{"class":297},[51,489,165],{"class":328},[51,491,339],{"class":301},[51,493,495,497,499,501,503],{"class":53,"line":494},10,[51,496,345],{"class":297},[51,498,348],{"class":301},[51,500,351],{"class":297},[51,502,354],{"class":328},[51,504,357],{"class":301},[51,506,508,510,512,515,517,519,521],{"class":53,"line":507},11,[51,509,363],{"class":297},[51,511,366],{"class":297},[51,513,514],{"class":301},"(fv ",[51,516,372],{"class":297},[51,518,375],{"class":301},[51,520,378],{"class":297},[51,522,381],{"class":301},[51,524,526,528,530,532,534,536,538,540],{"class":53,"line":525},12,[51,527,387],{"class":301},[51,529,325],{"class":297},[51,531,392],{"class":301},[51,533,395],{"class":328},[51,535,398],{"class":297},[51,537,401],{"class":301},[51,539,404],{"class":297},[51,541,407],{"class":301},[51,543,545,547,549,551,553,555,557,559,561,563,565,567,569,571,573,575,577,579,581,583,585,587],{"class":53,"line":544},13,[51,546,413],{"class":297},[51,548,366],{"class":297},[51,550,514],{"class":301},[51,552,372],{"class":297},[51,554,375],{"class":301},[51,556,378],{"class":297},[51,558,392],{"class":301},[51,560,395],{"class":328},[51,562,398],{"class":297},[51,564,348],{"class":301},[51,566,378],{"class":297},[51,568,441],{"class":328},[51,570,444],{"class":301},[51,572,378],{"class":297},[51,574,449],{"class":301},[51,576,452],{"class":297},[51,578,455],{"class":328},[51,580,444],{"class":301},[51,582,460],{"class":297},[51,584,401],{"class":301},[51,586,460],{"class":297},[51,588,589],{"class":301}," growth\n",[51,591,593],{"class":53,"line":592},14,[51,594,309],{"emptyLinePlaceholder":308},[51,596,598,601,604,607,610,614,617,620,622,625,628,631,633,635,638,640,642,645,648,651,654,657],{"class":53,"line":597},15,[51,599,600],{"class":328},"print",[51,602,603],{"class":301},"(",[51,605,606],{"class":297},"f",[51,608,609],{"class":61},"\"Save 200\u002Fmonth for 10 years at 5%, starting with 1,000: ",[51,611,613],{"class":612},"sSjpA","{",[51,615,616],{"class":301},"fv(",[51,618,619],{"class":328},"0.05",[51,621,460],{"class":297},[51,623,624],{"class":328},"12",[51,626,627],{"class":301},", ",[51,629,630],{"class":328},"120",[51,632,627],{"class":301},[51,634,452],{"class":297},[51,636,637],{"class":328},"200",[51,639,627],{"class":301},[51,641,452],{"class":297},[51,643,644],{"class":328},"1000",[51,646,647],{"class":301},")",[51,649,650],{"class":297},":,.2f",[51,652,653],{"class":612},"}",[51,655,656],{"class":61},"\"",[51,658,659],{"class":301},")\n",[51,661,663,665,667,669,672,674,676,678,680,682,684,686,688,690,692,694,696,698,700,703,705,707,709,711,713,715],{"class":53,"line":662},16,[51,664,600],{"class":328},[51,666,603],{"class":301},[51,668,606],{"class":297},[51,670,671],{"class":61},"\"  ...paying at the start of each month instead:        ",[51,673,613],{"class":612},[51,675,616],{"class":301},[51,677,619],{"class":328},[51,679,460],{"class":297},[51,681,624],{"class":328},[51,683,627],{"class":301},[51,685,630],{"class":328},[51,687,627],{"class":301},[51,689,452],{"class":297},[51,691,637],{"class":328},[51,693,627],{"class":301},[51,695,452],{"class":297},[51,697,644],{"class":328},[51,699,627],{"class":301},[51,701,140],{"class":702},"sa561",[51,704,325],{"class":297},[51,706,395],{"class":328},[51,708,647],{"class":301},[51,710,650],{"class":297},[51,712,653],{"class":612},[51,714,656],{"class":61},[51,716,659],{"class":301},[51,718,720,722,724,726,729,731,734,737,739,741,743,746,748,750,753,755,757,759,761],{"class":53,"line":719},17,[51,721,600],{"class":328},[51,723,603],{"class":301},[51,725,606],{"class":297},[51,727,728],{"class":61},"\"Lump sum worth 450\u002Fmonth for 5 years at 6%:            ",[51,730,613],{"class":612},[51,732,733],{"class":301},"pv(",[51,735,736],{"class":328},"0.06",[51,738,460],{"class":297},[51,740,624],{"class":328},[51,742,627],{"class":301},[51,744,745],{"class":328},"60",[51,747,627],{"class":301},[51,749,452],{"class":297},[51,751,752],{"class":328},"450",[51,754,647],{"class":301},[51,756,650],{"class":297},[51,758,653],{"class":612},[51,760,656],{"class":61},[51,762,659],{"class":301},[51,764,766,768,770,772,775,777,779,781,783,786,788,790,793,795,797,799,801],{"class":53,"line":765},18,[51,767,600],{"class":328},[51,769,603],{"class":301},[51,771,606],{"class":297},[51,773,774],{"class":61},"\"No interest, ten payments of 100:                       ",[51,776,613],{"class":612},[51,778,616],{"class":301},[51,780,165],{"class":328},[51,782,627],{"class":301},[51,784,785],{"class":328},"10",[51,787,627],{"class":301},[51,789,452],{"class":297},[51,791,792],{"class":328},"100",[51,794,647],{"class":301},[51,796,650],{"class":297},[51,798,653],{"class":612},[51,800,656],{"class":61},[51,802,659],{"class":301},[10,804,805,806,809,810,35],{},"The savings plan reaches 32,703.47; paying at the start of each month earns one extra month of interest on every deposit and gives 32,832.87. The payment stream of 450 a month for five years is worth 23,276.50 today at 6%. Monthly rates and periods are the annual figures divided and multiplied by 12 — the same convention Excel users follow. The ",[14,807,808],{},"rate == 0"," branch matters: the general formula divides by the rate, and a zero-interest promotion would otherwise raise ",[14,811,812],{},"ZeroDivisionError",[37,814,816],{"id":815},"step-3-nper-how-many-periods","Step 3 — NPER: how many periods",[10,818,819,820,822],{},"Solving the equation for ",[14,821,103],{}," gives a logarithm:",[42,824,826],{"className":288,"code":825,"language":290,"meta":47,"style":47},"def nper(rate, pmt, pv, fv=0.0, type=0):\n    if rate == 0:\n        return -(fv + pv) \u002F pmt\n    z = pmt * (1 + rate * type) \u002F rate\n    ratio = (-fv + z) \u002F (pv + z)\n    if ratio \u003C= 0:\n        raise ValueError(\"payment never repays the balance (Excel returns #NUM!)\")\n    return math.log(ratio) \u002F math.log(1 + rate)\n\nmonths = nper(0.07\u002F12, -500, 20000)\nprint(f\"20,000 at 7% with 500\u002Fmonth: {months:.2f} months ({math.ceil(months)} payments)\")\ntry:\n    nper(0.07\u002F12, -100, 20000)\nexcept ValueError as exc:\n    print(\"100\u002Fmonth:\", exc)\n",[14,827,828,850,862,880,910,939,953,968,986,990,1021,1057,1064,1087,1100],{"__ignoreMap":47},[51,829,830,832,835,838,840,842,844,846,848],{"class":53,"line":54},[51,831,315],{"class":297},[51,833,834],{"class":318}," nper",[51,836,837],{"class":301},"(rate, pmt, pv, fv",[51,839,325],{"class":297},[51,841,329],{"class":328},[51,843,332],{"class":301},[51,845,325],{"class":297},[51,847,165],{"class":328},[51,849,339],{"class":301},[51,851,852,854,856,858,860],{"class":53,"line":305},[51,853,345],{"class":297},[51,855,348],{"class":301},[51,857,351],{"class":297},[51,859,354],{"class":328},[51,861,357],{"class":301},[51,863,864,866,868,870,872,875,877],{"class":53,"line":312},[51,865,363],{"class":297},[51,867,366],{"class":297},[51,869,514],{"class":301},[51,871,372],{"class":297},[51,873,874],{"class":301}," pv) ",[51,876,460],{"class":297},[51,878,879],{"class":301}," pmt\n",[51,881,882,885,887,889,891,893,895,897,899,901,903,905,907],{"class":53,"line":342},[51,883,884],{"class":301},"    z ",[51,886,325],{"class":297},[51,888,375],{"class":301},[51,890,378],{"class":297},[51,892,392],{"class":301},[51,894,395],{"class":328},[51,896,398],{"class":297},[51,898,348],{"class":301},[51,900,378],{"class":297},[51,902,441],{"class":328},[51,904,444],{"class":301},[51,906,460],{"class":297},[51,908,909],{"class":301}," rate\n",[51,911,912,915,917,919,921,924,926,929,931,934,936],{"class":53,"line":360},[51,913,914],{"class":301},"    ratio ",[51,916,325],{"class":297},[51,918,392],{"class":301},[51,920,452],{"class":297},[51,922,923],{"class":301},"fv ",[51,925,372],{"class":297},[51,927,928],{"class":301}," z) ",[51,930,460],{"class":297},[51,932,933],{"class":301}," (pv ",[51,935,372],{"class":297},[51,937,938],{"class":301}," z)\n",[51,940,941,943,946,949,951],{"class":53,"line":384},[51,942,345],{"class":297},[51,944,945],{"class":301}," ratio ",[51,947,948],{"class":297},"\u003C=",[51,950,354],{"class":328},[51,952,357],{"class":301},[51,954,955,958,961,963,966],{"class":53,"line":410},[51,956,957],{"class":297},"        raise",[51,959,960],{"class":328}," ValueError",[51,962,603],{"class":301},[51,964,965],{"class":61},"\"payment never repays the balance (Excel returns #NUM!)\"",[51,967,659],{"class":301},[51,969,970,972,975,977,980,982,984],{"class":53,"line":466},[51,971,413],{"class":297},[51,973,974],{"class":301}," math.log(ratio) ",[51,976,460],{"class":297},[51,978,979],{"class":301}," math.log(",[51,981,395],{"class":328},[51,983,398],{"class":297},[51,985,463],{"class":301},[51,987,988],{"class":53,"line":471},[51,989,309],{"emptyLinePlaceholder":308},[51,991,992,995,997,1000,1003,1005,1007,1009,1011,1014,1016,1019],{"class":53,"line":494},[51,993,994],{"class":301},"months ",[51,996,325],{"class":297},[51,998,999],{"class":301}," nper(",[51,1001,1002],{"class":328},"0.07",[51,1004,460],{"class":297},[51,1006,624],{"class":328},[51,1008,627],{"class":301},[51,1010,452],{"class":297},[51,1012,1013],{"class":328},"500",[51,1015,627],{"class":301},[51,1017,1018],{"class":328},"20000",[51,1020,659],{"class":301},[51,1022,1023,1025,1027,1029,1032,1034,1037,1040,1042,1045,1047,1050,1052,1055],{"class":53,"line":507},[51,1024,600],{"class":328},[51,1026,603],{"class":301},[51,1028,606],{"class":297},[51,1030,1031],{"class":61},"\"20,000 at 7% with 500\u002Fmonth: ",[51,1033,613],{"class":612},[51,1035,1036],{"class":301},"months",[51,1038,1039],{"class":297},":.2f",[51,1041,653],{"class":612},[51,1043,1044],{"class":61}," months (",[51,1046,613],{"class":612},[51,1048,1049],{"class":301},"math.ceil(months)",[51,1051,653],{"class":612},[51,1053,1054],{"class":61}," payments)\"",[51,1056,659],{"class":301},[51,1058,1059,1062],{"class":53,"line":525},[51,1060,1061],{"class":297},"try",[51,1063,357],{"class":301},[51,1065,1066,1069,1071,1073,1075,1077,1079,1081,1083,1085],{"class":53,"line":544},[51,1067,1068],{"class":301},"    nper(",[51,1070,1002],{"class":328},[51,1072,460],{"class":297},[51,1074,624],{"class":328},[51,1076,627],{"class":301},[51,1078,452],{"class":297},[51,1080,792],{"class":328},[51,1082,627],{"class":301},[51,1084,1018],{"class":328},[51,1086,659],{"class":301},[51,1088,1089,1092,1094,1097],{"class":53,"line":592},[51,1090,1091],{"class":297},"except",[51,1093,960],{"class":328},[51,1095,1096],{"class":297}," as",[51,1098,1099],{"class":301}," exc:\n",[51,1101,1102,1105,1107,1110],{"class":53,"line":597},[51,1103,1104],{"class":328},"    print",[51,1106,603],{"class":301},[51,1108,1109],{"class":61},"\"100\u002Fmonth:\"",[51,1111,1112],{"class":301},", exc)\n",[10,1114,1115,1116,1119,1120,35],{},"The loan takes 45.68 months — 46 payments, the last one smaller. With only 100 a month, the payment does not even cover the interest (20,000 × 7% \u002F 12 ≈ 117), the balance grows for ever, and the logarithm has no real answer; Excel shows ",[14,1117,1118],{},"#NUM!",", and the function raises a clear error instead of returning ",[14,1121,1122],{},"nan",[143,1124,152,1130,152,1133,152,1136,152,1140,152,1147,152,1153,152,1158,152,1162,152,1168,152,1173,152,1177,152,1181,152,1184,152,1188,152,1191,152,1195,152,1198,152,1203,152,1206,152,1210,152,1214,152,1218,152,1222,152,1226,152,1229,152,1232,152,1235,152,1237,152,1240,152,1242,152,1245,152,1248,152,1251],{"viewBox":1125,"role":146,"ariaLabelledBy":1126,"xmlns":150,"style":1129},"4 0 748 280",[1127,1128],"tv-balance-t","tv-balance-d","width:100%;max-width:748px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[154,1131,1132],{"id":1127},"Loan balance over time at two instalments",[158,1134,1135],{"id":1128},"The balance of a 20,000 loan at 7 percent. With 500 a month it falls to zero after about 46 months. With 100 a month, which is less than the monthly interest, it keeps growing.",[162,1137],{"x":164,"y":165,"width":1138,"height":1139,"fill":168},"748","280",[162,1141],{"x":1142,"y":1143,"width":1144,"height":1144,"rx":1145,"fill":240,"stroke":236,"style":1146},"64.0","18.0","14.0","3","stroke-width:1.5px",[191,1148,1152],{"x":1149,"y":1150,"style":1151},"84.0","29.5","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:start","500 \u002F month",[162,1154],{"x":1155,"y":1143,"width":1144,"height":1144,"rx":1145,"fill":1156,"stroke":1157,"style":1146},"187.2","#fce9e9","var(--danger,#dc2626)",[191,1159,1161],{"x":1160,"y":1150,"style":1151},"207.2","100 \u002F month",[53,1163],{"x1":1164,"y1":1165,"x2":1166,"y2":1165,"stroke":1167,"style":202},"52.8","242.0","736.0","var(--line,#cdd5e6)",[191,1169,165],{"x":1170,"y":1171,"style":1172},"30.4","246.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle",[53,1174],{"x1":1164,"y1":1175,"x2":1166,"y2":1175,"stroke":1167,"style":1176},"178.7","stroke-width:1px;stroke-dasharray:5 4",[191,1178,1180],{"x":1170,"y":1179,"style":1172},"182.7","10k",[53,1182],{"x1":1164,"y1":1183,"x2":1166,"y2":1183,"stroke":1167,"style":1176},"115.3",[191,1185,1187],{"x":1170,"y":1186,"style":1172},"119.3","20k",[53,1189],{"x1":1164,"y1":1190,"x2":1166,"y2":1190,"stroke":1167,"style":1176},"52.0",[191,1192,1194],{"x":1170,"y":1193,"style":1172},"56.0","30k",[53,1196],{"x1":1164,"y1":1165,"x2":1166,"y2":1165,"stroke":1197,"style":1146},"var(--muted,#5b6780)",[191,1199,165],{"x":1200,"y":1201,"style":1202},"121.1","260.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[191,1204,624],{"x":1205,"y":1201,"style":1202},"257.7",[191,1207,1209],{"x":1208,"y":1201,"style":1202},"394.4","24",[191,1211,1213],{"x":1212,"y":1201,"style":1202},"531.0","36",[191,1215,1217],{"x":1216,"y":1201,"style":1202},"667.7","46",[184,1219],{"d":1220,"fill":187,"stroke":236,"style":1221},"M121.1,115.3 L257.7,145.4 L394.4,177.7 L531.0,212.3 L667.7,242.0","stroke-width:2.5px;stroke-linejoin:round;stroke-linecap:round",[1223,1224],"circle",{"cx":1200,"cy":1183,"r":1225,"fill":168,"stroke":236,"style":178},"4.0",[1223,1227],{"cx":1205,"cy":1228,"r":1225,"fill":168,"stroke":236,"style":178},"145.4",[1223,1230],{"cx":1208,"cy":1231,"r":1225,"fill":168,"stroke":236,"style":178},"177.7",[1223,1233],{"cx":1212,"cy":1234,"r":1225,"fill":168,"stroke":236,"style":178},"212.3",[1223,1236],{"cx":1216,"cy":1165,"r":1225,"fill":168,"stroke":236,"style":178},[184,1238],{"d":1239,"fill":187,"stroke":1157,"style":1221},"M121.1,115.3 L257.7,114.0 L394.4,112.6 L531.0,111.1 L667.7,109.8",[1223,1241],{"cx":1200,"cy":1183,"r":1225,"fill":168,"stroke":1157,"style":178},[1223,1243],{"cx":1205,"cy":1244,"r":1225,"fill":168,"stroke":1157,"style":178},"114.0",[1223,1246],{"cx":1208,"cy":1247,"r":1225,"fill":168,"stroke":1157,"style":178},"112.6",[1223,1249],{"cx":1212,"cy":1250,"r":1225,"fill":168,"stroke":1157,"style":178},"111.1",[1223,1252],{"cx":1216,"cy":1253,"r":1225,"fill":168,"stroke":1157,"style":178},"109.8",[37,1255,1257],{"id":1256},"step-4-rate-the-implied-interest-rate","Step 4 — RATE: the implied interest rate",[10,1259,1260,1262],{},[14,1261,28],{}," has no closed form; Excel searches for it. A bracketing root-finder on the same equation does the job reliably:",[42,1264,1266],{"className":288,"code":1265,"language":290,"meta":47,"style":47},"from scipy.optimize import brentq\n\ndef balance_equation(r, nper_, pmt, pv_, fv_, type_):\n    if abs(r) \u003C 1e-12:\n        return pv_ + pmt * nper_ + fv_\n    growth = (1 + r) ** nper_\n    return pv_ * growth + pmt * (1 + r * type_) * (growth - 1) \u002F r + fv_\n\ndef rate(nper_, pmt, pv_, fv_=0.0, type_=0, low=-0.99, high=1.0):\n    return brentq(balance_equation, low, high, args=(nper_, pmt, pv_, fv_, type_), xtol=1e-14)\n\nmonthly = rate(48, -500, 20000)\nprint(f\"20,000 repaid at 500\u002Fmonth for 48 months: {monthly:.6%} per month, {monthly * 12:.4%} per year\")\nprint(f\"  ...with payments in advance (type=1):    {rate(48, -500, 20000, type_=1) * 12:.4%} per year\")\n",[14,1267,1268,1281,1285,1295,1313,1334,1354,1402,1406,1446,1471,1475,1500,1543],{"__ignoreMap":47},[51,1269,1270,1273,1276,1278],{"class":53,"line":54},[51,1271,1272],{"class":297},"from",[51,1274,1275],{"class":301}," scipy.optimize ",[51,1277,298],{"class":297},[51,1279,1280],{"class":301}," brentq\n",[51,1282,1283],{"class":53,"line":305},[51,1284,309],{"emptyLinePlaceholder":308},[51,1286,1287,1289,1292],{"class":53,"line":312},[51,1288,315],{"class":297},[51,1290,1291],{"class":318}," balance_equation",[51,1293,1294],{"class":301},"(r, nper_, pmt, pv_, fv_, type_):\n",[51,1296,1297,1299,1302,1305,1308,1311],{"class":53,"line":342},[51,1298,345],{"class":297},[51,1300,1301],{"class":328}," abs",[51,1303,1304],{"class":301},"(r) ",[51,1306,1307],{"class":297},"\u003C",[51,1309,1310],{"class":328}," 1e-12",[51,1312,357],{"class":301},[51,1314,1315,1317,1320,1322,1324,1326,1329,1331],{"class":53,"line":360},[51,1316,363],{"class":297},[51,1318,1319],{"class":301}," pv_ ",[51,1321,372],{"class":297},[51,1323,375],{"class":301},[51,1325,378],{"class":297},[51,1327,1328],{"class":301}," nper_ ",[51,1330,372],{"class":297},[51,1332,1333],{"class":301}," fv_\n",[51,1335,1336,1338,1340,1342,1344,1346,1349,1351],{"class":53,"line":384},[51,1337,387],{"class":301},[51,1339,325],{"class":297},[51,1341,392],{"class":301},[51,1343,395],{"class":328},[51,1345,398],{"class":297},[51,1347,1348],{"class":301}," r) ",[51,1350,404],{"class":297},[51,1352,1353],{"class":301}," nper_\n",[51,1355,1356,1358,1360,1362,1364,1366,1368,1370,1372,1374,1376,1379,1381,1384,1386,1388,1390,1392,1394,1396,1398,1400],{"class":53,"line":410},[51,1357,413],{"class":297},[51,1359,1319],{"class":301},[51,1361,378],{"class":297},[51,1363,422],{"class":301},[51,1365,372],{"class":297},[51,1367,375],{"class":301},[51,1369,378],{"class":297},[51,1371,392],{"class":301},[51,1373,395],{"class":328},[51,1375,398],{"class":297},[51,1377,1378],{"class":301}," r ",[51,1380,378],{"class":297},[51,1382,1383],{"class":301}," type_) ",[51,1385,378],{"class":297},[51,1387,449],{"class":301},[51,1389,452],{"class":297},[51,1391,455],{"class":328},[51,1393,444],{"class":301},[51,1395,460],{"class":297},[51,1397,1378],{"class":301},[51,1399,372],{"class":297},[51,1401,1333],{"class":301},[51,1403,1404],{"class":53,"line":466},[51,1405,309],{"emptyLinePlaceholder":308},[51,1407,1408,1410,1413,1416,1418,1420,1423,1425,1427,1430,1433,1436,1439,1441,1444],{"class":53,"line":471},[51,1409,315],{"class":297},[51,1411,1412],{"class":318}," rate",[51,1414,1415],{"class":301},"(nper_, pmt, pv_, fv_",[51,1417,325],{"class":297},[51,1419,329],{"class":328},[51,1421,1422],{"class":301},", type_",[51,1424,325],{"class":297},[51,1426,165],{"class":328},[51,1428,1429],{"class":301},", low",[51,1431,1432],{"class":297},"=-",[51,1434,1435],{"class":328},"0.99",[51,1437,1438],{"class":301},", high",[51,1440,325],{"class":297},[51,1442,1443],{"class":328},"1.0",[51,1445,339],{"class":301},[51,1447,1448,1450,1453,1456,1458,1461,1464,1466,1469],{"class":53,"line":494},[51,1449,413],{"class":297},[51,1451,1452],{"class":301}," brentq(balance_equation, low, high, ",[51,1454,1455],{"class":702},"args",[51,1457,325],{"class":297},[51,1459,1460],{"class":301},"(nper_, pmt, pv_, fv_, type_), ",[51,1462,1463],{"class":702},"xtol",[51,1465,325],{"class":297},[51,1467,1468],{"class":328},"1e-14",[51,1470,659],{"class":301},[51,1472,1473],{"class":53,"line":507},[51,1474,309],{"emptyLinePlaceholder":308},[51,1476,1477,1480,1482,1485,1488,1490,1492,1494,1496,1498],{"class":53,"line":525},[51,1478,1479],{"class":301},"monthly ",[51,1481,325],{"class":297},[51,1483,1484],{"class":301}," rate(",[51,1486,1487],{"class":328},"48",[51,1489,627],{"class":301},[51,1491,452],{"class":297},[51,1493,1013],{"class":328},[51,1495,627],{"class":301},[51,1497,1018],{"class":328},[51,1499,659],{"class":301},[51,1501,1502,1504,1506,1508,1511,1513,1516,1519,1521,1524,1526,1528,1530,1533,1536,1538,1541],{"class":53,"line":544},[51,1503,600],{"class":328},[51,1505,603],{"class":301},[51,1507,606],{"class":297},[51,1509,1510],{"class":61},"\"20,000 repaid at 500\u002Fmonth for 48 months: ",[51,1512,613],{"class":612},[51,1514,1515],{"class":301},"monthly",[51,1517,1518],{"class":297},":.6%",[51,1520,653],{"class":612},[51,1522,1523],{"class":61}," per month, ",[51,1525,613],{"class":612},[51,1527,1479],{"class":301},[51,1529,378],{"class":297},[51,1531,1532],{"class":328}," 12",[51,1534,1535],{"class":297},":.4%",[51,1537,653],{"class":612},[51,1539,1540],{"class":61}," per year\"",[51,1542,659],{"class":301},[51,1544,1545,1547,1549,1551,1554,1556,1559,1561,1563,1565,1567,1569,1571,1573,1576,1578,1580,1582,1584,1586,1588,1590,1592],{"class":53,"line":592},[51,1546,600],{"class":328},[51,1548,603],{"class":301},[51,1550,606],{"class":297},[51,1552,1553],{"class":61},"\"  ...with payments in advance (type=1):    ",[51,1555,613],{"class":612},[51,1557,1558],{"class":301},"rate(",[51,1560,1487],{"class":328},[51,1562,627],{"class":301},[51,1564,452],{"class":297},[51,1566,1013],{"class":328},[51,1568,627],{"class":301},[51,1570,1018],{"class":328},[51,1572,627],{"class":301},[51,1574,1575],{"class":702},"type_",[51,1577,325],{"class":297},[51,1579,395],{"class":328},[51,1581,444],{"class":301},[51,1583,378],{"class":297},[51,1585,1532],{"class":328},[51,1587,1535],{"class":297},[51,1589,653],{"class":612},[51,1591,1540],{"class":61},[51,1593,659],{"class":301},[10,1595,1596,1597,1601],{},"The offer implies 0.770% a month, 9.24% a year (nominal, monthly compounding); if the first instalment is due on signing, the implied rate rises to 9.66%, because the borrower effectively receives less money for the same payments. The bracket from −99% to +100% per period covers any realistic deal. Comparing offers by their implied rate rather than their headline monthly payment is a common use — see ",[31,1598,1600],{"href":1599},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fpmt-npv-and-irr-in-python\u002F","PMT, NPV and IRR in Python"," for the payment side.",[37,1603,1605],{"id":1604},"step-5-whole-columns-at-once","Step 5 — Whole columns at once",[10,1607,1608,1609,1612],{},"Reports apply these functions to a table of accounts or offers. NumPy versions of FV and PV vectorise directly; ",[14,1610,1611],{},"np.where"," handles the zero-rate rows:",[42,1614,1616],{"className":288,"code":1615,"language":290,"meta":47,"style":47},"import numpy as np\nimport pandas as pd\n\nplans = pd.DataFrame({\n    \"Plan\": [\"Basic\", \"Plus\", \"Promo\", \"Long\"],\n    \"AnnualRate\": [0.03, 0.05, 0.0, 0.045],\n    \"Years\": [5, 10, 2, 25],\n    \"Monthly\": [100, 200, 300, 150],\n})\nr = plans[\"AnnualRate\"].to_numpy() \u002F 12\nn = plans[\"Years\"].to_numpy() * 12\np = -plans[\"Monthly\"].to_numpy()\nwith np.errstate(divide=\"ignore\", invalid=\"ignore\"):\n    growth = (1 + r) ** n\n    fv_vec = np.where(r == 0, -(p * n), -(p * (growth - 1) \u002F np.where(r == 0, 1, r)))\nplans[\"FutureValue\"] = fv_vec.round(2)\nplans[\"Deposited\"] = plans[\"Monthly\"] * n\nplans[\"Interest\"] = plans[\"FutureValue\"] - plans[\"Deposited\"]\nprint(plans.to_string(index=False))\n",[14,1617,1618,1631,1643,1647,1657,1686,1711,1737,1762,1767,1788,1806,1824,1851,1870,1925,1944,1965,1991],{"__ignoreMap":47},[51,1619,1620,1622,1625,1628],{"class":53,"line":54},[51,1621,298],{"class":297},[51,1623,1624],{"class":301}," numpy ",[51,1626,1627],{"class":297},"as",[51,1629,1630],{"class":301}," np\n",[51,1632,1633,1635,1638,1640],{"class":53,"line":305},[51,1634,298],{"class":297},[51,1636,1637],{"class":301}," pandas ",[51,1639,1627],{"class":297},[51,1641,1642],{"class":301}," pd\n",[51,1644,1645],{"class":53,"line":312},[51,1646,309],{"emptyLinePlaceholder":308},[51,1648,1649,1652,1654],{"class":53,"line":342},[51,1650,1651],{"class":301},"plans ",[51,1653,325],{"class":297},[51,1655,1656],{"class":301}," pd.DataFrame({\n",[51,1658,1659,1662,1665,1668,1670,1673,1675,1678,1680,1683],{"class":53,"line":360},[51,1660,1661],{"class":61},"    \"Plan\"",[51,1663,1664],{"class":301},": [",[51,1666,1667],{"class":61},"\"Basic\"",[51,1669,627],{"class":301},[51,1671,1672],{"class":61},"\"Plus\"",[51,1674,627],{"class":301},[51,1676,1677],{"class":61},"\"Promo\"",[51,1679,627],{"class":301},[51,1681,1682],{"class":61},"\"Long\"",[51,1684,1685],{"class":301},"],\n",[51,1687,1688,1691,1693,1696,1698,1700,1702,1704,1706,1709],{"class":53,"line":384},[51,1689,1690],{"class":61},"    \"AnnualRate\"",[51,1692,1664],{"class":301},[51,1694,1695],{"class":328},"0.03",[51,1697,627],{"class":301},[51,1699,619],{"class":328},[51,1701,627],{"class":301},[51,1703,329],{"class":328},[51,1705,627],{"class":301},[51,1707,1708],{"class":328},"0.045",[51,1710,1685],{"class":301},[51,1712,1713,1716,1718,1721,1723,1725,1727,1730,1732,1735],{"class":53,"line":410},[51,1714,1715],{"class":61},"    \"Years\"",[51,1717,1664],{"class":301},[51,1719,1720],{"class":328},"5",[51,1722,627],{"class":301},[51,1724,785],{"class":328},[51,1726,627],{"class":301},[51,1728,1729],{"class":328},"2",[51,1731,627],{"class":301},[51,1733,1734],{"class":328},"25",[51,1736,1685],{"class":301},[51,1738,1739,1742,1744,1746,1748,1750,1752,1755,1757,1760],{"class":53,"line":466},[51,1740,1741],{"class":61},"    \"Monthly\"",[51,1743,1664],{"class":301},[51,1745,792],{"class":328},[51,1747,627],{"class":301},[51,1749,637],{"class":328},[51,1751,627],{"class":301},[51,1753,1754],{"class":328},"300",[51,1756,627],{"class":301},[51,1758,1759],{"class":328},"150",[51,1761,1685],{"class":301},[51,1763,1764],{"class":53,"line":471},[51,1765,1766],{"class":301},"})\n",[51,1768,1769,1772,1774,1777,1780,1783,1785],{"class":53,"line":494},[51,1770,1771],{"class":301},"r ",[51,1773,325],{"class":297},[51,1775,1776],{"class":301}," plans[",[51,1778,1779],{"class":61},"\"AnnualRate\"",[51,1781,1782],{"class":301},"].to_numpy() ",[51,1784,460],{"class":297},[51,1786,1787],{"class":328}," 12\n",[51,1789,1790,1793,1795,1797,1800,1802,1804],{"class":53,"line":507},[51,1791,1792],{"class":301},"n ",[51,1794,325],{"class":297},[51,1796,1776],{"class":301},[51,1798,1799],{"class":61},"\"Years\"",[51,1801,1782],{"class":301},[51,1803,378],{"class":297},[51,1805,1787],{"class":328},[51,1807,1808,1811,1813,1815,1818,1821],{"class":53,"line":525},[51,1809,1810],{"class":301},"p ",[51,1812,325],{"class":297},[51,1814,366],{"class":297},[51,1816,1817],{"class":301},"plans[",[51,1819,1820],{"class":61},"\"Monthly\"",[51,1822,1823],{"class":301},"].to_numpy()\n",[51,1825,1826,1829,1832,1835,1837,1840,1842,1845,1847,1849],{"class":53,"line":544},[51,1827,1828],{"class":297},"with",[51,1830,1831],{"class":301}," np.errstate(",[51,1833,1834],{"class":702},"divide",[51,1836,325],{"class":297},[51,1838,1839],{"class":61},"\"ignore\"",[51,1841,627],{"class":301},[51,1843,1844],{"class":702},"invalid",[51,1846,325],{"class":297},[51,1848,1839],{"class":61},[51,1850,339],{"class":301},[51,1852,1853,1855,1857,1859,1861,1863,1865,1867],{"class":53,"line":592},[51,1854,387],{"class":301},[51,1856,325],{"class":297},[51,1858,392],{"class":301},[51,1860,395],{"class":328},[51,1862,398],{"class":297},[51,1864,1348],{"class":301},[51,1866,404],{"class":297},[51,1868,1869],{"class":301}," n\n",[51,1871,1872,1875,1877,1880,1882,1884,1886,1888,1891,1893,1896,1898,1900,1902,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922],{"class":53,"line":597},[51,1873,1874],{"class":301},"    fv_vec ",[51,1876,325],{"class":297},[51,1878,1879],{"class":301}," np.where(r ",[51,1881,351],{"class":297},[51,1883,354],{"class":328},[51,1885,627],{"class":301},[51,1887,452],{"class":297},[51,1889,1890],{"class":301},"(p ",[51,1892,378],{"class":297},[51,1894,1895],{"class":301}," n), ",[51,1897,452],{"class":297},[51,1899,1890],{"class":301},[51,1901,378],{"class":297},[51,1903,449],{"class":301},[51,1905,452],{"class":297},[51,1907,455],{"class":328},[51,1909,444],{"class":301},[51,1911,460],{"class":297},[51,1913,1879],{"class":301},[51,1915,351],{"class":297},[51,1917,354],{"class":328},[51,1919,627],{"class":301},[51,1921,395],{"class":328},[51,1923,1924],{"class":301},", r)))\n",[51,1926,1927,1929,1932,1935,1937,1940,1942],{"class":53,"line":662},[51,1928,1817],{"class":301},[51,1930,1931],{"class":61},"\"FutureValue\"",[51,1933,1934],{"class":301},"] ",[51,1936,325],{"class":297},[51,1938,1939],{"class":301}," fv_vec.round(",[51,1941,1729],{"class":328},[51,1943,659],{"class":301},[51,1945,1946,1948,1951,1953,1955,1957,1959,1961,1963],{"class":53,"line":719},[51,1947,1817],{"class":301},[51,1949,1950],{"class":61},"\"Deposited\"",[51,1952,1934],{"class":301},[51,1954,325],{"class":297},[51,1956,1776],{"class":301},[51,1958,1820],{"class":61},[51,1960,1934],{"class":301},[51,1962,378],{"class":297},[51,1964,1869],{"class":301},[51,1966,1967,1969,1972,1974,1976,1978,1980,1982,1984,1986,1988],{"class":53,"line":765},[51,1968,1817],{"class":301},[51,1970,1971],{"class":61},"\"Interest\"",[51,1973,1934],{"class":301},[51,1975,325],{"class":297},[51,1977,1776],{"class":301},[51,1979,1931],{"class":61},[51,1981,1934],{"class":301},[51,1983,452],{"class":297},[51,1985,1776],{"class":301},[51,1987,1950],{"class":61},[51,1989,1990],{"class":301},"]\n",[51,1992,1994,1996,1999,2002,2004,2007],{"class":53,"line":1993},19,[51,1995,600],{"class":328},[51,1997,1998],{"class":301},"(plans.to_string(",[51,2000,2001],{"class":702},"index",[51,2003,325],{"class":297},[51,2005,2006],{"class":328},"False",[51,2008,2009],{"class":301},"))\n",[10,2011,2012,2015,2016,2018],{},[14,2013,2014],{},"np.errstate"," silences the division warning for the zero-rate row, whose result comes from the other branch of ",[14,2017,1611],{},". The interest column makes the effect of time obvious: the 25-year plan earns 37,950 in interest on 45,000 of deposits, while the 5-year plan earns under 500. For RATE and NPER on a column, apply the scalar functions row by row with a list comprehension — they are cheap, and keeping the error handling per row is worth more than vectorising.",[37,2020,2022],{"id":2021},"step-6-check-against-the-spreadsheet","Step 6 — Check against the spreadsheet",[42,2024,2026],{"className":288,"code":2025,"language":290,"meta":47,"style":47},"import subprocess\nfrom openpyxl import Workbook, load_workbook\n\ncases = {\n    \"=FV(0.05\u002F12,120,-200,-1000,0)\": fv(0.05\u002F12, 120, -200, -1000),\n    \"=FV(0.05\u002F12,120,-200,-1000,1)\": fv(0.05\u002F12, 120, -200, -1000, 1),\n    \"=PV(0.06\u002F12,60,-450,5000,0)\": pv(0.06\u002F12, 60, -450, 5000),\n    \"=NPER(0.07\u002F12,-500,20000)\": nper(0.07\u002F12, -500, 20000),\n    \"=RATE(48,-500,20000)\": rate(48, -500, 20000),\n}\nwb = Workbook()\nfor i, formula in enumerate(cases, start=1):\n    wb.active[f\"A{i}\"] = formula\nwb.save(\"tvm.xlsx\")\nsubprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"xlsx\", \"--outdir\", \"recalc\", \"tvm.xlsx\"],\n               check=True, capture_output=True)\nws = load_workbook(\"recalc\u002Ftvm.xlsx\", data_only=True).active\nfor i, (formula, mine) in enumerate(cases.items(), start=1):\n    print(f\"{formula:32} sheet {ws[f'A{i}'].value:>14.6f}  python {mine:>14.6f}\")\n",[14,2027,2028,2035,2047,2051,2061,2094,2129,2160,2186,2208,2213,2223,2249,2275,2285,2324,2345,2370,2392],{"__ignoreMap":47},[51,2029,2030,2032],{"class":53,"line":54},[51,2031,298],{"class":297},[51,2033,2034],{"class":301}," subprocess\n",[51,2036,2037,2039,2042,2044],{"class":53,"line":305},[51,2038,1272],{"class":297},[51,2040,2041],{"class":301}," openpyxl ",[51,2043,298],{"class":297},[51,2045,2046],{"class":301}," Workbook, load_workbook\n",[51,2048,2049],{"class":53,"line":312},[51,2050,309],{"emptyLinePlaceholder":308},[51,2052,2053,2056,2058],{"class":53,"line":342},[51,2054,2055],{"class":301},"cases ",[51,2057,325],{"class":297},[51,2059,2060],{"class":301}," {\n",[51,2062,2063,2066,2069,2071,2073,2075,2077,2079,2081,2083,2085,2087,2089,2091],{"class":53,"line":360},[51,2064,2065],{"class":61},"    \"=FV(0.05\u002F12,120,-200,-1000,0)\"",[51,2067,2068],{"class":301},": fv(",[51,2070,619],{"class":328},[51,2072,460],{"class":297},[51,2074,624],{"class":328},[51,2076,627],{"class":301},[51,2078,630],{"class":328},[51,2080,627],{"class":301},[51,2082,452],{"class":297},[51,2084,637],{"class":328},[51,2086,627],{"class":301},[51,2088,452],{"class":297},[51,2090,644],{"class":328},[51,2092,2093],{"class":301},"),\n",[51,2095,2096,2099,2101,2103,2105,2107,2109,2111,2113,2115,2117,2119,2121,2123,2125,2127],{"class":53,"line":384},[51,2097,2098],{"class":61},"    \"=FV(0.05\u002F12,120,-200,-1000,1)\"",[51,2100,2068],{"class":301},[51,2102,619],{"class":328},[51,2104,460],{"class":297},[51,2106,624],{"class":328},[51,2108,627],{"class":301},[51,2110,630],{"class":328},[51,2112,627],{"class":301},[51,2114,452],{"class":297},[51,2116,637],{"class":328},[51,2118,627],{"class":301},[51,2120,452],{"class":297},[51,2122,644],{"class":328},[51,2124,627],{"class":301},[51,2126,395],{"class":328},[51,2128,2093],{"class":301},[51,2130,2131,2134,2137,2139,2141,2143,2145,2147,2149,2151,2153,2155,2158],{"class":53,"line":410},[51,2132,2133],{"class":61},"    \"=PV(0.06\u002F12,60,-450,5000,0)\"",[51,2135,2136],{"class":301},": pv(",[51,2138,736],{"class":328},[51,2140,460],{"class":297},[51,2142,624],{"class":328},[51,2144,627],{"class":301},[51,2146,745],{"class":328},[51,2148,627],{"class":301},[51,2150,452],{"class":297},[51,2152,752],{"class":328},[51,2154,627],{"class":301},[51,2156,2157],{"class":328},"5000",[51,2159,2093],{"class":301},[51,2161,2162,2165,2168,2170,2172,2174,2176,2178,2180,2182,2184],{"class":53,"line":466},[51,2163,2164],{"class":61},"    \"=NPER(0.07\u002F12,-500,20000)\"",[51,2166,2167],{"class":301},": nper(",[51,2169,1002],{"class":328},[51,2171,460],{"class":297},[51,2173,624],{"class":328},[51,2175,627],{"class":301},[51,2177,452],{"class":297},[51,2179,1013],{"class":328},[51,2181,627],{"class":301},[51,2183,1018],{"class":328},[51,2185,2093],{"class":301},[51,2187,2188,2191,2194,2196,2198,2200,2202,2204,2206],{"class":53,"line":471},[51,2189,2190],{"class":61},"    \"=RATE(48,-500,20000)\"",[51,2192,2193],{"class":301},": rate(",[51,2195,1487],{"class":328},[51,2197,627],{"class":301},[51,2199,452],{"class":297},[51,2201,1013],{"class":328},[51,2203,627],{"class":301},[51,2205,1018],{"class":328},[51,2207,2093],{"class":301},[51,2209,2210],{"class":53,"line":494},[51,2211,2212],{"class":301},"}\n",[51,2214,2215,2218,2220],{"class":53,"line":507},[51,2216,2217],{"class":301},"wb ",[51,2219,325],{"class":297},[51,2221,2222],{"class":301}," Workbook()\n",[51,2224,2225,2228,2231,2234,2237,2240,2243,2245,2247],{"class":53,"line":525},[51,2226,2227],{"class":297},"for",[51,2229,2230],{"class":301}," i, formula ",[51,2232,2233],{"class":297},"in",[51,2235,2236],{"class":328}," enumerate",[51,2238,2239],{"class":301},"(cases, ",[51,2241,2242],{"class":702},"start",[51,2244,325],{"class":297},[51,2246,395],{"class":328},[51,2248,339],{"class":301},[51,2250,2251,2254,2256,2259,2261,2264,2266,2268,2270,2272],{"class":53,"line":544},[51,2252,2253],{"class":301},"    wb.active[",[51,2255,606],{"class":297},[51,2257,2258],{"class":61},"\"A",[51,2260,613],{"class":612},[51,2262,2263],{"class":301},"i",[51,2265,653],{"class":612},[51,2267,656],{"class":61},[51,2269,1934],{"class":301},[51,2271,325],{"class":297},[51,2273,2274],{"class":301}," formula\n",[51,2276,2277,2280,2283],{"class":53,"line":592},[51,2278,2279],{"class":301},"wb.save(",[51,2281,2282],{"class":61},"\"tvm.xlsx\"",[51,2284,659],{"class":301},[51,2286,2287,2290,2293,2295,2298,2300,2303,2305,2308,2310,2313,2315,2318,2320,2322],{"class":53,"line":597},[51,2288,2289],{"class":301},"subprocess.run([",[51,2291,2292],{"class":61},"\"soffice\"",[51,2294,627],{"class":301},[51,2296,2297],{"class":61},"\"--headless\"",[51,2299,627],{"class":301},[51,2301,2302],{"class":61},"\"--convert-to\"",[51,2304,627],{"class":301},[51,2306,2307],{"class":61},"\"xlsx\"",[51,2309,627],{"class":301},[51,2311,2312],{"class":61},"\"--outdir\"",[51,2314,627],{"class":301},[51,2316,2317],{"class":61},"\"recalc\"",[51,2319,627],{"class":301},[51,2321,2282],{"class":61},[51,2323,1685],{"class":301},[51,2325,2326,2329,2331,2334,2336,2339,2341,2343],{"class":53,"line":662},[51,2327,2328],{"class":702},"               check",[51,2330,325],{"class":297},[51,2332,2333],{"class":328},"True",[51,2335,627],{"class":301},[51,2337,2338],{"class":702},"capture_output",[51,2340,325],{"class":297},[51,2342,2333],{"class":328},[51,2344,659],{"class":301},[51,2346,2347,2350,2352,2355,2358,2360,2363,2365,2367],{"class":53,"line":719},[51,2348,2349],{"class":301},"ws ",[51,2351,325],{"class":297},[51,2353,2354],{"class":301}," load_workbook(",[51,2356,2357],{"class":61},"\"recalc\u002Ftvm.xlsx\"",[51,2359,627],{"class":301},[51,2361,2362],{"class":702},"data_only",[51,2364,325],{"class":297},[51,2366,2333],{"class":328},[51,2368,2369],{"class":301},").active\n",[51,2371,2372,2374,2377,2379,2381,2384,2386,2388,2390],{"class":53,"line":765},[51,2373,2227],{"class":297},[51,2375,2376],{"class":301}," i, (formula, mine) ",[51,2378,2233],{"class":297},[51,2380,2236],{"class":328},[51,2382,2383],{"class":301},"(cases.items(), ",[51,2385,2242],{"class":702},[51,2387,325],{"class":297},[51,2389,395],{"class":328},[51,2391,339],{"class":301},[51,2393,2394,2396,2398,2400,2402,2404,2407,2410,2412,2415,2417,2420,2422,2425,2427,2429,2431,2434,2437,2440,2442,2445,2447,2450,2452,2454,2456],{"class":53,"line":1993},[51,2395,1104],{"class":328},[51,2397,603],{"class":301},[51,2399,606],{"class":297},[51,2401,656],{"class":61},[51,2403,613],{"class":612},[51,2405,2406],{"class":301},"formula",[51,2408,2409],{"class":297},":32",[51,2411,653],{"class":612},[51,2413,2414],{"class":61}," sheet ",[51,2416,613],{"class":612},[51,2418,2419],{"class":301},"ws[",[51,2421,606],{"class":297},[51,2423,2424],{"class":61},"'A",[51,2426,613],{"class":612},[51,2428,2263],{"class":301},[51,2430,653],{"class":612},[51,2432,2433],{"class":61},"'",[51,2435,2436],{"class":301},"].value",[51,2438,2439],{"class":297},":>14.6f",[51,2441,653],{"class":612},[51,2443,2444],{"class":61},"  python ",[51,2446,613],{"class":612},[51,2448,2449],{"class":301},"mine",[51,2451,2439],{"class":297},[51,2453,653],{"class":612},[51,2455,656],{"class":61},[51,2457,659],{"class":301},[10,2459,2460],{},"Every pair agrees to six decimal places, including the PV with a non-zero future value (a balloon payment of 5,000 at the end). Keeping this comparison as a test means a future edit to the functions cannot drift away from the spreadsheet unnoticed.",[143,2462,152,2467,152,2470,152,2473,152,2476,152,2480,152,2485,152,2491,152,2494,152,2498,152,2502,152,2506,152,2508,152,2512,152,2514,152,2517,152,2520,152,2523,152,2525,152,2528,152,2530,152,2533,152,2536,152,2539,152,2541,152,2545,152,2547,152,2550,152,2553,152,2556,152,2558,152,2562,152,2564],{"viewBox":2463,"role":146,"ariaLabelledBy":2464,"xmlns":150,"style":151},"4 0 752 208",[2465,2466],"tv-map-t","tv-map-d",[154,2468,2469],{"id":2465},"Which function answers which question",[158,2471,2472],{"id":2466},"FV gives the balance after saving or the remaining balance of a loan. PV gives today's value of a payment stream or the loan a payment affords. NPER gives the number of payments needed. RATE gives the interest rate a deal implies.",[162,2474],{"x":164,"y":165,"width":166,"height":2475,"fill":168},"208",[162,2477],{"x":171,"y":172,"width":2478,"height":2479,"rx":165,"fill":168,"stroke":1167,"style":202},"63.1","40.0",[162,2481],{"x":2482,"y":172,"width":2483,"height":2479,"rx":165,"fill":176,"stroke":177,"style":2484},"83.1","328.4","stroke-width:1.2px",[191,2486,2490],{"x":2487,"y":2488,"style":2489},"247.4","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","solves for",[162,2492],{"x":2493,"y":172,"width":2483,"height":2479,"rx":165,"fill":176,"stroke":177,"style":2484},"411.6",[191,2495,2497],{"x":2496,"y":2488,"style":2489},"575.8","typical question",[162,2499],{"x":171,"y":1193,"width":2478,"height":2500,"rx":165,"fill":2501,"stroke":1167,"style":202},"34.0","#e7ebef",[191,2503,16],{"x":2504,"y":194,"style":2505},"32.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[162,2507],{"x":2482,"y":1193,"width":2483,"height":2500,"rx":165,"fill":168,"stroke":1167,"style":202},[191,2509,2511],{"x":2487,"y":2510,"style":1202},"77.0","future value",[162,2513],{"x":2493,"y":1193,"width":2483,"height":2500,"rx":165,"fill":168,"stroke":1167,"style":202},[191,2515,2516],{"x":2496,"y":2510,"style":1202},"What will my savings be worth?",[162,2518],{"x":171,"y":2519,"width":2478,"height":2500,"rx":165,"fill":2501,"stroke":1167,"style":202},"90.0",[191,2521,20],{"x":2504,"y":2522,"style":2505},"112.0",[162,2524],{"x":2482,"y":2519,"width":2483,"height":2500,"rx":165,"fill":168,"stroke":1167,"style":202},[191,2526,2527],{"x":2487,"y":206,"style":1202},"present value",[162,2529],{"x":2493,"y":2519,"width":2483,"height":2500,"rx":165,"fill":168,"stroke":1167,"style":202},[191,2531,2532],{"x":2496,"y":206,"style":1202},"How big a loan can I afford?",[162,2534],{"x":171,"y":2535,"width":2478,"height":2500,"rx":165,"fill":2501,"stroke":1167,"style":202},"124.0",[191,2537,24],{"x":2504,"y":2538,"style":2505},"146.0",[162,2540],{"x":2482,"y":2535,"width":2483,"height":2500,"rx":165,"fill":168,"stroke":1167,"style":202},[191,2542,2544],{"x":2487,"y":2543,"style":1202},"145.0","number of periods",[162,2546],{"x":2493,"y":2535,"width":2483,"height":2500,"rx":165,"fill":168,"stroke":1167,"style":202},[191,2548,2549],{"x":2496,"y":2543,"style":1202},"How long until it is repaid?",[162,2551],{"x":171,"y":2552,"width":2478,"height":2500,"rx":165,"fill":2501,"stroke":1167,"style":202},"158.0",[191,2554,28],{"x":2504,"y":2555,"style":2505},"180.0",[162,2557],{"x":2482,"y":2552,"width":2483,"height":2500,"rx":165,"fill":168,"stroke":1167,"style":202},[191,2559,2561],{"x":2487,"y":2560,"style":1202},"179.0","interest per period",[162,2563],{"x":2493,"y":2552,"width":2483,"height":2500,"rx":165,"fill":168,"stroke":1167,"style":202},[191,2565,2566],{"x":2496,"y":2560,"style":1202},"What rate does this offer imply?",[37,2568,2570],{"id":2569},"common-pitfalls","Common pitfalls",[2572,2573,2574,2582,2588,2597,2605],"ul",{},[2575,2576,2577,2581],"li",{},[2578,2579,2580],"strong",{},"Signs."," Payments out are negative, money in is positive; mixing them flips or breaks results.",[2575,2583,2584,2587],{},[2578,2585,2586],{},"Annual versus monthly."," Divide annual rates by 12 and multiply years by 12 for monthly payments — and convert RATE's monthly answer back.",[2575,2589,2590,2593,2594,2596],{},[2578,2591,2592],{},"Zero rates."," The general formulas divide by the rate; handle ",[14,2595,808],{}," separately.",[2575,2598,2599,2602,2603,35],{},[2578,2600,2601],{},"Impossible schedules."," A payment smaller than the interest has no NPER; raise instead of returning ",[14,2604,1122],{},[2575,2606,2607,2610,2611,35],{},[2578,2608,2609],{},"Nominal versus effective rates."," RATE × 12 is the nominal annual rate; the effective rate is ",[14,2612,2613],{},"(1 + rate)**12 - 1",[37,2615,2617],{"id":2616},"conclusion","Conclusion",[10,2619,2620,2621,2624,2625,2627],{},"FV, PV, NPER and RATE are four views of one equation linking a present value, a regular payment, a number of periods, a rate and a future value, with money out negative and money in positive. Write FV and PV directly, NPER with a logarithm, and RATE with ",[14,2622,2623],{},"brentq","; handle zero rates, the start-of-period ",[14,2626,140],{},", and schedules that never repay; vectorise with NumPy for whole columns; and confirm against the spreadsheet's own formulas with a LibreOffice recalculation.",[37,2629,2631],{"id":2630},"frequently-asked-questions","Frequently asked questions",[10,2633,2634,2637,2638,2644,2645,2648],{},[2578,2635,2636],{},"How do I calculate Excel's FV in Python?","\nUse fv = -(pv*(1+rate)",[122,2639,2640,2643],{},[122,2641,2642],{},"nper + pmt","(1+rate","type)",[122,2646,2647],{},"((1+rate)**nper - 1)\u002Frate), or -(pv + pmt","nper) when the rate is zero. Pass payments as negative numbers, as Excel does.",[10,2650,2651,2654],{},[2578,2652,2653],{},"Why does my Python NPER or RATE give a different answer from Excel?","\nUsually the signs or periods differ. Excel expects money paid out as negative and money received as positive, and rates and periods in the same unit, for example a monthly rate with a number of months.",[10,2656,2657,2660],{},[2578,2658,2659],{},"How do I calculate RATE in Python without numpy-financial?","\nSolve the time-value equation for the rate numerically, for example with scipy.optimize.brentq between -0.99 and 1.0 per period, then multiply a monthly result by 12 for the nominal annual rate.",[10,2662,2663,2666],{},[2578,2664,2665],{},"What does the type argument do in FV and PV?","\ntype=0 means payments at the end of each period and type=1 at the start. Payments at the start earn or cost one extra period of interest, so the results are slightly larger.",[37,2668,2670],{"id":2669},"related","Related",[2572,2672,2673,2679,2686,2693],{},[2575,2674,2675,2676,2678],{},"Up one level: ",[31,2677,34],{"href":33}," — Excel functions in pandas and Python.",[2575,2680,2681,2685],{},[31,2682,2684],{"href":2683},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fxirr-and-xnpv-equivalents-in-python\u002F","XIRR and XNPV Equivalents in Python"," — cash flows on irregular dates.",[2575,2687,2688,2692],{},[31,2689,2691],{"href":2690},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-loan-amortization-schedule-in-excel\u002F","Build a Loan Amortization Schedule in Excel with Python"," — the period-by-period view of a loan.",[2575,2694,2695,2699],{},[31,2696,2698],{"href":2697},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel\u002F","Goal Seek in Python Instead of Excel"," — the general technique behind RATE.",[2701,2702,2703],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":47,"searchDepth":305,"depth":305,"links":2705},[2706,2707,2708,2709,2710,2711,2712,2713,2714,2715,2716],{"id":39,"depth":305,"text":40},{"id":88,"depth":305,"text":89},{"id":284,"depth":305,"text":285},{"id":815,"depth":305,"text":816},{"id":1256,"depth":305,"text":1257},{"id":1604,"depth":305,"text":1605},{"id":2021,"depth":305,"text":2022},{"id":2569,"depth":305,"text":2570},{"id":2616,"depth":305,"text":2617},{"id":2630,"depth":305,"text":2631},{"id":2669,"depth":305,"text":2670},"2026-10-02","Reproduce Excel's time-value-of-money functions in Python: FV for a savings plan, PV for what a payment stream is worth, NPER for how long a loan takes, and RATE for the interest rate implied by a deal — with Excel's sign convention, the type argument for payments at the start of a period, zero-rate cases, vectorised versions for whole DataFrames, and a LibreOffice check that the numbers match.","md",[2721,2723,2725,2727],{"q":2636,"a":2722},"Use fv = -(pv*(1+rate)**nper + pmt*(1+rate*type)*((1+rate)**nper - 1)\u002Frate), or -(pv + pmt*nper) when the rate is zero. Pass payments as negative numbers, as Excel does.",{"q":2653,"a":2724},"Usually the signs or periods differ. Excel expects money paid out as negative and money received as positive, and rates and periods in the same unit, for example a monthly rate with a number of months.",{"q":2659,"a":2726},"Solve the time-value equation for the rate numerically, for example with scipy.optimize.brentq between -0.99 and 1.0 per period, then multiply a monthly result by 12 for the nominal annual rate.",{"q":2665,"a":2728},"type=0 means payments at the end of each period and type=1 at the start. Payments at the start earn or cost one extra period of interest, so the results are slightly larger.","fv pv rate and nper equivalents in python",null,{"breadcrumb":2732},[2733,2735,2738],{"name":2734,"item":460},"Home",{"name":2736,"item":2737},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":34,"item":33},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffv-pv-rate-and-nper-equivalents-in-python",{"title":2741,"description":2742},"Excel FV, PV, RATE and NPER Functions in Python","Python equivalents of Excel FV, PV, NPER and RATE that match the spreadsheet: the cash-flow sign convention, type 0 vs 1 (end or start of period), rate = 0, #NUM! cases, numpy versions for DataFrame columns, and verification against recalculated formulas.","fv-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",[2746],"formula-equivalents","how-to","LL1goXyrfAHgwM5LOAMMNKi_pYAducqMvaGS0doxwHE",[2750,2754],{"title":2751,"path":2752,"stem":2753,"children":-1},"FORECAST, TREND, SLOPE and RSQ Equivalents in Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python\u002Findex",{"title":2698,"path":2755,"stem":2756,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fgoal-seek-in-python-instead-of-excel\u002Findex",[2758,2760],{"path":2759,"title":34},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":2761,"title":2736},"\u002Fadvanced-data-transformation-and-cleaning",[],[2764,2767,2770,2773,2776,2779],{"path":2765,"title":2766,"kind":2747},"\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":2768,"title":2769,"kind":2747},"\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":2771,"title":2772,"kind":2747},"\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":2774,"title":2775,"kind":2747},"\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":2777,"title":2778,"kind":2747},"\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":2780,"title":2781,"kind":2747},"\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",1790972088200]