務(wù)信用分析與評(píng)分卡建模全流程解析)
簡(jiǎn)介基于Python的財(cái)務(wù)信用分析完整項(xiàng)目包聚焦財(cái)務(wù)數(shù)據(jù)預(yù)處理、特征工程與信用評(píng)分建模適合金融數(shù)據(jù)分析學(xué)習(xí)者、科研人員及需要快速上手信用評(píng)估項(xiàng)目的開發(fā)者。壓縮包共二十三個(gè)文件總大小約二點(diǎn)二兆內(nèi)含七份Python源碼腳本、五份Excel數(shù)據(jù)表、四份CSV數(shù)據(jù)集、兩份MAT數(shù)據(jù)文件、論文PDF、答辯PPT及多份Markdown說明文檔從原始數(shù)據(jù)、核心算法到最終報(bào)告形成完整閉環(huán)。項(xiàng)目按附錄將代碼拆分為多個(gè)獨(dú)立模塊數(shù)據(jù)表覆蓋信用評(píng)級(jí)、突發(fā)制裁等多種財(cái)務(wù)業(yè)務(wù)場(chǎng)景便于讀者按需調(diào)用與對(duì)照學(xué)習(xí)。目前該資源已有181人學(xué)習(xí)。借助論文草稿和答辯材料讀者既能深入理解信用評(píng)分的研究思路又能直接運(yùn)行源碼復(fù)現(xiàn)數(shù)據(jù)清洗、特征構(gòu)造、模型評(píng)估與結(jié)果可視化的完整過程還可參考Excel與MAT中間結(jié)果進(jìn)行驗(yàn)證是一份適合系統(tǒng)學(xué)習(xí)財(cái)務(wù)信用分析的緊湊型參考資源。1. 基于Python的財(cái)務(wù)信用分析從zip到建模先搞清這個(gè)項(xiàng)目在解決什么問題拿到一個(gè)命名為《基于python的財(cái)務(wù)信用分析內(nèi)含數(shù)據(jù)集和論文.zip》的壓縮包時(shí)大部分人的第一反應(yīng)是解壓、翻數(shù)據(jù)集、跑通代碼。但如果只停留在這一步你得到的只是一堆 CSV 和一段能出圖的腳本——而不是一套能寫進(jìn)簡(jiǎn)歷、能應(yīng)對(duì)業(yè)務(wù)提問的財(cái)務(wù)信用分析方案。這個(gè)標(biāo)題背后其實(shí)是一個(gè)完整的技術(shù)鏈路從 zip 中提取結(jié)構(gòu)化財(cái)務(wù)數(shù)據(jù)基于 Python 做特征工程、構(gòu)建信用評(píng)分模型最后用論文格式把結(jié)論沉淀下來。它適合的人群很具體正在做畢業(yè)設(shè)計(jì)的學(xué)生、剛轉(zhuǎn)行風(fēng)控的數(shù)據(jù)分析師、以及需要在內(nèi)部做信用評(píng)估原型驗(yàn)證的工程師。我準(zhǔn)備用一套常見且可靠的落地路徑來拆解這個(gè)標(biāo)題——不依賴任何特定項(xiàng)目源碼而是把你手頭任何類似的財(cái)務(wù)數(shù)據(jù)包都能做出同樣完整的結(jié)果。你會(huì)發(fā)現(xiàn)真正的技術(shù)難點(diǎn)不在模型本身而在數(shù)據(jù)集的質(zhì)量診斷、特征語義的對(duì)齊、評(píng)分卡閾值的可解釋性以及如何把結(jié)果完整交付。下面這套流程我會(huì)從解壓與數(shù)據(jù)結(jié)構(gòu)檢查開始一路做到論文復(fù)現(xiàn)和最終 zip 工件的校驗(yàn)。2. 數(shù)據(jù)集與論文的預(yù)處理解壓 zip、裝載數(shù)據(jù)和字段質(zhì)量檢查2.1 解壓 zip 壓縮包并檢查內(nèi)部目錄結(jié)構(gòu)標(biāo)題里的 zip 是一個(gè)信號(hào)這份數(shù)據(jù)集中大概率既有結(jié)構(gòu)化文件CSV、Excel也有非結(jié)構(gòu)化內(nèi)容PDF 論文、說明文檔。動(dòng)手建模前必須把 zip 當(dāng)做一個(gè)待勘察的目錄樹來處理而不是順手雙擊解壓就往下走。import zipfile from pathlib import Path zf_path Path(財(cái)務(wù)信用分析.zip) with zipfile.ZipFile(zf_path, r) as zf: for info in zf.infolist(): print(f{info.filename} {info.file_size} bytes)這段代碼把 zip 內(nèi)部的文件清單完整打印出來。infolist()返回每個(gè)條目的元信息包括文件名、壓縮前后大小file_size能大致看出哪些是主體數(shù)據(jù)文件。我一般會(huì)先跑這一步而不是直接解壓原因有兩個(gè)一是判斷是否有明顯的重復(fù)文件或臨時(shí)文件殘留二是確認(rèn)是否存在加密條目——如果flag_bits的加密位被置位普通解壓會(huì)失敗需要單獨(dú)處理。注意zip 加密不等于安全加密。業(yè)務(wù)場(chǎng)景中如果拿到加密壓縮包且無從得知密碼正規(guī)做法是聯(lián)系文件提供方獲取授權(quán)而不是使用暴力破解工具。確認(rèn)結(jié)構(gòu)后再?zèng)Q定解壓到工作目錄還是流式讀取。常見做法是解壓到一個(gè)data/raw/目錄下保持原始文件不被改動(dòng)后續(xù)所有派生數(shù)據(jù)都寫入data/processed/。這一步的目錄規(guī)劃直接決定論文復(fù)現(xiàn)時(shí)別人能否順利跑通建議在一開始就嚴(yán)格區(qū)分原始層、中間層和輸出層。2.2 用 pandas 裝載財(cái)務(wù)數(shù)據(jù)并做缺失值診斷解壓之后要先回答一個(gè)問題這份財(cái)務(wù)數(shù)據(jù)集是什么粒度常見的有三種。第一種是企業(yè)財(cái)務(wù)比率表每行是一家公司在某年的流動(dòng)比率、資產(chǎn)負(fù)債率、凈資產(chǎn)收益率等指標(biāo)第二種是財(cái)務(wù)報(bào)表明細(xì)包含利潤(rùn)表、資產(chǎn)負(fù)債表、現(xiàn)金流量表的科目級(jí)數(shù)據(jù)第三種是帶標(biāo)簽的樣本最后一列是“是否違約”或者信用評(píng)級(jí)等級(jí)。粒度不同建模策略完全不同。import pandas as pd df pd.read_csv(data/raw/financial_data.csv, encodingutf-8-sig) print(df.shape) print(df.dtypes) print(df.isnull().sum().sort_values(ascendingFalse).head(20))utf-8-sig是處理中文財(cái)務(wù)數(shù)據(jù)的常見編碼選擇它能自動(dòng)去除 Excel 導(dǎo)出 CSV 時(shí)可能殘留的 BOM 頭。shape先確認(rèn)行列規(guī)模dtypes檢查是否有列被錯(cuò)誤解析成 object——這在財(cái)務(wù)數(shù)據(jù)里太常見了比如數(shù)值列里混入了逗號(hào)千分位、百分比符號(hào)或者“-”表示缺失值。缺失值診斷要分列來看不能直接丟給模型。財(cái)務(wù)數(shù)據(jù)缺失通常有三種語義一是該科目不適用比如金融企業(yè)沒有存貨二是數(shù)據(jù)未披露三是字段本身有默認(rèn)值 0。我一般會(huì)用一個(gè)小表來輔助判斷缺失模式可能語義處理方式缺失率 40% 且與目標(biāo)變量無關(guān)采集不全刪除列缺失集中在某幾個(gè)行業(yè)科目不適用按行業(yè)填充或置 0缺失與違約標(biāo)簽相關(guān)非隨機(jī)缺失單獨(dú)編碼為“缺失”類別這段判斷邏輯寫不進(jìn)自動(dòng)腳本但可以在論文的方法章節(jié)里以數(shù)據(jù)預(yù)處理說明的形式呈現(xiàn)——這也是標(biāo)題里“論文”二字的實(shí)際價(jià)值它要求你解釋選擇而不僅僅是執(zhí)行代碼。2.3 論文附件中的評(píng)分卡字段與數(shù)據(jù)集字段對(duì)齊數(shù)據(jù)集里通常沒有附字段字典但論文里往往有一張描述性統(tǒng)計(jì)表或變量定義表。字段對(duì)齊是整個(gè)流程中最容易被跳過、卻最能體現(xiàn)專業(yè)度的一步。fields_in_paper [ROA, DebtRatio, CurrentRatio, Default] fields_in_data [roa_2023, debt_to_asset, cur_ratio, is_default] mapping dict(zip(fields_in_paper, fields_in_data)) df_renamed df.rename(columnsmapping) print(df_renamed.columns.tolist())這段重命名代碼的背后有一個(gè)檢查項(xiàng)對(duì)齊后必須核對(duì)數(shù)值范圍。比如財(cái)務(wù)領(lǐng)域常用的資產(chǎn)負(fù)債率DebtRatio一般在 0 到 1 之間如果數(shù)據(jù)里出現(xiàn) 2.5那要么是單位用了百分比要么是公式定義不同。我一般會(huì)為每個(gè)關(guān)鍵字段寫一個(gè)簡(jiǎn)單斷言assert df_renamed[DebtRatio].between(0, 1).all(), DebtRatio 超出合理范圍如果斷言失敗優(yōu)先回論文里查變量的原始定義——有些論文用的是“總負(fù)債/總資產(chǎn)”有些則是“總負(fù)債/所有者權(quán)益”差異巨大。這種細(xì)節(jié)修復(fù)進(jìn)不了模型復(fù)雜度但直接決定之后評(píng)分卡的可解釋性和業(yè)務(wù)認(rèn)可度。3. 基于Python構(gòu)建財(cái)務(wù)信用分析的特征工程與評(píng)分卡初版3.1 財(cái)務(wù)比率特征的構(gòu)造與篩選財(cái)務(wù)信用分析里原始報(bào)表科目很少直接入模常見做法是構(gòu)造成比率特征。原因很簡(jiǎn)單不同規(guī)模的公司絕對(duì)值不可比一家資產(chǎn) 100 億的公司利潤(rùn) 1 億和資產(chǎn) 1 億的公司利潤(rùn) 1 億信用含義完全不同但凈資產(chǎn)收益率ROE都可以算到同一尺度上。df[ROE] df[NetProfit] / df[Equity] df[LiquidityRatio] (df[Cash] df[MarketableSecurities]) / df[CurrentLiabilities] df[DebtServiceCoverage] df[EBIT] / (df[InterestExpense] 1e-6)這三個(gè)特征分別覆蓋盈利、短期償債、長(zhǎng)期償債三個(gè)維度。1e-6是為了防止除零但要注意加一個(gè)小常數(shù)會(huì)讓特征分布出現(xiàn)一個(gè)極小值集群分箱時(shí)會(huì)形成單獨(dú)箱體。如果樣本量夠大我一般更傾向把利息費(fèi)用為 0 的樣本單獨(dú)編碼成 -999讓后續(xù)分箱自動(dòng)處理缺失語義。特征篩選用 IVInformation Value是最貼合信用評(píng)分場(chǎng)景的方法。IV 值高于 0.1 的變量通常認(rèn)為有預(yù)測(cè)力低于 0.02 的變量建議剔除。計(jì)算 IV 前必須先分箱分箱方法可以用等頻、卡方分箱或者決策樹分箱——卡方分箱在這個(gè)場(chǎng)景里效果最穩(wěn)定因?yàn)樗紤]了對(duì)目標(biāo)變量的區(qū)分能力。3.2 用 sklearn 和 statsmodels 建立初始邏輯回歸信用評(píng)分模型中邏輯回歸是默認(rèn)基線模型原因不完全是精度而是可解釋性和監(jiān)管認(rèn)可度。無論是巴塞爾協(xié)議框架還是國(guó)內(nèi)的信貸風(fēng)控實(shí)踐邏輯回歸輸出的概率值可以通過系數(shù)直接轉(zhuǎn)化為分?jǐn)?shù)這是樹模型做不到的。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression feature_cols [ROE, DebtRatio, CurrentRatio, DebtServiceCoverage] X df_renamed[feature_cols].fillna(df_renamed[feature_cols].median()) y df_renamed[Default] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) scaler StandardScaler() X_train_s scaler.fit_transform(X_train) X_test_s scaler.transform(X_test) model LogisticRegression(C1.0, solverlbfgs, max_iter1000) model.fit(X_train_s, y_train)注意stratifyy這個(gè)參數(shù)在違約率較低的財(cái)務(wù)數(shù)據(jù)集中是必須的——如果不分層抽樣訓(xùn)練集里可能一個(gè)違約樣本都沒有模型學(xué)到的永遠(yuǎn)是“全部不違約”。StandardScaler讓系數(shù)可比較同時(shí)便于將系數(shù)應(yīng)用到評(píng)分卡公式中。C1.0是默認(rèn)正則化強(qiáng)度這里先不做調(diào)參等基線指標(biāo)出來之后再統(tǒng)一優(yōu)化。3.3 WOE 編碼與 IV 值計(jì)算的 Python 實(shí)現(xiàn)邏輯回歸直接吃連續(xù)特征也能跑但在財(cái)務(wù)信用分析的標(biāo)準(zhǔn)流程中WOEWeight of Evidence編碼是更規(guī)范的做法。WOE 編碼把連續(xù)變量分箱后的每一箱映射為一個(gè)對(duì)數(shù)值衡量該箱內(nèi)好客戶與壞客戶的分布差異天然處理了非線性關(guān)系。import numpy as np def calc_woe_iv(df_feature, y, bins10): df_temp pd.DataFrame({feature: df_feature, target: y}) df_temp[bin] pd.qcut(df_temp[feature], qbins, duplicatesdrop) grouped df_temp.groupby(bin, observedTrue)[target].agg([sum, count]) grouped.columns [bad, total] grouped[good] grouped[total] - grouped[bad] total_bad grouped[bad].sum() total_good grouped[good].sum() grouped[bad_dist] grouped[bad] / total_bad grouped[good_dist] grouped[good] / total_good grouped[woe] np.log((grouped[good_dist] 1e-6) / (grouped[bad_dist] 1e-6)) grouped[iv] (grouped[good_dist] - grouped[bad_dist]) * grouped[woe] iv_total grouped[iv].sum() return grouped, iv_total這段函數(shù)里最關(guān)鍵的是duplicatesdrop它處理分位數(shù)重復(fù)的問題——當(dāng)大量樣本的 feature 值相同時(shí)pd.qcut會(huì)報(bào)錯(cuò)或者生成空箱。WOE 的計(jì)算窗口里加1e-6同樣是防除零但要注意如果某個(gè)箱中 bad 為 0WOE 會(huì)是一個(gè)很大的正數(shù)這在實(shí)際數(shù)據(jù)里往往意味著過擬合信號(hào)后續(xù)應(yīng)該手工檢查和合并這類箱體。IV 值計(jì)算完成后可以按 IV 排序篩選特征一般選中 IV 在 0.02 到 0.5 之間的變量入模。超過 0.5 的變量要警惕可能泄漏了未來信息比如直接用“是否已逾期”預(yù)測(cè)“是否違約”。4. 信用評(píng)分卡參數(shù)調(diào)優(yōu)與數(shù)據(jù)集驗(yàn)證策略4.1 數(shù)據(jù)集劃分方式與交叉驗(yàn)證參數(shù)設(shè)置財(cái)務(wù)數(shù)據(jù)集普遍樣本量不大——很多公開數(shù)據(jù)集只有幾千條記錄違約樣本更是稀缺。此時(shí)如果還按 7:3 的方式劃分訓(xùn)練集和測(cè)試集測(cè)試集里可能只有幾十個(gè)違約樣本導(dǎo)致評(píng)估指標(biāo)波動(dòng)劇烈。我一般會(huì)用分層 K 折交叉驗(yàn)證來替代單次劃分常見設(shè)置為 5 折。對(duì)于邏輯回歸這種低方差模型5 折是偏差和方差之間的合理折中——折數(shù)太多會(huì)讓每折訓(xùn)練集過小折數(shù)太少則評(píng)估不穩(wěn)定。from sklearn.model_selection import StratifiedKFold, cross_val_score cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores cross_val_score(model, X_train_s, y_train, cvcv, scoringroc_auc) print(fAUC: {scores.mean():.4f} ± {scores.std():.4f})shuffleTrue結(jié)合random_state42保證每次運(yùn)行結(jié)果一致這在論文復(fù)現(xiàn)中是硬性要求——任何隨機(jī)過程都必須設(shè)定種子值并在論文附錄中說明。我通常會(huì)記錄每一次跑動(dòng)的 AUC、KS、準(zhǔn)確率做成一個(gè) CSV 文件存檔這樣后續(xù)寫論文時(shí)可以直接引用這些數(shù)字不用重新跑。4.2 評(píng)分卡刻度參數(shù)與閾值選擇信用評(píng)分卡有一個(gè)標(biāo)準(zhǔn)映射公式將模型輸出的概率轉(zhuǎn)換為整數(shù)分?jǐn)?shù)。常見刻度設(shè)置為評(píng)分每降低 20 分違約幾率翻一倍基準(zhǔn)分 600 分對(duì)應(yīng)違約幾率 1:1。odds_base 1.0 score_base 600 pdo 20 factor pdo / np.log(2) offset score_base - factor * np.log(odds_base) def prob_to_score(prob): odds prob / (1 - prob) return offset - factor * np.log(odds) df_renamed[Score] prob_to_score(model.predict_proba(X_scaled)[:, 1])這個(gè)公式的邏輯是邏輯回歸的線性輸出log(odds)與分?jǐn)?shù)之間建立線性關(guān)系pdopoints to double the odds決定刻度伸縮。業(yè)務(wù)上一般會(huì)找業(yè)務(wù)方確認(rèn)兩個(gè)錨點(diǎn)多少分是最低放貸線多少分是優(yōu)質(zhì)客戶分界線。這兩個(gè)參數(shù)不需要機(jī)器學(xué)習(xí)知識(shí)但直接影響風(fēng)控策略。閾值選擇的常見做法是畫 KS 曲線和 ROC 曲線取 KS 最大的位置作為參考閾值。但要注意KS 最大點(diǎn)只代表區(qū)分度最大不代表業(yè)務(wù)上最優(yōu)。如果財(cái)務(wù)數(shù)據(jù)集中違約率是 5%放貸利率是年化 12%那最優(yōu)閾值應(yīng)該在收益覆蓋損失的邊界上這需要用業(yè)務(wù)成本矩陣來算不是純統(tǒng)計(jì)指標(biāo)能決定的。4.3 與論文基線結(jié)果對(duì)比時(shí)的復(fù)現(xiàn)檢查項(xiàng)標(biāo)題里的論文通常是這個(gè)數(shù)據(jù)集的源頭里面大概率有基線模型的 AUC 或準(zhǔn)確率數(shù)字。復(fù)現(xiàn)論文結(jié)果時(shí)最容易踩的坑有三個(gè)我列成檢查清單第一特征定義不一致。論文可能用了 T 年報(bào)表預(yù)測(cè) T1 年違約數(shù)據(jù)集中則是同一時(shí)期的橫截面數(shù)據(jù)——這會(huì)導(dǎo)致論文 AUC 偏高而你復(fù)現(xiàn)偏低因?yàn)槟愕奶卣骼镆呀?jīng)包含了一部分結(jié)果信息。第二樣本篩選條件不同。論文可能排除了金融行業(yè)或者 ST 公司你沒有。第三缺失值處理方式不同。論文可能用的是均值填充你也可以用均值填充但填充之前是否剔除了異常值結(jié)果差異會(huì)很大。提示和論文對(duì)比時(shí)先確認(rèn)目標(biāo)變量定義。是否違約的標(biāo)準(zhǔn)可能是“逾期 90 天以上”也可能是“被法院列入失信名單”兩者在數(shù)據(jù)集中如果共存必須明確選哪一列并在復(fù)現(xiàn)說明中寫清楚。驗(yàn)證方法上除了直接對(duì)比 AUC還可以對(duì)比訓(xùn)練集和測(cè)試集的 KS 差異。如果訓(xùn)練集 KS 為 0.45、測(cè)試集 KS 只有 0.2大概率是過擬合或者特征泄漏——這時(shí)候要回到特征工程檢查而不是調(diào)模型參數(shù)。5. 用 Python 將模型結(jié)果、特征分析和論文整理成可交付的 zip 工件5.1 用 zipfile 模塊打包最終交付物財(cái)務(wù)信用分析項(xiàng)目的終態(tài)不是一個(gè) jupyter notebook而是一個(gè)結(jié)構(gòu)清晰、能在新環(huán)境重新運(yùn)行的交付包。我常用的目錄結(jié)構(gòu)有三種固定層級(jí)和兩個(gè)必要說明文件——README 和環(huán)境依賴清單。from pathlib import Path import zipfile output_dir Path(deliverables) subdirs [data/processed, model, reports, notebooks] for d in subdirs: (output_dir / d).mkdir(parentsTrue, exist_okTrue) # 將模型參數(shù)保存為可讀格式 import json with open(output_dir / model / params.json, w, encodingutf-8) as f: json.dump({ factor: factor, offset: offset, features: feature_cols, auc_train: float(scores.mean()) }, f, ensure_asciiFalse, indent2)用 json 保存模型參數(shù)比用 pickle 保存模型對(duì)象更穩(wěn)妥原因在于跨版本兼容性——Python 3.10 和 3.12 之間 pickle 協(xié)議可能不兼容而 json 永遠(yuǎn)可讀。回歸模型本來就只需要存系數(shù)、截距、刻度和特征名關(guān)鍵詞是“可復(fù)現(xiàn)”。真正的模型對(duì)象如果體積大就用save_dataframe存 WOE 表比存 pkl 文件更直觀。打包階段用zipfile寫一個(gè)循環(huán)即可但要注意寫入模式with zipfile.ZipFile(財(cái)務(wù)信用分析_結(jié)果.zip, w, compressionzipfile.ZIP_DEFLATED) as zf: for file_path in output_dir.rglob(*): if file_path.is_file(): zf.write(file_path, arcnamefile_path.relative_to(output_dir))ZIP_DEFLATED是 Python 默認(rèn)支持的壓縮算法壓縮率高且不依賴外部庫(kù)。arcname參數(shù)保證 zip 內(nèi)部路徑以 deliverables 為根解壓后不會(huì)級(jí)聯(lián)出一大堆嵌套目錄。這里不要用ZIP_BZIP2或ZIP_LZMA因?yàn)榻邮辗饺绻玫氖抢习姹?Python 或系統(tǒng)自帶 unzip可能無法正常解壓。5.2 校驗(yàn) zip 完整性與最終自檢清單最后一步是校驗(yàn)不是生成完 zip 就結(jié)束。我用兩個(gè)層面的校驗(yàn)文件完整性校驗(yàn)和內(nèi)容可讀性校驗(yàn)。with zipfile.ZipFile(財(cái)務(wù)信用分析_結(jié)果.zip, r) as zf: bad_file zf.testzip() if bad_file: print(f損壞文件: {bad_file}) else: print(zip 完整性校驗(yàn)通過)testzip()會(huì)逐個(gè)讀取壓縮包內(nèi)文件的 CRC 校驗(yàn)值任何損壞都能定位到具體文件。這一步在網(wǎng)盤傳輸、郵件附件等場(chǎng)景尤其重要zip 在傳輸過程中可能被截?cái)嗷蚋淖止?jié)——少一個(gè)模型參數(shù)文件對(duì)方跑出來的結(jié)果就是另一個(gè)模型。內(nèi)容可讀性校驗(yàn)則包括三件事新環(huán)境能否pip install -r requirements.txt一次成功README 里記錄的python main.py或jupyter nbconvert --execute能不能直接跑通以及最關(guān)鍵的一步——?jiǎng)h除原始 zip只依賴你生成的交付包能否從零復(fù)現(xiàn)全部結(jié)果。這個(gè)自測(cè)能暴露所有“我本地能跑別人跑不了”的問題。本文還有配套的精品資源點(diǎn)擊獲取