towardsinnovationlab commited on
Commit
1794ce8
·
verified ·
1 Parent(s): e189100

Upload 3 files

Browse files
Files changed (3) hide show
  1. pages/CDM_Trial_1.py +953 -0
  2. pages/CDM_Trial_2.py +953 -0
  3. pages/CDM_Trial_3.py +957 -0
pages/CDM_Trial_1.py ADDED
@@ -0,0 +1,953 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import random
4
+ import numpy as np
5
+ import pandas as pd
6
+ import matplotlib.pyplot as plt
7
+ import matplotlib.image as mpimg
8
+ import seaborn as sns
9
+ from matplotlib.pyplot import subplots
10
+ from sklearn.model_selection import train_test_split
11
+ from sklearn.model_selection import KFold
12
+ from sklearn.metrics import mean_poisson_deviance, mean_gamma_deviance, make_scorer
13
+ from scipy.stats import ks_2samp
14
+ from sklearn.decomposition import PCA
15
+ from sklearn.preprocessing import StandardScaler
16
+ from mpl_toolkits.mplot3d import Axes3D
17
+ from sklearn.linear_model import TweedieRegressor
18
+ import shap
19
+ from sklearn.mixture import GaussianMixture
20
+ from joblib import dump
21
+ from joblib import load
22
+ import streamlit as st
23
+
24
+ import warnings
25
+ warnings.filterwarnings('ignore')
26
+
27
+
28
+ DEFAULT_RANDOM_SEED = 0
29
+ random.seed(DEFAULT_RANDOM_SEED)
30
+ os.environ['PYTHONHASHSEED'] = str(DEFAULT_RANDOM_SEED)
31
+ np.random.seed(DEFAULT_RANDOM_SEED)
32
+
33
+ st.title("Conditional Diffusion Model: Synthetic Data Generation Analysis")
34
+
35
+
36
+ def compare_real_vs_synthetic(real_df, synthetic_df, columns=None, kind='hist', bins=30, figsize=(15, 10)):
37
+ if columns is None:
38
+ columns = [col for col in real_df.columns if real_df[col].dtype != 'object']
39
+
40
+ n_cols = 2
41
+ n_rows = (len(columns) + 1) // n_cols
42
+
43
+ fig= plt.figure(figsize=figsize)
44
+
45
+ for idx, col in enumerate(columns, 1):
46
+ plt.subplot(n_rows, n_cols, idx)
47
+
48
+ if kind == 'hist':
49
+ sns.histplot(real_df[col], color='blue', label='Real', kde=False, stat='density', bins=bins, alpha=0.6)
50
+ sns.histplot(synthetic_df[col], color='red', label='Synthetic', kde=False, stat='density', bins=bins, alpha=0.6)
51
+
52
+ elif kind == 'kde':
53
+ sns.kdeplot(real_df[col], color='blue', label='Real')
54
+ sns.kdeplot(synthetic_df[col], color='red', label='Synthetic')
55
+
56
+ elif kind == 'box':
57
+ sns.boxplot(data=[real_df[col], synthetic_df[col]], palette=['blue', 'red'])
58
+ plt.xticks([0, 1], ['Real', 'Synthetic'])
59
+
60
+ else:
61
+ raise ValueError("Unsupported plot kind. Choose from 'hist', 'kde', or 'box'.")
62
+
63
+ plt.title(f"Comparison for '{col}'")
64
+ plt.legend()
65
+
66
+ plt.tight_layout()
67
+ st.pyplot(fig)
68
+
69
+
70
+ def run_glm_frequency_analysis(
71
+ X_train, X_test, model=None, clip_exposure=False, random_state=0, label="Model", var=None):
72
+ np.random.seed(0)
73
+
74
+ if clip_exposure:
75
+ X_train = X_train.copy()
76
+ X_train['Exposure'] = np.where(X_train['Exposure'] > 1, 1, X_train['Exposure'])
77
+
78
+ mask_tr = X_train['Exposure'] > 0
79
+ mask_te = X_test['Exposure'] > 0
80
+ X_train_f = X_train[mask_tr].copy()
81
+ X_test_f = X_test[mask_te].copy()
82
+
83
+ y_train = X_train_f['ClaimNb']
84
+ y_test = X_test_f['ClaimNb']
85
+ exposure_train = X_train_f['Exposure']
86
+ exposure_test = X_test_f['Exposure']
87
+
88
+ X_train_ = X_train_f.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
89
+ X_test_ = X_test_f.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
90
+
91
+ if model is None:
92
+ model = TweedieRegressor(power=1, link='log')
93
+
94
+ cv = KFold(n_splits=5)
95
+ mpd_scores = []
96
+
97
+ for fold_idx, (train_idx, val_idx) in enumerate(cv.split(X_train_)):
98
+ X_tr, X_val = X_train_.iloc[train_idx], X_train_.iloc[val_idx]
99
+ y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]
100
+ w_tr, w_val = exposure_train.iloc[train_idx], exposure_train.iloc[val_idx]
101
+
102
+ model.fit(X_tr, y_tr / w_tr, sample_weight=w_tr)
103
+ y_pred = model.predict(X_val)
104
+
105
+ score = mean_poisson_deviance(y_val / w_val, y_pred)
106
+ mpd_scores.append(score)
107
+
108
+ model.fit(X_train_, y_train / exposure_train, sample_weight=exposure_train)
109
+
110
+ pred_train = model.predict(X_train_)
111
+ pred_test = model.predict(X_test_)
112
+
113
+ mpd_train = mean_poisson_deviance(y_train / exposure_train, pred_train)
114
+ mpd_test = mean_poisson_deviance(y_test / exposure_test, pred_test)
115
+
116
+ st.write(f"Train Poisson {var} Deviance: {mpd_train:.4f}")
117
+ st.write(f"Test Poisson {var} Deviance: {mpd_test:.4f}")
118
+
119
+ return model, {
120
+ "cv_scores": mpd_scores,
121
+ "mpd_train": mpd_train,
122
+ "mpd_test": mpd_test,
123
+ "train_predictions": pred_train,
124
+ "test_predictions": pred_test
125
+ }
126
+
127
+
128
+ def run_glm_cost_analysis(X_train, X_test, is_sampled=False, verbose=True, var=None):
129
+ np.random.seed(0)
130
+
131
+ if is_sampled:
132
+ X_train = X_train.copy()
133
+ X_train['Exposure'] = np.where(X_train['Exposure'] > 1, 1, X_train['Exposure'])
134
+
135
+ X_train_co = X_train.copy()
136
+ X_test_co = X_test.copy()
137
+
138
+ X_train_co['Acost'] = np.where(X_train_co['ClaimNb'] != 0,
139
+ X_train_co['ClaimAmount'] / X_train_co['ClaimNb'], 0)
140
+ X_test_co['Acost'] = np.where(X_test_co['ClaimNb'] != 0,
141
+ X_test_co['ClaimAmount'] / X_test_co['ClaimNb'], 0)
142
+
143
+ X_train_cost = X_train_co[X_train_co['ClaimAmount'] != 0].copy()
144
+ X_test_cost = X_test_co[X_test_co['ClaimAmount'] != 0].copy()
145
+
146
+ y_train = X_train_cost['Acost']
147
+ claim_tr = X_train_cost['ClaimNb']
148
+ y_test = X_test_cost['Acost']
149
+ claim_te = X_test_cost['ClaimNb']
150
+
151
+ drop_cols = ['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb']
152
+ X_train_ = X_train_cost.drop(columns=drop_cols)
153
+ X_test_ = X_test_cost.drop(columns=drop_cols)
154
+
155
+ glm_cl = TweedieRegressor(power=2, link='log')
156
+
157
+ cv = KFold(n_splits=5, shuffle=True, random_state=0)
158
+ mgd_scores = []
159
+
160
+ for fold_idx, (train_idx, val_idx) in enumerate(cv.split(X_train_)):
161
+ X_tr, X_val = X_train_.iloc[train_idx], X_train_.iloc[val_idx]
162
+ y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]
163
+ w_tr, w_val = claim_tr.iloc[train_idx], claim_tr.iloc[val_idx]
164
+
165
+ glm_cl.fit(X_tr, y_tr, sample_weight=w_tr)
166
+ y_pred_val = glm_cl.predict(X_val)
167
+ score = mean_gamma_deviance(y_val, y_pred_val)
168
+ mgd_scores.append(score)
169
+
170
+ glm_cl.fit(X_train_, y_train, sample_weight=claim_tr)
171
+
172
+ y_pred_train = glm_cl.predict(X_train_)
173
+ y_pred_test = glm_cl.predict(X_test_)
174
+
175
+ mgd_train = mean_gamma_deviance(y_train, y_pred_train)
176
+ mgd_test = mean_gamma_deviance(y_test, y_pred_test)
177
+
178
+ if verbose:
179
+ st.write(f"Train Gamma {var} Deviance: {mgd_train:.4f}")
180
+ st.write(f"Test Gamma {var} Deviance: {mgd_test:.4f}")
181
+
182
+ return {
183
+ "cv_scores": mgd_scores,
184
+ 'mgd_train': mgd_train,
185
+ 'mgd_test': mgd_test,
186
+ 'y_pred_train': y_pred_train,
187
+ 'y_pred_test': y_pred_test
188
+ }
189
+
190
+
191
+ def plot_glm_shap_importance(
192
+ X_train, X_test, y_train, sample_weight,
193
+ power: int, title: str, max_display: int = 10, figsize: tuple = (5, 5), seed: int = 0):
194
+
195
+ np.random.seed(seed)
196
+
197
+ model = TweedieRegressor(power=power, link='log')
198
+ model.fit(X_train, y_train, sample_weight=sample_weight)
199
+
200
+ masker = shap.maskers.Independent(X_train)
201
+ explainer = shap.LinearExplainer(model, masker=masker)
202
+ shap_values = explainer.shap_values(X_test)
203
+
204
+ plt.figure(figsize=figsize)
205
+ shap.summary_plot(
206
+ shap_values, features=X_test,
207
+ feature_names=X_test.columns,
208
+ plot_type='bar',
209
+ max_display=max_display,
210
+ show=False
211
+ )
212
+ plt.title(title, fontsize=12)
213
+ plt.tight_layout()
214
+ fig = plt.gcf()
215
+ st.pyplot(fig)
216
+
217
+
218
+ # ### Upload datasets
219
+
220
+ #-------------------
221
+ # DATASETS
222
+ #-------------------
223
+ df1=pd.read_csv('./data/ausprivauto0405.csv')
224
+ df2=pd.read_csv('./data/swmotorcycle.csv')
225
+ df1_synthetic=pd.read_csv('./CDM/d1_cdm_80_encod.csv')
226
+ df1_synthetic = df1_synthetic.drop(columns=["Unnamed: 0"])
227
+ df2_synthetic=pd.read_csv('./CDM/d2_cdm_80_encod.csv')
228
+ df2_synthetic = df2_synthetic.drop(columns=["Unnamed: 0"])
229
+
230
+
231
+
232
+ # ### dataset 1 and data handling
233
+
234
+ st.header('Dataset 1: ausprivauto0405')
235
+
236
+ df1_duplicated_rows=df1[df1.duplicated()]
237
+ df1=df1.drop_duplicates()
238
+ df1_duplicated_col=df1.columns[df1.columns.duplicated()]
239
+
240
+
241
+ # ### Encoding
242
+
243
+ df1_encod=df1.copy()
244
+ # VehAge
245
+ VehAge_group = {'old cars':'1','young cars':'2','oldest cars':'3','youngest cars':'4'}
246
+ df1_encod['VehAge'] = df1_encod['VehAge'].map(VehAge_group)
247
+ df1_encod['VehAge']= df1_encod['VehAge'].astype(int)
248
+ # DrivAge
249
+ DrivAge_group = {'young people':'1','older work. people':'2','oldest people':'3','working people':'4','old people':'5','youngest people':'6'}
250
+ df1_encod['DrivAge'] = df1_encod['DrivAge'].map(DrivAge_group)
251
+ df1_encod['DrivAge']= df1_encod['DrivAge'].astype(int)
252
+ # VehBody
253
+ VehBody_group = {'Hatchback':'1','Utility':'2','Station wagon':'3','Hardtop':'4','Panel van':'5','Sedan':'6','Truck':'7',\
254
+ 'Coupe':'8', 'Minibus':'9', 'Motorized caravan':'10', 'Bus':'11', 'Convertible':'12','Roadster':'13'}
255
+ df1_encod['VehBody'] = df1_encod['VehBody'].map(VehBody_group)
256
+ df1_encod['VehBody']= df1_encod['VehBody'].astype(int)
257
+ # Gender
258
+ Gender_group = {'Female':'0','Male':'1'}
259
+ df1_encod['Gender'] = df1_encod['Gender'].map(Gender_group)
260
+ df1_encod['Gender']= df1_encod['Gender'].astype(int)
261
+
262
+
263
+
264
+
265
+ # ### Split dataset
266
+ # Split the dataset into train/test split
267
+ X_train, X_test = train_test_split(df1_encod, test_size=0.2, random_state=0)
268
+ st.markdown(f"**Train shape:** {X_train.shape} \n**Test shape:** {X_test.shape}")
269
+
270
+
271
+ # ### Use Generate Samples Dataframe
272
+ new_samples_df=df1_synthetic.copy()
273
+
274
+ # Check consistency
275
+ st.subheader(f"Check consistency")
276
+ # Find inconsistencies
277
+ inconsistent_records = new_samples_df[
278
+ ~(((new_samples_df["ClaimNb"] == 0) & (new_samples_df["ClaimOcc"] == 0) & (new_samples_df["ClaimAmount"] == 0)) |
279
+ ((new_samples_df["ClaimNb"] > 0) & (new_samples_df["ClaimOcc"] > 0) & (new_samples_df["ClaimAmount"] > 0)))
280
+ ]
281
+
282
+ st.write(f"Number of inconsistent records on synthetic data: {len(inconsistent_records)}")
283
+ st.write(inconsistent_records.head())
284
+ st.write('Helps assess basic data fidelity by checking structural or logical violations.')
285
+
286
+
287
+ # ### Visual Comparison
288
+
289
+ # Compare selected variables using histograms
290
+ st.subheader(f"Univariate distribution comparison: real vs synthetic")
291
+ st.write('Shows how well each individual feature is mimicked by the synthetic data.')
292
+
293
+ compare_real_vs_synthetic(
294
+ real_df=X_train,
295
+ synthetic_df=df1_synthetic,
296
+ columns=['Exposure','VehBody','VehValue','ClaimOcc','ClaimNb', 'ClaimAmount', 'DrivAge', 'VehAge','Gender'],
297
+ kind='hist'
298
+ )
299
+
300
+
301
+ st.subheader(f"Correlation matrix comparison: real vs synthetic")
302
+ st.write('Evaluates preservation of feature-to-feature relationships.')
303
+
304
+ # Compute correlation matrices
305
+ corr_matrix_X_train = X_train.corr()
306
+ corr_matrix_new_samples = new_samples_df.corr()
307
+
308
+ fig=plt.figure(figsize=(30,15))
309
+
310
+ plt.subplot(1, 2, 1)
311
+ sns.heatmap(corr_matrix_X_train, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
312
+ plt.title('Correlation Heatmap of X_train', size=15)
313
+ plt.yticks(rotation=0,fontsize=15)
314
+ plt.xticks(rotation=90,fontsize=15)
315
+
316
+ plt.subplot(1, 2, 2)
317
+ sns.heatmap(corr_matrix_new_samples, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
318
+ plt.title('Correlation Heatmap of New Samples', size=15)
319
+ plt.yticks(rotation=0,fontsize=15)
320
+ plt.xticks(rotation=90,fontsize=15)
321
+ plt.tight_layout()
322
+ st.pyplot(fig)
323
+
324
+ # ### Statistical Analysis
325
+ # Kolmogorov-Smirnov test
326
+ st.subheader("Kolmogorov–Smirnov Test Results")
327
+ st.write('Quantifies the statistical distance between real and synthetic distributions.')
328
+
329
+ results = []
330
+
331
+ for column in X_train.columns:
332
+ original = X_train[column].values
333
+ generated = new_samples_df[column].values
334
+ statistic, p_value = ks_2samp(original, generated)
335
+
336
+ results.append({
337
+ "Feature": column,
338
+ "KS Statistic": statistic,
339
+ "P-value": p_value
340
+ })
341
+
342
+ results_df = pd.DataFrame(results)
343
+
344
+ def color_pval(val):
345
+ color = "red" if val < 0.05 else "green"
346
+ return f"color: {color};"
347
+
348
+ styled_df = results_df.style.applymap(color_pval, subset=["P-value"]) \
349
+ .format({"KS Statistic": "{:.4f}", "P-value": "{:.4f}"})
350
+
351
+ st.markdown("""
352
+ **Legend:**
353
+ - <span style='color:green;'>Green P-value</span>: distributions are **similar** (p ≥ 0.05)
354
+ - <span style='color:red;'>Red P-value</span>: distributions are **significantly different** (p < 0.05)
355
+ """, unsafe_allow_html=True)
356
+ st.dataframe(styled_df)
357
+
358
+
359
+ # ### PCA Analysis
360
+
361
+ st.subheader('PCA comparison')
362
+ st.write('Assesses similarity in global variance structure and major latent components.')
363
+ # Load the saved models
364
+ scaler = load('./CDM/scaler_pca_model_d1_cdm_80.pkl')
365
+ pca = load('./CDM/pca_model_d1_cdm_80.pkl')
366
+
367
+ def pca_visual_comparison_3d_with_saved_model(real_df, synthetic_df, scaler, pca, title_suffix=""):
368
+ assert set(real_df.columns) == set(synthetic_df.columns), "Datasets must have the same columns."
369
+
370
+ combined_df = pd.concat([real_df, synthetic_df])
371
+
372
+ combined_scaled = scaler.transform(combined_df)
373
+
374
+ pca_result = pca.transform(combined_scaled)
375
+
376
+ n_real = len(real_df)
377
+ real_pca = pca_result[:n_real]
378
+ synth_pca = pca_result[n_real:]
379
+
380
+ fig = plt.figure(figsize=(10, 8))
381
+ ax = fig.add_subplot(111, projection='3d')
382
+
383
+ ax.scatter(real_pca[:, 0], real_pca[:, 1], real_pca[:, 2],
384
+ c='blue', label='Real', s=40, alpha=0.6, edgecolor='k')
385
+
386
+ ax.scatter(synth_pca[:, 0], synth_pca[:, 1], synth_pca[:, 2],
387
+ c='red', label='Synthetic', s=40, alpha=0.6, edgecolor='k')
388
+
389
+ ax.set_title(f"3D PCA: Real vs. Synthetic {title_suffix}", fontsize=12, weight='bold')
390
+ ax.set_xlabel("PC1")
391
+ ax.set_ylabel("PC2")
392
+ ax.set_zlabel("PC3")
393
+
394
+ ax.grid(False)
395
+ ax.legend()
396
+ plt.tight_layout()
397
+ st.pyplot(fig)
398
+
399
+
400
+ pca_visual_comparison_3d_with_saved_model(X_train, df1_synthetic, scaler, pca)
401
+
402
+
403
+ # ### UMAP Analysis
404
+
405
+ st.subheader('UMAP comparison')
406
+ st.write('Examines nonlinear manifold structure and clustering behavior.')
407
+ img = mpimg.imread('./CDM/umap_d1_80.png')
408
+ fig=plt.figure(figsize=(10, 8))
409
+ plt.imshow(img)
410
+ plt.axis('off')
411
+ st.pyplot(fig)
412
+
413
+
414
+ # ### GLM Frequency Analysis
415
+ st.subheader('Frequency GLM Analysis')
416
+ st.write('Tests how well synthetic data preserves predictive relationships for claim frequency.')
417
+ results_frequency_1 = run_glm_frequency_analysis(X_train, X_test, label="Baseline", var='Real')
418
+ results_frequency_2 = run_glm_frequency_analysis(new_samples_df, X_test, clip_exposure=True, label="Synthetic Clipped",var= 'Synthetic')
419
+
420
+
421
+ # ### GLM Cost Analysis
422
+ st.subheader('Severity GLM Analysis')
423
+ st.write('Evaluates whether severity-related predictors behave similarly on real and synthetic data.')
424
+ results_cost_1 = run_glm_cost_analysis(X_train, X_test,var='Real')
425
+ results_cost_2 = run_glm_cost_analysis(new_samples_df, X_test, is_sampled=True,var='Synthetic')
426
+
427
+
428
+ # ### Feature Importance Analysis
429
+ # --- SHAP Feature Importance for Frequency ---
430
+ st.subheader('SHAP Feature Importance for Frequency Model')
431
+ st.write('Shows whether drivers of frequency predictions remain consistent across datasets.')
432
+ X_train_freq = X_train.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
433
+ y_train_freq = X_train['ClaimNb']
434
+ sample_weight_freq = X_train['Exposure']
435
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
436
+ mask_train_freq = sample_weight_freq > 0
437
+ X_train_freq_filtered = X_train_freq[mask_train_freq]
438
+ y_train_freq_filtered = y_train_freq[mask_train_freq]
439
+ sample_weight_freq_filtered = sample_weight_freq[mask_train_freq]
440
+ mask_test_freq = X_test['Exposure'] > 0
441
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
442
+
443
+ plot_glm_shap_importance(
444
+ X_train=X_train_freq_filtered,
445
+ X_test=X_test_freq_filtered,
446
+ y_train=y_train_freq_filtered / sample_weight_freq_filtered,
447
+ sample_weight=sample_weight_freq_filtered,
448
+ power=1,
449
+ title="SHAP Feature Importance for Frequency Model (Real Data)",
450
+ max_display=10
451
+ )
452
+
453
+ # --- SHAP Feature Importance for Frequency (Synthetic Data) ---
454
+ X_train_freq_synth = new_samples_df.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
455
+ y_train_freq_synth = new_samples_df['ClaimNb']
456
+ sample_weight_freq_synth = new_samples_df['Exposure']
457
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
458
+ mask_train_freq_synth = sample_weight_freq_synth > 0
459
+ X_train_freq_synth_filtered = X_train_freq_synth[mask_train_freq_synth]
460
+ y_train_freq_synth_filtered = y_train_freq_synth[mask_train_freq_synth]
461
+ sample_weight_freq_synth_filtered = sample_weight_freq_synth[mask_train_freq_synth]
462
+ mask_test_freq = X_test['Exposure'] > 0
463
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
464
+
465
+ plot_glm_shap_importance(
466
+ X_train=X_train_freq_synth_filtered,
467
+ X_test=X_test_freq_filtered,
468
+ y_train=y_train_freq_synth_filtered / sample_weight_freq_synth_filtered,
469
+ sample_weight=sample_weight_freq_synth_filtered,
470
+ power=1,
471
+ title="SHAP Feature Importance for Frequency Model (Synthetic Data)",
472
+ max_display=10
473
+ )
474
+
475
+ # --- SHAP Feature Importance for Severity ---
476
+ st.subheader('SHAP Feature Importance for Severity Model')
477
+ st.write('Assesses stability of model explanations for severity outcomes.')
478
+ X_train_cost_prep = X_train[X_train['ClaimAmount'] != 0].copy()
479
+ X_test_cost_prep = X_test[X_test['ClaimAmount'] != 0].copy()
480
+
481
+ X_train_sev = X_train_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
482
+ y_train_sev = X_train_cost_prep['ClaimAmount'] / X_train_cost_prep['ClaimNb']
483
+ sample_weight_sev = X_train_cost_prep['ClaimNb']
484
+ X_test_sev = X_test_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
485
+
486
+ plot_glm_shap_importance(
487
+ X_train=X_train_sev,
488
+ X_test=X_test_sev,
489
+ y_train=y_train_sev,
490
+ sample_weight=sample_weight_sev,
491
+ power=2,
492
+ title="SHAP Feature Importance for Severity Model (Real Data)",
493
+ max_display=10
494
+ )
495
+
496
+
497
+ # --- SHAP Feature Importance for Severity (Synthetic Data) ---
498
+ X_train_cost_prep_synth = new_samples_df[new_samples_df['ClaimAmount'] != 0].copy()
499
+ X_test_cost_prep_synth = X_test[X_test['ClaimAmount'] != 0].copy() # Keep using real test data for explanation
500
+ X_train_sev_synth = X_train_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
501
+ y_train_sev_synth = X_train_cost_prep_synth['ClaimAmount'] / X_train_cost_prep_synth['ClaimNb']
502
+ sample_weight_sev_synth = X_train_cost_prep_synth['ClaimNb'] # Number of claims is the weight for severity
503
+ X_test_sev_synth = X_test_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
504
+
505
+
506
+ plot_glm_shap_importance(
507
+ X_train=X_train_sev_synth,
508
+ X_test=X_test_sev_synth,
509
+ y_train=y_train_sev_synth,
510
+ sample_weight=sample_weight_sev_synth,
511
+ power=2,
512
+ title="SHAP Feature Importance for Severity Model (Synthetic Data)",
513
+ max_display=10
514
+ )
515
+
516
+
517
+ # ### dataset 2 and data handling
518
+ st.header('Dataset 2: swmotorcycle')
519
+
520
+ df2_duplicated_rows=df2[df2.duplicated()]
521
+ df2=df2.drop_duplicates()
522
+ df2_duplicated_col=df2.columns[df2.columns.duplicated()]
523
+
524
+
525
+ # add ClaimOcc feature
526
+ df_2 = df2.copy()
527
+ df_2['ClaimOcc'] = np.where(df_2['ClaimNb'] > 0, 1, 0)
528
+ # Feature transformation
529
+ df_2['Exposure'] = df_2['Exposure'].clip(upper=1)
530
+ df_2['VehAge'] = df_2['VehAge'].clip(upper=20)
531
+
532
+
533
+ # ### Encoding
534
+ df2_encod=df_2.copy()
535
+ # RiskClass
536
+ RiskClass_group = {'EV ratio 13-15':'1','EV ratio 20-24':'2','EV ratio 9-12':'3','EV ratio <5':'4','EV ratio 6-8':'5',\
537
+ 'EV ratio 16-19':'6','EV ratio >25':'7'}
538
+ df2_encod['RiskClass'] = df2_encod['RiskClass'].map(RiskClass_group)
539
+ df2_encod['RiskClass']= df2_encod['RiskClass'].astype(int)
540
+ # BonusClass
541
+ BonusClass_group = {'BM1':'1','BM2':'2','BM3':'3','BM4':'4','BM5':'5','BM6':'6','BM7':'7'}
542
+ df2_encod['BonusClass'] = df2_encod['BonusClass'].map(BonusClass_group)
543
+ df2_encod['BonusClass']= df2_encod['BonusClass'].astype(int)
544
+ # Area
545
+ Area_group = {"Central parts of Sweden's three largest cities":'1','Lesser towns except Gotland; Northern towns':'2',\
546
+ 'Small towns; countryside except Gotland; Northern towns':'3','Suburbs; middle-sized cities':'4',\
547
+ 'Northern countryside':'5','Northern towns':'6',"Gotland (Sweden's largest island)":'7'}
548
+ df2_encod['Area'] = df2_encod['Area'].map(Area_group)
549
+ df2_encod['Area']= df2_encod['Area'].astype(int)
550
+ # Gender
551
+ Gender_group = {'Female':'0','Male':'1'}
552
+ df2_encod['Gender'] = df2_encod['Gender'].map(Gender_group)
553
+ df2_encod['Gender']= df2_encod['Gender'].astype(int)
554
+
555
+
556
+
557
+
558
+ # ### Split dataset
559
+ # Split the dataset into train/test split
560
+ X_train, X_test = train_test_split(df2_encod, test_size=0.2, random_state=0)
561
+ st.markdown(f"**Train shape:** {X_train.shape} \n**Test shape:** {X_test.shape}")
562
+
563
+
564
+ # ### Use Generate Samples Dataframe
565
+ new_samples_df=df2_synthetic.copy()
566
+
567
+ # Check consistency
568
+ st.subheader(f"Check consistency")
569
+ # Find inconsistencies
570
+ inconsistent_records = new_samples_df[
571
+ ~(((new_samples_df["ClaimNb"] == 0) & (new_samples_df["ClaimOcc"] == 0) & (new_samples_df["ClaimAmount"] == 0)) |
572
+ ((new_samples_df["ClaimNb"] > 0) & (new_samples_df["ClaimOcc"] > 0) & (new_samples_df["ClaimAmount"] > 0)))
573
+ ]
574
+
575
+ st.write(f"Number of inconsistent records on synthetic data: {len(inconsistent_records)}")
576
+ st.write(inconsistent_records.head()) # Show a few inconsistent rows
577
+ st.write('Helps assess basic data fidelity by checking structural or logical violations.')
578
+
579
+
580
+ # ### Visual Comparison
581
+ st.subheader('Univariate distribution comparison: real vs synthetic')
582
+ st.write('Shows how well each individual feature is mimicked by the synthetic data.')
583
+
584
+ # Compare selected variables using histograms
585
+ compare_real_vs_synthetic(
586
+ real_df=X_train,
587
+ synthetic_df=df2_synthetic,
588
+ columns=['Exposure','VehAge','ClaimOcc','ClaimNb', 'ClaimAmount', 'RiskClass', 'Area','BonusClass','Gender'],
589
+ kind='hist'
590
+ )
591
+
592
+ st.subheader('Correlation matrix comparison: real vs synthetic')
593
+ st.write('Evaluates preservation of feature-to-feature relationships.')
594
+
595
+ # Compute correlation matrices
596
+ corr_matrix_X_train = X_train.corr()
597
+ corr_matrix_new_samples = new_samples_df.corr()
598
+
599
+ fig=plt.figure(figsize=(30,15))
600
+
601
+ plt.subplot(1, 2, 1)
602
+ sns.heatmap(corr_matrix_X_train, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
603
+ plt.title('Correlation Heatmap of X_train', size=15)
604
+ plt.yticks(rotation=0,fontsize=15)
605
+ plt.xticks(rotation=90,fontsize=15)
606
+
607
+ plt.subplot(1, 2, 2)
608
+ sns.heatmap(corr_matrix_new_samples, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
609
+ plt.title('Correlation Heatmap of New Samples', size=15)
610
+ plt.yticks(rotation=0,fontsize=15)
611
+ plt.xticks(rotation=90,fontsize=15)
612
+ plt.tight_layout()
613
+ st.pyplot(fig)
614
+
615
+
616
+ # ### Statistical Analysis
617
+ # Kolmogorov-Smirnov test
618
+ st.subheader('Kolmogorov–Smirnov Test Results')
619
+ st.write('Quantifies the statistical distance between real and synthetic distributions.')
620
+
621
+
622
+ results = []
623
+
624
+ for column in X_train.columns:
625
+ original = X_train[column].values
626
+ generated = new_samples_df[column].values
627
+ statistic, p_value = ks_2samp(original, generated)
628
+
629
+ results.append({
630
+ "Feature": column,
631
+ "KS Statistic": statistic,
632
+ "P-value": p_value
633
+ })
634
+
635
+ results_df = pd.DataFrame(results)
636
+
637
+ def color_pval(val):
638
+ color = "red" if val < 0.05 else "green"
639
+ return f"color: {color};"
640
+
641
+ styled_df = results_df.style.applymap(color_pval, subset=["P-value"]) \
642
+ .format({"KS Statistic": "{:.4f}", "P-value": "{:.4f}"})
643
+
644
+ st.markdown("""
645
+ **Legend:**
646
+ - <span style='color:green;'>Green P-value</span>: distributions are **similar** (p ≥ 0.05)
647
+ - <span style='color:red;'>Red P-value</span>: distributions are **significantly different** (p < 0.05)
648
+ """, unsafe_allow_html=True)
649
+ st.dataframe(styled_df)
650
+
651
+
652
+ # ### PCA Analysis
653
+ st.subheader('PCA comparison')
654
+ st.write('Assesses similarity in global variance structure and major latent components.')
655
+ # Load the saved models
656
+ scaler = load('./CDM/scaler_pca_model_d2_cdm_80.pkl')
657
+ pca = load('./CDM/pca_model_d2_cdm_80.pkl')
658
+
659
+ def pca_visual_comparison_3d_with_saved_model(real_df, synthetic_df, scaler, pca, title_suffix=""):
660
+ assert set(real_df.columns) == set(synthetic_df.columns), "Datasets must have the same columns."
661
+
662
+ combined_df = pd.concat([real_df, synthetic_df])
663
+ combined_scaled = scaler.transform(combined_df)
664
+ pca_result = pca.transform(combined_scaled)
665
+ n_real = len(real_df)
666
+ real_pca = pca_result[:n_real]
667
+ synth_pca = pca_result[n_real:]
668
+ fig = plt.figure(figsize=(10, 8))
669
+ ax = fig.add_subplot(111, projection='3d')
670
+ ax.scatter(real_pca[:, 0], real_pca[:, 1], real_pca[:, 2],
671
+ c='blue', label='Real', s=40, alpha=0.6, edgecolor='k')
672
+
673
+ ax.scatter(synth_pca[:, 0], synth_pca[:, 1], synth_pca[:, 2],
674
+ c='red', label='Synthetic', s=40, alpha=0.6, edgecolor='k')
675
+
676
+ ax.set_title(f"3D PCA: Real vs. Synthetic {title_suffix}", fontsize=12, weight='bold')
677
+ ax.set_xlabel("PC1")
678
+ ax.set_ylabel("PC2")
679
+ ax.set_zlabel("PC3")
680
+
681
+ ax.grid(False)
682
+ ax.legend()
683
+ plt.tight_layout()
684
+ st.pyplot(fig)
685
+
686
+ pca_visual_comparison_3d_with_saved_model(X_train, df2_synthetic, scaler, pca)
687
+
688
+
689
+ # ### UMAP Analysis
690
+ st.subheader('UMAP comparison')
691
+ st.write('Examines nonlinear manifold structure and clustering behavior.')
692
+ img = mpimg.imread('./CDM/umap_d2_80.png')
693
+ fig=plt.figure(figsize=(10, 8))
694
+ plt.imshow(img)
695
+ plt.axis('off')
696
+ st.pyplot(fig)
697
+
698
+
699
+ # ### GLM Frequency Analysis
700
+ st.subheader('Frequency GLM Analysis')
701
+ st.write('Tests how well synthetic data preserves predictive relationships for claim frequency.')
702
+ results_frequency_3 = run_glm_frequency_analysis(X_train, X_test, label="Baseline", var='Real')
703
+ results_frequency_4 = run_glm_frequency_analysis(new_samples_df, X_test, clip_exposure=True, label="Synthetic Clipped", var='Synthetic')
704
+
705
+
706
+ # ### GLM Cost Analysis
707
+ st.subheader('Severity GLM Analysis')
708
+ st.write('Evaluates whether severity-related predictors behave similarly on real and synthetic data.')
709
+ results_cost_3 = run_glm_cost_analysis(X_train, X_test, var='Real')
710
+ results_cost_4 = run_glm_cost_analysis(new_samples_df, X_test, is_sampled=True, var= 'Synthetic')
711
+
712
+
713
+ # ### Feature Importance Analysis
714
+
715
+ # --- SHAP Feature Importance for Frequency ---
716
+ st.subheader('SHAP Feature Importance for Frequency Model')
717
+ st.write('Shows whether drivers of frequency predictions remain consistent across datasets.')
718
+ X_train_freq = X_train.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
719
+ y_train_freq = X_train['ClaimNb']
720
+ sample_weight_freq = X_train['Exposure']
721
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
722
+ mask_train_freq = sample_weight_freq > 0
723
+ X_train_freq_filtered = X_train_freq[mask_train_freq]
724
+ y_train_freq_filtered = y_train_freq[mask_train_freq]
725
+ sample_weight_freq_filtered = sample_weight_freq[mask_train_freq]
726
+ mask_test_freq = X_test['Exposure'] > 0
727
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
728
+
729
+ plot_glm_shap_importance(
730
+ X_train=X_train_freq_filtered,
731
+ X_test=X_test_freq_filtered,
732
+ y_train=y_train_freq_filtered / sample_weight_freq_filtered,
733
+ sample_weight=sample_weight_freq_filtered,
734
+ power=1,
735
+ title="SHAP Feature Importance for Frequency Model (Real Data)",
736
+ max_display=10
737
+ )
738
+
739
+ # --- SHAP Feature Importance for Frequency (Synthetic Data) ---
740
+ X_train_freq_synth = new_samples_df.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
741
+ y_train_freq_synth = new_samples_df['ClaimNb']
742
+ sample_weight_freq_synth = new_samples_df['Exposure']
743
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
744
+ mask_train_freq_synth = sample_weight_freq_synth > 0
745
+ X_train_freq_synth_filtered = X_train_freq_synth[mask_train_freq_synth]
746
+ y_train_freq_synth_filtered = y_train_freq_synth[mask_train_freq_synth]
747
+ sample_weight_freq_synth_filtered = sample_weight_freq_synth[mask_train_freq_synth]
748
+ mask_test_freq = X_test['Exposure'] > 0
749
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
750
+
751
+ plot_glm_shap_importance(
752
+ X_train=X_train_freq_synth_filtered,
753
+ X_test=X_test_freq_filtered,
754
+ y_train=y_train_freq_synth_filtered / sample_weight_freq_synth_filtered,
755
+ sample_weight=sample_weight_freq_synth_filtered,
756
+ power=1,
757
+ title="SHAP Feature Importance for Frequency Model (Synthetic Data)",
758
+ max_display=10
759
+ )
760
+
761
+ # --- SHAP Feature Importance for Severity ---
762
+ st.subheader('SHAP Feature Importance for Severity Model')
763
+ st.write('Assesses stability of model explanations for severity outcomes')
764
+ X_train_cost_prep = X_train[X_train['ClaimAmount'] != 0].copy()
765
+ X_test_cost_prep = X_test[X_test['ClaimAmount'] != 0].copy()
766
+
767
+ X_train_sev = X_train_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
768
+ y_train_sev = X_train_cost_prep['ClaimAmount'] / X_train_cost_prep['ClaimNb']
769
+ sample_weight_sev = X_train_cost_prep['ClaimNb']
770
+ X_test_sev = X_test_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
771
+
772
+ plot_glm_shap_importance(
773
+ X_train=X_train_sev,
774
+ X_test=X_test_sev,
775
+ y_train=y_train_sev,
776
+ sample_weight=sample_weight_sev,
777
+ power=2,
778
+ title="SHAP Feature Importance for Severity Model (Real Data)",
779
+ max_display=10
780
+ )
781
+
782
+ # --- SHAP Feature Importance for Severity (Synthetic Data) ---
783
+ X_train_cost_prep_synth = new_samples_df[new_samples_df['ClaimAmount'] != 0].copy()
784
+ X_test_cost_prep_synth = X_test[X_test['ClaimAmount'] != 0].copy() # Keep using real test data for explanation
785
+ X_train_sev_synth = X_train_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
786
+ y_train_sev_synth = X_train_cost_prep_synth['ClaimAmount'] / X_train_cost_prep_synth['ClaimNb']
787
+ sample_weight_sev_synth = X_train_cost_prep_synth['ClaimNb'] # Number of claims is the weight for severity
788
+ X_test_sev_synth = X_test_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
789
+
790
+ plot_glm_shap_importance(
791
+ X_train=X_train_sev_synth,
792
+ X_test=X_test_sev_synth,
793
+ y_train=y_train_sev_synth,
794
+ sample_weight=sample_weight_sev_synth,
795
+ power=2,
796
+ title="SHAP Feature Importance for Severity Model (Synthetic Data)",
797
+ max_display=10
798
+ )
799
+
800
+
801
+ # ### Results
802
+ st.subheader('Overall results')
803
+ # The dictionary dataset 1
804
+ metrics_dict_1 = results_frequency_1[1]
805
+ mpd_train_1 = metrics_dict_1['mpd_train']
806
+ mpd_test_1 = metrics_dict_1['mpd_test']
807
+
808
+
809
+ # The dictionary synthetic dataset 1
810
+ metrics_dict_2 = results_frequency_2[1]
811
+ mpd_train_2 = metrics_dict_2['mpd_train']
812
+ mpd_test_2 = metrics_dict_2['mpd_test']
813
+
814
+
815
+
816
+ # The dictionary dataset 2
817
+ metrics_dict_3 = results_frequency_3[1]
818
+ mpd_train_3 = metrics_dict_3['mpd_train']
819
+ mpd_test_3 = metrics_dict_3['mpd_test']
820
+
821
+
822
+
823
+ # The dictionary synthetic dataset 2
824
+ metrics_dict_4 = results_frequency_4[1]
825
+ mpd_train_4 = metrics_dict_4['mpd_train']
826
+ mpd_test_4 = metrics_dict_4['mpd_test']
827
+
828
+
829
+
830
+ # The dictionary dataset 1
831
+ mgd_train_1 = results_cost_1['mgd_train']
832
+ mgd_test_1 = results_cost_1['mgd_test']
833
+
834
+
835
+
836
+ # The dictionary synthetic dataset 1
837
+ mgd_train_2 = results_cost_2['mgd_train']
838
+ mgd_test_2 = results_cost_2['mgd_test']
839
+
840
+
841
+
842
+ # The dictionary dataset 2
843
+ mgd_train_3 = results_cost_3['mgd_train']
844
+ mgd_test_3 = results_cost_3['mgd_test']
845
+
846
+
847
+
848
+ # The dictionary synthetic dataset 2
849
+ mgd_train_4 = results_cost_4['mgd_train']
850
+ mgd_test_4 = results_cost_4['mgd_test']
851
+
852
+
853
+
854
+ # Create the DataFrame
855
+ results_df1 = {
856
+ 'mpd_train': mpd_train_1,
857
+ 'mpd_test': mpd_test_1,
858
+ 'mgd_train': mgd_train_1,
859
+ 'mgd_test': mgd_test_1,
860
+ }
861
+ results_df2 = {
862
+ 'mpd_train': mpd_train_2,
863
+ 'mpd_test': mpd_test_2,
864
+ 'mgd_train': mgd_train_2,
865
+ 'mgd_test': mgd_test_2,
866
+ }
867
+ results_df3 = {
868
+ 'mpd_train': mpd_train_3,
869
+ 'mpd_test': mpd_test_3,
870
+ 'mgd_train': mgd_train_3,
871
+ 'mgd_test': mgd_test_3,
872
+ }
873
+ results_df4 = {
874
+ 'mpd_train': mpd_train_4,
875
+ 'mpd_test': mpd_test_4,
876
+ 'mgd_train': mgd_train_4,
877
+ 'mgd_test': mgd_test_4,
878
+ }
879
+ d1=pd.DataFrame(results_df1, index=['dataset 1'])
880
+ d2=pd.DataFrame(results_df2, index=['synthetic dataset 1'])
881
+ d3=pd.DataFrame(results_df3, index=['dataset 2'])
882
+ d4=pd.DataFrame(results_df4, index=['synthetic dataset 2'])
883
+ df_tot= pd.concat([d1,d2,d3,d4])
884
+ st.dataframe(df_tot)
885
+
886
+
887
+ # barplot comparison
888
+ fig, ax = plt.subplots(figsize=(9, 5))
889
+ df_tot.plot(kind='bar', ax=ax)
890
+ ax.set_title('Comparison of MPD and MGD Metrics')
891
+ ax.set_ylabel('Value')
892
+ ax.set_xticklabels(ax.get_xticklabels(), rotation=45)
893
+ ax.legend(title='Metric')
894
+ for container in ax.containers:
895
+ labels = ax.bar_label(container, fmt='%.2f', label_type='edge', padding=2)
896
+ for label in labels:
897
+ label.set_fontsize(8)
898
+
899
+ plt.tight_layout()
900
+ st.pyplot(fig)
901
+
902
+
903
+ # MPD: Train vs Test Comparison
904
+ fig, axes = plt.subplots(1, 2, figsize=(15, 6))
905
+
906
+ # --- MPD Comparison ---
907
+ mpd_data = df_tot[['mpd_train', 'mpd_test']]
908
+ mpd_data.plot(kind='bar', ax=axes[0], color=['#2ecc71', '#e74c3c'])
909
+
910
+ axes[0].set_title('Mean Poisson Deviance: Train vs Test', fontsize=16, fontweight='bold')
911
+ axes[0].set_ylabel('MPD Value', fontsize=14)
912
+ axes[0].set_xlabel('Dataset', fontsize=14)
913
+ axes[0].legend(['Train', 'Test'], fontsize=10)
914
+
915
+ # Larger tick labels
916
+ axes[0].tick_params(axis='x', labelsize=12, rotation=45)
917
+ axes[0].tick_params(axis='y', labelsize=12)
918
+
919
+ axes[0].grid(axis='y', alpha=0.3)
920
+ for container in axes[0].containers:
921
+ axes[0].bar_label(container, fmt='%.3f', fontsize=15)
922
+
923
+ # --- MGD Comparison ---
924
+ mgd_data = df_tot[['mgd_train', 'mgd_test']]
925
+ mgd_data.plot(kind='bar', ax=axes[1], color=['#3498db', '#f39c12'])
926
+
927
+ axes[1].set_title('Mean Gamma Deviance: Train vs Test', fontsize=16, fontweight='bold')
928
+ axes[1].set_ylabel('MGD Value', fontsize=14)
929
+ axes[1].set_xlabel('Dataset', fontsize=14)
930
+ axes[1].legend(['Train', 'Test'], fontsize=10)
931
+
932
+ # Larger tick labels
933
+ axes[1].tick_params(axis='x', labelsize=12, rotation=45)
934
+ axes[1].tick_params(axis='y', labelsize=12)
935
+
936
+ axes[1].grid(axis='y', alpha=0.3)
937
+ for container in axes[1].containers:
938
+ axes[1].bar_label(container, fmt='%.3f', fontsize=15)
939
+
940
+ plt.tight_layout()
941
+ st.pyplot(fig)
942
+
943
+ # Create a heatmap
944
+ fig, ax = plt.subplots(figsize=(10, 6))
945
+
946
+ sns.heatmap(df_tot, annot=True, fmt='.3f', cmap='RdYlGn_r',
947
+ linewidths=0.5, ax=ax, cbar_kws={'label': 'Deviance Value'})
948
+ ax.set_title('Performance Heatmap: All Metrics Across Datasets', fontsize=15, fontweight='bold', pad=20)
949
+ ax.set_xlabel('Metrics')
950
+ ax.set_ylabel('Datasets')
951
+
952
+ plt.tight_layout()
953
+ st.pyplot(fig)
pages/CDM_Trial_2.py ADDED
@@ -0,0 +1,953 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import random
4
+ import numpy as np
5
+ import pandas as pd
6
+ import matplotlib.pyplot as plt
7
+ import matplotlib.image as mpimg
8
+ import seaborn as sns
9
+ from matplotlib.pyplot import subplots
10
+ from sklearn.model_selection import train_test_split
11
+ from sklearn.model_selection import KFold
12
+ from sklearn.metrics import mean_poisson_deviance, mean_gamma_deviance, make_scorer
13
+ from scipy.stats import ks_2samp
14
+ from sklearn.decomposition import PCA
15
+ from sklearn.preprocessing import StandardScaler
16
+ from mpl_toolkits.mplot3d import Axes3D
17
+ from sklearn.linear_model import TweedieRegressor
18
+ import shap
19
+ from sklearn.mixture import GaussianMixture
20
+ from joblib import dump
21
+ from joblib import load
22
+ import streamlit as st
23
+
24
+ import warnings
25
+ warnings.filterwarnings('ignore')
26
+
27
+
28
+ DEFAULT_RANDOM_SEED = 0
29
+ random.seed(DEFAULT_RANDOM_SEED)
30
+ os.environ['PYTHONHASHSEED'] = str(DEFAULT_RANDOM_SEED)
31
+ np.random.seed(DEFAULT_RANDOM_SEED)
32
+
33
+ st.title("Conditional Diffusion Model: Synthetic Data Generation Analysis")
34
+
35
+
36
+ def compare_real_vs_synthetic(real_df, synthetic_df, columns=None, kind='hist', bins=30, figsize=(15, 10)):
37
+ if columns is None:
38
+ columns = [col for col in real_df.columns if real_df[col].dtype != 'object']
39
+
40
+ n_cols = 2
41
+ n_rows = (len(columns) + 1) // n_cols
42
+
43
+ fig= plt.figure(figsize=figsize)
44
+
45
+ for idx, col in enumerate(columns, 1):
46
+ plt.subplot(n_rows, n_cols, idx)
47
+
48
+ if kind == 'hist':
49
+ sns.histplot(real_df[col], color='blue', label='Real', kde=False, stat='density', bins=bins, alpha=0.6)
50
+ sns.histplot(synthetic_df[col], color='red', label='Synthetic', kde=False, stat='density', bins=bins, alpha=0.6)
51
+
52
+ elif kind == 'kde':
53
+ sns.kdeplot(real_df[col], color='blue', label='Real')
54
+ sns.kdeplot(synthetic_df[col], color='red', label='Synthetic')
55
+
56
+ elif kind == 'box':
57
+ sns.boxplot(data=[real_df[col], synthetic_df[col]], palette=['blue', 'red'])
58
+ plt.xticks([0, 1], ['Real', 'Synthetic'])
59
+
60
+ else:
61
+ raise ValueError("Unsupported plot kind. Choose from 'hist', 'kde', or 'box'.")
62
+
63
+ plt.title(f"Comparison for '{col}'")
64
+ plt.legend()
65
+
66
+ plt.tight_layout()
67
+ st.pyplot(fig)
68
+
69
+
70
+ def run_glm_frequency_analysis(
71
+ X_train, X_test, model=None, clip_exposure=False, random_state=0, label="Model", var=None):
72
+ np.random.seed(0)
73
+
74
+ if clip_exposure:
75
+ X_train = X_train.copy()
76
+ X_train['Exposure'] = np.where(X_train['Exposure'] > 1, 1, X_train['Exposure'])
77
+
78
+ mask_tr = X_train['Exposure'] > 0
79
+ mask_te = X_test['Exposure'] > 0
80
+ X_train_f = X_train[mask_tr].copy()
81
+ X_test_f = X_test[mask_te].copy()
82
+
83
+ y_train = X_train_f['ClaimNb']
84
+ y_test = X_test_f['ClaimNb']
85
+ exposure_train = X_train_f['Exposure']
86
+ exposure_test = X_test_f['Exposure']
87
+
88
+ X_train_ = X_train_f.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
89
+ X_test_ = X_test_f.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
90
+
91
+ if model is None:
92
+ model = TweedieRegressor(power=1, link='log')
93
+
94
+ cv = KFold(n_splits=5)
95
+ mpd_scores = []
96
+
97
+ for fold_idx, (train_idx, val_idx) in enumerate(cv.split(X_train_)):
98
+ X_tr, X_val = X_train_.iloc[train_idx], X_train_.iloc[val_idx]
99
+ y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]
100
+ w_tr, w_val = exposure_train.iloc[train_idx], exposure_train.iloc[val_idx]
101
+
102
+ model.fit(X_tr, y_tr / w_tr, sample_weight=w_tr)
103
+ y_pred = model.predict(X_val)
104
+
105
+ score = mean_poisson_deviance(y_val / w_val, y_pred)
106
+ mpd_scores.append(score)
107
+
108
+ model.fit(X_train_, y_train / exposure_train, sample_weight=exposure_train)
109
+
110
+ pred_train = model.predict(X_train_)
111
+ pred_test = model.predict(X_test_)
112
+
113
+ mpd_train = mean_poisson_deviance(y_train / exposure_train, pred_train)
114
+ mpd_test = mean_poisson_deviance(y_test / exposure_test, pred_test)
115
+
116
+ st.write(f"Train Poisson {var} Deviance: {mpd_train:.4f}")
117
+ st.write(f"Test Poisson {var} Deviance: {mpd_test:.4f}")
118
+
119
+ return model, {
120
+ "cv_scores": mpd_scores,
121
+ "mpd_train": mpd_train,
122
+ "mpd_test": mpd_test,
123
+ "train_predictions": pred_train,
124
+ "test_predictions": pred_test
125
+ }
126
+
127
+
128
+ def run_glm_cost_analysis(X_train, X_test, is_sampled=False, verbose=True, var=None):
129
+ np.random.seed(0)
130
+
131
+ if is_sampled:
132
+ X_train = X_train.copy()
133
+ X_train['Exposure'] = np.where(X_train['Exposure'] > 1, 1, X_train['Exposure'])
134
+
135
+ X_train_co = X_train.copy()
136
+ X_test_co = X_test.copy()
137
+
138
+ X_train_co['Acost'] = np.where(X_train_co['ClaimNb'] != 0,
139
+ X_train_co['ClaimAmount'] / X_train_co['ClaimNb'], 0)
140
+ X_test_co['Acost'] = np.where(X_test_co['ClaimNb'] != 0,
141
+ X_test_co['ClaimAmount'] / X_test_co['ClaimNb'], 0)
142
+
143
+ X_train_cost = X_train_co[X_train_co['ClaimAmount'] != 0].copy()
144
+ X_test_cost = X_test_co[X_test_co['ClaimAmount'] != 0].copy()
145
+
146
+ y_train = X_train_cost['Acost']
147
+ claim_tr = X_train_cost['ClaimNb']
148
+ y_test = X_test_cost['Acost']
149
+ claim_te = X_test_cost['ClaimNb']
150
+
151
+ drop_cols = ['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb']
152
+ X_train_ = X_train_cost.drop(columns=drop_cols)
153
+ X_test_ = X_test_cost.drop(columns=drop_cols)
154
+
155
+ glm_cl = TweedieRegressor(power=2, link='log')
156
+
157
+ cv = KFold(n_splits=5, shuffle=True, random_state=0)
158
+ mgd_scores = []
159
+
160
+ for fold_idx, (train_idx, val_idx) in enumerate(cv.split(X_train_)):
161
+ X_tr, X_val = X_train_.iloc[train_idx], X_train_.iloc[val_idx]
162
+ y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]
163
+ w_tr, w_val = claim_tr.iloc[train_idx], claim_tr.iloc[val_idx]
164
+
165
+ glm_cl.fit(X_tr, y_tr, sample_weight=w_tr)
166
+ y_pred_val = glm_cl.predict(X_val)
167
+ score = mean_gamma_deviance(y_val, y_pred_val)
168
+ mgd_scores.append(score)
169
+
170
+ glm_cl.fit(X_train_, y_train, sample_weight=claim_tr)
171
+
172
+ y_pred_train = glm_cl.predict(X_train_)
173
+ y_pred_test = glm_cl.predict(X_test_)
174
+
175
+ mgd_train = mean_gamma_deviance(y_train, y_pred_train)
176
+ mgd_test = mean_gamma_deviance(y_test, y_pred_test)
177
+
178
+ if verbose:
179
+ st.write(f"Train Gamma {var} Deviance: {mgd_train:.4f}")
180
+ st.write(f"Test Gamma {var} Deviance: {mgd_test:.4f}")
181
+
182
+ return {
183
+ "cv_scores": mgd_scores,
184
+ 'mgd_train': mgd_train,
185
+ 'mgd_test': mgd_test,
186
+ 'y_pred_train': y_pred_train,
187
+ 'y_pred_test': y_pred_test
188
+ }
189
+
190
+
191
+ def plot_glm_shap_importance(
192
+ X_train, X_test, y_train, sample_weight,
193
+ power: int, title: str, max_display: int = 10, figsize: tuple = (5, 5), seed: int = 0):
194
+
195
+ np.random.seed(seed)
196
+
197
+ model = TweedieRegressor(power=power, link='log')
198
+ model.fit(X_train, y_train, sample_weight=sample_weight)
199
+
200
+ masker = shap.maskers.Independent(X_train)
201
+ explainer = shap.LinearExplainer(model, masker=masker)
202
+ shap_values = explainer.shap_values(X_test)
203
+
204
+ plt.figure(figsize=figsize)
205
+ shap.summary_plot(
206
+ shap_values, features=X_test,
207
+ feature_names=X_test.columns,
208
+ plot_type='bar',
209
+ max_display=max_display,
210
+ show=False
211
+ )
212
+ plt.title(title, fontsize=12)
213
+ plt.tight_layout()
214
+ fig = plt.gcf()
215
+ st.pyplot(fig)
216
+
217
+
218
+ # ### Upload datasets
219
+
220
+ #-------------------
221
+ # DATASETS
222
+ #-------------------
223
+ df1=pd.read_csv('./data/ausprivauto0405.csv')
224
+ df2=pd.read_csv('./data/swmotorcycle.csv')
225
+ df1_synthetic=pd.read_csv('./CDM/d1_cdm_60_encod.csv')
226
+ df1_synthetic = df1_synthetic.drop(columns=["Unnamed: 0"])
227
+ df2_synthetic=pd.read_csv('./CDM/d2_cdm_60_encod.csv')
228
+ df2_synthetic = df2_synthetic.drop(columns=["Unnamed: 0"])
229
+
230
+
231
+
232
+ # ### dataset 1 and data handling
233
+
234
+ st.header('Dataset 1: ausprivauto0405')
235
+
236
+ df1_duplicated_rows=df1[df1.duplicated()]
237
+ df1=df1.drop_duplicates()
238
+ df1_duplicated_col=df1.columns[df1.columns.duplicated()]
239
+
240
+
241
+ # ### Encoding
242
+
243
+ df1_encod=df1.copy()
244
+ # VehAge
245
+ VehAge_group = {'old cars':'1','young cars':'2','oldest cars':'3','youngest cars':'4'}
246
+ df1_encod['VehAge'] = df1_encod['VehAge'].map(VehAge_group)
247
+ df1_encod['VehAge']= df1_encod['VehAge'].astype(int)
248
+ # DrivAge
249
+ DrivAge_group = {'young people':'1','older work. people':'2','oldest people':'3','working people':'4','old people':'5','youngest people':'6'}
250
+ df1_encod['DrivAge'] = df1_encod['DrivAge'].map(DrivAge_group)
251
+ df1_encod['DrivAge']= df1_encod['DrivAge'].astype(int)
252
+ # VehBody
253
+ VehBody_group = {'Hatchback':'1','Utility':'2','Station wagon':'3','Hardtop':'4','Panel van':'5','Sedan':'6','Truck':'7',\
254
+ 'Coupe':'8', 'Minibus':'9', 'Motorized caravan':'10', 'Bus':'11', 'Convertible':'12','Roadster':'13'}
255
+ df1_encod['VehBody'] = df1_encod['VehBody'].map(VehBody_group)
256
+ df1_encod['VehBody']= df1_encod['VehBody'].astype(int)
257
+ # Gender
258
+ Gender_group = {'Female':'0','Male':'1'}
259
+ df1_encod['Gender'] = df1_encod['Gender'].map(Gender_group)
260
+ df1_encod['Gender']= df1_encod['Gender'].astype(int)
261
+
262
+
263
+
264
+
265
+ # ### Split dataset
266
+ # Split the dataset into train/test split
267
+ X_train, X_test = train_test_split(df1_encod, test_size=0.2, random_state=0)
268
+ st.markdown(f"**Train shape:** {X_train.shape} \n**Test shape:** {X_test.shape}")
269
+
270
+
271
+ # ### Use Generate Samples Dataframe
272
+ new_samples_df=df1_synthetic.copy()
273
+
274
+ # Check consistency
275
+ st.subheader(f"Check consistency")
276
+ # Find inconsistencies
277
+ inconsistent_records = new_samples_df[
278
+ ~(((new_samples_df["ClaimNb"] == 0) & (new_samples_df["ClaimOcc"] == 0) & (new_samples_df["ClaimAmount"] == 0)) |
279
+ ((new_samples_df["ClaimNb"] > 0) & (new_samples_df["ClaimOcc"] > 0) & (new_samples_df["ClaimAmount"] > 0)))
280
+ ]
281
+
282
+ st.write(f"Number of inconsistent records on synthetic data: {len(inconsistent_records)}")
283
+ st.write(inconsistent_records.head())
284
+ st.write('Helps assess basic data fidelity by checking structural or logical violations.')
285
+
286
+
287
+ # ### Visual Comparison
288
+
289
+ # Compare selected variables using histograms
290
+ st.subheader(f"Univariate distribution comparison: real vs synthetic")
291
+ st.write('Shows how well each individual feature is mimicked by the synthetic data.')
292
+
293
+ compare_real_vs_synthetic(
294
+ real_df=X_train,
295
+ synthetic_df=df1_synthetic,
296
+ columns=['Exposure','VehBody','VehValue','ClaimOcc','ClaimNb', 'ClaimAmount', 'DrivAge', 'VehAge','Gender'],
297
+ kind='hist'
298
+ )
299
+
300
+
301
+ st.subheader(f"Correlation matrix comparison: real vs synthetic")
302
+ st.write('Evaluates preservation of feature-to-feature relationships.')
303
+
304
+ # Compute correlation matrices
305
+ corr_matrix_X_train = X_train.corr()
306
+ corr_matrix_new_samples = new_samples_df.corr()
307
+
308
+ fig=plt.figure(figsize=(30,15))
309
+
310
+ plt.subplot(1, 2, 1)
311
+ sns.heatmap(corr_matrix_X_train, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
312
+ plt.title('Correlation Heatmap of X_train', size=15)
313
+ plt.yticks(rotation=0,fontsize=15)
314
+ plt.xticks(rotation=90,fontsize=15)
315
+
316
+ plt.subplot(1, 2, 2)
317
+ sns.heatmap(corr_matrix_new_samples, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
318
+ plt.title('Correlation Heatmap of New Samples', size=15)
319
+ plt.yticks(rotation=0,fontsize=15)
320
+ plt.xticks(rotation=90,fontsize=15)
321
+ plt.tight_layout()
322
+ st.pyplot(fig)
323
+
324
+ # ### Statistical Analysis
325
+ # Kolmogorov-Smirnov test
326
+ st.subheader("Kolmogorov–Smirnov Test Results")
327
+ st.write('Quantifies the statistical distance between real and synthetic distributions.')
328
+
329
+ results = []
330
+
331
+ for column in X_train.columns:
332
+ original = X_train[column].values
333
+ generated = new_samples_df[column].values
334
+ statistic, p_value = ks_2samp(original, generated)
335
+
336
+ results.append({
337
+ "Feature": column,
338
+ "KS Statistic": statistic,
339
+ "P-value": p_value
340
+ })
341
+
342
+ results_df = pd.DataFrame(results)
343
+
344
+ def color_pval(val):
345
+ color = "red" if val < 0.05 else "green"
346
+ return f"color: {color};"
347
+
348
+ styled_df = results_df.style.applymap(color_pval, subset=["P-value"]) \
349
+ .format({"KS Statistic": "{:.4f}", "P-value": "{:.4f}"})
350
+
351
+ st.markdown("""
352
+ **Legend:**
353
+ - <span style='color:green;'>Green P-value</span>: distributions are **similar** (p ≥ 0.05)
354
+ - <span style='color:red;'>Red P-value</span>: distributions are **significantly different** (p < 0.05)
355
+ """, unsafe_allow_html=True)
356
+ st.dataframe(styled_df)
357
+
358
+
359
+ # ### PCA Analysis
360
+
361
+ st.subheader('PCA comparison')
362
+ st.write('Assesses similarity in global variance structure and major latent components.')
363
+ # Load the saved models
364
+ scaler = load('./CDM/scaler_pca_model_d1_cdm_60.pkl')
365
+ pca = load('./CDM/pca_model_d1_cdm_60.pkl')
366
+
367
+ def pca_visual_comparison_3d_with_saved_model(real_df, synthetic_df, scaler, pca, title_suffix=""):
368
+ assert set(real_df.columns) == set(synthetic_df.columns), "Datasets must have the same columns."
369
+
370
+ combined_df = pd.concat([real_df, synthetic_df])
371
+
372
+ combined_scaled = scaler.transform(combined_df)
373
+
374
+ pca_result = pca.transform(combined_scaled)
375
+
376
+ n_real = len(real_df)
377
+ real_pca = pca_result[:n_real]
378
+ synth_pca = pca_result[n_real:]
379
+
380
+ fig = plt.figure(figsize=(10, 8))
381
+ ax = fig.add_subplot(111, projection='3d')
382
+
383
+ ax.scatter(real_pca[:, 0], real_pca[:, 1], real_pca[:, 2],
384
+ c='blue', label='Real', s=40, alpha=0.6, edgecolor='k')
385
+
386
+ ax.scatter(synth_pca[:, 0], synth_pca[:, 1], synth_pca[:, 2],
387
+ c='red', label='Synthetic', s=40, alpha=0.6, edgecolor='k')
388
+
389
+ ax.set_title(f"3D PCA: Real vs. Synthetic {title_suffix}", fontsize=12, weight='bold')
390
+ ax.set_xlabel("PC1")
391
+ ax.set_ylabel("PC2")
392
+ ax.set_zlabel("PC3")
393
+
394
+ ax.grid(False)
395
+ ax.legend()
396
+ plt.tight_layout()
397
+ st.pyplot(fig)
398
+
399
+
400
+ pca_visual_comparison_3d_with_saved_model(X_train, df1_synthetic, scaler, pca)
401
+
402
+
403
+ # ### UMAP Analysis
404
+
405
+ st.subheader('UMAP comparison')
406
+ st.write('Examines nonlinear manifold structure and clustering behavior.')
407
+ img = mpimg.imread('./CDM/umap_d1_60.png')
408
+ fig=plt.figure(figsize=(10, 8))
409
+ plt.imshow(img)
410
+ plt.axis('off')
411
+ st.pyplot(fig)
412
+
413
+
414
+ # ### GLM Frequency Analysis
415
+ st.subheader('Frequency GLM Analysis')
416
+ st.write('Tests how well synthetic data preserves predictive relationships for claim frequency.')
417
+ results_frequency_1 = run_glm_frequency_analysis(X_train, X_test, label="Baseline", var='Real')
418
+ results_frequency_2 = run_glm_frequency_analysis(new_samples_df, X_test, clip_exposure=True, label="Synthetic Clipped",var= 'Synthetic')
419
+
420
+
421
+ # ### GLM Cost Analysis
422
+ st.subheader('Severity GLM Analysis')
423
+ st.write('Evaluates whether severity-related predictors behave similarly on real and synthetic data.')
424
+ results_cost_1 = run_glm_cost_analysis(X_train, X_test,var='Real')
425
+ results_cost_2 = run_glm_cost_analysis(new_samples_df, X_test, is_sampled=True,var='Synthetic')
426
+
427
+
428
+ # ### Feature Importance Analysis
429
+ # --- SHAP Feature Importance for Frequency ---
430
+ st.subheader('SHAP Feature Importance for Frequency Model')
431
+ st.write('Shows whether drivers of frequency predictions remain consistent across datasets.')
432
+ X_train_freq = X_train.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
433
+ y_train_freq = X_train['ClaimNb']
434
+ sample_weight_freq = X_train['Exposure']
435
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
436
+ mask_train_freq = sample_weight_freq > 0
437
+ X_train_freq_filtered = X_train_freq[mask_train_freq]
438
+ y_train_freq_filtered = y_train_freq[mask_train_freq]
439
+ sample_weight_freq_filtered = sample_weight_freq[mask_train_freq]
440
+ mask_test_freq = X_test['Exposure'] > 0
441
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
442
+
443
+ plot_glm_shap_importance(
444
+ X_train=X_train_freq_filtered,
445
+ X_test=X_test_freq_filtered,
446
+ y_train=y_train_freq_filtered / sample_weight_freq_filtered,
447
+ sample_weight=sample_weight_freq_filtered,
448
+ power=1,
449
+ title="SHAP Feature Importance for Frequency Model (Real Data)",
450
+ max_display=10
451
+ )
452
+
453
+ # --- SHAP Feature Importance for Frequency (Synthetic Data) ---
454
+ X_train_freq_synth = new_samples_df.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
455
+ y_train_freq_synth = new_samples_df['ClaimNb']
456
+ sample_weight_freq_synth = new_samples_df['Exposure']
457
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
458
+ mask_train_freq_synth = sample_weight_freq_synth > 0
459
+ X_train_freq_synth_filtered = X_train_freq_synth[mask_train_freq_synth]
460
+ y_train_freq_synth_filtered = y_train_freq_synth[mask_train_freq_synth]
461
+ sample_weight_freq_synth_filtered = sample_weight_freq_synth[mask_train_freq_synth]
462
+ mask_test_freq = X_test['Exposure'] > 0
463
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
464
+
465
+ plot_glm_shap_importance(
466
+ X_train=X_train_freq_synth_filtered,
467
+ X_test=X_test_freq_filtered,
468
+ y_train=y_train_freq_synth_filtered / sample_weight_freq_synth_filtered,
469
+ sample_weight=sample_weight_freq_synth_filtered,
470
+ power=1,
471
+ title="SHAP Feature Importance for Frequency Model (Synthetic Data)",
472
+ max_display=10
473
+ )
474
+
475
+ # --- SHAP Feature Importance for Severity ---
476
+ st.subheader('SHAP Feature Importance for Severity Model')
477
+ st.write('Assesses stability of model explanations for severity outcomes.')
478
+ X_train_cost_prep = X_train[X_train['ClaimAmount'] != 0].copy()
479
+ X_test_cost_prep = X_test[X_test['ClaimAmount'] != 0].copy()
480
+
481
+ X_train_sev = X_train_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
482
+ y_train_sev = X_train_cost_prep['ClaimAmount'] / X_train_cost_prep['ClaimNb']
483
+ sample_weight_sev = X_train_cost_prep['ClaimNb']
484
+ X_test_sev = X_test_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
485
+
486
+ plot_glm_shap_importance(
487
+ X_train=X_train_sev,
488
+ X_test=X_test_sev,
489
+ y_train=y_train_sev,
490
+ sample_weight=sample_weight_sev,
491
+ power=2,
492
+ title="SHAP Feature Importance for Severity Model (Real Data)",
493
+ max_display=10
494
+ )
495
+
496
+
497
+ # --- SHAP Feature Importance for Severity (Synthetic Data) ---
498
+ X_train_cost_prep_synth = new_samples_df[new_samples_df['ClaimAmount'] != 0].copy()
499
+ X_test_cost_prep_synth = X_test[X_test['ClaimAmount'] != 0].copy() # Keep using real test data for explanation
500
+ X_train_sev_synth = X_train_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
501
+ y_train_sev_synth = X_train_cost_prep_synth['ClaimAmount'] / X_train_cost_prep_synth['ClaimNb']
502
+ sample_weight_sev_synth = X_train_cost_prep_synth['ClaimNb'] # Number of claims is the weight for severity
503
+ X_test_sev_synth = X_test_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
504
+
505
+
506
+ plot_glm_shap_importance(
507
+ X_train=X_train_sev_synth,
508
+ X_test=X_test_sev_synth,
509
+ y_train=y_train_sev_synth,
510
+ sample_weight=sample_weight_sev_synth,
511
+ power=2,
512
+ title="SHAP Feature Importance for Severity Model (Synthetic Data)",
513
+ max_display=10
514
+ )
515
+
516
+
517
+ # ### dataset 2 and data handling
518
+ st.header('Dataset 2: swmotorcycle')
519
+
520
+ df2_duplicated_rows=df2[df2.duplicated()]
521
+ df2=df2.drop_duplicates()
522
+ df2_duplicated_col=df2.columns[df2.columns.duplicated()]
523
+
524
+
525
+ # add ClaimOcc feature
526
+ df_2 = df2.copy()
527
+ df_2['ClaimOcc'] = np.where(df_2['ClaimNb'] > 0, 1, 0)
528
+ # Feature transformation
529
+ df_2['Exposure'] = df_2['Exposure'].clip(upper=1)
530
+ df_2['VehAge'] = df_2['VehAge'].clip(upper=20)
531
+
532
+
533
+ # ### Encoding
534
+ df2_encod=df_2.copy()
535
+ # RiskClass
536
+ RiskClass_group = {'EV ratio 13-15':'1','EV ratio 20-24':'2','EV ratio 9-12':'3','EV ratio <5':'4','EV ratio 6-8':'5',\
537
+ 'EV ratio 16-19':'6','EV ratio >25':'7'}
538
+ df2_encod['RiskClass'] = df2_encod['RiskClass'].map(RiskClass_group)
539
+ df2_encod['RiskClass']= df2_encod['RiskClass'].astype(int)
540
+ # BonusClass
541
+ BonusClass_group = {'BM1':'1','BM2':'2','BM3':'3','BM4':'4','BM5':'5','BM6':'6','BM7':'7'}
542
+ df2_encod['BonusClass'] = df2_encod['BonusClass'].map(BonusClass_group)
543
+ df2_encod['BonusClass']= df2_encod['BonusClass'].astype(int)
544
+ # Area
545
+ Area_group = {"Central parts of Sweden's three largest cities":'1','Lesser towns except Gotland; Northern towns':'2',\
546
+ 'Small towns; countryside except Gotland; Northern towns':'3','Suburbs; middle-sized cities':'4',\
547
+ 'Northern countryside':'5','Northern towns':'6',"Gotland (Sweden's largest island)":'7'}
548
+ df2_encod['Area'] = df2_encod['Area'].map(Area_group)
549
+ df2_encod['Area']= df2_encod['Area'].astype(int)
550
+ # Gender
551
+ Gender_group = {'Female':'0','Male':'1'}
552
+ df2_encod['Gender'] = df2_encod['Gender'].map(Gender_group)
553
+ df2_encod['Gender']= df2_encod['Gender'].astype(int)
554
+
555
+
556
+
557
+
558
+ # ### Split dataset
559
+ # Split the dataset into train/test split
560
+ X_train, X_test = train_test_split(df2_encod, test_size=0.2, random_state=0)
561
+ st.markdown(f"**Train shape:** {X_train.shape} \n**Test shape:** {X_test.shape}")
562
+
563
+
564
+ # ### Use Generate Samples Dataframe
565
+ new_samples_df=df2_synthetic.copy()
566
+
567
+ # Check consistency
568
+ st.subheader(f"Check consistency")
569
+ # Find inconsistencies
570
+ inconsistent_records = new_samples_df[
571
+ ~(((new_samples_df["ClaimNb"] == 0) & (new_samples_df["ClaimOcc"] == 0) & (new_samples_df["ClaimAmount"] == 0)) |
572
+ ((new_samples_df["ClaimNb"] > 0) & (new_samples_df["ClaimOcc"] > 0) & (new_samples_df["ClaimAmount"] > 0)))
573
+ ]
574
+
575
+ st.write(f"Number of inconsistent records on synthetic data: {len(inconsistent_records)}")
576
+ st.write(inconsistent_records.head()) # Show a few inconsistent rows
577
+ st.write('Helps assess basic data fidelity by checking structural or logical violations.')
578
+
579
+
580
+ # ### Visual Comparison
581
+ st.subheader('Univariate distribution comparison: real vs synthetic')
582
+ st.write('Shows how well each individual feature is mimicked by the synthetic data.')
583
+
584
+ # Compare selected variables using histograms
585
+ compare_real_vs_synthetic(
586
+ real_df=X_train,
587
+ synthetic_df=df2_synthetic,
588
+ columns=['Exposure','VehAge','ClaimOcc','ClaimNb', 'ClaimAmount', 'RiskClass', 'Area','BonusClass','Gender'],
589
+ kind='hist'
590
+ )
591
+
592
+ st.subheader('Correlation matrix comparison: real vs synthetic')
593
+ st.write('Evaluates preservation of feature-to-feature relationships.')
594
+
595
+ # Compute correlation matrices
596
+ corr_matrix_X_train = X_train.corr()
597
+ corr_matrix_new_samples = new_samples_df.corr()
598
+
599
+ fig=plt.figure(figsize=(30,15))
600
+
601
+ plt.subplot(1, 2, 1)
602
+ sns.heatmap(corr_matrix_X_train, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
603
+ plt.title('Correlation Heatmap of X_train', size=15)
604
+ plt.yticks(rotation=0,fontsize=15)
605
+ plt.xticks(rotation=90,fontsize=15)
606
+
607
+ plt.subplot(1, 2, 2)
608
+ sns.heatmap(corr_matrix_new_samples, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
609
+ plt.title('Correlation Heatmap of New Samples', size=15)
610
+ plt.yticks(rotation=0,fontsize=15)
611
+ plt.xticks(rotation=90,fontsize=15)
612
+ plt.tight_layout()
613
+ st.pyplot(fig)
614
+
615
+
616
+ # ### Statistical Analysis
617
+ # Kolmogorov-Smirnov test
618
+ st.subheader('Kolmogorov–Smirnov Test Results')
619
+ st.write('Quantifies the statistical distance between real and synthetic distributions.')
620
+
621
+
622
+ results = []
623
+
624
+ for column in X_train.columns:
625
+ original = X_train[column].values
626
+ generated = new_samples_df[column].values
627
+ statistic, p_value = ks_2samp(original, generated)
628
+
629
+ results.append({
630
+ "Feature": column,
631
+ "KS Statistic": statistic,
632
+ "P-value": p_value
633
+ })
634
+
635
+ results_df = pd.DataFrame(results)
636
+
637
+ def color_pval(val):
638
+ color = "red" if val < 0.05 else "green"
639
+ return f"color: {color};"
640
+
641
+ styled_df = results_df.style.applymap(color_pval, subset=["P-value"]) \
642
+ .format({"KS Statistic": "{:.4f}", "P-value": "{:.4f}"})
643
+
644
+ st.markdown("""
645
+ **Legend:**
646
+ - <span style='color:green;'>Green P-value</span>: distributions are **similar** (p ≥ 0.05)
647
+ - <span style='color:red;'>Red P-value</span>: distributions are **significantly different** (p < 0.05)
648
+ """, unsafe_allow_html=True)
649
+ st.dataframe(styled_df)
650
+
651
+
652
+ # ### PCA Analysis
653
+ st.subheader('PCA comparison')
654
+ st.write('Assesses similarity in global variance structure and major latent components.')
655
+ # Load the saved models
656
+ scaler = load('./CDM/scaler_pca_model_d2_cdm_60.pkl')
657
+ pca = load('./CDM/pca_model_d2_cdm_60.pkl')
658
+
659
+ def pca_visual_comparison_3d_with_saved_model(real_df, synthetic_df, scaler, pca, title_suffix=""):
660
+ assert set(real_df.columns) == set(synthetic_df.columns), "Datasets must have the same columns."
661
+
662
+ combined_df = pd.concat([real_df, synthetic_df])
663
+ combined_scaled = scaler.transform(combined_df)
664
+ pca_result = pca.transform(combined_scaled)
665
+ n_real = len(real_df)
666
+ real_pca = pca_result[:n_real]
667
+ synth_pca = pca_result[n_real:]
668
+ fig = plt.figure(figsize=(10, 8))
669
+ ax = fig.add_subplot(111, projection='3d')
670
+ ax.scatter(real_pca[:, 0], real_pca[:, 1], real_pca[:, 2],
671
+ c='blue', label='Real', s=40, alpha=0.6, edgecolor='k')
672
+
673
+ ax.scatter(synth_pca[:, 0], synth_pca[:, 1], synth_pca[:, 2],
674
+ c='red', label='Synthetic', s=40, alpha=0.6, edgecolor='k')
675
+
676
+ ax.set_title(f"3D PCA: Real vs. Synthetic {title_suffix}", fontsize=12, weight='bold')
677
+ ax.set_xlabel("PC1")
678
+ ax.set_ylabel("PC2")
679
+ ax.set_zlabel("PC3")
680
+
681
+ ax.grid(False)
682
+ ax.legend()
683
+ plt.tight_layout()
684
+ st.pyplot(fig)
685
+
686
+ pca_visual_comparison_3d_with_saved_model(X_train, df2_synthetic, scaler, pca)
687
+
688
+
689
+ # ### UMAP Analysis
690
+ st.subheader('UMAP comparison')
691
+ st.write('Examines nonlinear manifold structure and clustering behavior.')
692
+ img = mpimg.imread('./CDM/umap_d2_60.png')
693
+ fig=plt.figure(figsize=(10, 8))
694
+ plt.imshow(img)
695
+ plt.axis('off')
696
+ st.pyplot(fig)
697
+
698
+
699
+ # ### GLM Frequency Analysis
700
+ st.subheader('Frequency GLM Analysis')
701
+ st.write('Tests how well synthetic data preserves predictive relationships for claim frequency.')
702
+ results_frequency_3 = run_glm_frequency_analysis(X_train, X_test, label="Baseline", var='Real')
703
+ results_frequency_4 = run_glm_frequency_analysis(new_samples_df, X_test, clip_exposure=True, label="Synthetic Clipped", var='Synthetic')
704
+
705
+
706
+ # ### GLM Cost Analysis
707
+ st.subheader('Severity GLM Analysis')
708
+ st.write('Evaluates whether severity-related predictors behave similarly on real and synthetic data.')
709
+ results_cost_3 = run_glm_cost_analysis(X_train, X_test, var='Real')
710
+ results_cost_4 = run_glm_cost_analysis(new_samples_df, X_test, is_sampled=True, var= 'Synthetic')
711
+
712
+
713
+ # ### Feature Importance Analysis
714
+
715
+ # --- SHAP Feature Importance for Frequency ---
716
+ st.subheader('SHAP Feature Importance for Frequency Model')
717
+ st.write('Shows whether drivers of frequency predictions remain consistent across datasets.')
718
+ X_train_freq = X_train.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
719
+ y_train_freq = X_train['ClaimNb']
720
+ sample_weight_freq = X_train['Exposure']
721
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
722
+ mask_train_freq = sample_weight_freq > 0
723
+ X_train_freq_filtered = X_train_freq[mask_train_freq]
724
+ y_train_freq_filtered = y_train_freq[mask_train_freq]
725
+ sample_weight_freq_filtered = sample_weight_freq[mask_train_freq]
726
+ mask_test_freq = X_test['Exposure'] > 0
727
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
728
+
729
+ plot_glm_shap_importance(
730
+ X_train=X_train_freq_filtered,
731
+ X_test=X_test_freq_filtered,
732
+ y_train=y_train_freq_filtered / sample_weight_freq_filtered,
733
+ sample_weight=sample_weight_freq_filtered,
734
+ power=1,
735
+ title="SHAP Feature Importance for Frequency Model (Real Data)",
736
+ max_display=10
737
+ )
738
+
739
+ # --- SHAP Feature Importance for Frequency (Synthetic Data) ---
740
+ X_train_freq_synth = new_samples_df.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
741
+ y_train_freq_synth = new_samples_df['ClaimNb']
742
+ sample_weight_freq_synth = new_samples_df['Exposure']
743
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
744
+ mask_train_freq_synth = sample_weight_freq_synth > 0
745
+ X_train_freq_synth_filtered = X_train_freq_synth[mask_train_freq_synth]
746
+ y_train_freq_synth_filtered = y_train_freq_synth[mask_train_freq_synth]
747
+ sample_weight_freq_synth_filtered = sample_weight_freq_synth[mask_train_freq_synth]
748
+ mask_test_freq = X_test['Exposure'] > 0
749
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
750
+
751
+ plot_glm_shap_importance(
752
+ X_train=X_train_freq_synth_filtered,
753
+ X_test=X_test_freq_filtered,
754
+ y_train=y_train_freq_synth_filtered / sample_weight_freq_synth_filtered,
755
+ sample_weight=sample_weight_freq_synth_filtered,
756
+ power=1,
757
+ title="SHAP Feature Importance for Frequency Model (Synthetic Data)",
758
+ max_display=10
759
+ )
760
+
761
+ # --- SHAP Feature Importance for Severity ---
762
+ st.subheader('SHAP Feature Importance for Severity Model')
763
+ st.write('Assesses stability of model explanations for severity outcomes')
764
+ X_train_cost_prep = X_train[X_train['ClaimAmount'] != 0].copy()
765
+ X_test_cost_prep = X_test[X_test['ClaimAmount'] != 0].copy()
766
+
767
+ X_train_sev = X_train_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
768
+ y_train_sev = X_train_cost_prep['ClaimAmount'] / X_train_cost_prep['ClaimNb']
769
+ sample_weight_sev = X_train_cost_prep['ClaimNb']
770
+ X_test_sev = X_test_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
771
+
772
+ plot_glm_shap_importance(
773
+ X_train=X_train_sev,
774
+ X_test=X_test_sev,
775
+ y_train=y_train_sev,
776
+ sample_weight=sample_weight_sev,
777
+ power=2,
778
+ title="SHAP Feature Importance for Severity Model (Real Data)",
779
+ max_display=10
780
+ )
781
+
782
+ # --- SHAP Feature Importance for Severity (Synthetic Data) ---
783
+ X_train_cost_prep_synth = new_samples_df[new_samples_df['ClaimAmount'] != 0].copy()
784
+ X_test_cost_prep_synth = X_test[X_test['ClaimAmount'] != 0].copy() # Keep using real test data for explanation
785
+ X_train_sev_synth = X_train_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
786
+ y_train_sev_synth = X_train_cost_prep_synth['ClaimAmount'] / X_train_cost_prep_synth['ClaimNb']
787
+ sample_weight_sev_synth = X_train_cost_prep_synth['ClaimNb'] # Number of claims is the weight for severity
788
+ X_test_sev_synth = X_test_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
789
+
790
+ plot_glm_shap_importance(
791
+ X_train=X_train_sev_synth,
792
+ X_test=X_test_sev_synth,
793
+ y_train=y_train_sev_synth,
794
+ sample_weight=sample_weight_sev_synth,
795
+ power=2,
796
+ title="SHAP Feature Importance for Severity Model (Synthetic Data)",
797
+ max_display=10
798
+ )
799
+
800
+
801
+ # ### Results
802
+ st.subheader('Overall results')
803
+ # The dictionary dataset 1
804
+ metrics_dict_1 = results_frequency_1[1]
805
+ mpd_train_1 = metrics_dict_1['mpd_train']
806
+ mpd_test_1 = metrics_dict_1['mpd_test']
807
+
808
+
809
+ # The dictionary synthetic dataset 1
810
+ metrics_dict_2 = results_frequency_2[1]
811
+ mpd_train_2 = metrics_dict_2['mpd_train']
812
+ mpd_test_2 = metrics_dict_2['mpd_test']
813
+
814
+
815
+
816
+ # The dictionary dataset 2
817
+ metrics_dict_3 = results_frequency_3[1]
818
+ mpd_train_3 = metrics_dict_3['mpd_train']
819
+ mpd_test_3 = metrics_dict_3['mpd_test']
820
+
821
+
822
+
823
+ # The dictionary synthetic dataset 2
824
+ metrics_dict_4 = results_frequency_4[1]
825
+ mpd_train_4 = metrics_dict_4['mpd_train']
826
+ mpd_test_4 = metrics_dict_4['mpd_test']
827
+
828
+
829
+
830
+ # The dictionary dataset 1
831
+ mgd_train_1 = results_cost_1['mgd_train']
832
+ mgd_test_1 = results_cost_1['mgd_test']
833
+
834
+
835
+
836
+ # The dictionary synthetic dataset 1
837
+ mgd_train_2 = results_cost_2['mgd_train']
838
+ mgd_test_2 = results_cost_2['mgd_test']
839
+
840
+
841
+
842
+ # The dictionary dataset 2
843
+ mgd_train_3 = results_cost_3['mgd_train']
844
+ mgd_test_3 = results_cost_3['mgd_test']
845
+
846
+
847
+
848
+ # The dictionary synthetic dataset 2
849
+ mgd_train_4 = results_cost_4['mgd_train']
850
+ mgd_test_4 = results_cost_4['mgd_test']
851
+
852
+
853
+
854
+ # Create the DataFrame
855
+ results_df1 = {
856
+ 'mpd_train': mpd_train_1,
857
+ 'mpd_test': mpd_test_1,
858
+ 'mgd_train': mgd_train_1,
859
+ 'mgd_test': mgd_test_1,
860
+ }
861
+ results_df2 = {
862
+ 'mpd_train': mpd_train_2,
863
+ 'mpd_test': mpd_test_2,
864
+ 'mgd_train': mgd_train_2,
865
+ 'mgd_test': mgd_test_2,
866
+ }
867
+ results_df3 = {
868
+ 'mpd_train': mpd_train_3,
869
+ 'mpd_test': mpd_test_3,
870
+ 'mgd_train': mgd_train_3,
871
+ 'mgd_test': mgd_test_3,
872
+ }
873
+ results_df4 = {
874
+ 'mpd_train': mpd_train_4,
875
+ 'mpd_test': mpd_test_4,
876
+ 'mgd_train': mgd_train_4,
877
+ 'mgd_test': mgd_test_4,
878
+ }
879
+ d1=pd.DataFrame(results_df1, index=['dataset 1'])
880
+ d2=pd.DataFrame(results_df2, index=['synthetic dataset 1'])
881
+ d3=pd.DataFrame(results_df3, index=['dataset 2'])
882
+ d4=pd.DataFrame(results_df4, index=['synthetic dataset 2'])
883
+ df_tot= pd.concat([d1,d2,d3,d4])
884
+ st.dataframe(df_tot)
885
+
886
+
887
+ # barplot comparison
888
+ fig, ax = plt.subplots(figsize=(9, 5))
889
+ df_tot.plot(kind='bar', ax=ax)
890
+ ax.set_title('Comparison of MPD and MGD Metrics')
891
+ ax.set_ylabel('Value')
892
+ ax.set_xticklabels(ax.get_xticklabels(), rotation=45)
893
+ ax.legend(title='Metric')
894
+ for container in ax.containers:
895
+ labels = ax.bar_label(container, fmt='%.2f', label_type='edge', padding=2)
896
+ for label in labels:
897
+ label.set_fontsize(8)
898
+
899
+ plt.tight_layout()
900
+ st.pyplot(fig)
901
+
902
+
903
+ # MPD: Train vs Test Comparison
904
+ fig, axes = plt.subplots(1, 2, figsize=(15, 6))
905
+
906
+ # --- MPD Comparison ---
907
+ mpd_data = df_tot[['mpd_train', 'mpd_test']]
908
+ mpd_data.plot(kind='bar', ax=axes[0], color=['#2ecc71', '#e74c3c'])
909
+
910
+ axes[0].set_title('Mean Poisson Deviance: Train vs Test', fontsize=16, fontweight='bold')
911
+ axes[0].set_ylabel('MPD Value', fontsize=14)
912
+ axes[0].set_xlabel('Dataset', fontsize=14)
913
+ axes[0].legend(['Train', 'Test'], fontsize=10)
914
+
915
+ # Larger tick labels
916
+ axes[0].tick_params(axis='x', labelsize=12, rotation=45)
917
+ axes[0].tick_params(axis='y', labelsize=12)
918
+
919
+ axes[0].grid(axis='y', alpha=0.3)
920
+ for container in axes[0].containers:
921
+ axes[0].bar_label(container, fmt='%.3f', fontsize=15)
922
+
923
+ # --- MGD Comparison ---
924
+ mgd_data = df_tot[['mgd_train', 'mgd_test']]
925
+ mgd_data.plot(kind='bar', ax=axes[1], color=['#3498db', '#f39c12'])
926
+
927
+ axes[1].set_title('Mean Gamma Deviance: Train vs Test', fontsize=16, fontweight='bold')
928
+ axes[1].set_ylabel('MGD Value', fontsize=14)
929
+ axes[1].set_xlabel('Dataset', fontsize=14)
930
+ axes[1].legend(['Train', 'Test'], fontsize=10)
931
+
932
+ # Larger tick labels
933
+ axes[1].tick_params(axis='x', labelsize=12, rotation=45)
934
+ axes[1].tick_params(axis='y', labelsize=12)
935
+
936
+ axes[1].grid(axis='y', alpha=0.3)
937
+ for container in axes[1].containers:
938
+ axes[1].bar_label(container, fmt='%.3f', fontsize=15)
939
+
940
+ plt.tight_layout()
941
+ st.pyplot(fig)
942
+
943
+ # Create a heatmap
944
+ fig, ax = plt.subplots(figsize=(10, 6))
945
+
946
+ sns.heatmap(df_tot, annot=True, fmt='.3f', cmap='RdYlGn_r',
947
+ linewidths=0.5, ax=ax, cbar_kws={'label': 'Deviance Value'})
948
+ ax.set_title('Performance Heatmap: All Metrics Across Datasets', fontsize=15, fontweight='bold', pad=20)
949
+ ax.set_xlabel('Metrics')
950
+ ax.set_ylabel('Datasets')
951
+
952
+ plt.tight_layout()
953
+ st.pyplot(fig)
pages/CDM_Trial_3.py ADDED
@@ -0,0 +1,957 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import random
4
+ import numpy as np
5
+ import pandas as pd
6
+ import matplotlib.pyplot as plt
7
+ import matplotlib.image as mpimg
8
+ import seaborn as sns
9
+ from matplotlib.pyplot import subplots
10
+ from sklearn.model_selection import train_test_split
11
+ from sklearn.model_selection import KFold
12
+ from sklearn.metrics import mean_poisson_deviance, mean_gamma_deviance, make_scorer
13
+ from scipy.stats import ks_2samp
14
+ from sklearn.decomposition import PCA
15
+ from sklearn.preprocessing import StandardScaler
16
+ from mpl_toolkits.mplot3d import Axes3D
17
+ from sklearn.linear_model import TweedieRegressor
18
+ import shap
19
+ from sklearn.mixture import GaussianMixture
20
+ from joblib import dump
21
+ from joblib import load
22
+ import streamlit as st
23
+
24
+ import warnings
25
+ warnings.filterwarnings('ignore')
26
+
27
+
28
+ DEFAULT_RANDOM_SEED = 0
29
+ random.seed(DEFAULT_RANDOM_SEED)
30
+ os.environ['PYTHONHASHSEED'] = str(DEFAULT_RANDOM_SEED)
31
+ np.random.seed(DEFAULT_RANDOM_SEED)
32
+
33
+ st.title("Conditional Diffusion Model: Synthetic Data Generation Analysis")
34
+
35
+
36
+ def compare_real_vs_synthetic(real_df, synthetic_df, columns=None, kind='hist', bins=30, figsize=(15, 10)):
37
+ if columns is None:
38
+ columns = [col for col in real_df.columns if real_df[col].dtype != 'object']
39
+
40
+ n_cols = 2
41
+ n_rows = (len(columns) + 1) // n_cols
42
+
43
+ fig= plt.figure(figsize=figsize)
44
+
45
+ for idx, col in enumerate(columns, 1):
46
+ plt.subplot(n_rows, n_cols, idx)
47
+
48
+ if kind == 'hist':
49
+ sns.histplot(real_df[col], color='blue', label='Real', kde=False, stat='density', bins=bins, alpha=0.6)
50
+ sns.histplot(synthetic_df[col], color='red', label='Synthetic', kde=False, stat='density', bins=bins, alpha=0.6)
51
+
52
+ elif kind == 'kde':
53
+ sns.kdeplot(real_df[col], color='blue', label='Real')
54
+ sns.kdeplot(synthetic_df[col], color='red', label='Synthetic')
55
+
56
+ elif kind == 'box':
57
+ sns.boxplot(data=[real_df[col], synthetic_df[col]], palette=['blue', 'red'])
58
+ plt.xticks([0, 1], ['Real', 'Synthetic'])
59
+
60
+ else:
61
+ raise ValueError("Unsupported plot kind. Choose from 'hist', 'kde', or 'box'.")
62
+
63
+ plt.title(f"Comparison for '{col}'")
64
+ plt.legend()
65
+
66
+ plt.tight_layout()
67
+ st.pyplot(fig)
68
+
69
+
70
+ def run_glm_frequency_analysis(
71
+ X_train, X_test, model=None, clip_exposure=False, random_state=0, label="Model", var=None):
72
+ np.random.seed(0)
73
+
74
+ if clip_exposure:
75
+ X_train = X_train.copy()
76
+ X_train['Exposure'] = np.where(X_train['Exposure'] > 1, 1, X_train['Exposure'])
77
+
78
+ mask_tr = X_train['Exposure'] > 0
79
+ mask_te = X_test['Exposure'] > 0
80
+ X_train_f = X_train[mask_tr].copy()
81
+ X_test_f = X_test[mask_te].copy()
82
+
83
+ y_train = X_train_f['ClaimNb']
84
+ y_test = X_test_f['ClaimNb']
85
+ exposure_train = X_train_f['Exposure']
86
+ exposure_test = X_test_f['Exposure']
87
+
88
+ X_train_ = X_train_f.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
89
+ X_test_ = X_test_f.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
90
+
91
+ if model is None:
92
+ model = TweedieRegressor(power=1, link='log')
93
+
94
+ cv = KFold(n_splits=5)
95
+ mpd_scores = []
96
+
97
+ for fold_idx, (train_idx, val_idx) in enumerate(cv.split(X_train_)):
98
+ X_tr, X_val = X_train_.iloc[train_idx], X_train_.iloc[val_idx]
99
+ y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]
100
+ w_tr, w_val = exposure_train.iloc[train_idx], exposure_train.iloc[val_idx]
101
+
102
+ model.fit(X_tr, y_tr / w_tr, sample_weight=w_tr)
103
+ y_pred = model.predict(X_val)
104
+
105
+ score = mean_poisson_deviance(y_val / w_val, y_pred)
106
+ mpd_scores.append(score)
107
+
108
+ model.fit(X_train_, y_train / exposure_train, sample_weight=exposure_train)
109
+
110
+ pred_train = model.predict(X_train_)
111
+ pred_test = model.predict(X_test_)
112
+
113
+ mpd_train = mean_poisson_deviance(y_train / exposure_train, pred_train)
114
+ mpd_test = mean_poisson_deviance(y_test / exposure_test, pred_test)
115
+
116
+ st.write(f"Train Poisson {var} Deviance: {mpd_train:.4f}")
117
+ st.write(f"Test Poisson {var} Deviance: {mpd_test:.4f}")
118
+
119
+ return model, {
120
+ "cv_scores": mpd_scores,
121
+ "mpd_train": mpd_train,
122
+ "mpd_test": mpd_test,
123
+ "train_predictions": pred_train,
124
+ "test_predictions": pred_test
125
+ }
126
+
127
+
128
+ def run_glm_cost_analysis(X_train, X_test, is_sampled=False, verbose=True, var=None):
129
+ np.random.seed(0)
130
+
131
+ if is_sampled:
132
+ X_train = X_train.copy()
133
+ X_train['Exposure'] = np.where(X_train['Exposure'] > 1, 1, X_train['Exposure'])
134
+
135
+ X_train_co = X_train.copy()
136
+ X_test_co = X_test.copy()
137
+
138
+ X_train_co['Acost'] = np.where(X_train_co['ClaimNb'] != 0,
139
+ X_train_co['ClaimAmount'] / X_train_co['ClaimNb'], 0)
140
+ X_test_co['Acost'] = np.where(X_test_co['ClaimNb'] != 0,
141
+ X_test_co['ClaimAmount'] / X_test_co['ClaimNb'], 0)
142
+
143
+ X_train_cost = X_train_co[X_train_co['ClaimAmount'] != 0].copy()
144
+ X_test_cost = X_test_co[X_test_co['ClaimAmount'] != 0].copy()
145
+
146
+ y_train = X_train_cost['Acost']
147
+ claim_tr = X_train_cost['ClaimNb']
148
+ y_test = X_test_cost['Acost']
149
+ claim_te = X_test_cost['ClaimNb']
150
+
151
+ drop_cols = ['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb']
152
+ X_train_ = X_train_cost.drop(columns=drop_cols)
153
+ X_test_ = X_test_cost.drop(columns=drop_cols)
154
+
155
+ glm_cl = TweedieRegressor(power=2, link='log')
156
+
157
+ cv = KFold(n_splits=5, shuffle=True, random_state=0)
158
+ mgd_scores = []
159
+
160
+ for fold_idx, (train_idx, val_idx) in enumerate(cv.split(X_train_)):
161
+ X_tr, X_val = X_train_.iloc[train_idx], X_train_.iloc[val_idx]
162
+ y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]
163
+ w_tr, w_val = claim_tr.iloc[train_idx], claim_tr.iloc[val_idx]
164
+
165
+ glm_cl.fit(X_tr, y_tr, sample_weight=w_tr)
166
+ y_pred_val = glm_cl.predict(X_val)
167
+ score = mean_gamma_deviance(y_val, y_pred_val)
168
+ mgd_scores.append(score)
169
+
170
+ glm_cl.fit(X_train_, y_train, sample_weight=claim_tr)
171
+
172
+ y_pred_train = glm_cl.predict(X_train_)
173
+ y_pred_test = glm_cl.predict(X_test_)
174
+
175
+ mgd_train = mean_gamma_deviance(y_train, y_pred_train)
176
+ mgd_test = mean_gamma_deviance(y_test, y_pred_test)
177
+
178
+ if verbose:
179
+ st.write(f"Train Gamma {var} Deviance: {mgd_train:.4f}")
180
+ st.write(f"Test Gamma {var} Deviance: {mgd_test:.4f}")
181
+
182
+ return {
183
+ "cv_scores": mgd_scores,
184
+ 'mgd_train': mgd_train,
185
+ 'mgd_test': mgd_test,
186
+ 'y_pred_train': y_pred_train,
187
+ 'y_pred_test': y_pred_test
188
+ }
189
+
190
+
191
+ def plot_glm_shap_importance(
192
+ X_train, X_test, y_train, sample_weight,
193
+ power: int, title: str, max_display: int = 10, figsize: tuple = (5, 5), seed: int = 0):
194
+
195
+ np.random.seed(seed)
196
+
197
+ model = TweedieRegressor(power=power, link='log')
198
+ model.fit(X_train, y_train, sample_weight=sample_weight)
199
+
200
+ masker = shap.maskers.Independent(X_train)
201
+ explainer = shap.LinearExplainer(model, masker=masker)
202
+ shap_values = explainer.shap_values(X_test)
203
+
204
+ plt.figure(figsize=figsize)
205
+ shap.summary_plot(
206
+ shap_values, features=X_test,
207
+ feature_names=X_test.columns,
208
+ plot_type='bar',
209
+ max_display=max_display,
210
+ show=False
211
+ )
212
+ plt.title(title, fontsize=12)
213
+ plt.tight_layout()
214
+ fig = plt.gcf()
215
+ st.pyplot(fig)
216
+
217
+
218
+ # ### Upload datasets
219
+
220
+ #-------------------
221
+ # DATASETS
222
+ #-------------------
223
+ df1=pd.read_csv('./data/ausprivauto0405.csv')
224
+ df2=pd.read_csv('./data/swmotorcycle.csv')
225
+ df1_synthetic=pd.read_csv('./CDM/d1_cdm_gender_encod.csv')
226
+ df1_synthetic = df1_synthetic.drop(columns=["Unnamed: 0"])
227
+ df2_synthetic=pd.read_csv('./CDM/d2_cdm_gender_encod.csv')
228
+ df2_synthetic = df2_synthetic.drop(columns=["Unnamed: 0"])
229
+
230
+
231
+
232
+ # ### dataset 1 and data handling
233
+
234
+ st.header('Dataset 1: ausprivauto0405')
235
+
236
+ df1_duplicated_rows=df1[df1.duplicated()]
237
+ df1=df1.drop_duplicates()
238
+ df1_duplicated_col=df1.columns[df1.columns.duplicated()]
239
+
240
+
241
+ # ### Encoding
242
+
243
+ df1_encod=df1.copy()
244
+ df1_encod=df1_encod.drop(columns=['Gender'], axis=1)
245
+ # VehAge
246
+ VehAge_group = {'old cars':'1','young cars':'2','oldest cars':'3','youngest cars':'4'}
247
+ df1_encod['VehAge'] = df1_encod['VehAge'].map(VehAge_group)
248
+ df1_encod['VehAge']= df1_encod['VehAge'].astype(int)
249
+ # DrivAge
250
+ DrivAge_group = {'young people':'1','older work. people':'2','oldest people':'3','working people':'4','old people':'5','youngest people':'6'}
251
+ df1_encod['DrivAge'] = df1_encod['DrivAge'].map(DrivAge_group)
252
+ df1_encod['DrivAge']= df1_encod['DrivAge'].astype(int)
253
+ # VehBody
254
+ VehBody_group = {'Hatchback':'1','Utility':'2','Station wagon':'3','Hardtop':'4','Panel van':'5','Sedan':'6','Truck':'7',\
255
+ 'Coupe':'8', 'Minibus':'9', 'Motorized caravan':'10', 'Bus':'11', 'Convertible':'12','Roadster':'13'}
256
+ df1_encod['VehBody'] = df1_encod['VehBody'].map(VehBody_group)
257
+ df1_encod['VehBody']= df1_encod['VehBody'].astype(int)
258
+ # Gender
259
+ #Gender_group = {'Female':'0','Male':'1'}
260
+ #df1_encod['Gender'] = df1_encod['Gender'].map(Gender_group)
261
+ #df1_encod['Gender']= df1_encod['Gender'].astype(int)
262
+
263
+
264
+
265
+
266
+ # ### Split dataset
267
+ # Split the dataset into train/test split
268
+ X_train, X_test = train_test_split(df1_encod, test_size=0.2, random_state=0)
269
+ st.markdown(f"**Train shape:** {X_train.shape} \n**Test shape:** {X_test.shape}")
270
+
271
+
272
+ # ### Use Generate Samples Dataframe
273
+ new_samples_df=df1_synthetic.copy()
274
+
275
+ # Check consistency
276
+ st.subheader(f"Check consistency")
277
+ # Find inconsistencies
278
+ inconsistent_records = new_samples_df[
279
+ ~(((new_samples_df["ClaimNb"] == 0) & (new_samples_df["ClaimOcc"] == 0) & (new_samples_df["ClaimAmount"] == 0)) |
280
+ ((new_samples_df["ClaimNb"] > 0) & (new_samples_df["ClaimOcc"] > 0) & (new_samples_df["ClaimAmount"] > 0)))
281
+ ]
282
+
283
+ st.write(f"Number of inconsistent records on synthetic data: {len(inconsistent_records)}")
284
+ st.write(inconsistent_records.head())
285
+ st.write('Helps assess basic data fidelity by checking structural or logical violations.')
286
+
287
+
288
+ # ### Visual Comparison
289
+
290
+ # Compare selected variables using histograms
291
+ st.subheader(f"Univariate distribution comparison: real vs synthetic")
292
+ st.write('Shows how well each individual feature is mimicked by the synthetic data.')
293
+
294
+ compare_real_vs_synthetic(
295
+ real_df=X_train,
296
+ synthetic_df=df1_synthetic,
297
+ columns=['Exposure','VehBody','VehValue','ClaimOcc','ClaimNb', 'ClaimAmount', 'DrivAge', 'VehAge'],
298
+ kind='hist'
299
+ )
300
+
301
+
302
+ st.subheader(f"Correlation matrix comparison: real vs synthetic")
303
+ st.write('Evaluates preservation of feature-to-feature relationships.')
304
+
305
+ # Compute correlation matrices
306
+ corr_matrix_X_train = X_train.corr()
307
+ corr_matrix_new_samples = new_samples_df.corr()
308
+
309
+ fig=plt.figure(figsize=(30,15))
310
+
311
+ plt.subplot(1, 2, 1)
312
+ sns.heatmap(corr_matrix_X_train, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
313
+ plt.title('Correlation Heatmap of X_train', size=15)
314
+ plt.yticks(rotation=0,fontsize=15)
315
+ plt.xticks(rotation=90,fontsize=15)
316
+
317
+ plt.subplot(1, 2, 2)
318
+ sns.heatmap(corr_matrix_new_samples, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
319
+ plt.title('Correlation Heatmap of New Samples', size=15)
320
+ plt.yticks(rotation=0,fontsize=15)
321
+ plt.xticks(rotation=90,fontsize=15)
322
+ plt.tight_layout()
323
+ st.pyplot(fig)
324
+
325
+ # ### Statistical Analysis
326
+ # Kolmogorov-Smirnov test
327
+ st.subheader("Kolmogorov–Smirnov Test Results")
328
+ st.write('Quantifies the statistical distance between real and synthetic distributions.')
329
+
330
+ results = []
331
+
332
+ for column in X_train.columns:
333
+ original = X_train[column].values
334
+ generated = new_samples_df[column].values
335
+ statistic, p_value = ks_2samp(original, generated)
336
+
337
+ results.append({
338
+ "Feature": column,
339
+ "KS Statistic": statistic,
340
+ "P-value": p_value
341
+ })
342
+
343
+ results_df = pd.DataFrame(results)
344
+
345
+ def color_pval(val):
346
+ color = "red" if val < 0.05 else "green"
347
+ return f"color: {color};"
348
+
349
+ styled_df = results_df.style.applymap(color_pval, subset=["P-value"]) \
350
+ .format({"KS Statistic": "{:.4f}", "P-value": "{:.4f}"})
351
+
352
+ st.markdown("""
353
+ **Legend:**
354
+ - <span style='color:green;'>Green P-value</span>: distributions are **similar** (p ≥ 0.05)
355
+ - <span style='color:red;'>Red P-value</span>: distributions are **significantly different** (p < 0.05)
356
+ """, unsafe_allow_html=True)
357
+ st.dataframe(styled_df)
358
+
359
+
360
+ # ### PCA Analysis
361
+
362
+ st.subheader('PCA comparison')
363
+ st.write('Assesses similarity in global variance structure and major latent components.')
364
+ # Load the saved models
365
+ scaler = load('./CDM/scaler_pca_model_d1_cdm_gender.pkl')
366
+ pca = load('./CDM/pca_model_d1_cdm_gender.pkl')
367
+
368
+ def pca_visual_comparison_3d_with_saved_model(real_df, synthetic_df, scaler, pca, title_suffix=""):
369
+ assert set(real_df.columns) == set(synthetic_df.columns), "Datasets must have the same columns."
370
+
371
+ combined_df = pd.concat([real_df, synthetic_df])
372
+
373
+ combined_scaled = scaler.transform(combined_df)
374
+
375
+ pca_result = pca.transform(combined_scaled)
376
+
377
+ n_real = len(real_df)
378
+ real_pca = pca_result[:n_real]
379
+ synth_pca = pca_result[n_real:]
380
+
381
+ fig = plt.figure(figsize=(10, 8))
382
+ ax = fig.add_subplot(111, projection='3d')
383
+
384
+ ax.scatter(real_pca[:, 0], real_pca[:, 1], real_pca[:, 2],
385
+ c='blue', label='Real', s=40, alpha=0.6, edgecolor='k')
386
+
387
+ ax.scatter(synth_pca[:, 0], synth_pca[:, 1], synth_pca[:, 2],
388
+ c='red', label='Synthetic', s=40, alpha=0.6, edgecolor='k')
389
+
390
+ ax.set_title(f"3D PCA: Real vs. Synthetic {title_suffix}", fontsize=12, weight='bold')
391
+ ax.set_xlabel("PC1")
392
+ ax.set_ylabel("PC2")
393
+ ax.set_zlabel("PC3")
394
+
395
+ ax.grid(False)
396
+ ax.legend()
397
+ plt.tight_layout()
398
+ st.pyplot(fig)
399
+
400
+
401
+ pca_visual_comparison_3d_with_saved_model(X_train, df1_synthetic, scaler, pca)
402
+
403
+
404
+ # ### UMAP Analysis
405
+
406
+ st.subheader('UMAP comparison')
407
+ st.write('Examines nonlinear manifold structure and clustering behavior.')
408
+ img = mpimg.imread('./CDM/umap_d1_gender.png')
409
+ fig=plt.figure(figsize=(10, 8))
410
+ plt.imshow(img)
411
+ plt.axis('off')
412
+ st.pyplot(fig)
413
+
414
+
415
+ # ### GLM Frequency Analysis
416
+ st.subheader('Frequency GLM Analysis')
417
+ st.write('Tests how well synthetic data preserves predictive relationships for claim frequency.')
418
+ results_frequency_1 = run_glm_frequency_analysis(X_train, X_test, label="Baseline", var='Real')
419
+ results_frequency_2 = run_glm_frequency_analysis(new_samples_df, X_test, clip_exposure=True, label="Synthetic Clipped",var= 'Synthetic')
420
+
421
+
422
+ # ### GLM Cost Analysis
423
+ st.subheader('Severity GLM Analysis')
424
+ st.write('Evaluates whether severity-related predictors behave similarly on real and synthetic data.')
425
+ results_cost_1 = run_glm_cost_analysis(X_train, X_test,var='Real')
426
+ results_cost_2 = run_glm_cost_analysis(new_samples_df, X_test, is_sampled=True,var='Synthetic')
427
+
428
+
429
+ # ### Feature Importance Analysis
430
+ # --- SHAP Feature Importance for Frequency ---
431
+ st.subheader('SHAP Feature Importance for Frequency Model')
432
+ st.write('Shows whether drivers of frequency predictions remain consistent across datasets.')
433
+ X_train_freq = X_train.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
434
+ y_train_freq = X_train['ClaimNb']
435
+ sample_weight_freq = X_train['Exposure']
436
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
437
+ mask_train_freq = sample_weight_freq > 0
438
+ X_train_freq_filtered = X_train_freq[mask_train_freq]
439
+ y_train_freq_filtered = y_train_freq[mask_train_freq]
440
+ sample_weight_freq_filtered = sample_weight_freq[mask_train_freq]
441
+ mask_test_freq = X_test['Exposure'] > 0
442
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
443
+
444
+ plot_glm_shap_importance(
445
+ X_train=X_train_freq_filtered,
446
+ X_test=X_test_freq_filtered,
447
+ y_train=y_train_freq_filtered / sample_weight_freq_filtered,
448
+ sample_weight=sample_weight_freq_filtered,
449
+ power=1,
450
+ title="SHAP Feature Importance for Frequency Model (Real Data)",
451
+ max_display=10
452
+ )
453
+
454
+ # --- SHAP Feature Importance for Frequency (Synthetic Data) ---
455
+ X_train_freq_synth = new_samples_df.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
456
+ y_train_freq_synth = new_samples_df['ClaimNb']
457
+ sample_weight_freq_synth = new_samples_df['Exposure']
458
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
459
+ mask_train_freq_synth = sample_weight_freq_synth > 0
460
+ X_train_freq_synth_filtered = X_train_freq_synth[mask_train_freq_synth]
461
+ y_train_freq_synth_filtered = y_train_freq_synth[mask_train_freq_synth]
462
+ sample_weight_freq_synth_filtered = sample_weight_freq_synth[mask_train_freq_synth]
463
+ mask_test_freq = X_test['Exposure'] > 0
464
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
465
+
466
+ plot_glm_shap_importance(
467
+ X_train=X_train_freq_synth_filtered,
468
+ X_test=X_test_freq_filtered,
469
+ y_train=y_train_freq_synth_filtered / sample_weight_freq_synth_filtered,
470
+ sample_weight=sample_weight_freq_synth_filtered,
471
+ power=1,
472
+ title="SHAP Feature Importance for Frequency Model (Synthetic Data)",
473
+ max_display=10
474
+ )
475
+
476
+ # --- SHAP Feature Importance for Severity ---
477
+ st.subheader('SHAP Feature Importance for Severity Model')
478
+ st.write('Assesses stability of model explanations for severity outcomes.')
479
+ X_train_cost_prep = X_train[X_train['ClaimAmount'] != 0].copy()
480
+ X_test_cost_prep = X_test[X_test['ClaimAmount'] != 0].copy()
481
+
482
+ X_train_sev = X_train_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
483
+ y_train_sev = X_train_cost_prep['ClaimAmount'] / X_train_cost_prep['ClaimNb']
484
+ sample_weight_sev = X_train_cost_prep['ClaimNb']
485
+ X_test_sev = X_test_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
486
+
487
+ plot_glm_shap_importance(
488
+ X_train=X_train_sev,
489
+ X_test=X_test_sev,
490
+ y_train=y_train_sev,
491
+ sample_weight=sample_weight_sev,
492
+ power=2,
493
+ title="SHAP Feature Importance for Severity Model (Real Data)",
494
+ max_display=10
495
+ )
496
+
497
+
498
+ # --- SHAP Feature Importance for Severity (Synthetic Data) ---
499
+ X_train_cost_prep_synth = new_samples_df[new_samples_df['ClaimAmount'] != 0].copy()
500
+ X_test_cost_prep_synth = X_test[X_test['ClaimAmount'] != 0].copy() # Keep using real test data for explanation
501
+ X_train_sev_synth = X_train_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
502
+ y_train_sev_synth = X_train_cost_prep_synth['ClaimAmount'] / X_train_cost_prep_synth['ClaimNb']
503
+ sample_weight_sev_synth = X_train_cost_prep_synth['ClaimNb'] # Number of claims is the weight for severity
504
+ X_test_sev_synth = X_test_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
505
+
506
+
507
+ plot_glm_shap_importance(
508
+ X_train=X_train_sev_synth,
509
+ X_test=X_test_sev_synth,
510
+ y_train=y_train_sev_synth,
511
+ sample_weight=sample_weight_sev_synth,
512
+ power=2,
513
+ title="SHAP Feature Importance for Severity Model (Synthetic Data)",
514
+ max_display=10
515
+ )
516
+
517
+
518
+ # ### dataset 2 and data handling
519
+ st.header('Dataset 2: swmotorcycle')
520
+
521
+ df2_duplicated_rows=df2[df2.duplicated()]
522
+ df2=df2.drop_duplicates()
523
+ df2_duplicated_col=df2.columns[df2.columns.duplicated()]
524
+
525
+
526
+ # add ClaimOcc feature
527
+ df_2 = df2.copy()
528
+ df_2['ClaimOcc'] = np.where(df_2['ClaimNb'] > 0, 1, 0)
529
+ # Feature transformation
530
+ df_2['Exposure'] = df_2['Exposure'].clip(upper=1)
531
+ df_2['VehAge'] = df_2['VehAge'].clip(upper=20)
532
+
533
+
534
+ # ### Encoding
535
+ df2_encod=df_2.copy()
536
+ df2_encod=df2_encod.drop(columns=['Gender'], axis=1)
537
+ # RiskClass
538
+ RiskClass_group = {'EV ratio 13-15':'1','EV ratio 20-24':'2','EV ratio 9-12':'3','EV ratio <5':'4','EV ratio 6-8':'5',\
539
+ 'EV ratio 16-19':'6','EV ratio >25':'7'}
540
+ df2_encod['RiskClass'] = df2_encod['RiskClass'].map(RiskClass_group)
541
+ df2_encod['RiskClass']= df2_encod['RiskClass'].astype(int)
542
+ # BonusClass
543
+ BonusClass_group = {'BM1':'1','BM2':'2','BM3':'3','BM4':'4','BM5':'5','BM6':'6','BM7':'7'}
544
+ df2_encod['BonusClass'] = df2_encod['BonusClass'].map(BonusClass_group)
545
+ df2_encod['BonusClass']= df2_encod['BonusClass'].astype(int)
546
+ # Area
547
+ Area_group = {"Central parts of Sweden's three largest cities":'1','Lesser towns except Gotland; Northern towns':'2',\
548
+ 'Small towns; countryside except Gotland; Northern towns':'3','Suburbs; middle-sized cities':'4',\
549
+ 'Northern countryside':'5','Northern towns':'6',"Gotland (Sweden's largest island)":'7'}
550
+ df2_encod['Area'] = df2_encod['Area'].map(Area_group)
551
+ df2_encod['Area']= df2_encod['Area'].astype(int)
552
+ # Gender
553
+ #Gender_group = {'Female':'0','Male':'1'}
554
+ #df2_encod['Gender'] = df2_encod['Gender'].map(Gender_group)
555
+ #df2_encod['Gender']= df2_encod['Gender'].astype(int)
556
+
557
+
558
+
559
+
560
+ # ### Split dataset
561
+ # Split the dataset into train/test split
562
+ X_train, X_test = train_test_split(df2_encod, test_size=0.2, random_state=0)
563
+ st.markdown(f"**Train shape:** {X_train.shape} \n**Test shape:** {X_test.shape}")
564
+
565
+
566
+ # ### Use Generate Samples Dataframe
567
+ new_samples_df=df2_synthetic.copy()
568
+
569
+ # Check consistency
570
+ st.subheader(f"Check consistency")
571
+ # Find inconsistencies
572
+ inconsistent_records = new_samples_df[
573
+ ~(((new_samples_df["ClaimNb"] == 0) & (new_samples_df["ClaimOcc"] == 0) & (new_samples_df["ClaimAmount"] == 0)) |
574
+ ((new_samples_df["ClaimNb"] > 0) & (new_samples_df["ClaimOcc"] > 0) & (new_samples_df["ClaimAmount"] > 0)))
575
+ ]
576
+
577
+ st.write(f"Number of inconsistent records on synthetic data: {len(inconsistent_records)}")
578
+ st.write(inconsistent_records.head()) # Show a few inconsistent rows
579
+ st.write('Helps assess basic data fidelity by checking structural or logical violations.')
580
+
581
+
582
+ # ### Visual Comparison
583
+ st.subheader('Univariate distribution comparison: real vs synthetic')
584
+ st.write('Shows how well each individual feature is mimicked by the synthetic data.')
585
+
586
+ # Compare selected variables using histograms
587
+ compare_real_vs_synthetic(
588
+ real_df=X_train,
589
+ synthetic_df=df2_synthetic,
590
+ columns=['Exposure','VehAge','ClaimOcc','ClaimNb', 'ClaimAmount', 'RiskClass', 'Area','BonusClass'],
591
+ kind='hist'
592
+ )
593
+
594
+ st.subheader('Correlation matrix comparison: real vs synthetic')
595
+ st.write('Evaluates preservation of feature-to-feature relationships.')
596
+
597
+ # Compute correlation matrices
598
+ corr_matrix_X_train = X_train.corr()
599
+ corr_matrix_new_samples = new_samples_df.corr()
600
+
601
+ fig=plt.figure(figsize=(30,15))
602
+
603
+ plt.subplot(1, 2, 1)
604
+ sns.heatmap(corr_matrix_X_train, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
605
+ plt.title('Correlation Heatmap of X_train', size=15)
606
+ plt.yticks(rotation=0,fontsize=15)
607
+ plt.xticks(rotation=90,fontsize=15)
608
+
609
+ plt.subplot(1, 2, 2)
610
+ sns.heatmap(corr_matrix_new_samples, square=True, annot=True, cmap='coolwarm', fmt='.2f',annot_kws={"size": 15})
611
+ plt.title('Correlation Heatmap of New Samples', size=15)
612
+ plt.yticks(rotation=0,fontsize=15)
613
+ plt.xticks(rotation=90,fontsize=15)
614
+ plt.tight_layout()
615
+ st.pyplot(fig)
616
+
617
+
618
+ # ### Statistical Analysis
619
+ # Kolmogorov-Smirnov test
620
+ st.subheader('Kolmogorov–Smirnov Test Results')
621
+ st.write('Quantifies the statistical distance between real and synthetic distributions.')
622
+
623
+
624
+ results = []
625
+
626
+ for column in X_train.columns:
627
+ original = X_train[column].values
628
+ generated = new_samples_df[column].values
629
+ statistic, p_value = ks_2samp(original, generated)
630
+
631
+ results.append({
632
+ "Feature": column,
633
+ "KS Statistic": statistic,
634
+ "P-value": p_value
635
+ })
636
+
637
+ results_df = pd.DataFrame(results)
638
+
639
+ def color_pval(val):
640
+ color = "red" if val < 0.05 else "green"
641
+ return f"color: {color};"
642
+
643
+ styled_df = results_df.style.applymap(color_pval, subset=["P-value"]) \
644
+ .format({"KS Statistic": "{:.4f}", "P-value": "{:.4f}"})
645
+
646
+ st.markdown("""
647
+ **Legend:**
648
+ - <span style='color:green;'>Green P-value</span>: distributions are **similar** (p ≥ 0.05)
649
+ - <span style='color:red;'>Red P-value</span>: distributions are **significantly different** (p < 0.05)
650
+ """, unsafe_allow_html=True)
651
+ st.dataframe(styled_df)
652
+
653
+
654
+ # ### PCA Analysis
655
+ st.subheader('PCA comparison')
656
+ st.write('Assesses similarity in global variance structure and major latent components.')
657
+ # Load the saved models
658
+ scaler = load('./CDM/scaler_pca_model_d2_cdm_gender.pkl')
659
+ pca = load('./CDM/pca_model_d2_cdm_gender.pkl')
660
+
661
+ def pca_visual_comparison_3d_with_saved_model(real_df, synthetic_df, scaler, pca, title_suffix=""):
662
+ assert set(real_df.columns) == set(synthetic_df.columns), "Datasets must have the same columns."
663
+
664
+ combined_df = pd.concat([real_df, synthetic_df])
665
+ combined_scaled = scaler.transform(combined_df)
666
+ pca_result = pca.transform(combined_scaled)
667
+ n_real = len(real_df)
668
+ real_pca = pca_result[:n_real]
669
+ synth_pca = pca_result[n_real:]
670
+ fig = plt.figure(figsize=(10, 8))
671
+ ax = fig.add_subplot(111, projection='3d')
672
+ ax.scatter(real_pca[:, 0], real_pca[:, 1], real_pca[:, 2],
673
+ c='blue', label='Real', s=40, alpha=0.6, edgecolor='k')
674
+
675
+ ax.scatter(synth_pca[:, 0], synth_pca[:, 1], synth_pca[:, 2],
676
+ c='red', label='Synthetic', s=40, alpha=0.6, edgecolor='k')
677
+
678
+ ax.set_title(f"3D PCA: Real vs. Synthetic {title_suffix}", fontsize=12, weight='bold')
679
+ ax.set_xlabel("PC1")
680
+ ax.set_ylabel("PC2")
681
+ ax.set_zlabel("PC3")
682
+
683
+ ax.grid(False)
684
+ ax.legend()
685
+ plt.tight_layout()
686
+ st.pyplot(fig)
687
+
688
+ pca_visual_comparison_3d_with_saved_model(X_train, df2_synthetic, scaler, pca)
689
+
690
+
691
+ # ### UMAP Analysis
692
+ st.subheader('UMAP comparison')
693
+ st.write('Examines nonlinear manifold structure and clustering behavior.')
694
+ img = mpimg.imread('./CDM/umap_d2_gender.png')
695
+ fig=plt.figure(figsize=(10, 8))
696
+ plt.imshow(img)
697
+ plt.axis('off')
698
+ st.pyplot(fig)
699
+
700
+
701
+ # ### GLM Frequency Analysis
702
+ st.subheader('Frequency GLM Analysis')
703
+ st.write('Tests how well synthetic data preserves predictive relationships for claim frequency.')
704
+ results_frequency_3 = run_glm_frequency_analysis(X_train, X_test, label="Baseline", var='Real')
705
+ results_frequency_4 = run_glm_frequency_analysis(new_samples_df, X_test, clip_exposure=True, label="Synthetic Clipped", var='Synthetic')
706
+
707
+
708
+ # ### GLM Cost Analysis
709
+ st.subheader('Severity GLM Analysis')
710
+ st.write('Evaluates whether severity-related predictors behave similarly on real and synthetic data.')
711
+ results_cost_3 = run_glm_cost_analysis(X_train, X_test, var='Real')
712
+ results_cost_4 = run_glm_cost_analysis(new_samples_df, X_test, is_sampled=True, var= 'Synthetic')
713
+
714
+
715
+ # ### Feature Importance Analysis
716
+
717
+ # --- SHAP Feature Importance for Frequency ---
718
+ st.subheader('SHAP Feature Importance for Frequency Model')
719
+ st.write('Shows whether drivers of frequency predictions remain consistent across datasets.')
720
+ X_train_freq = X_train.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
721
+ y_train_freq = X_train['ClaimNb']
722
+ sample_weight_freq = X_train['Exposure']
723
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
724
+ mask_train_freq = sample_weight_freq > 0
725
+ X_train_freq_filtered = X_train_freq[mask_train_freq]
726
+ y_train_freq_filtered = y_train_freq[mask_train_freq]
727
+ sample_weight_freq_filtered = sample_weight_freq[mask_train_freq]
728
+ mask_test_freq = X_test['Exposure'] > 0
729
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
730
+
731
+ plot_glm_shap_importance(
732
+ X_train=X_train_freq_filtered,
733
+ X_test=X_test_freq_filtered,
734
+ y_train=y_train_freq_filtered / sample_weight_freq_filtered,
735
+ sample_weight=sample_weight_freq_filtered,
736
+ power=1,
737
+ title="SHAP Feature Importance for Frequency Model (Real Data)",
738
+ max_display=10
739
+ )
740
+
741
+ # --- SHAP Feature Importance for Frequency (Synthetic Data) ---
742
+ X_train_freq_synth = new_samples_df.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
743
+ y_train_freq_synth = new_samples_df['ClaimNb']
744
+ sample_weight_freq_synth = new_samples_df['Exposure']
745
+ X_test_freq = X_test.drop(['Exposure', 'ClaimNb', 'ClaimAmount'], axis=1, errors='ignore')
746
+ mask_train_freq_synth = sample_weight_freq_synth > 0
747
+ X_train_freq_synth_filtered = X_train_freq_synth[mask_train_freq_synth]
748
+ y_train_freq_synth_filtered = y_train_freq_synth[mask_train_freq_synth]
749
+ sample_weight_freq_synth_filtered = sample_weight_freq_synth[mask_train_freq_synth]
750
+ mask_test_freq = X_test['Exposure'] > 0
751
+ X_test_freq_filtered = X_test_freq[mask_test_freq]
752
+
753
+ plot_glm_shap_importance(
754
+ X_train=X_train_freq_synth_filtered,
755
+ X_test=X_test_freq_filtered,
756
+ y_train=y_train_freq_synth_filtered / sample_weight_freq_synth_filtered,
757
+ sample_weight=sample_weight_freq_synth_filtered,
758
+ power=1,
759
+ title="SHAP Feature Importance for Frequency Model (Synthetic Data)",
760
+ max_display=10
761
+ )
762
+
763
+ # --- SHAP Feature Importance for Severity ---
764
+ st.subheader('SHAP Feature Importance for Severity Model')
765
+ st.write('Assesses stability of model explanations for severity outcomes')
766
+ X_train_cost_prep = X_train[X_train['ClaimAmount'] != 0].copy()
767
+ X_test_cost_prep = X_test[X_test['ClaimAmount'] != 0].copy()
768
+
769
+ X_train_sev = X_train_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
770
+ y_train_sev = X_train_cost_prep['ClaimAmount'] / X_train_cost_prep['ClaimNb']
771
+ sample_weight_sev = X_train_cost_prep['ClaimNb']
772
+ X_test_sev = X_test_cost_prep.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
773
+
774
+ plot_glm_shap_importance(
775
+ X_train=X_train_sev,
776
+ X_test=X_test_sev,
777
+ y_train=y_train_sev,
778
+ sample_weight=sample_weight_sev,
779
+ power=2,
780
+ title="SHAP Feature Importance for Severity Model (Real Data)",
781
+ max_display=10
782
+ )
783
+
784
+ # --- SHAP Feature Importance for Severity (Synthetic Data) ---
785
+ X_train_cost_prep_synth = new_samples_df[new_samples_df['ClaimAmount'] != 0].copy()
786
+ X_test_cost_prep_synth = X_test[X_test['ClaimAmount'] != 0].copy() # Keep using real test data for explanation
787
+ X_train_sev_synth = X_train_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
788
+ y_train_sev_synth = X_train_cost_prep_synth['ClaimAmount'] / X_train_cost_prep_synth['ClaimNb']
789
+ sample_weight_sev_synth = X_train_cost_prep_synth['ClaimNb'] # Number of claims is the weight for severity
790
+ X_test_sev_synth = X_test_cost_prep_synth.drop(columns=['Acost', 'Exposure', 'ClaimAmount', 'ClaimNb'], errors='ignore')
791
+
792
+ plot_glm_shap_importance(
793
+ X_train=X_train_sev_synth,
794
+ X_test=X_test_sev_synth,
795
+ y_train=y_train_sev_synth,
796
+ sample_weight=sample_weight_sev_synth,
797
+ power=2,
798
+ title="SHAP Feature Importance for Severity Model (Synthetic Data)",
799
+ max_display=10
800
+ )
801
+
802
+
803
+ # ### Results
804
+ st.subheader('Overall results')
805
+ # The dictionary dataset 1
806
+ metrics_dict_1 = results_frequency_1[1]
807
+ mpd_train_1 = metrics_dict_1['mpd_train']
808
+ mpd_test_1 = metrics_dict_1['mpd_test']
809
+
810
+
811
+ # The dictionary synthetic dataset 1
812
+ metrics_dict_2 = results_frequency_2[1]
813
+ mpd_train_2 = metrics_dict_2['mpd_train']
814
+ mpd_test_2 = metrics_dict_2['mpd_test']
815
+
816
+
817
+
818
+ # The dictionary dataset 2
819
+ metrics_dict_3 = results_frequency_3[1]
820
+ mpd_train_3 = metrics_dict_3['mpd_train']
821
+ mpd_test_3 = metrics_dict_3['mpd_test']
822
+
823
+
824
+
825
+ # The dictionary synthetic dataset 2
826
+ metrics_dict_4 = results_frequency_4[1]
827
+ mpd_train_4 = metrics_dict_4['mpd_train']
828
+ mpd_test_4 = metrics_dict_4['mpd_test']
829
+
830
+
831
+
832
+ # The dictionary dataset 1
833
+ mgd_train_1 = results_cost_1['mgd_train']
834
+ mgd_test_1 = results_cost_1['mgd_test']
835
+
836
+
837
+
838
+ # The dictionary synthetic dataset 1
839
+ mgd_train_2 = results_cost_2['mgd_train']
840
+ mgd_test_2 = results_cost_2['mgd_test']
841
+
842
+
843
+
844
+ # The dictionary dataset 2
845
+ mgd_train_3 = results_cost_3['mgd_train']
846
+ mgd_test_3 = results_cost_3['mgd_test']
847
+
848
+
849
+
850
+ # The dictionary synthetic dataset 2
851
+ mgd_train_4 = results_cost_4['mgd_train']
852
+ mgd_test_4 = results_cost_4['mgd_test']
853
+
854
+
855
+
856
+ # Create the DataFrame
857
+ results_df1 = {
858
+ 'mpd_train': mpd_train_1,
859
+ 'mpd_test': mpd_test_1,
860
+ 'mgd_train': mgd_train_1,
861
+ 'mgd_test': mgd_test_1,
862
+ }
863
+ results_df2 = {
864
+ 'mpd_train': mpd_train_2,
865
+ 'mpd_test': mpd_test_2,
866
+ 'mgd_train': mgd_train_2,
867
+ 'mgd_test': mgd_test_2,
868
+ }
869
+ results_df3 = {
870
+ 'mpd_train': mpd_train_3,
871
+ 'mpd_test': mpd_test_3,
872
+ 'mgd_train': mgd_train_3,
873
+ 'mgd_test': mgd_test_3,
874
+ }
875
+ results_df4 = {
876
+ 'mpd_train': mpd_train_4,
877
+ 'mpd_test': mpd_test_4,
878
+ 'mgd_train': mgd_train_4,
879
+ 'mgd_test': mgd_test_4,
880
+ }
881
+ d1=pd.DataFrame(results_df1, index=['dataset 1'])
882
+ d2=pd.DataFrame(results_df2, index=['synthetic dataset 1'])
883
+ d3=pd.DataFrame(results_df3, index=['dataset 2'])
884
+ d4=pd.DataFrame(results_df4, index=['synthetic dataset 2'])
885
+ df_tot= pd.concat([d1,d2,d3,d4])
886
+ st.dataframe(df_tot)
887
+ st.write("For the Frequency, the first synthetic data closely matches real data patterns, with similar train-test gaps.\
888
+ In the second synthetic dataset the train frequency seems not fitted adeguately. For the severity, both datasets are well-aligned.\
889
+ Overall, the synthetic data don't show degradation with gender masking variable, except for the frequency in the second dataset.")
890
+
891
+ # barplot comparison
892
+ fig, ax = plt.subplots(figsize=(9, 5))
893
+ df_tot.plot(kind='bar', ax=ax)
894
+ ax.set_title('Comparison of MPD and MGD Metrics')
895
+ ax.set_ylabel('Value')
896
+ ax.set_xticklabels(ax.get_xticklabels(), rotation=45)
897
+ ax.legend(title='Metric')
898
+ for container in ax.containers:
899
+ labels = ax.bar_label(container, fmt='%.2f', label_type='edge', padding=2)
900
+ for label in labels:
901
+ label.set_fontsize(8)
902
+
903
+ plt.tight_layout()
904
+ st.pyplot(fig)
905
+
906
+
907
+ # MPD: Train vs Test Comparison
908
+ fig, axes = plt.subplots(1, 2, figsize=(15, 6))
909
+
910
+ # --- MPD Comparison ---
911
+ mpd_data = df_tot[['mpd_train', 'mpd_test']]
912
+ mpd_data.plot(kind='bar', ax=axes[0], color=['#2ecc71', '#e74c3c'])
913
+
914
+ axes[0].set_title('Mean Poisson Deviance: Train vs Test', fontsize=16, fontweight='bold')
915
+ axes[0].set_ylabel('MPD Value', fontsize=14)
916
+ axes[0].set_xlabel('Dataset', fontsize=14)
917
+ axes[0].legend(['Train', 'Test'], fontsize=10)
918
+
919
+ # Larger tick labels
920
+ axes[0].tick_params(axis='x', labelsize=12, rotation=45)
921
+ axes[0].tick_params(axis='y', labelsize=12)
922
+
923
+ axes[0].grid(axis='y', alpha=0.3)
924
+ for container in axes[0].containers:
925
+ axes[0].bar_label(container, fmt='%.3f', fontsize=15)
926
+
927
+ # --- MGD Comparison ---
928
+ mgd_data = df_tot[['mgd_train', 'mgd_test']]
929
+ mgd_data.plot(kind='bar', ax=axes[1], color=['#3498db', '#f39c12'])
930
+
931
+ axes[1].set_title('Mean Gamma Deviance: Train vs Test', fontsize=16, fontweight='bold')
932
+ axes[1].set_ylabel('MGD Value', fontsize=14)
933
+ axes[1].set_xlabel('Dataset', fontsize=14)
934
+ axes[1].legend(['Train', 'Test'], fontsize=10)
935
+
936
+ # Larger tick labels
937
+ axes[1].tick_params(axis='x', labelsize=12, rotation=45)
938
+ axes[1].tick_params(axis='y', labelsize=12)
939
+
940
+ axes[1].grid(axis='y', alpha=0.3)
941
+ for container in axes[1].containers:
942
+ axes[1].bar_label(container, fmt='%.3f', fontsize=15)
943
+
944
+ plt.tight_layout()
945
+ st.pyplot(fig)
946
+
947
+ # Create a heatmap
948
+ fig, ax = plt.subplots(figsize=(10, 6))
949
+
950
+ sns.heatmap(df_tot, annot=True, fmt='.3f', cmap='RdYlGn_r',
951
+ linewidths=0.5, ax=ax, cbar_kws={'label': 'Deviance Value'})
952
+ ax.set_title('Performance Heatmap: All Metrics Across Datasets', fontsize=15, fontweight='bold', pad=20)
953
+ ax.set_xlabel('Metrics')
954
+ ax.set_ylabel('Datasets')
955
+
956
+ plt.tight_layout()
957
+ st.pyplot(fig)