Python代码输出0而非目标值:SAS转Python贷款计算问题
SAS转Python代码修复:循环内prinbal/intbal计算异常问题
问题背景
我在将SAS代码转换为Python的项目中,大部分脚本已完成,但以下SAS逻辑的Python复现存在问题:循环内的prinbal和intbal始终返回0,无法得到期望的计算结果。
SAS原始代码
if balloondt_i <= intnx('month',datadt,1,'end') then prinamt = currbal; else do; do i = 1 to dpd_mult; if currbal > 0 then do; intbal = currbal * rate / 1200 * pmtfreq; if int_only = 'IO' then prinbal = 0; else if intbal >= pmtamt then prinbal = mort(currbal,.,rate*pmtfreq/1200,max(1,(intck('month',datadt,balloondt)-((i-1)*pmtfreq)))/pmtfreq) - intbal; else prinbal = pmtamt - intbal; currbal = currbal - prinbal; prinamt = prinamt + prinbal; intamt = intamt + intbal; end; end; end;
Python示例数据集
import pandas as pd import datetime as dt import numpy_financial as npf from dateutil.relativedelta import relativedelta df = pd.DataFrame({ 'loannum': [111, 222], 'datadt': [dt.datetime(2023, 12, 31), dt.datetime(2023, 12, 31)], 'balloondt_i': [dt.datetime(2026, 6, 1), dt.datetime(2031, 3, 1)], 'balloondt': [dt.datetime(2026, 6, 1), dt.datetime(2031, 3, 1)], 'currbal': [2171044.89, 2020983.87], 'rate': [5.50, 4.17], 'pmtfreq': [1, 1], 'int_only': [None, None], 'dpd_mult': [1, 3], 'prinbal': [0, 0], 'prinamt': [0, 0], 'pmtamt': [77622.93, 26188.26], 'intamt': [0, 0], 'intbal': [0, 0] })
错误的Python复现代码
for idx in df.index: datadt = df.loc[idx, 'datadt'] balloondt_i = df.loc[idx, 'balloondt'] # 错误:应该取balloondt_i currbal = df.loc[idx, 'currbal'] rate = df.loc[idx, 'rate'] pmtfreq = df.loc[idx, 'pmtfreq'] pmtamt = df.loc[idx, 'pmtamt'] if not pd.isna(df.loc[idx, 'pmtamt']) else None int_only = df.loc[idx, 'int_only'] if balloondt_i <= datadt + relativedelta(months=+1): # Approximates 'intnx' df.loc[idx, 'prinamt'] = df.loc[idx, 'currbal'] else: for i in range(df.loc[idx, 'dpd_mult']): if currbal <= 0: # Break if current balance is zero or negative break intbal = df.loc[idx, 'currbal'] * rate / 1200 * pmtfreq # 错误:应该用当前循环的currbal变量 if int_only == 'IO': prinbal = 0 elif intbal >= pmtamt: nperiods = max(1, (balloondt_i.year - datadt.year) * 12 + (balloondt_i.month - datadt.month) - (i - 1) * pmtfreq) # 错误:pmt参数符号和利率计算错误 prinbal = npf.pmt(rate * pmtfreq / 1200 / 12, nperiods, df.loc[idx, 'currbal']) - intbal else: prinbal = pmtamt - intbal # 错误:仅在else分支更新,SAS逻辑是所有分支都要更新 df.loc[idx, 'currbal'] -= prinbal df.loc[idx, 'prinamt'] += prinbal df.loc[idx, 'intamt'] += intbal
关键错误分析
- 变量赋值错误:将
balloondt赋值给balloondt_i,导致日期判断逻辑错误 - 循环内数据引用错误:
intbal计算始终取DataFrame初始的currbal,而非循环中实时更新的变量值 - 更新逻辑范围错误:仅在
else分支更新currbal/prinamt/intamt,SAS逻辑中所有分支计算完prinbal后都要执行更新 - 金融函数参数错误:
npf.pmt的利率参数计算错误,且SASmort与Pythonpmt的符号逻辑相反(pmt返回负值,需取绝对值) - 未写入计算结果:未将循环中计算的
prinbal/intbal写入DataFrame - 循环索引不匹配:Python
range生成的索引与SAS的i起始值不同,需调整nperiods计算中的索引逻辑
修复后的Python代码
for idx in df.index: datadt = df.loc[idx, 'datadt'] # 修正:取正确的balloondt_i字段 balloondt_i = df.loc[idx, 'balloondt_i'] # 复制当前currbal到变量,循环中实时更新 currbal = df.loc[idx, 'currbal'] rate = df.loc[idx, 'rate'] pmtfreq = df.loc[idx, 'pmtfreq'] pmtamt = df.loc[idx, 'pmtamt'] if not pd.isna(df.loc[idx, 'pmtamt']) else None int_only = df.loc[idx, 'int_only'] dpd_mult = df.loc[idx, 'dpd_mult'] # 初始化当前行的累计值 total_prin = 0 total_int = 0 last_prinbal = 0 last_intbal = 0 # 修正intnx的计算:取datadt下一个月的月末 next_month_end = datadt + relativedelta(months=1, day=31) if balloondt_i <= next_month_end: df.loc[idx, 'prinamt'] = currbal df.loc[idx, 'currbal'] = 0 df.loc[idx, 'intamt'] = 0 else: for i in range(1, dpd_mult + 1): # 匹配SAS的i从1到dpd_mult if currbal <= 0: break # 用实时更新的currbal计算intbal intbal = currbal * rate / 1200 * pmtfreq if int_only == 'IO': prinbal = 0 elif intbal >= pmtamt: # 计算剩余期数 months_diff = (balloondt_i.year - datadt.year) * 12 + (balloondt_i.month - datadt.month) nperiods = max(1, months_diff - (i - 1) * pmtfreq) # 修正pmt参数:利率无需再除以12,取绝对值匹配SAS mort逻辑 monthly_pmt = abs(npf.pmt(rate * pmtfreq / 1200, nperiods, currbal)) prinbal = monthly_pmt - intbal else: prinbal = pmtamt - intbal # 更新变量 currbal -= prinbal total_prin += prinbal total_int += intbal last_prinbal = prinbal last_intbal = intbal # 将计算结果写入DataFrame df.loc[idx, 'currbal'] = currbal df.loc[idx, 'prinamt'] = total_prin df.loc[idx, 'intamt'] = total_int df.loc[idx, 'prinbal'] = last_prinbal df.loc[idx, 'intbal'] = last_intbal
期望输出
| loannum | datadt | balloondt_i | balloondt | currbal | rate | pmtfreq | int_only | dpd_mult | prinbal | prinamt | pmtamt | intamt | intbal |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 111 | 2023-12-31 | 2026-06-01 | 2026-06-01 | 2103372.582 | 5.50 | 1 | None | 1 | 67672.30759 | 67672.30759 | 77622.93 | 9950.622413 | 9950.622413 |
| 222 | 2023-12-31 | 2031-03-01 | 2031-03-01 | 1963287.817 | 4.17 | 1 | None | 3 | 19298.77161 | 57696.05327 | 26188.26 | 20868.72673 | 6889.488395 |
内容的提问来源于stack exchange,提问作者gernworm
相关产品推荐
相关产品推荐

