Kiran Jain
Kiran Jain

Reputation: 301

Create Power Query Column Recursively

EMI Calculation:

Number.Round((rate/12*pv)/(1-Number.Power(1+rate/12,-nper)),0)

where PV is initial loan amount Rate is the initial rate of interest at the beginning and nper is 180 = Tenure in months

here the EMI = 107485

Keeping EMI Fixed, now i need to compute monthly interest and principal

The interest calculated formula will be Previous balance * Rate of interest/12 * no. of days / no of days in the previous month So if there is no rate change the interest will be prev bal * rate of interest /12 else it will be for the no of days new interest rate applicable.

Principal = EMI-Interest paid
New Balance = Previous Balance - Principal
Amount

What i am not able to achieve is :

Every time Interest is calculated, i want to refer to the previous balance which is not available.

I have tried creating Recursive functions to compute Interest, principal and balance columns:

Below function computes EMI value at the beginning

PMT

    (pv,rate,nper)=>
    let
    payment = Number.Round((rate/12*pv)/(1-Number.Power(1+rate/12,-nper)),0)
    in
    payment

Below function computes Interest amount (Index Value is the row number / payment no)

Get_InterestValue

    (indexValue,table,rate,no_of_days,no_of_days_in_Month)=>
    let
    prev_idx=indexValue-1,
    previousbal = get_previous_balance(indexValue,table),
    interest=previousbal*rate/12/no_of_days_in_Month*no_of_days
    in
    interest

Below function computes Principal amount paid

GetPrincipalPaid
    (indexvalue,table,EMI_amount,Interest_amount)=>
    let
    principal_amount= EMI_amount-Interest_amount
    in
    principal_amount

Below function computes Previous balance

get_previous_balance

    (index_value,table)=>
    let
    prev_idx=index_value-1,
    prev_bal=if prev_idx=0 then fParameter("ParameterTable","Initial_Loan_Amount") else Table.SelectRows(table, each ([Index] = prev_idx)){0}[Bal]
    in 
    prev_bal

fParameter function reads the intial values table

The columns gives errors

Upvotes: 0

Views: 128

Answers (1)

Kiran Jain
Kiran Jain

Reputation: 301

I have found a solution as in following code:

Now I am facing performance issue. Below solution works good with 10 Months or smaller. once u go for a higher Tenure, the calculations are very slow and doesnt generate any output for long time or probably crashes before calculations are completed, is there any way i can speed up the query processing?

PMT Function:

(pv,rate,nper)=>
let
    payment = Number.Round((rate/12*pv)/(1-Number.Power(1+rate/12,-nper)),0)
in
    payment

fParameter Function:

let Parameter=(TableName,ParameterLabel) =>
let
    Source=Excel.CurrentWorkbook(){[Name=TableName]}[Content],
    value=Source{[Parameter=ParameterLabel]}[Value]
in
    value
in Parameter

Payment Schedule Query:

let
    Custom1 = List.Generate(()=>Date.Month(fParameter("ParameterTable","StartDate")),each _<=fParameter("ParameterTable","Tenure_in_Months")*1.5+Date.Month(fParameter("ParameterTable","StartDate"))-1,each _+1),
    #"Converted to Table" = Table.FromList(Custom1, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Added Custom" = Table.AddColumn(#"Converted to Table", "Month", each if Number.Mod([Column1],12)=0 then 12 else Number.Mod([Column1],12)),
    #"Added Custom1" = Table.AddColumn(#"Added Custom", "Year1", each Date.Year(fParameter("ParameterTable","StartDate"))+Number.RoundDown([Column1]/12)),
    #"Added Custom2" = Table.AddColumn(#"Added Custom1", "Year", each if [Month]=12 then [Year1]-1 else [Year1]),
    #"Renamed Columns1" = Table.RenameColumns(#"Added Custom2",{{"Column1", "PaymentMonth"}}),
    #"Added Custom3" = Table.AddColumn(#"Renamed Columns1", "Payment Date", each #date([Year],[Month],Date.Day(fParameter("ParameterTable","StartDate")))),
    #"Removed Other Columns" = Table.SelectColumns(#"Added Custom3",{"Payment Date"}),
    #"Changed Type" = Table.TransformColumnTypes(#"Removed Other Columns",{{"Payment Date", type date}}),
    #"Added Custom4" = Table.AddColumn(#"Changed Type", "EMI", each EMIValue),
    #"Merged Queries" = Table.NestedJoin(#"Added Custom4",{"Payment Date"},Loan_Rates,{"Date"},"Loan_Rates",JoinKind.FullOuter),
    #"Expanded Loan_Rates" = Table.ExpandTableColumn(#"Merged Queries", "Loan_Rates", {"Date", "Loan Rate"}, {"Date", "Loan Rate"}),
    #"Added Custom5" = Table.AddColumn(#"Expanded Loan_Rates", "New_Date", each if [Date] = null then [Payment Date] else [Date]),
    #"Sorted Rows" = Table.Sort(#"Added Custom5",{{"New_Date", Order.Ascending}}),
    #"Filled Down" = Table.FillDown(#"Sorted Rows",{"Loan Rate"}),
    #"Removed Columns" = Table.RemoveColumns(#"Filled Down",{"Payment Date", "Date"}),
    #"Renamed Columns" = Table.RenameColumns(#"Removed Columns",{{"New_Date", "Payment Date"}}),
    #"Reordered Columns" = Table.ReorderColumns(#"Renamed Columns",{"Payment Date", "EMI", "Loan Rate"}),
    #"Added Index" = Table.AddIndexColumn(#"Reordered Columns", "Index", 0, 1),
    #"Added Index1" = Table.AddIndexColumn(#"Added Index", "Index.1", 1, 1),
    #"Merged Queries1" = Table.NestedJoin(#"Added Index1",{"Index"},#"Added Index1",{"Index.1"},"Added Index1",JoinKind.LeftOuter),
    #"Expanded Added Index1" = Table.ExpandTableColumn(#"Merged Queries1", "Added Index1", {"Payment Date"}, {"Payment Date.1"}),
    #"Added Custom7" = Table.AddColumn(#"Expanded Added Index1", "Payment.Date.2", each if [Index.1]=1 then fParameter("ParameterTable","Disbursement Date") else [Payment Date.1]),
    #"Changed Type1" = Table.TransformColumnTypes(#"Added Custom7",{{"Payment.Date.2", type date}}),
    #"Removed Columns2" = Table.RemoveColumns(#"Changed Type1",{"Payment Date.1"}),
    #"Renamed Columns2" = Table.RenameColumns(#"Removed Columns2",{{"Payment.Date.2", "Payment Date.1"}}),
    #"Added Custom6" = Table.AddColumn(#"Renamed Columns2", "Days", each [Payment Date]-[Payment Date.1]),
    #"Extracted Days" = Table.TransformColumns(#"Added Custom6",{{"Days", Duration.Days, Int64.Type}}),
    #"Added Custom8" = Table.AddColumn(#"Extracted Days", "No Of Days in Month", each Date.EndOfMonth([Payment Date.1])-Date.StartOfMonth([Payment Date.1])+#duration(1,0,0,0)),
    #"Removed Columns1" = Table.RemoveColumns(#"Added Custom8",{"Index",  "Payment Date.1"}),
    #"Extracted Days1" = Table.TransformColumns(#"Removed Columns1",{{"No Of Days in Month", Duration.Days, Int64.Type}}),
    #"Reordered Columns1" = Table.ReorderColumns(#"Extracted Days1",{"Payment Date", "Days", "EMI", "Loan Rate"}),
    #"Renamed Columns3" = Table.RenameColumns(#"Reordered Columns1",{{"Index.1", "Index"}}),
    #"Reordered Columns2" = Table.ReorderColumns(#"Renamed Columns3",{"Index", "Payment Date", "EMI", "No Of Days in Month", "Days", "Loan Rate"}),
    #"Replaced Value" = Table.ReplaceValue(#"Reordered Columns2",null,0,Replacer.ReplaceValue,{"EMI"}),
    #"Invoked Custom Function" = Table.AddColumn(#"Replaced Value", "Interest", each Get_InterestValue([Index], Table.Buffer(#"Replaced Value"))),
    #"Added Custom9" = Table.AddColumn(#"Invoked Custom Function", "Principal", each [EMI]-[Interest]),
    #"Added Custom11" = Table.AddColumn(#"Added Custom9", "Balance", each fParameter("ParameterTable","Initial_Loan_Amount")-List.Sum(List.FirstN(#"Added Custom9"[Principal],[Index]))),
    #"Filtered Rows" = Table.SelectRows(#"Added Custom11", each [Interest] > 0)
    
in
    #"Filtered Rows"

Loan_Rates table Query == This reads change of loan rates over a period:

let
    Source = Excel.CurrentWorkbook(){[Name="Loan_Rates"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Date", type date}, {"Loan Rate", Percentage.Type}})
in
    #"Changed Type"

Get_InterestValue Function

(indexValue,table)=>
let
    prev_idx=indexValue-1,
    rate=Table.SelectRows(table, each ([Index] = indexValue)){0}[Loan Rate],
    no_of_days_in_Month=Table.SelectRows(table, each ([Index] = indexValue)){0}[No Of Days in Month],
    no_of_days=Table.SelectRows(table, each ([Index] = indexValue)){0}[Days],
    previousbal = get_previous_balance(indexValue,table),
    interest=previousbal*rate/12/no_of_days_in_Month*no_of_days
in
    interest

GetPrincipalPaid Function:

(indexValue,table)=>
let
    EMI_Amount=Table.SelectRows(table, each ([Index] = indexValue)){0}[EMI],
    principal_amount= EMI_Amount-Get_InterestValue(indexValue,table)
in
    principal_amount

GetBalance Function:

(indexValue,table)=>
let
    bal = get_previous_balance(indexValue,table)-GetPrincipalPaid(indexValue,table) 
in
    bal

get_previous_balance function:

(indexValue,table)=>
let
prev_idx=indexValue-1,
prev_bal=if prev_idx=0 then fParameter("ParameterTable","Initial_Loan_Amount") else  GetBalance(prev_idx,table)
in 
prev_bal

EMIValue Function: let

    Source = PMT(fParameter("ParameterTable","Initial_Loan_Amount"), fParameter("ParameterTable","Rate_int_PA"), fParameter("ParameterTable","Tenure_in_Months")),
    #"Converted to Table" = #table(1, {{Source}}),
    #"Renamed Columns" = Table.RenameColumns(#"Converted to Table",{{"Column1", "EMI"}}){0}[EMI]
in
    #"Renamed Columns"

Upvotes: 0

Related Questions