Package: lapras
LAPRASLapras is designed to make the model developing job easily and conveniently.
It contains these functions below in one key operation: data exploratory analysis, feature selection, feature binning,
data visualization, scorecard modeling(a logistic regression model with excellent interpretability), performance measure.Let's get started.Usage1.Exploratory Data Analysis
lapras.detect()
lapras.eda()
lapras.quality()
lapras.IV()
lapras.VIF()
lapras.PSI()2.Feature Selection
lapras.select()
lapras.stepwise()3.Binning
lapras.Combiner()
lapras.WOETransformer()
lapras.bin_stats()
lapras.bin_plot()4.Modeling
lapras.ScoreCard()5.Performance Measure
lapras.perform()
lapras.LIFT()
lapras.score_plot()
lapras.KS_bucket()
lapras.PPSI()
lapras.KS()
lapras.AUC()6.One Key Auto Modeling
Lapras also provides a function which runs all the steps above automatically:
lapras.auto_model()Installvia pippipinstalllapras--upgrade-ihttps://pypi.org/simplevia source codepythonsetup.pyinstallinstall_requires = [
'numpy >= 1.18.4',
'pandas >= 0.25.1',
'scipy >= 1.3.2',
'scikit-learn =0.22.2',
'seaborn >= 0.10.1',
'statsmodels >= 0.13.1',
'tensorflow >= 2.2.0',
'hyperopt >= 0.2.7',
'pickle >= 4.0',
'plotly >= 5.9.0',
]Documentsimportlaprasimportpandasaspdimportnumpyasnpfromsklearn.model_selectionimporttrain_test_splitimportmatplotlibasmplimportmatplotlib.pyplotaspltpd.options.display.max_colwidth=100importmath%matplotlibinline# Read in data filedf=pd.read_csv('data/demo.csv',encoding="utf-8")to_drop=['id']# exclude the features which not being used， eg:idtarget='bad'# Y label nametrain_df,test_df,_,_=train_test_split(df,df[[target]],test_size=0.3,random_state=42)# to divide the training set and testing set, strongly recommended# EDA(Exploratory Data Analysis)# Parameter details：# dataframe=Nonelapras.detect(train_df).sort_values("missing")typesizemissinguniquemean_or_top1std_or_top2min_or_top31%_or_top410%_or_top550%_or_bottom575%_or_bottom490%_or_bottom399%_or_bottom2max_or_bottom1idint6455020.000055023947.2666302252.3956712.087.03820.13931.55889.257077.87782.997861.0badint6455020.000020.0732460.2605640.00.000.00.00.000.01.001.0scoreint6455020.0000265295.28062566.2431810.00.00223.0303.0336.00366.0416.00461.0agefloat6455020.00023427.6598804.77029919.021.0023.027.030.0034.043.0053.0wealthfloat6455020.0244184.5298061.8231491.01.003.04.05.007.010.0022.0educationfloat6455020.142753.3194831.0056601.01.002.04.04.004.05.005.0periodfloat6455020.171457.2463261.9820604.04.006.06.010.0010.010.0014.0max_unpay_dayfloat6455020.925311185.47688622.33964728.086.00171.0188.0201.00208.0208.00208.0# Exploratory Data Analysis# feature_list = ['age', 'education', 'score']# exclude_list = ['id']# bins_map = {'age':[-1,20,30,99]}# data_type_map = {'education':'discrete'}# labels_map = {'education':{'1.0':'111','2.0':'222','3.0':'333','4.0':'444','5.0':'555'}}# lapras.eda(df,feature_list=feature_list , exclude_list = exclude_list, bins_map=bins_map,#            labels_map=labels_map, data_type_map=data_type_map, max_bins=6)lapras.eda(df)# Calculate IV value of features（Calculate by default decision tree binning）# Parameter details：# dataframe=None original data# target = 'target' Y label namelapras.quality(train_df.drop(to_drop,axis=1),target=target)ivuniquescore0.758342265.0age0.50458835.0wealth0.27577519.0education0.2305536.0max_unpay_day0.17006112.0period0.0737166.0# Calculate PSI betweet features# Parameter details：# actual=None actual feature# predict=None prediction feature# bins=10 count of binning# return_frame=False return the dataframe of binning if set to truecols=list(lapras.quality(train_df,target=target).reset_index()['index'])forcolincols:ifcolnotin[target]:print("%s:%.4f"%(col,lapras.PSI(train_df[col],test_df[col])))score:0.1500age:0.0147wealth:0.0070education:0.0010max_unpay_day:0.0042id:0.0000period:0.0030# Calculate VIF# Parameter details：# dataframe=Nonelapras.VIF(train_df.drop(['id','bad'],axis=1))wealth1.124927max_unpay_day2.205619score18.266471age17.724547period1.193605education1.090158dtype:float64# Calculate IV value# Parameter details：# feature=None feature data# target=None Y label datalapras.IV(train_df['age'],train_df[target])0.5045879202656338# Features filtering# Parameter details：# frame=None original data# target=None Y label name# empty=0.9 empty feature filtering， feature will be removed if data missing ratio greater than the threshold# iv=0.02 IV value filtering， feature will be removed if IV value lesser than the threshold# corr=0.7 correlation filtering， feature will be removed if correlation value greater than the threshold# vif=False multicollinearity filtering， feature will be removed if multicollinearity value greater than the threshold, default False due to a large number of calculations# return_drop=False reture the removed features if set to true# exclude=None features will be remained if set into this parametertrain_selected,dropped=lapras.select(train_df.drop(to_drop,axis=1),target=target,empty=0.95,\iv=0.05,corr=0.9,vif=False,return_drop=True,exclude=[])print(dropped)print(train_selected.shape)train_selected{'empty':array([],dtype=float64),'iv':array([],dtype=object),'corr':array([],dtype=object)}(5502,7)badwealthmax_unpay_dayscoreageperiodeducation416804.0NaN28823.06.04.060504.0NaN21632.06.04.0301805.0NaN25023.06.02.0458607.0171.041331.0NaN2.0146805.0NaN20429.06.02.0........................522604.0171.034623.0NaN3.0539005.0NaN20732.0NaN3.086006.0NaN35642.04.03.0760303.0NaN32334.0NaN3.0727004.0NaN37824.010.04.05502 rows × 7 columns# Feature Binning， following methods are supported: monotonous binning， decision tree binning， Kmeans binning， equal frequency binning， equal step size binning# Parameter details：# X=None original data# y=None Y label name# method='dt' Binning method：'dt':decision tree binning(default),'mono':monotonous binning,'kmeans':Kmeans binning,'quantile':equal frequency binning,'step':equal step size binning# min_samples=1 the least sample numbers in each binning， represent the count of numbers when greater than 1， represent the ratio of total count when between 0 and 1# n_bins=10 maximun binning count# c.load(dict) adjust the binning by loading a customized dict# c.export() export current binning information by dict formatc=lapras.Combiner()c.fit(train_selected,y=target,method='mono',min_samples=0.05,n_bins=8)#empty_separate = False# # c.load({'age': [22.5, 23.5, 24.5, 25.5, 28.5,36.5],# #  'education': [ 3.5],# #  'max_unpay_day': [59.5],# #  'period': [5.0, 9.0],# #  'score': [205.5, 236.5, 265.5, 275.5, 294.5, 329.5, 381.5],# #  'wealth': [2.5, 3.5, 6.5]})c.export(){'age':[23.0,24.0,25.0,26.0,28.0,29.0,37.0],'education':[3.0,4.0],'max_unpay_day':[171.0],'period':[6.0,10.0],'score':[237.0,272.0,288.0,296.0,330.0,354.0,384.0],'wealth':[3.0,4.0,5.0,7.0]}# To transform the original data into binning data# Parameter details：# X=None original data# labels=False binning label will be shown when set to truec.transform(train_selected,labels=True).iloc[0:10,:]badwealthmax_unpay_dayscoreageperiodeducation4168002.[4.0,5.0)00.[-inf,171.0)03.[288.0,296.0)01.[23.0,24.0)01.[6.0,10.0)02.[4.0,inf)605002.[4.0,5.0)00.[-inf,171.0)00.[-inf,237.0)06.[29.0,37.0)01.[6.0,10.0)02.[4.0,inf)3018003.[5.0,7.0)00.[-inf,171.0)01.[237.0,272.0)01.[23.0,24.0)01.[6.0,10.0)00.[-inf,3.0)4586004.[7.0,inf)01.[171.0,inf)07.[384.0,inf)06.[29.0,37.0)00.[-inf,6.0)00.[-inf,3.0)1468003.[5.0,7.0)00.[-inf,171.0)00.[-inf,237.0)06.[29.0,37.0)01.[6.0,10.0)00.[-inf,3.0)6251003.[5.0,7.0)00.[-inf,171.0)01.[237.0,272.0)01.[23.0,24.0)02.[10.0,inf)00.[-inf,3.0)3686000.[-inf,3.0)00.[-inf,171.0)00.[-inf,237.0)01.[23.0,24.0)01.[6.0,10.0)00.[-inf,3.0)3615002.[4.0,5.0)00.[-inf,171.0)03.[288.0,296.0)06.[29.0,37.0)02.[10.0,inf)02.[4.0,inf)5338000.[-inf,3.0)00.[-inf,171.0)04.[296.0,330.0)03.[25.0,26.0)02.[10.0,inf)00.[-inf,3.0)3985003.[5.0,7.0)00.[-inf,171.0)01.[237.0,272.0)01.[23.0,24.0)01.[6.0,10.0)02.[4.0,inf)# To output bin_stats and bin_plot# Parameter details：# frame=None data transformed by Combiner, keeping binning labels# col=None features to be outputed# target='target' Y label name# Note：The binning details may be different between traning set and testing set due to Population Stability.cols=list(lapras.quality(train_selected,target=target).reset_index()['index'])forcolincols:ifcol!=target:print(lapras.bin_stats(c.transform(train_selected[[col,target]],labels=True),col=col,target=target))lapras.bin_plot(c.transform(train_selected[[col,target]],labels=True),col=col,target=target)scorebad_counttotal_countbad_rateratiowoe\000.[-inf,237.0)1368050.1689440.1463100.944734101.[237.0,272.0)1018320.1213940.1512180.558570202.[272.0,288.0)465330.0863040.0968740.178240303.[288.0,296.0)202950.0677970.053617-0.083176404.[296.0,330.0)7313850.0527080.251727-0.350985505.[330.0,354.0)188120.0221670.147583-1.248849606.[354.0,384.0)85610.0142600.101963-1.698053707.[384.0,inf)12790.0035840.050709-3.089758ivtotal_iv00.1948670.73511610.0599120.73511620.0033220.73511630.0003580.73511640.0267320.73511650.1386870.73511660.1504500.73511670.1607880.735116agebad_counttotal_countbad_rateratiowoe\000.[-inf,23.0)904970.1810870.0903311.028860101.[23.0,24.0)775210.1477930.0946930.785844202.[24.0,25.0)576020.0946840.1094150.280129303.[25.0,26.0)385390.0705010.097964-0.041157404.[26.0,28.0)589970.0581750.181207-0.246509505.[28.0,29.0)203790.0527700.068884-0.349727606.[29.0,37.0)5716570.0344000.301163-0.796844707.[37.0,inf)63100.0193550.056343-1.387405ivtotal_iv00.1476470.4557910.0817210.4557920.0096800.4557930.0001630.4557940.0099180.4557950.0072670.4557960.1373340.4557970.0620600.45579wealthbad_counttotal_countbad_rateratiowoe\000.[-inf,3.0)1065930.1787520.1077791.013038101.[3.0,4.0)8410670.0787250.1939290.078071202.[4.0,5.0)8814750.0596610.268084-0.219698303.[5.0,7.0)9917330.0571260.314976-0.265803404.[7.0,inf)266340.0410090.115231-0.614215ivtotal_iv00.1697020.23620510.0012220.23620520.0117870.23620530.0198810.23620540.0336120.236205educationbad_counttotal_countbad_rateratiowoe\000.[-inf,3.0)22521230.1059820.3858600.405408101.[3.0,4.0)616480.0941360.1177750.273712202.[4.0,inf)11727310.0428410.496365-0.568600ivtotal_iv00.0754390.21177510.0099200.21177520.1264150.211775max_unpay_daybad_counttotal_countbad_rateratiowoe\000.[-inf,171.0)33050980.0647310.926572-0.132726101.[171.0,inf)734040.1806930.0734281.026204ivtotal_iv00.0154260.13469910.1192720.134699periodbad_counttotal_countbad_rateratiowoe\000.[-inf,6.0)5211580.0449050.210469-0.519398101.[6.0,10.0)21828710.0759320.5218100.038912202.[10.0,inf)13314730.0902920.2677210.227787ivtotal_iv00.0456410.06175810.0008030.06175820.0153140.061758# WOE value transformation# transer.fit()：# X=None data transformed by Combiner# y=None Y label# exclude=None features exclude from transformation# transer.transform()：# X=None# transer.export()：# Note： Only training set need to be fittransfer=lapras.WOETransformer()transfer.fit(c.transform(train_selected),train_selected[target],exclude=[target])train_woe=transfer.transform(c.transform(train_selected))transfer.export(){'age':{0:1.0288596439961428,1:0.7858440185299318,2:0.2801286322797789,3:-0.041156782250006324,4:-0.24650930955337075,5:-0.34972695582581514,6:-0.7968444812848496,7:-1.387405073069694},'education':{0:0.4054075821430197,1:0.27371220345368763,2:-0.5685998002779383},'max_unpay_day':{0:-0.13272639517618706,1:1.026204224879801},'period':{0:-0.51939830439238,1:0.0389118677598222,2:0.22778739438526965},'score':{0:0.9447339847162963,1:0.5585702161999536,2:0.17824043251497793,3:-0.08317566500410743,4:-0.3509853692471706,5:-1.2488485442424984,6:-1.6980533007340262,7:-3.089757954582164},'wealth':{0:1.01303813013795,1:0.0780708378046198,2:-0.21969844672815222,3:-0.2658032661768855,4:-0.6142151848362123}}# Features filtering could be done once more after transformed into WOE value. This is optional.train_woe,dropped=lapras.select(train_woe,target=target,empty=0.9,\iv=0.02,corr=0.9,vif=False,return_drop=True,exclude=[])print(dropped)print(train_woe.shape)train_woe.head(10){'empty':array([],dtype=float64),'iv':array([],dtype=object),'corr':array([],dtype=object)}(5502,7)badwealthmax_unpay_dayscoreageperiodeducation41680-0.219698-0.132726-0.0831760.7858440.038912-0.5686006050-0.219698-0.1327260.944734-0.7968440.038912-0.56860030180-0.265803-0.1327260.5585700.7858440.0389120.40540845860-0.6142151.026204-3.089758-0.796844-0.5193980.40540814680-0.265803-0.1327260.944734-0.7968440.0389120.40540862510-0.265803-0.1327260.5585700.7858440.2277870.405408368601.013038-0.1327260.9447340.7858440.0389120.40540836150-0.219698-0.132726-0.083176-0.7968440.227787-0.568600533801.013038-0.132726-0.350985-0.0411570.2277870.40540839850-0.265803-0.1327260.5585700.7858440.038912-0.568600# stepwise regression, to select best features, this is optional# Parameter details：# frame=None original data# target='target' Y label name# estimator='ols' model for regression, supporting 'ols', 'lr', 'lasso', 'ridge'# direction='both' direction for stepwise, supporting 'forward', 'backward', 'both'# criterion='aic' metric, supporting 'aic', 'bic', 'ks', 'auc'# max_iter=None max iteration times# return_drop=False return cols being removed if set to true# exclude=None exclude featuresfinal_data=lapras.stepwise(train_woe,target=target,estimator='ols',direction='both',criterion='aic',exclude=[])final_databadwealthmax_unpay_dayscoreage41680-0.219698-0.132726-0.0831760.7858446050-0.219698-0.1327260.944734-0.79684430180-0.265803-0.1327260.5585700.78584445860-0.6142151.026204-3.089758-0.79684414680-0.265803-0.1327260.944734-0.796844..................52260-0.2196981.026204-1.2488490.78584453900-0.265803-0.1327260.944734-0.7968448600-0.265803-0.132726-1.698053-1.387405760300.078071-0.132726-0.350985-0.79684472700-0.219698-0.132726-1.6980530.2801295502 rows × 5 columns# Scorecard modeling# Parameter details：# base_odds=1/60,base_score=600 When base_odds is 1/60, the corresponding base_score will be 600# pdo=40,rate=2 If the base_odds decrease by half, the corresponding pdo will increase by 40, these are the default parameters# combiner=None Combiner, input the fitted object# transfer=None WOETransformer, input the fitted object# model_type='lr' enumerate:'lr':sklearn LR   'ols':statsmodels ols# ScoreCard.fit()：# X=None WOE value# y=None Y labelcard=lapras.ScoreCard(combiner=c,transfer=transfer)col=list(final_data.drop([target],axis=1).columns)card.fit(final_data[col],final_data[target])ScoreCard(base_odds=0.016666666666666666,base_score=600,card=None,combiner=<lapras.transform.Combinerobjectat0x000001EC0FB72438>,pdo=40,rate=2,transfer=<lapras.transform.WOETransformerobjectat0x000001EC0FDAEF98>)# ScoreCard class method expaination# ScoreCard.predict() predict score for each sample：# X=None# ScoreCard.predict_prob() predict prob for each sample：# X=None# ScoreCard.export() output the details of scorecard by dict format# ScoreCard.get_params() to get the parameters of scorecard by dict format, usually used in deployment# card.intercept_  intercept of logical regression# card.coef_  coefficient of logical regressionfinal_result=final_data[[target]].copy()score=card.predict(final_data[col])prob=card.predict_prob(final_data[col])final_result['score']=scorefinal_result['prob']=probprint("card.intercept_:%s"%(card.intercept_))print("card.coef_:%s"%(card.coef_))card.get_params()['combiner']card.get_params()['transfer']card.export()card.intercept_:-2.5207582925622476card.coef_:[0.320809440.34529880.682946430.66842902]{'age':{'[-inf,23.0)':-39.69,'[23.0,24.0)':-30.31,'[24.0,25.0)':-10.81,'[25.0,26.0)':1.59,'[26.0,28.0)':9.51,'[28.0,29.0)':13.49,'[29.0,37.0)':30.74,'[37.0,inf)':53.52},'intercept':{'[-inf,inf)':509.19},'max_unpay_day':{'[-inf,171.0)':2.64,'[171.0,inf)':-20.45},'score':{'[-inf,237.0)':-37.23,'[237.0,272.0)':-22.01,'[272.0,288.0)':-7.02,'[288.0,296.0)':3.28,'[296.0,330.0)':13.83,'[330.0,354.0)':49.22,'[354.0,384.0)':66.92,'[384.0,inf)':121.77},'wealth':{'[-inf,3.0)':-18.75,'[3.0,4.0)':-1.45,'[4.0,5.0)':4.07,'[5.0,7.0)':4.92,'[7.0,inf)':11.37}}# model performance metrics, including KS, AUC, ROC curve, KS curve, PR curve# Parameter details# feature=None predicted value# target=None actual labellapras.perform(prob,final_result[target])KS:0.4160AUC:0.7602# Parameter details# frame=None original dataframe# score='score' score label name# target='target' Y label name# score_bond=None score boundary, default by 30, customized by list, e.g. [100,200,300]lapras.score_plot(final_result,score='score',target=target)bad:[42,78,70,104,61,28,18,1,1,0]good:[129,249,494,795,1075,972,825,282,164,114]all:[171,327,564,899,1136,1000,843,283,165,114]all_rate:['3.11%','5.94%','10.25%','16.34%','20.65%','18.18%','15.32%','5.14%','3.00%','2.07%']bad_rate:['24.56%','23.85%','12.41%','11.57%','5.37%','2.80%','2.14%','0.35%','0.61%','0.00%']# LIFT show# feature=None predicted value# target=None actual label# recall_list=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] defaultlapras.LIFT(prob,final_data[target])recallprecisionimprove00.10.2400003.20277910.20.2612903.48689720.30.2409643.21564230.40.1895352.52932740.50.1791702.39101350.60.1743522.32670760.70.1616222.15683170.80.1269721.69442580.90.1139361.52046691.00.0749351.000000Automatical modeling# auto_model parameters  df,target,to_drop are necessary, others are optional# bins_show=False showing the binning graphs when set to true# iv_rank=False feature IV values will be ranked when set to true# perform_show=False showing performance(training set)# coef_negative=True coefficient can be negative if set to true# return: ScoreCard objectauto_card=lapras.auto_model(df=train_df,target=target,to_drop=to_drop,bins_show=False,iv_rank=False,perform_show=False,coef_negative=False,empty=0.95,iv=0.02,corr=0.9,vif=False,method='mono',n_bins=8,min_samples=0.05,pdo=40,rate=2,base_odds=1/60,base_score=600)——datafiltering——originalfeature：6filteredfeatures：6——featurebinning————WOEvaluetransformation————featurefilteringoncemore——originalfeature：6filteredfeatures：6——scorecardmodeling——intercept:-2.520670026708529coef:[0.669286710.597439680.317232780.229728380.287508810.26435224]——modelperformancemetrics——KS:0.4208AUC:0.7626recallprecisionimprove00.10.2380953.18858610.20.2547773.41199020.30.2395213.20767930.40.1937422.59461140.50.1828052.44814150.60.1715102.29686660.70.1605012.14943770.80.1302591.74443580.90.1106031.48120691.00.0746711.000000Automaticmodelingfinished,timecosting：0second