虚构一份应收账款的明细账,为制作利用Power BI监控应收账款风险的经验分享使用。
import numpy as np
import pandas as pd
from numpy.random import *
from faker import Faker
from datetime import datetime
from datetime import timedelta
np.random.seed(1)
faker=Faker(locale="en-US")
dates=pd.date_range(datetime(2016,1,1),datetime(2022,12,31))
dates=pd.Series([None] * len(dates),index=dates)
companies=[]
comCnt=200
delayDays=choice(range(1,181),comCnt,replace=True)
probInYear=pd.Series([0.05,0.05,0.1,0.15,0.2,0.2,0.25])
probInYear=probInYear.cumsum()
for i in range(comCnt):
companies.append(dict())
companies[i]['name']=faker.company()
prob=probInYear[probInYear>random()]
yearIndex=prob.index[0]
iyear=2016 + yearIndex
startDate=choice(dates[str(iyear)].index)
companies[i]['startDate']=startDate
companies[i]['willInDebt']=True if random()<0.05 else False
noPayDate=choice(dates.index[dates.index>startDate]) if companies[i]['willInDebt'] else None
companies[i]['noPayDate']=noPayDate
delayDay=delayDays[i]
companies[i]['delayDay']=delayDay
if noPayDate != None:
endDate=datetime.fromtimestamp(noPayDate.astype(datetime)/1000000000)+timedelta(int(delayDay),0)
else:
endDate=datetime.fromtimestamp(startDate.astype(datetime)/1000000000)+timedelta(int(random()*3000),0)
companies[i]['endDate']=endDate
scale=round(10**(randn()+4 ),2)
companies[i]['scale']=scale
companies[i]['busProb']=random()+0.1
companies[i]['payRate']=random()*0.6+0.4
companies[i]['delayProb']=random()*0.3
comData=pd.DataFrame(companies)
yearMonths=pd.date_range('2016-01-01','2022-12-31',freq='M').map(lambda x: str(x.year) + '-' + str(x.month))
yearMonthsRepeated=pd.DataFrame([*yearMonths] * len(comData),columns=['yearMonth'])
yearMonthsRepeated['happenDate']=yearMonthsRepeated['yearMonth'].map(lambda x: choice(dates[x].index))
yearMonthsRepeated['payDate']=yearMonthsRepeated['yearMonth'].map(lambda x: choice(dates[x].index))
comDataToList=[list(comData.iloc[i].values) for i in range(len(comData))]
comDataRepeated=pd.DataFrame(list(pd.Series(comDataToList).repeat(len(yearMonths)).values),columns=comData.columns)
receivableTable=pd.concat([comDataRepeated,yearMonthsRepeated],axis=1)
def receivableHappen(happenDate,scale,startDate=datetime(2016,1,1),endDate=datetime(2022,12,31),busProb=1,*arg,**args):
if random()>busProb:
return 0
if happenDate>endDate:
return 0
if happenDate<startDate:
return 0
receivable=(random()+0.5)*scale
return round(receivable,2)
receivableHappened=pd.DataFrame([receivableHappen(**receivableTable.iloc[i].to_dict()) for i in range(len(receivableTable))],columns=['receivable'])
receivableTable=pd.concat([receivableTable,receivableHappened],axis=1)
receivableTable['randomNum']=[random() for _ in range(len(receivableTable))]
receivableTable['noPayDate']=np.where(~pd.isnull(receivableTable['noPayDate']),receivableTable['noPayDate'],np.datetime64('2099-12-31T00:00:00.000000000'))
grouped=receivableTable.groupby(['name'])[['name','yearMonth','happenDate','payDate','noPayDate','receivable','scale','payRate','delayProb','randomNum']]
def detailPay(i,receiList,randomNumList,payDateList,happenDateList,noPayDate=None,payRate=0.6,scale=999999,delayProb=1,*args,**kwargs):
if i == 0:
randomNum=randomNumList.iloc[i]
cumReceivableForPay=receiList.iloc[i]*(payDateList.iloc[i]>happenDateList.iloc[i])
cumReceivable=receiList.iloc[i]
if randomNum<1-delayProb and noPayDate>payDateList.iloc[i]:
if cumReceivableForPay <=100:
pay=cumReceivableForPay
else:
pay=min(round(cumReceivableForPay*payRate,2),scale*2)
else:
pay=0
cumNotPay=round(cumReceivable-pay,2)
return[pay,cumNotPay]
else:
randomNum=randomNumList.iloc[i]
cumReceivabePre=detailPay(i-1,receiList,randomNumList,payDateList,happenDateList,noPayDate,payRate,scale,delayProb,**kwargs)[1]
cumReceivableForPay=receiList.iloc[i]*(payDateList.iloc[i]>happenDateList.iloc[i])+cumReceivabePre
cumReceivable=receiList.iloc[i]+cumReceivabePre
if randomNum<1-delayProb and noPayDate>payDateList.iloc[i]:
if cumReceivableForPay <=100:
pay=cumReceivableForPay
else:
pay=min(round(cumReceivableForPay*payRate,2),scale*2)
else:
pay=0
cumNotPay=round(cumReceivable-pay,2)
return[pay,cumNotPay]
def calPayNotPay(part):
part['pay']=[0]*len(part)
part['cumNotPay']=[0]*len(part)
receiList=part['receivable']
randomNumList=part['randomNum']
payDateList=part['payDate']
happenDateList=part['happenDate']
for i in range(len(part)):
part['pay'].iloc[i],part['cumNotPay'].iloc[i]=detailPay(i,receiList,randomNumList,payDateList,happenDateList,**part.iloc[i])
return part
payTable=grouped.apply(calPayNotPay)
payTable.to_excel('payTable.xlsx',index=False)
debitTable=payTable.reindex(columns=['name','happenDate','receivable']).rename(columns={'receivable':'debit','happenDate':'Date'})
creditTable=payTable.reindex(columns=['name','payDate','pay']).rename(columns={'pay':'credit','payDate':'Date'})
happenTable=pd.concat([debitTable,creditTable])
happenTable[['debit','credit']]=happenTable[['debit','credit']].fillna(0)
happenTable=happenTable[ happenTable['debit'].map(lambda x:1 if x!=0 else 0) + happenTable['credit'].map(lambda x:1 if x!=0 else 0) >0]
happenTable=happenTable.sort_values(['Date','name'])
happenTable.to_excel('receivableHappen.xlsx',index=False)