From d715463dd2627cda2a5f21d810f685c46a5203a0 Mon Sep 17 00:00:00 2001 From: Caroline DE POURTALES <cdepourt@montana.irit.fr> Date: Thu, 17 Feb 2022 13:17:59 +0100 Subject: [PATCH] decisiontree color --- app.py | 83 +++- data_retriever.json | 9 +- pages/__pycache__/layout.cpython-38.pyc | Bin 731 -> 0 bytes .../DecisionTree/DecisionTreeComponent.py | 37 +- .../DecisionTreeComponent.cpython-38.pyc | Bin 1308 -> 0 bytes .../map/australian/{ITI => }/australian.map | 0 .../map/cancer/{ITI => }/cancer.map | 0 .../DecisionTree/map/car/{ITI => }/car.map | 0 .../utils/__pycache__/_fileio.cpython-38.pyc | Bin 3674 -> 0 bytes .../utils/__pycache__/dtree.cpython-38.pyc | Bin 21333 -> 0 bytes .../utils/__pycache__/dtviz.cpython-38.pyc | Bin 1471 -> 0 bytes pages/application/DecisionTree/utils/dtree.py | 50 +- pages/application/DecisionTree/utils/dtviz.py | 65 ++- .../NaiveBayesComponent.py} | 0 .../layout_application.cpython-38.pyc | Bin 4500 -> 0 bytes .../__pycache__/utils_data.cpython-38.pyc | Bin 1467 -> 0 bytes pages/application/component.svg | 467 ------------------ pages/application/layout_application.py | 109 +--- pages/application/utils_data.py | 18 +- pages/layout.py | 20 - requirements.txt | 4 +- 21 files changed, 239 insertions(+), 623 deletions(-) delete mode 100644 pages/__pycache__/layout.cpython-38.pyc delete mode 100644 pages/application/DecisionTree/__pycache__/DecisionTreeComponent.cpython-38.pyc rename pages/application/DecisionTree/map/australian/{ITI => }/australian.map (100%) rename pages/application/DecisionTree/map/cancer/{ITI => }/cancer.map (100%) rename pages/application/DecisionTree/map/car/{ITI => }/car.map (100%) delete mode 100644 pages/application/DecisionTree/utils/__pycache__/_fileio.cpython-38.pyc delete mode 100644 pages/application/DecisionTree/utils/__pycache__/dtree.cpython-38.pyc delete mode 100644 pages/application/DecisionTree/utils/__pycache__/dtviz.cpython-38.pyc rename pages/application/{DecisionTree/decision_tree_explicability.py => NaiveBayes/NaiveBayesComponent.py} (100%) delete mode 100644 pages/application/__pycache__/layout_application.cpython-38.pyc delete mode 100644 pages/application/__pycache__/utils_data.cpython-38.pyc delete mode 100644 pages/application/component.svg delete mode 100644 pages/layout.py diff --git a/app.py b/app.py index 7ccf049..bb9a5c6 100644 --- a/app.py +++ b/app.py @@ -1,15 +1,88 @@ # Run this app with `python app.py` and # visit http://127.0.0.1:8050/ in your web browser. -from pages.layout import create_layout - import dash import json +from dash import dcc +from dash import html +from dash import dcc, html, Input, Output +import dash_bootstrap_components as dbc + +from pages.application.layout_application import Model, View +''' +Loading data +''' models_data = open('data_retriever.json') -data = json.load(models_data) +data = json.load(models_data)["data"] + +app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP]) + + + + + +''' +Construction of the layout +''' +model = Model(data) +view = View(model) +tabs = dcc.Tabs([ + dcc.Tab(label='Course on Explainable AI', children=[]), + view.tab, +]) + +app.layout = html.Div([ + html.H1('FXToolKit'), + tabs]) + + + + + + + +''' +Callback for the app +''' +@app.callback( + Output('ml_datasets_choice', 'options'), + Output('ml_instances_choice', 'options'), + Output('graph', 'children'), + Output('explanation', 'children'), + Input('ml_model_choice', 'value'), + Input('ml_datasets_choice', 'value'), + Input('ml_instances_choice', 'value'), + prevent_initial_call=True +) +def update_ml_type(value_ml_model, value_dataset, value_instance): + + + ctx = dash.callback_context + if ctx.triggered: + dropdown_id = ctx.triggered[0]['prop_id'].split('.')[0] + if dropdown_id == 'ml_model_choice' : + model.update_ml_model(value_ml_model) + return model.datasets, [], "", "" + + elif dropdown_id == 'ml_datasets_choice': + + model.update_dataset(value_dataset) + view.update_dataset() + return model.datasets, model.instances, view.component.network, "" + + elif dropdown_id == 'ml_instances_choice' : + + model.update_instance(value_instance) + view.update_instance() + return model.datasets, model.instances, view.component.network, view.component.explanation + + + + -app = dash.Dash(__name__) +''' +Launching app +''' if __name__ == '__main__': - app.layout = create_layout(data) app.run_server(debug=True) \ No newline at end of file diff --git a/data_retriever.json b/data_retriever.json index 059c843..986722f 100644 --- a/data_retriever.json +++ b/data_retriever.json @@ -5,8 +5,15 @@ "ml_type" : "DecisionTree", "trained_models" : "pages/application/DecisionTree/trained_models", "instances" : "pages/application/DecisionTree/instances/", - "explicability_algorithm" : "pages/application/DecisionTree/decision_tree_explicability", + "map" : "pages/application/DecisionTree/map/", "component" : "DecisionTreeComponent" + }, + { + "ml_type" : "NaiveBayes", + "trained_models" : "pages/application/NaiveBayes/trained_models", + "instances" : "pages/application/NaiveBayes/instances/", + "map" : "pages/application/NaiveBayes/map/", + "component" : "NaiveBayesComponent" } ] diff --git a/pages/__pycache__/layout.cpython-38.pyc b/pages/__pycache__/layout.cpython-38.pyc deleted file mode 100644 index 7eafe19fdf636c56577100fc7a56db2e308501d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 731 zcmWIL<>g{vU|{H3%$>BKk%8ech=Yuo7#J8F7#J9e0~i<>QW#Pga~N_NqZk=MY^EHB zT&5@{Fq=7tIf^-jA%!`IC5jbHvqiC|Fr={LaO84EaWXQbu%@uJFhp^su%~deFhp^u zFb6Yea=rxFrO9}UIVCw+lkpZyMoDgtpC;oivE-uE#FEtblEkF=#DapH%;dz9%>2A$ zMv!_a204w5fq}sp<U9oi28I%b1&k#OSxhO6=?p1My-X#{H4M#+=}Ze5YZ+@8gBdiL z{i-CK^Gl10Qx)>_6kIC`auPH15|eUL6&yV^nW9*85|dJMqBxQ>GILUjQu8!fia>Tm zv4kWh6-P0LBqkNHFfcG^GTmZHNi0c>Vkt>XDqhJ@1aibL8~u#@+*JMKl+=R!(xMW5 zr_!R-#8Ul&yrRrR-GZY0tke?Sl+@IMoYb<^BK?BI^weVgoW#of(h|Lb%3ItB{}zKH z1WYh6vM};6vi)OYDdJ#YU_kLaC?Hs2o|k}oegRVo<3dJ+$AcL(nf#(S-6BHr^K-m2 zOEg)Em>C!tqL^JW%c7V(48aC#G8Tc10{a6(urn|)+~S403F1mYP$+|(0df@wBip|s zPEc6zfWo3U0~}BgquI(bi&K)5qu2@(^HLIvqnHX(qFD1va|<e?nDPpuRKQ->LyA;A zhy}>;3=U_o+rYL!90dwy2n*~y4x8Nkl+v73J5W#*^D!_m@G$Z)a)2Nc4>Jcl0JiO= A&;S4c diff --git a/pages/application/DecisionTree/DecisionTreeComponent.py b/pages/application/DecisionTree/DecisionTreeComponent.py index 1aa7531..bfdc1aa 100644 --- a/pages/application/DecisionTree/DecisionTreeComponent.py +++ b/pages/application/DecisionTree/DecisionTreeComponent.py @@ -1,18 +1,35 @@ from dash import dcc, html, Input, Output, callback -from pages.application.DecisionTree.utils.dtviz import visualize +from pages.application.DecisionTree.utils.dtviz import visualize, visualize_instance from pages.application.DecisionTree.utils.dtree import DecisionTree +import dash_interactive_graphviz class DecisionTreeComponent(): - def __init__(self): + def __init__(self, dataset): - self.network = html.Img() + map = "pages/application/DecisionTree/map/"+ dataset + "/" + dataset + ".map" - def update(self, dataset) : - print(dataset) - dt = DecisionTree(from_file = "pages/application/DecisionTree/trained_models/"+dataset+'/'+dataset+".dt") - visualize(dt, "svg", "pages/application/component.svg") - self.network = html.Img(src="pages/application/component.svg") + self.dt = DecisionTree(from_file = "pages/application/DecisionTree/trained_models/"+dataset+'/'+dataset+".dt", mapfile = map) + dot_source = visualize(self.dt) - def update_with_explicability(self, instance) : - self.network = self.network \ No newline at end of file + self.network = dash_interactive_graphviz.DashInteractiveGraphviz( + dot_source=dot_source + ) + + self.explanation = dcc.Textarea(value = "", style = { "font_size" : "15px", + "width": "40rem", + "height": "40rem", + "margin-bottom": "5rem", + "background-color": "#f8f9fa", + }) + + def update_with_explicability(self, dataset, instance) : + instance = open("pages/application/DecisionTree/instances/"+ dataset + "/" + instance, "r") + instance = str(instance.read()).strip().split(',') + + dot_source = visualize_instance(self.dt, instance) + self.network = dash_interactive_graphviz.DashInteractiveGraphviz( + dot_source=dot_source + ) + + self.explanation.value = self.dt.explain(instance) diff --git a/pages/application/DecisionTree/__pycache__/DecisionTreeComponent.cpython-38.pyc b/pages/application/DecisionTree/__pycache__/DecisionTreeComponent.cpython-38.pyc deleted file mode 100644 index f5252efad4cc8643fff3886e9cf6c5e95f5a45f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1308 zcmWIL<>g{vU|?Xm!JRaZiGkrUh=Yuo85kHG7#J9e-53}cQW#Pga~N_NqZo6UqL^}- zqnLA9qF8cSqgWY1a!fgFx$IHwU^a6OM=oa+CnJM9Lkdd@YYRgPYco?6R|;D&gC_e+ zkPVuwx0q9slW(zPl;q~zV)e``C@s0g=3iO@qB)Wib8?arle0A$Z*i7o7MCXGWLBkW zGT!2GNlnfy&dkpXDN0RE2APA5nP9F}VPIfLWr$)-VTfW%VN79aVTfW*VNPLbVTfW$ zVNGFcVTfW)VGm}|<hUh@Y@&01Zb5!tYF<e)Ba*owHppmaP(bi8Fff!bEMQ#7P{R<< zl)@0qu#(YFleq{KQn#2rbJK6J=cSgE=NEz8&QhG3leUtf2&Cy(ihf3ZZmNEAN@_uV zX;F#3Q)y9ZVyS*XUQuSEZb4CgR%(fEN@{9BPHI_dk$yp9dTOzLVnIPpW^!UlW`3SN zvO`fga98UURNmr<kI&4@EQyb22e}sHYzC$(Ib^*E<uIkmU}aDrgFM2*z`y|ZEhz3w z7-|@s85tQ$7*iN)7{wV<n8X>tG_yEE3QI3zEmH|o4HGC>o0%CIN|>`)QrJ@1dl_pO z;#ooAqsie{rANR!B}Ivud8sM!x%nxnImP<F81<`|^-@Ye(Vte7pBtZ+nUi{pxwtI7 zN*>dsWLT`}6_=%JGTve?E=ty9yTw{il$lpj#KFM8Py`ALa1<4BgSb2l3=EpgMZ63Q z47b=*5=#<`Q%i0!rIdgp3PSLM<k(6JQW8s2L3)Zo<}fgFFiJ7<FjmQ64^W75l;8vf zEeL~y6J&1<Ll!9MFo1%l2$cIY8H+&TnoJP)aAf8cmn7yTr$XETN;*Yg$4Ej<k1x+G z$%s#_04I#3%$&@UN<ny3Ggje9894R&X|jRROkQGcYJB`Hu6R%sl;(igJn`{`rHMHZ znIZvDxJiHrDG&j67J>jdu!xg^fq@m|6gCD11`Z|;77jMBn4c#1EtZtT;*26tKtQ4a zl){QYii<#sqqM-Ws)v-^^^ikYue2mHr&uqgq%5-v9G66EC`wI*qyUiPkU|vfbdalW ZaoFVMr<CTT+JUlpF~~JCj694yOaLJOOZor+ diff --git a/pages/application/DecisionTree/map/australian/ITI/australian.map b/pages/application/DecisionTree/map/australian/australian.map similarity index 100% rename from pages/application/DecisionTree/map/australian/ITI/australian.map rename to pages/application/DecisionTree/map/australian/australian.map diff --git a/pages/application/DecisionTree/map/cancer/ITI/cancer.map b/pages/application/DecisionTree/map/cancer/cancer.map similarity index 100% rename from pages/application/DecisionTree/map/cancer/ITI/cancer.map rename to pages/application/DecisionTree/map/cancer/cancer.map diff --git a/pages/application/DecisionTree/map/car/ITI/car.map b/pages/application/DecisionTree/map/car/car.map similarity index 100% rename from pages/application/DecisionTree/map/car/ITI/car.map rename to pages/application/DecisionTree/map/car/car.map diff --git a/pages/application/DecisionTree/utils/__pycache__/_fileio.cpython-38.pyc b/pages/application/DecisionTree/utils/__pycache__/_fileio.cpython-38.pyc deleted file mode 100644 index 7cbc69f32333697f6146d5270c0652e8d7410e9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3674 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 E0asMB-T(jq diff --git a/pages/application/DecisionTree/utils/__pycache__/dtree.cpython-38.pyc b/pages/application/DecisionTree/utils/__pycache__/dtree.cpython-38.pyc deleted file mode 100644 index 94a4fc9fd19d1416fbac2d2b8582f1ad6d77b472..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21333 zcmWIL<>g{vU|=Y^%$>xL#lY|w#6iX!3=9ko3=9m#`xqD)QW#Pga~N_NqZk=MY^EHh zT;?cdFq=7tC5k15A%!J}HJ2@l4JyVS#h${D!kWVo0#eVG!<ow!#RZmQ&*9GHiQ>uS zjp7CKIdb@N`J?#3Y~~z+D1j=06owSe9Kl?nC?Q6MaE3eu7KT*eY_6g?j0_+MmKA}> zMl$3vMKHKCq;REhw=kq|H#0?vx-+Ekr0}*dr0_N~MTw;_2Qz5$y#)D8lkpZ`K~ZL2 zNqkyqUUErhex4@dEiSjroK*j$tkmQZKTXD4Y(=RlrOBz9jJFuIKwOW^lH9~RO{QDS z&VFvU*gRbV^7C^*62bX7WvNA)jJG&~ONuh{(mnl?L3SWxW>^TyF)%QsGDI<^Fhnt> zFh((_Fr_o3F{Lo4u(U8nv81pDGib8iV)4sQNv)FPQh)*wS0OnkvA9?dEOv__8Dt>L z0uY;tfq}sp6h#6I3=B1lH4O0#HB2=O@r)^q!3>&Azm&ilq2?(#=jRod6qP2I<QG9q z1WEX<WV*$YmYP^{i=`|vr&yEu7E5tzPFfK^C~1I<UCB@+#K6GtD@s2jKQ~oBIVH6q zzqF`C->I}HHL+B`Ag?GhQMaHdKP$CFHzhT-ASbmfwMf4pF+H_dKe3=7Co?$_6w>-G zsmYnenfZAkMX9O!r6rj;#ri2FMX9NJ1(mlr;^Q;(GE3s)RY0C$0eO*?jj@Us6a;!O zIX_KSkixvg+|>B^TU_z+x%nxjIUqJqe0*VPVh&V>Jw84qKRG_WNCe~sklVo?fDpnU zF>VG126>oG9E>b1MIaH3=wpLL-#ql_Q%_+{VFO1UdkRMjLlkQYX9`yfLlj#IcM4An zLlk=oZwg-vLlj2}e~Lg0LlkF<V2V%+LljqvMv8EXNDE^WcZz6=SPMfGPl{%Wbc#$1 zV-#<SY>He9Llj?%e2PK~Lll3CVv14=LzF<OV3tq{a~e~Ma*9d|W0WwQubQIP!Wboz z!W7J)sdbA7Ie@D);V~banOl&Ps*nN=eg#mNBPT0Aw<1Oc28LvIP^d#Os9*r`!D;IY zC~dKU(-v#KKn+U@V>UB5je$}cQwoS=P64GomUM<1hIrOY5XlB6*})<lU^XY1<N}l2 zB|HmwLCHUbHJxc8Q!RT9dnQ9BLke3j$TYqh#-h3!h6VgJj0+hV8S><6n2Y*q7#0ZB zFoXGgHB3cwY8Vy>)-ZwiH7rGIY8Vy>)v$ngd1f^XSsX45v3j)}H4Ir?H5`%*E)219 zwVWyJ3xsPpYB(1%)i7jnmxzGa=}gUxHVlOa3OCg-WHA<fs9{(jx{!g90i1t0ig+0q z82rG&bBoR2!__yj;1+XAX3;I?{DJ~arXp!jVv=ECV5nlRHmJ3&HmubYyTx9XT9lMu zoO+8bH#H?awfGiiUVd79UVaLUS&~|mTYQVNC_ldhEPRU<BEkw$!&;D7l2Lq%6_h8z zRAvf@O3BX!QDwP_1-H19^K)`i!39|HEiO>9Pbn!a$Vt7$nx6rZ=S(Zg&y7#Z%t^h) z4Q3XDOQ>7yU`|@WE#88}qT<x}+{A){%)InlY-#yLxrrsWSRtl}Cg<msr52UMm*mIi zmgbaXg8iY%RU`=VuMUXN0};9m3=Fr}a}x_dHWh)&xFT7QxD-g7G3^#(86?kvvmiL* zfpVk?DC3!d5*iyDBOenRBL-v^VB}+BVdP<iU@j1wiHnhsQI3g?u}Ty<ox=-<WKdBE zG8BYCMI$GyXw+h0U?^dzVN796VM<}{W$qWMWvXFVz*xhS1x`NU45kc$3_%PL3@I$Z z44SM}YVfQBDjx$tA*GP0;1Z$$@+P9-yv0})#a>#R8lPHGqRDiNB{x4M6%;eM1x2aF zpn}d%leq}wk|I!G-C{~B&}0UO;4Kc2L7-T>#gdkvl!YEhpa6~q1rnqj6kx0pL-8^s z5;JpBQTz-ls#zHr7(g}_voL|Gg9Qu=8ETnI7)zKyes5u@VQOY-W~^l@VXk3HVU}cA zz_O5`nX!W*o)s*@f+WIL!d}Ca!Yav-!j{6`$^?;FzyVdynaR+>5YJV@-NBH>+05v| z5UW$mT*FkuoWdc_fKVI5T+33+TFX|$*1?d)Rl;1u0?uqXC7{gDnZne<un=TBUkQIY z#2o@)87{btU=33W2gnX_28b(M7-CaEg$2mvAlJmO)N<5v)^gQy*Kl?)Wbu_S*Kjm5 zN;1R?l`z+EgZzdN%MvbOuHgc?po1YxV1Y;tC#bmSV8{}z;gn=pAOevEi4|T0ndQO| z8&S&xb`dwkMYX&j@fsdUhAd%FG2y}xD-pv~%U8=^!&}1-GNXnMq<;cqVG5)LQo{>T z2XP5VXT0zP#zLnOF_0TTqVWPLe4w^Ui8w^W1ribx?F?y*Df}tCEg=63LPZ5j*deMq z7~&<tE)#&btb-w5s)Hd*x`QE$EuA4nu$PIEA%#7dK~t#893?for<N#`WTZkW0Z>9x zNCYQqg@XLdypmL;c0n?zQUGNp5C*Y92`d<sEO|in0mA}@6vl;&JPe?AhTktnWl&41 zxFoTtq_{k@Bm>lZ%0Wxmw>WHa5_6MM676ys7#Kc-%&k(xlClf*Y;y9G6LX5~^k5pm zMes|ID_(+}@e-8T{a%9d2S%k05`;Umgdv47lOctv7gR+u`xSwlUc|@1z@W(puJVyw zh$y7&85kJqLFE{z@rz+T$ZD|ppyqK211RMNGpuCvTgiBfxj3~1r-_ZEnONisQtt*L zz%D}&;DQw7#z`Paup2=E0}4(M289ni3_v9@sKSjEieah+<$O>Xz*xgLnW>N^7}BoO zWV{8cE~--Vic?E8nQt-a8QfyZGb%C#nT~4fEiRjs%;b{f{M-V&si2eqY9O&OfMAsd zfrx}_L`hdDjum5IU;sOo58~JwCKQK)LspZy$P-+<WEO$y5wIA900)aV0|UcMWS229 zpx6hBJdmS7ii<%NH`qRrTE-g25{3?jEXEo}Nrq;W;9<JOR-9i{lA2Pa&cML1k_l`- z*z%Q(;Nl$3`dJ9;nHZQDFwzewuE6OB6iMj}!3>a?Muaqi1#)E(D5e*H;sO-Yptd@w z-EfPopeQvhvqDqm7H3gvVoFYCUTX0zmYmGul3Q$PnK>n?MYotU^Ga^9f@=PPB2Z(u zND&lp%sHufw^)k{axzPb6hOu*fkau067$kii!?!O?v&KD#L}D+P>dBRgOs>~2vraP z3IasW;1+XYO3E$P%#zgH;v!#=Ue46K(%jUd#FEq^H83C4^Z_?yia;&<TfC_iB}IwJ zCGp@kj-~)404+gES;2lN@&U1#@=|Uw#@}Kr0eLAe^%iqpX38z*w9J%SENNwlIk#Ao zGct2hZZYK<MzJR6=j9bcQmYrpiQwS#2eH7x2daBhK~Z7_${eg5%tBx&$IQma$0)@p zz%0PX#VEii#w5Zlz$nAa!z{)OYH3I?3Ngwt^Dv4rim~u82{2ZPU`yX96)-4mf#MY0 zW(K*UhG79i31baoGq@ECZc#E->0%VekfudZey&2I0w}if(h+S8KTRfZ>!AqLAlGC9 z2iGk&h$=|B16foA_9>_}wix77kV6<)t0XX84AF?<YETOo-c(9pgyg_@u)9+jo0<Cg zYME-77BDSjsAWbplxmn$KurSX6y}ADMLSAZN?2={Q&>Pb2izEls%5ERu3<@G1vduR z7BVbgPhnfgSj$?&3es1?QNx<T0;$lO8JQSr*=pHKIBM9N8EY7_IJ3BF*lZYT*j*T6 z1!_5J*uY}kH5@h!g<XYCHOvURK;}bCsb$WSg1d=hA!E@ckb7WqU>zX$vVoc}H4O1Q zpj;o%i%{dj5bIaVS;M&i?AC=0G0dPQMJ-PX=K{VOt{NV2kAb0tqlT-571XSN^crfo zY8bM3v)PNjmGFaDVE6OZaM>^vB8%{3GfiMDjwumnVgy&oTvc``X#mo|1Z9%aqEy|o z#GKMp1!%)u0jY*YB#I(X;~88dMlt()xE48r5<4i>{bJPdyT$FCSdyBaUzC}gm{Y}8 zZBPs9l?WDTfbtbfenDzp5vcSjvIS|10uf;#A{<0Sf(Szpp$#J7y_=$V5Eqo8iwr=l z2#{HPj>W~PMWCL&Yf({tktQ!VaTXba6o4{xQ6h*1%FacgqT?1zeo<z66mxz?D!A}0 z0%b~YQF4nJl%-jUQWMb|3`P133=FG4NgC9KXJBPx;$akG;$swH6a!^lMh->+CMhO9 zCLU%XMm8n^P?lxnViaPkl0r%PD19B2>HyRn09E6lz$;$D2<j&+0QVC>Ez?@Y5{3ng zDa?=-7O2mV#k7EVA*eH%6T?)?T+5QeQp1wXRuomk3~DKZ+rMm*43Z2rEDKmt*cUQ_ zL_sZR8-_x)8payt6tL_9P-bD3WB|1k3Fu@6=>)ri1L_La8YUNp*pOPb8m1aHNd~BU zoMM=2*=sp!I6yX}aAq?XZ7Tt_;2A+`ni&^>WLPB`G8h*!HZ#_+*07bZLDXh2HZ!I% z*)SAtD_jQ3Xf6z~Wwo4{4B)OaC#YSV#kGJN>}S7PP;!Te@zijoG1YR{aM!SAvrS+u z3IgecsIK9XWT@e&VXfhoWUyh#6Ty(-vSFCOSSVEk>YRg3i|3laSjYq(yy6A>725u< zVTk9eVaNhyErE1~6mD>vmNS?^lc&lZB?CD_daDYaA)erNZLtEV1*)K~XsfOO?zt+I z<ST&tvASR`A}3L$a=rpe=b*~D$^<3ZVeLd?Hf=H70IEYkS+p3`P6Y=DBP8;J88n&w zFqDHzd$4j)LQY{wVMu2ps!een?rb-5oQ>%bO(t;rp(q+;Ft}Pm5a0?5)RKYQ`in`? z7E*G78brSsZ81Cs5(j$>6d;+zHIyEL>;}aJW;mf3M`XL}8_c+(08pPn5Xpm}^0Wxl zOD_VInzz^z3kp*6Qi?o4Iza8ETU;rbX=$lNsd>q%kX96^gMN!8JGHX72t2k5ZcBk$ zMMVi9O`z79CJ&?#1xZD*=B0wha+uP}Zn2b<CFVr2rsd@(7C=hR6p$9MiHv#R#?LLL zv^?|{fe`}(!#Pm#32Fo}u<<eTF$pnpFsd+#F{&{0F>)}9F!C@OF>^7pFp4mO+CfrG z5{v@OY|H|TRZ4^t1<GhNXtWTN0>MRT4ycC+s?8a)*^1O^7_t~Z?dn=)&=^|{XyAbf zGIE#aRKt+P43f)YfwNed7(l}YHLNZSu}ZaUH4F>bKt+}$Lk&|2dksqs8>se8XB1~x z$i&D{=mKq5IinN_Aw`L0sYS)9h`w%OUWx*=@u`qn0UDLgEKvZrLJ`G*pC(5Us5uO7 zs}_}lf*9QX1xFVsupu!8sv$MmAu$yQQWONL4_QEic#xO{D=h@cpydEiME?Xu2&m#= zVC7=sViaPQV&q^%tFfRCg|w1Uf*E8qr~?BIW-dks22kMEGN&-svZOQAvVumqS{P~= zT^O1fYuReR0iMlXbfboK0TX2K6dcUVk_^xQ2eCi_&R)xr!UAq=vDL7HN^VejoCRh} zGJxu_TFx5gTCNnfTJ9Q-1?=D&!7hfWmZz4thO>qzo2_U{4R0+MBSYc7!m1h`@PI2{ z3Oi^hwuZfiFO5kOloV#v@YZrOG89%7&M2&^;jZPX;i_TIW-dNe!r8=F!&M7zG@3Ki zvJ}eHFctkO;i_Q+Hz-A7m}>cJ`D*xzpQNzWFxK$fFcdziVTC298kRKB91llTC`uv- zD$PTU|NNwq#7u}-K~XBGf0LP?r;wgmmYS!Kn5U4LS5R7_keOFpl9-pAibxjVrWQ)O z0aWIKx~|~j2^1xuVeVLtTE-NHiQt~KCZii9Gx}ksf?HfRpne*-7cIodz)%b--`E(K z7|{CAsTHZor6sAz1#M<Ar1KpDE;;>*ib16kH@IU5Nx0b3EV$hUE~&s}RzAo@aUhM5 z-q$S_kdJS1fbv34W=U}oC`sR9O-U^%$+*P=61~M<mRXz$>H#A%LL~zOLlkFTY6WBn zJBm3kweS{GS@A8Fl+@x9aES!&%0WT_oDD#Az%6#DuQ)&+w*Ys^*ce$DS(y2lxEQ$@ zr5O1bg_v0wSr`QvIT%?O*_b#Og&0{FIha_uc$ioixtLg(1Q<bd6>5hJYAi}#0ri>S zT^mqdNoN2TK@1BR!Rhx+3=_DZVX0xtW-3xB<O3HuS8CbP8EV;U*jyN58)`Yg`GBd0 zBZU#vvXf*eGC&srb!loiKx8&MrYxvi0n!V~cUhp;;<6g{1*|2YZVhu5OARMTPLd&o z8C0l&T7-oLAbH5xWT8O`v}4rFSj%3+4A#dE=}v;$RYfWwGKDFdrRY@&R|?Al?iw~w z3y+bZge!%00e1~Mgjc9i$XCKs!n=SER0cFNf@T}58d36!M`lS$W?s5NacYS|Qetsx zib85$a()U(C_fFHiwcVJlX6mX5!nJXIIfXgT2z#pSCUhyke3h2JIO_<xv6<2i8%_o zx(dY^`K3823Q4I7X_*zNDGE8DaRJ1{6gXc&QgW4GvO-BwVwoPeNVZa_HmC&&fm~CP zuaJ>h0v6Kbhm_3V#0*Z>;C>2d90yWbgGy%bKtvI!3I<O`++qb!_(D1^A>e`<l<{wI zfVx(o${04SQ3uKwHK1h8Qk+^+Tm-IR!PPTp<n9(*Nq&4rX2~rU&}c_d1xPVCRf7p| z(&i~BN-an%N{t6;w*#ebkP{i$SQv$vI2f6jLFt);$$(LaQGii_iHC^?Jo&-J$j2na zTqTK8-6ER=4xn31>BhI16O&TFzC)R+19jjzVB`LJj0_AZj5Unl)W}w(Rl-mL>cOzI zFn}j486Y(Scou*$PXR2;o&u8PNMUPXC}9E(R)Fdbh>9A<6wVY*(CB|BV+Yd$W>6ny zAyW)9sIO7WS;CUSwSYB+djVTJV>?qCQwmQCTMGxM;|@xpH5?0>ni&}xN?1~OYdD*k z7#TX4ve>gYioT@qrSQY*s7|Qp0#0y_w1{B>b-Ysqz@{>QO%(*2$_;YWLPl|h6d`et z9<CDZ8m<&hP_D6IDBRV-w1B6E5oBjAPYr8|FsRF2B-g>TfVYOHh6&VYsNq=1%E*w% z)4{ZWuZ9Ptl93?=)SD2gvPH=W&iT0or6s8fiO`B6uQWF)wMZd9O##$wNX!FIW<zJ> zL0O=v1e6v)txJ?D0we&MjsaH@5|Ak<22f@%VXR>QMNcy$$noGdJt8A$G8KW-FsM(y zk`Y@83LX#4Vq{<_0yT5d#?Qen%*;csG9Ux)C{6>_UT~*@23^w`Y8YbqK!czQ7*ZG( zGQ#RGu-7yh!7W@(=)fnqLk~{$U;>o#i)tAd7(h<KU6U2$B%(SCR+FJP3?<A!(}^Vv zDU1uC;kA&lgsFxBG;IYBITpVvmb7YvTBPt}hJ+t@m;w}tMSY;y0Xr2;fZYq~h~&Y7 z5XA+cN*z=k!($B`Gy>4^XvP|*G$u&kAnG;fh%`8^ZZTp^=7GWnWEV7S*ckk(6qDhN z3@ZgsH&9`x5aQwL2bq)y%_9F|RdDokQ9yATDAquYY>*F%K{YwpX$rNB9gJBF9Sj|e zSs)e@xG~KPsjHBjs>uQ#0RoT8Lb@ZMx*Qg%RiNeo(=AR=OCTOJP6KJ&B0>!0i&9XL z1_~T-TZKCrTvRLMq~=*+xD%eb5c3b9)Rh7n8({=x0?7P>CKDp&zyS&yUB^<q7J-KL zD?v^GHGe@)V8d)Mp(is?Ne^yk{bJO>a3nl1K}~*eU`jy(6EtSq0q(JYa;PM<Ne6bI zCKDtWL75a9c(<6+GC`|@(u~Tmg<37h$*@rS#i&`-3kqxgB5+m24C>E;+G0hmpk&$x zB0x#^7E4xsW?m7vb_0*XfonWaq(hQ-H>e&HhL5$!CnlwoCYNNErQQ-n2qx#}l@uiw zgM?v&bD%oz77Iv85om%7G_wI(!*q+aqNK7Q^%iReh$;frcSWF@u&4oK4^vux6mwc; zaZxA8d7w0*$y@}Q{wbOW5&*9W03}LL+YTH7NgywSvM&Q050e~=0%+8ZMU9b*iGz`k zS%lGunTLssQG^Mj1qarg42oV*yAXsyEjUnD36w31??DPt#u`vBfw7ml{}`mDR>D}r z25QlzFg1h5#{6QKKy$(+OexF@m{V95uz+V$LCbYGN?2<+Q@{&$QaF2=AT>%2E2N&P zVPC)oX?4|bfZKygj0}ZEg-Rt%DO@!ip!%zXDTTX)9bC(Sho_qvn;D_vyd@kde4rW> zQv1Qh`4=#kaHa?#%am||2D+sgAT2Y*ka~(Bmf2cY*ldadJce=9LYmCC_>%JTONvX1 z5)0x%<6lK{K(PZ_xdd)ORLLfTwc;poiY9=T0D%f%P`HCIsJsECTTs*%Bi9lg(C$1^ z>9Ud$oGqZ)_ZDM85qJUylvW^f6QJ^A0w`I4Oax_Kw6QW!k_1m1A(Aq3C5E%M067v| zR)bnVHQ@R+m|-OoWXTRV$F5{7S^^3Puv@?c$U{Y-DgfjlNG)oF()uFc7)Xr@n(oa` zt^CE4tdIw78x&jp;!ak`&CG)YG)92~@;lhEpj9y70*41Q#tdq6p@t=Bd>tN^sbFtF z2(Z&ZVL2T+EWx%WR#0SX(P~g2tpO3>KmrqR>t>)>C!DO1Se6d4PNCYsO3x^*mQ2s$ zw{|A7wSJo1MWC{w2wYymi^`%IAUA_1AHiLKTg<tcdAFEz6Dw{p7nkN1fr1j&!UvaM zvq8!cSp!teMX_ao*LdDy1+|}wAvHM*hz$vKa9Re31!!uy1LP=Beqdk)^+d%O4VbtX zIT+cPxELiEB^av|2&E;IJ}SsZptXnK67Cvkoh^8<x0V^SI1biI2GyNe&=!0OdoQR} z4QdQA*D$6tG&46d*0R*FxG=<;)UuW^r*JG_N#R_;3NH1yQdn9zO4w>xQ@BB8PzpP| z)MJ8_dWBXc%qcveVKr!(#hd~u%UD_%KxV?rIOY_7a7l(KF1UcDgeye|)Qf=WfS3m> z<5Gl!88k(zEKyq7h>{JdngS;&%oaAvd?hHLKp32nK;5$%(1eTtXqK{uF`Kc7g^{69 zp^ybM_X=4(SOXh5)?@^y5YPhGl>EHZTWlqXMd_&}w-}2d(F{-Wx3~+6@{?1Gi$OiP z1)%5#)k6%79MEX4GRG0cNM1pdUPxw<n@K>cK%lLSqV1sc3wA1)04GaOiLe6XPEZL0 zE?vz?aUo(j3dMn-$^fN&En%o(01X~M7A$}Y3{6I??g169D`D+xoFzTh(1I`cSq@rN zDTlMpQUo=Vt5_8LgD_jkpgaW%7w`fPa5*ai8kt)FU*Li0;Z(6GRvXl6GC_t1zzr<W z6gsx*Yb|mB;}0lUMO3sD9$CxaEJU<{f(c|QxDF~>2~q)?>_jme6ak=E1t)hA1_lPO zf3VdX_>0mF$gVG1hpV!u)S8Vb)~u(MHJgyF@zdnK#R6KQ18!3nfkLYYTlD~{3W_#> zLU|*I*aT{avq75d;F<wc6%_3NNo@uZTR;RjvJmAzXl>7Iklmn|U|{9o;A0eE6ky_D zG-Bd|l>VsGCWw{<QesCb&Om7u<YMr6K4=~RJf6=48mEcX0*_WQ)-XvjWPz3}Ls-nP z>5&>{P)|ydp_ZwT3$*ATG`d*qQNo(S)XdlfntWja=>!chpp9_2LI#!<5+Oafl6(c| zz<4602da>hn3S4>80dqv9~D9}QXv{r@>7e!!+klai7BAD)kKAy)WkGRHpoCU+LQ^X zcU6>+6xiVI1!#~NGKvQ3hZcdx(9ug_9_W}Zcot*}DAYk+0njP{4$wF;lMoZys2Q^Q zWKf`jnz0}Zass@P-Np)<=5J=`WKL(OW$9qb0xe%%(8*B31Y&nEfL5`jFlI9qHFPj9 zU@l?l01Y=Wg2X|iHlQ9MWF-qXLkDvfD~dd`Bm+p^h5?&s2Xhu%4J$W;4MU+oJ3~8Y zJwHndYYPX=R#11OmaT?m0ecBY7H17x2WXKlTQ7LUd!YbmNV<fpgDH!90S{;uiwi@n zOf5$Z`vP`|D^u8OI6!tU1g|wJJW|5j%uvhO!IZ@ZQt!erfiZSkEmsW}nAKIw4b#=Z zw1B^cdm(r%Y9V7SPdY;_Zw)tCRZu5m2~!6nWW*(f89b!6s)iS0V+VMMv4jOK&m_r^ z#<Y-;8@#NcgDFb@y9!BAXbN;N+Ax4zfL+Z(Mo`Ft)a0G&0JF2%ComNyL3Au+1Wg2S zGo&-t@+0gAFN$D=m{-G^#so3}v^1gkLy2G$V+|*=t3mQ0m)7v5Gl7QOYxpr`LE;k_ z3$K85*6@RPDI5!gQaBd~w?hUyxj=D;Y(Hqwb0H(d=Qa$8a01x`(m#Q*Pzh`rZw(K4 z_L7m2q3}=Pry9-$BH$QjM~-n&Qz}-VRsbYhBLLRVRJbgKsa7zBsa7b3saCi|w1%;U z6SOR10#kodtq4+FGsS`yYk=aKDb}HuJB4R~a63~PV+t?G&7jx?xwTdl6rLq4DeUNJ zpq)95F@+DIwpOe}Y=JmrC(A;{TJdy-T8R>g8txh~aECi7hN+XWRuU5S9ZXr0SyI_- z6PSzIAZ&=ZBtxxKjd+ceB*Oys8cC3U^3-aWComVys*wPRq=VCKo=}ZAOg%^xq;3Lp zp-+i)jW{SBH#62qh%?lRmB`eHfhGngFvhyYFx5)e%G8LZ2-Zl0hN^4CYNS(`#Tilr zK|FDWTG<+rEZGvd6d}+~oElkBlevZmlzzk+QiOXMQ$(_vCNLMBD3MPQT_98=3h9`4 zFl8yEFlVz&U@3Abkxvl=#cK_BjWlRrEk(SUnUNurp;itQma!h4j1V{1$YrxlU@5Ar zk%RCh85Sr)^2b6(m>6hDObHi6v>8cs0!yJ!iCl^V$c-tIDXc9FHF6*ygGz-I=4R$v z`4VYx{%2gE1j_#lluNi$q`>OHL*t+rZDy=hs1dA@FOf@;2AKnna~W_vD!|MUVK8T? z6|50XVK!%|6=r0Zz*?v<fw>Umds&3<YlOh^LKBz@Urb;syoVU3D3ME%1DRc-k|GbH zL8TYSoy|;OS1Ojsrzq42frmxpQxq2nm8jMTrznXq)Cfs4K-|d4P$HkAyg(>L2~|!4 zIwVpepQ5rrC`GkKP=ujI5#EUa4Uee(GDGP^IA^3LXDfiVn^i(;rnLMb1<>}ryp+Ve z<VvI(3{<@$#u_nNSn#1h(5O{9Xf#|5G!zJ0z6lx#gcPjEeRQT<OqnU*u{dZy9X#W? zk_o&x;}&aKL4H1TF&n5M3LYT80;<74wGz02Ziv!APX>7;9?9iM3*cd6A1j%P4uFQ^ zIA9ZVw^-tHlHz}{Mpqlu#{Ocg!SEGm6c%KCF{-aXqs5SsSkSCI#8)dBA)&Jf)aU_i z_gu*Yshz>2d*D_O$Rw~wz9V}Cv>+L@lci`O0|Ub^W(7OU<~YdDAkBmZk&3{U<7$5M zF(J*A<Mt}3V-Ai$zFNi(h6N0uJ|M)4;O50j#-hC-FM^iNuVe%VC|DdZN+p0|52*Ek zj6r=Oa2pxqsdQM+5@G~6ln?~S%iv)qVJ6sQ4aycsJ$#W48DsK8X)1!c0w4^wA2Qxm z!zhm8hLudW7%OfuRw6kF?hIv^GgdN2aVHiRm*y6L7H<~2{SpUF@+)Y7IzkGW#qbPN z@{1!nC$l8AC^6?3e{y0TXdbsHH8;O3H6@m?!$eRVR&*8=>R1BD0Ol%;C<TQu*sGwn zHaJRg_j*9liSB1mrx;{9bm9feMgXif)G&hb1~kbWgS#4+4aO+R1r$B7<N{)YFxU^U z=pr&yOmT(^JWqpWR=}Ym1R6s`Y+Zo592C?mnZUze;C%~4;DspQo(xC|k={X8!Gq|R zBx+K~EP|CuB~*)Xd#ru~Wh|7?!W?W^$#{z~1wEEQeQ3Ceu)$c6CyK3pi6tY_GPr&y zwo<6BRecGX+5aU48n?~Uh5HXl!7nWgjS9*6WvNB5!A@29{9W;LNOVU!XsQ)~A{i3c z;I1aPpaD%3-D1m6$}-AKxy6>5Vg%an2U?|bi`_Y~D8)4|`4(F$Xzy|{WXS@kt8$AC zya>Fw2vjXWRy^ziB}DM>L_UZGnj-=aeBNS9ttbJlI|QwBL99-^#axwH0GU1lFOC3B zHi5@YZ?P7GcddhxKBSWj+OT<x2fX*I7_y_Zs2pTP8Hkt;B0$rqno5w4GI-$h5U3hr z&&y9Q$}cUr#hhB046gldF((!k-C_fG9wEyoZn1;*m6qltLOcQL_}ya7Ny^DAxy78E zV|a_HqzuF`0-0fWivzS*1T+H)VSys?78ghi5=Y>Xrduo}sTC!+SV7xYif^$NfaWSe zD?C6OP{70d^Fb~K&r#cfT**?B3fd6^ngaxFZ%ximO-sAQnwFbcTzrcMWH!jY_{1DY zWxbLGTz@0ge4rW^oL6oMpcmJNLDe8AJQC+F9o`50xG<QNs0r5GicSr~;FrN9d* zxxhRwMj=KuMioXiW*+c7m;j>$qa33QBNvk%lNh52vluf6BOfbh-4hG50C*`TXe?QX znU4`<5@^MU0264ltQeyJlMtf}lNuu*6Az;pGYcaJBOjvzlLVt2vl^2c69*#~6CaZl zvjP)nEtMQ&l`P(N21+j(G?)tSB`;tb?In-)l0kjfq1{VPXQ&lH=_x;}6-DVOUxJQ` zFvT9h*;5uHx~D9T(o>d5XQ-6~_mstvddgCe-XWxS4DKm+LD&#+NrqbK8VOKOa{+se z6sQLb?J3Wzkpzjrd&&|p^&nA@x(UpM0VUEk65#$AV~wOZL#=oUQwmcFQ;m2tBQpaN zL#<4$Y^_|43`8WGVFF80Ta9>%V2uo@uUsQuBZJa&mah@bk|~je_MGLh_MBOY0y>zo z<e+_JFHkQU+E<nZ_m#y!edQYYTJbzBBy~j?>cD+!kp1QiwK6rb;QpuV1m?mU5VzLI zWiw4+DcS%wgDIP30&CHV68RK~1wu7qkRE&954apl(Ww&o6iI|!jmQG_5``K$h*=U0 zpq@9VX9@1bE1>n_6|&hTuolVHD8R!P(Tj(Qq4nb7q7zsO-;~IvNP&9BDbnEHxdJFQ zV7>2JMJ&B|P!F8}<Oa}W8^|xUN;QHtiY2lsvLJK7y?8lrFJ1{|4r(tRski=S0#o55 zq~5wLq?cZzlA-`I3$3@VTq2*M2=A>cL3``U7`=5+e_R>dTZhXb_10Av2&Jfjd+W-e zFa<CAW2zBpW&|xvoxoN!r$!jYDv?i72dOSm1IddZdi)w-QFSy?O|Ym2M6?8KCd>x# zatW=X-Jt58%ANb7Js?9to#3MVAQozSIUKpcUxm7G0KJJ|gqQ_H%&`?61Q~i5L>vJT zN3l(kz?=DmyY5BcP71j140iKzkfEq<u0nQm(Fu@(Q`l?;wF*J~Map{WlaX!1=+?n~ zMxdu&bQ<JLaJTCWh=uB-sVH_p+TaKh)R_QxyFeZyvX?#s)-9<rz}sX(iBmGW={S4C zt6`2Py1>A|;8t`A<nzm*nNa@BVg;n$SZvW%kkmy`EP}dCFu#M?APn|Bs6SG}P{T-M zr}G%ha*Q|w1tZu*3cBJT)8YM0;%p$XD}E9sO@Kmy8liF;XQ;qaG7(+zBJd_bTzznm z9f+`@mdh_<bvY<+fNTKACn$wrcC?E?>rk+DwBaTe-2kP&n;_yAh`0?R?tqB9pu{Q# zSp@~@w}D1tL96${%Y;;`Kyrx{@Q!(MC8``~VG%ZcnwmJf*F~U(fkpR0wu3s>MGrtM z@J2xJCIC>6y66!|jBt0l=rKqgYG1nO2}lgQ6cf>xehLx;Wee~g18`6J7He8gequ?{ zGmsRPE;RbovkF>|x(GCsQ1k+%8+`Nz=)k(7S0FBEyrSqehz07RL6&E}0de1gh<703 zJ&5=KB0hqMPapzxz)BQHW>HZ*cv}y6;Rxs`ucEIYS@71z?;sXhzqudWZ%!-C$tn5) zlKu%Ieu0SJAmR^*02ju8K`c;paZ3=R;&TJ#MbKP2)jG#);Lb54;qI{rBOjv-iy9*j zqZkut!?+j|2O|#?A0rQ=0BFkvqZE3lnUMpj!z{)q!6?Qk##kkXzZTGB^YinAo}8x1 z=%&dEo+NiG>H!4`Xo)X)qlTtvktj$2RM!=WgIJ)Q0FZO(K&L!G&UOGD+yLI{UStGP z0@@@9Sq}_a=34|l`vbJ_tO&IFpa?V)6vghEmmC6GvCC1Am|0YwS)5w*52Te5wCDyL zIS2w2Lq(Dd3=E*n^u?f^pr8Zq7(nF!1H*qVO%4tYI}UaZ6%K6<el9Z(6$VCz|6FPu z!W@zu`~vk1EMUEw;-K^P(n?E8i&Ep`i$DcPln(faKRu)){`64741C0&9{3cp%zVf} zSm5(TIKgKx=I7@WgV$=@V#+U$;wq>tPAt(&PAp1^5{0l+D-uEH))nhP7UzN2{zdUZ z<Y2=};2aGq#9$JT(XHYlP!_($TwGavi!&K^IwSZDja%HFxdr(}CEzm)ZZT!%LyiFg wg%47R2M#?@z}@1ofgCbu2ij%>o=D+g<YD9hV-P6-Vlgo>3V=4QvG5510OlNemH+?% diff --git a/pages/application/DecisionTree/utils/__pycache__/dtviz.cpython-38.pyc b/pages/application/DecisionTree/utils/__pycache__/dtviz.cpython-38.pyc deleted file mode 100644 index 0ad57d58fe6e5a6d997f8691f57169a46e1f1829..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1471 zcmWIL<>g{vU|?{%#GSO3je+4Yh=Yuo7#J8F7#J9eEf^RWQW#Pga~N_NqZk=MY^EHh zD5eyK6s8>JC}uFt62$_hS)*7}m{V9<7^2uxn1UHJSzm(8)MUKH<C2=3S)7@l7gCg( z>X*z65{6<<1_lNY28k7SF)}ceFk~^NfFM&ZQ!R4}a}9GAQwoa>LkepRa~5+7n+-z; zLl%n*L##tBOAT`tYYj^Z`vNwQSPhFLLl%1t%R;6Uj$S54h8pGt95pNp8EaWnIBQt5 zI8(T67*e>wEFK$%LX8fFEG`#@SUWUxxIpG$HH8<f7uA#kkj*X(v0FMBL97l&Nro(L z7lznHwO}_v_%&>j47Kbv%nNur7;D%ULfpm+Vb^k$@YS$2Gp6umGfiMD(&}Ky;x7?^ zo5fxt0G8#KWJqUfW|U;8;mG1l5wKy%(@GIsz?LGkK(L*moiU9mML0#Ig#)76g`t^o zA>%?unA#LkFpI~AAw>+#61QO}JXW}^hB-?pMIuGAmk|_NS;93;HH;}zps-7k4rb7l zsnX_BfP%2h;?l&N%&Js{L<N@+h4Rdj42ATf#Da{n%ql&wst`@4D2|lOqSWM))RbFn z#U(|V$tAbg)AEbT6N^%AF{fk}-C_!HiefKH%*#&6EUMzFHmHrs)2KGA)zoCX#hR0t zl$sO8mYi9XoRfNswKyZOAoV3L0|Ub?j<o!|lH$y&)LU%Dg{6r_sV^BB7#NDc1Umx* zLltwiLG3L@u4Ir1G+i-(k}w+s1A{Oq8LKfcFr+ioFvQB$f})9`1Vl1{BZs+$5fZQy znF?8g8CEiAvfN@#E6YtRxW!shS^#oNUVchy@hz6L)Wi}^rdv#U2Dg~Y5_48E-C{~9 zxy6)cSj5M`!0;<dKO;XkRX;f;wIIK=s6^kXv?w*PRKFmvC^J#FpeR2pwL~{1HMJlo zwJfzrzaTL^wOBu~pdcqRIk6-&KTjVykLZ_{Wabp>r<8y~x1jPCmrYJ)aY=H1Zh@US z$UiI$3=C`xObk`BWw3bBv&qR%PRuE`(?h6z3CiW^vu1+8OZzC!#G<17@?uaxGN<I1 zXfobnDJaTM*JQfIQjl1Zaf>Z2zbH4c#7|S?7FR(fEOw*V9Nj??9mScNlAao$SW;3H z#RZlGvx@{77#N~B5>ry*LE%&+2Z|aYaI~eTmVm`>v4Aw)Vl7E6$}PUdQkIxg4AB5G zFp2}D48*#{oRgYY#1GOh0MgHrm7ke+i!CRyGQYGWiY28ev0Rh0NDL&!oR(X1i!Hyj zq@c9q7GwG?#=KiBAlq*-mfd2?GrGkBG7lUrMWP_{BtV)t;b8?bqu2<PQds#IxtO?^ zxftabxfr<^d6>BvW#B9UMgc}1Mjl2!MlB{SMlMD^W*){OeFg>wP4-(_;0V-1ia<T& z2-E{dpk7KzQEF-tC@J1zOHVDyFDSXilwVvV4{{E3ab+>sG>AJvX$ZmsJC?&HH$SB` SC)Ey=1ByZM$iv71fvf;PS%oS9 diff --git a/pages/application/DecisionTree/utils/dtree.py b/pages/application/DecisionTree/utils/dtree.py index 32adfde..fd881ee 100644 --- a/pages/application/DecisionTree/utils/dtree.py +++ b/pages/application/DecisionTree/utils/dtree.py @@ -351,8 +351,7 @@ class DecisionTree(): # returning the set of sets with no duplicates return list(dict.fromkeys(sets)) - def explain(self, inst, enum=1, pathlits=False, solver='g3', xtype='abd', - htype='sorted'): + def explain(self, inst, enum=5, pathlits=False, solver='g3', htype='sorted'): """ Compute a given number of explanations. """ @@ -364,14 +363,16 @@ class DecisionTree(): else: # input expected by Yacine - 'value1,value2,...' inst = list(map(lambda i : tuple(['f{0}'.format(i[0]), int(i[1])]), [(i, j) for i,j in enumerate(inst)])) - + print(inst) inst_orig = inst[:] path, term, depth = self.execute(inst, pathlits) + print(path) + explanation = str(inst) + "\n \n" #print('c instance: IF {0} THEN class={1}'.format(' AND '.join([self.fvmap[p] for p in inst_orig]), term)) #print(term) - print('c instance: IF {0} THEN class={1}'.format(' AND '.join([self.fvmap[ inst_orig[self.feids[self.nodes[n].feat]] ] for n in path]), term)) - print('c path len:', depth) + explanation += 'c instance: IF {0} THEN class={1}'.format(' AND '.join([self.fvmap[ inst_orig[self.feids[self.nodes[n].feat]] ] for n in path]), term) + "\n" + explanation +='c path len:'+ str(depth)+ "\n \n \n" if self.ohmap.dir: f2v = {fv[0]: fv[1] for fv in inst} @@ -383,30 +384,34 @@ class DecisionTree(): # computing the sets to hit to_hit = self.prepare_sets(inst, term) - if xtype == 'abd': - self.enumerate_abductive(to_hit, enum, solver, htype, term) - else: - self.enumerate_contrastive(to_hit, term) + explanation += "Abductive explanation : " + "\n \n" + explanation += self.enumerate_abductive(to_hit, enum, solver, htype, term) + explanation += "Contrastive explanation : "+ "\n \n" + explanation += self.enumerate_contrastive(to_hit, term) + + return explanation def enumerate_abductive(self, to_hit, enum, solver, htype, term): """ Enumerate abductive explanations. """ - + explanation = "" with Hitman(bootstrap_with=to_hit, solver=solver, htype=htype) as hitman: expls = [] for i, expl in enumerate(hitman.enumerate(), 1): - print('c expl: IF {0} THEN class={1}'.format(' AND '.join([self.fvmap[p] for p in sorted(expl, key=lambda p: p[0])]), term)) + explanation += 'c expl: IF {0} THEN class={1}'.format(' AND '.join([self.fvmap[p] for p in sorted(expl, key=lambda p: p[0])]), term) + "\n" expls.append(expl) if i == enum: break - print('c nof expls:', i) - print('c min expl:', min([len(e) for e in expls])) - print('c max expl:', max([len(e) for e in expls])) - print('c avg expl: {0:.2f}'.format(sum([len(e) for e in expls]) / len(expls))) + explanation += 'c nof expls:' + str(i)+ "\n" + explanation += 'c min expl:'+ str( min([len(e) for e in expls]))+ "\n" + explanation += 'c max expl:'+ str( max([len(e) for e in expls]))+ "\n" + explanation += 'c avg expl: {0:.2f}'.format(sum([len(e) for e in expls]) / len(expls))+ "\n \n \n" + + return explanation def enumerate_contrastive(self, to_hit, term): """ @@ -424,14 +429,17 @@ class DecisionTree(): to_hit = [set(s) for s in to_hit] to_hit.sort(key=lambda s: len(s)) expls = list(reduce(process_set, to_hit, [])) - + explanation = "" for expl in expls: - print('c expl: IF {0} THEN class!={1}'.format(' OR '.join(['!{0}'.format(self.fvmap[p]) for p in sorted(expl, key=lambda p: p[0])]), term)) + explanation += 'c expl: IF {0} THEN class!={1}'.format(' OR '.join(['!{0}'.format(self.fvmap[p]) for p in sorted(expl, key=lambda p: p[0])]), term)+ "\n" + + + explanation +='c nof expls:'+ str(len(expls))+ "\n" + explanation +='c min expl:'+ str( min([len(e) for e in expls]))+ "\n" + explanation +='c max expl:'+ str( max([len(e) for e in expls]))+ "\n" + explanation +='c avg expl: {0:.2f}'.format(sum([len(e) for e in expls]) / len(expls))+ "\n" - print('c nof expls:', len(expls)) - print('c min expl:', min([len(e) for e in expls])) - print('c max expl:', max([len(e) for e in expls])) - print('c avg expl: {0:.2f}'.format(sum([len(e) for e in expls]) / len(expls))) + return explanation def execute_path(self, path): """ diff --git a/pages/application/DecisionTree/utils/dtviz.py b/pages/application/DecisionTree/utils/dtviz.py index bc91989..f91189d 100755 --- a/pages/application/DecisionTree/utils/dtviz.py +++ b/pages/application/DecisionTree/utils/dtviz.py @@ -19,7 +19,7 @@ import sys # #============================================================================== -def visualize(dt, fmt, output): +def visualize(dt): """ Visualize a DT with graphviz. """ @@ -57,6 +57,67 @@ def visualize(dt, fmt, output): edge.attr['arrowsize'] = 0.8 # saving file + g.in_edges g.layout(prog='dot') - g.draw(path=output, format=fmt) + return(g.to_string()) +# +#============================================================================== +def visualize_instance(dt, instance): + """ + Visualize a DT with graphviz and plot the running instance. + """ + if '=' in instance[0]: + instance = list(map(lambda i: tuple([i[0], int(i[1])]), [i.split('=') for i in instance])) + + else: + instance = list(map(lambda i : tuple(['f{0}'.format(i[0]), int(i[1])]), [(i, j) for i,j in enumerate(instance)])) + + #path that follows the instance - colored in blue + path, term, depth = dt.execute(instance) + edges_instance = [] + for i in range (len(path)-1) : + edges_instance.append((path[i], path[i+1])) + edges_instance.append((path[-1],"term:"+term)) + + g = pygraphviz.AGraph(directed=True, strict=True) + g.edge_attr['dir'] = 'forward' + + g.graph_attr['rankdir'] = 'TB' + + # non-terminal nodes + for n in dt.nodes: + g.add_node(n, label='{0}\\n({1})'.format(dt.nodes[n].feat, n)) + node = g.get_node(n) + node.attr['shape'] = 'circle' + node.attr['fontsize'] = 13 + + # terminal nodes + for n in dt.terms: + g.add_node(n, label='{0}\\n({1})'.format(dt.terms[n], n)) + node = g.get_node(n) + node.attr['shape'] = 'square' + node.attr['fontsize'] = 13 + + # transitions + for n1 in dt.nodes: + for v in dt.nodes[n1].vals: + n2 = dt.nodes[n1].vals[v] + n2_type = g.get_node(n2).attr['shape'] + g.add_edge(n1, n2) + edge = g.get_edge(n1, n2) + if len(v) == 1: + edge.attr['label'] = dt.fvmap[tuple([dt.nodes[n1].feat, tuple(v)[0]])] + else: + edge.attr['label'] = '{0}'.format('\n'.join([dt.fvmap[tuple([dt.nodes[n1].feat, val])] for val in tuple(v)])) + + #instance path in blue + if ((n1,n2) in edges_instance) or (n2_type=='square' and (n1, "term:"+ dt.terms[n2]) in edges_instance): + edge.attr['color'] = 'blue' + + edge.attr['fontsize'] = 10 + edge.attr['arrowsize'] = 0.8 + + # saving file + g.layout(prog='dot') + return(g.to_string()) diff --git a/pages/application/DecisionTree/decision_tree_explicability.py b/pages/application/NaiveBayes/NaiveBayesComponent.py similarity index 100% rename from pages/application/DecisionTree/decision_tree_explicability.py rename to pages/application/NaiveBayes/NaiveBayesComponent.py diff --git a/pages/application/__pycache__/layout_application.cpython-38.pyc b/pages/application/__pycache__/layout_application.cpython-38.pyc deleted file mode 100644 index 3d0fbeba535a0e13a76bce789577bdda2964becc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4500 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 aF!3;PXfiP}F*5ySV`pMu`p?43rwITw+#HMm diff --git a/pages/application/__pycache__/utils_data.cpython-38.pyc b/pages/application/__pycache__/utils_data.cpython-38.pyc deleted file mode 100644 index 3ce531e0f54819e127c16fbc8130884baa58077d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1467 zcmWIL<>g{vU|=|PjXP;M3j@Pr5C<7EF)%PVFfcF_moP9eq%fo~<}l<kMlt3xMKR?v zM=|HJM6u+uMzJ!2<d||8q98O|6nhFo3R4b86bG2*jN$^*+)+F!3@OYxyt#Z)d|*8+ zIsCZ-Q3APwQG#GTQ;txSPzq}bTMI*!a0*i}gC_e+kb5*)Z!xDNC*NYpD9O#a#p;<? zP+D?}&A+q+L~|r3=Hw(LCTII;GTvg($t*5O$t+sQbc-#sI4v_L^%hH3er8@WBgh;m z=3ro8U}IolU<P@Ofsuisogs}eg)xPxg`<R_gt3F6nX#GCg`t_TmNAo|mMN2=mN}E5 zmL-#+mK9k(c2_4;2SW<8Btr+2Btr^|BttD55}#F)p@yY~&4vL<j7^fEh7}~1!k)s^ z!cc;&pF@%XVrvZ(NCn7NkZm9yNCqSqYgfw-;n%QBGITPwGqf|dGo>-7aDv^AY#Ns& zLkFWc%tYo6MjM906*bH?%pkK1&1x8Hm};17SZY|)Sc4ffx&2TA2o&!u3=9mypa7C# zU|>jRs9}f|sb%b7NMVp<s9}_3NMV#@n8;Mf63n2<bc-b=u_W;pdu~pANo7ImN{|qf zp201~OijijZUzR1B3=dthF?DV8Tq-X`pGG&1^J~#CHhXKMX8CU`UQDKnTfguMfq8& zCAul8sRcQyWvNB_1&QgY#rlZ_1v#0?i6xo&dHSU#nK{MrAglBWDsOSwfTB7%Kexb+ z2jonUd)XM67^)OfD@uwIlS?2fZF2IH6LX5~^bpD+P6BDV#R<}%pO>0fa!Wk50&G=M zW=>{FWqe{zdVW!6Nk%S4z<^RsFvvy$Sio@9GS)Cc0t4(*FtL)Uh>wAR;TB^Wm<u65 zT8ac27#PGr2Ed#g#aB|4n3<QF5}%u&lA2SDVI?TBg5yFDW~Ev!BPek|;sc!g7$Ff} z!`RH!%vi(%bC4zz*t@qlGxLf|67!N%i;F<Xzet3Efng;xM2;O4rp2ixMf|Y1DuO#y z3gl3b{h+XwV<-YST$A}0Yf)ledg?9aoYcG`b_NCpO%8}rJb8(^sl`x#+!9F1gvJ9* zL>M6gchD_<upm4tif?g(1wau}1db-gEJ*N)gWLyl+ASXBNahB)Q2~^>8KoFmAdpFb zQGii|QG`*6k%du&k%!TMk&CHF5#&@6P<k!SC<3MOB2a7=fg+-a6=XQbr?(_i5{on9 zlk)RRic5+T3*wPn#+;Ire2c9tvlvtyu@xldr6d;LVk$_v#hO=|TTpq6DX-ubQ+_cx zOm4B~7wZ)ymShxhf{X?gwYNAka|`l|N^&xjz;1zf3zRz`EU@1=Y;yBcN^?@}KxJ03 T5Ca2)3?l~|@-PZ8axeh^d}~nE diff --git a/pages/application/component.svg b/pages/application/component.svg deleted file mode 100644 index ad48872..0000000 --- a/pages/application/component.svg +++ /dev/null @@ -1,467 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" - "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<!-- Generated by graphviz version 2.43.0 (0) - --> -<!-- Pages: 1 --> -<svg width="749pt" height="797pt" - viewBox="0.00 0.00 749.00 796.82" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> -<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 792.82)"> -<polygon fill="white" stroke="transparent" points="-4,4 -4,-792.82 745,-792.82 745,4 -4,4"/> -<!-- 1 --> -<g id="node1" class="node"> -<title>1</title> -<ellipse fill="none" stroke="black" cx="217" cy="-763.32" rx="25.5" ry="25.5"/> -<text text-anchor="middle" x="217" y="-766.92" font-family="Times,serif" font-size="13.00">f7</text> -<text text-anchor="middle" x="217" y="-752.92" font-family="Times,serif" font-size="13.00">(1)</text> -</g> -<!-- 2 --> -<g id="node2" class="node"> -<title>2</title> -<ellipse fill="none" stroke="black" cx="180" cy="-664" rx="27" ry="27"/> -<text text-anchor="middle" x="180" y="-667.6" font-family="Times,serif" font-size="13.00">f11</text> -<text text-anchor="middle" x="180" y="-653.6" font-family="Times,serif" font-size="13.00">(2)</text> -</g> -<!-- 1->2 --> -<g id="edge1" class="edge"> -<title>1->2</title> -<path fill="none" stroke="black" d="M208.23,-739.26C203.44,-726.66 197.44,-710.87 192.2,-697.1"/> -<polygon fill="black" stroke="black" points="194.68,-695.74 189.22,-689.25 189.44,-697.72 194.68,-695.74"/> -<text text-anchor="middle" x="213.5" y="-711.87" font-family="Times,serif" font-size="10.00">f7=0</text> -</g> -<!-- 9 --> -<g id="node3" class="node"> -<title>9</title> -<ellipse fill="none" stroke="black" cx="268" cy="-664" rx="25.5" ry="25.5"/> -<text text-anchor="middle" x="268" y="-667.6" font-family="Times,serif" font-size="13.00">f8</text> -<text text-anchor="middle" x="268" y="-653.6" font-family="Times,serif" font-size="13.00">(9)</text> -</g> -<!-- 1->9 --> -<g id="edge2" class="edge"> -<title>1->9</title> -<path fill="none" stroke="black" d="M228.57,-740.24C235.68,-726.67 244.87,-709.14 252.59,-694.41"/> -<polygon fill="black" stroke="black" points="255.24,-695.38 256.47,-686.99 250.28,-692.78 255.24,-695.38"/> -<text text-anchor="middle" x="257.5" y="-711.87" font-family="Times,serif" font-size="10.00">f7=1</text> -</g> -<!-- 3 --> -<g id="node4" class="node"> -<title>3</title> -<ellipse fill="none" stroke="black" cx="72" cy="-559.01" rx="25.5" ry="25.5"/> -<text text-anchor="middle" x="72" y="-562.61" font-family="Times,serif" font-size="13.00">f6</text> -<text text-anchor="middle" x="72" y="-548.61" font-family="Times,serif" font-size="13.00">(3)</text> -</g> -<!-- 2->3 --> -<g id="edge3" class="edge"> -<title>2->3</title> -<path fill="none" stroke="black" d="M160.94,-644.82C142.83,-627.55 115.53,-601.52 95.96,-582.86"/> -<polygon fill="black" stroke="black" points="97.87,-580.82 90.15,-577.32 94.01,-584.87 97.87,-580.82"/> -<text text-anchor="middle" x="148.5" y="-611.13" font-family="Times,serif" font-size="10.00">f11=3</text> -</g> -<!-- 6 --> -<g id="node5" class="node"> -<title>6</title> -<ellipse fill="none" stroke="black" cx="180" cy="-559.01" rx="25.5" ry="25.5"/> -<text text-anchor="middle" x="180" y="-562.61" font-family="Times,serif" font-size="13.00">f6</text> -<text text-anchor="middle" x="180" y="-548.61" font-family="Times,serif" font-size="13.00">(6)</text> -</g> -<!-- 2->6 --> -<g id="edge4" class="edge"> -<title>2->6</title> -<path fill="none" stroke="black" d="M180,-637.01C180,-623.56 180,-607.01 180,-592.7"/> -<polygon fill="black" stroke="black" points="182.8,-592.57 180,-584.57 177.2,-592.57 182.8,-592.57"/> -<text text-anchor="middle" x="195.5" y="-611.13" font-family="Times,serif" font-size="10.00">f11=2</text> -</g> -<!-- 10 --> -<g id="node6" class="node"> -<title>10</title> -<ellipse fill="none" stroke="black" cx="268" cy="-559.01" rx="31" ry="31"/> -<text text-anchor="middle" x="268" y="-562.61" font-family="Times,serif" font-size="13.00">f3</text> -<text text-anchor="middle" x="268" y="-548.61" font-family="Times,serif" font-size="13.00">(10)</text> -</g> -<!-- 9->10 --> -<g id="edge5" class="edge"> -<title>9->10</title> -<path fill="none" stroke="black" d="M268,-638.33C268,-626.4 268,-611.82 268,-598.48"/> -<polygon fill="black" stroke="black" points="270.8,-598.37 268,-590.37 265.2,-598.37 270.8,-598.37"/> -<text text-anchor="middle" x="280.5" y="-611.13" font-family="Times,serif" font-size="10.00">f8=0</text> -</g> -<!-- 23 --> -<g id="node7" class="node"> -<title>23</title> -<ellipse fill="none" stroke="black" cx="461" cy="-559.01" rx="31" ry="31"/> -<text text-anchor="middle" x="461" y="-562.61" font-family="Times,serif" font-size="13.00">f9</text> -<text text-anchor="middle" x="461" y="-548.61" font-family="Times,serif" font-size="13.00">(23)</text> -</g> -<!-- 9->23 --> -<g id="edge6" class="edge"> -<title>9->23</title> -<path fill="none" stroke="black" d="M290.14,-651.18C323.6,-633.33 387.34,-599.32 426.8,-578.27"/> -<polygon fill="black" stroke="black" points="428.14,-580.73 433.87,-574.49 425.49,-575.79 428.14,-580.73"/> -<text text-anchor="middle" x="383.5" y="-611.13" font-family="Times,serif" font-size="10.00">f8=1</text> -</g> -<!-- 4 --> -<g id="node8" class="node"> -<title>4</title> -<polygon fill="none" stroke="black" points="36,-467.79 0,-467.79 0,-431.79 36,-431.79 36,-467.79"/> -<text text-anchor="middle" x="18" y="-453.39" font-family="Times,serif" font-size="13.00">0</text> -<text text-anchor="middle" x="18" y="-439.39" font-family="Times,serif" font-size="13.00">(4)</text> -</g> -<!-- 3->4 --> -<g id="edge7" class="edge"> -<title>3->4</title> -<path fill="none" stroke="black" d="M55.85,-538.8C49.33,-530.34 42.15,-520.03 37,-509.9 31.55,-499.18 27.25,-486.58 24.12,-475.77"/> -<polygon fill="black" stroke="black" points="26.78,-474.88 21.96,-467.91 21.38,-476.37 26.78,-474.88"/> -<text text-anchor="middle" x="52.5" y="-501.9" font-family="Times,serif" font-size="10.00">f6=31</text> -</g> -<!-- 5 --> -<g id="node9" class="node"> -<title>5</title> -<polygon fill="none" stroke="black" points="90,-467.79 54,-467.79 54,-431.79 90,-431.79 90,-467.79"/> -<text text-anchor="middle" x="72" y="-453.39" font-family="Times,serif" font-size="13.00">0</text> -<text text-anchor="middle" x="72" y="-439.39" font-family="Times,serif" font-size="13.00">(5)</text> -</g> -<!-- 3->5 --> -<g id="edge8" class="edge"> -<title>3->5</title> -<path fill="none" stroke="black" d="M72,-533.41C72,-516.2 72,-493.27 72,-475.94"/> -<polygon fill="black" stroke="black" points="74.8,-475.93 72,-467.93 69.2,-475.93 74.8,-475.93"/> -<text text-anchor="middle" x="87.5" y="-501.9" font-family="Times,serif" font-size="10.00">f6=30</text> -</g> -<!-- 7 --> -<g id="node10" class="node"> -<title>7</title> -<polygon fill="none" stroke="black" points="144,-467.79 108,-467.79 108,-431.79 144,-431.79 144,-467.79"/> -<text text-anchor="middle" x="126" y="-453.39" font-family="Times,serif" font-size="13.00">1</text> -<text text-anchor="middle" x="126" y="-439.39" font-family="Times,serif" font-size="13.00">(7)</text> -</g> -<!-- 6->7 --> -<g id="edge9" class="edge"> -<title>6->7</title> -<path fill="none" stroke="black" d="M162.93,-540.03C155.59,-531.47 147.46,-520.74 142,-509.9 136.67,-499.31 132.94,-486.72 130.44,-475.89"/> -<polygon fill="black" stroke="black" points="133.17,-475.25 128.76,-468.01 127.69,-476.42 133.17,-475.25"/> -<text text-anchor="middle" x="160.5" y="-501.9" font-family="Times,serif" font-size="10.00">f6=133</text> -</g> -<!-- 8 --> -<g id="node11" class="node"> -<title>8</title> -<polygon fill="none" stroke="black" points="204,-467.79 168,-467.79 168,-431.79 204,-431.79 204,-467.79"/> -<text text-anchor="middle" x="186" y="-453.39" font-family="Times,serif" font-size="13.00">0</text> -<text text-anchor="middle" x="186" y="-439.39" font-family="Times,serif" font-size="13.00">(8)</text> -</g> -<!-- 6->8 --> -<g id="edge10" class="edge"> -<title>6->8</title> -<path fill="none" stroke="black" d="M181.38,-533.41C182.34,-516.2 183.62,-493.27 184.59,-475.94"/> -<polygon fill="black" stroke="black" points="187.39,-476.07 185.04,-467.93 181.8,-475.76 187.39,-476.07"/> -<text text-anchor="middle" x="202.5" y="-501.9" font-family="Times,serif" font-size="10.00">f6=132</text> -</g> -<!-- 11 --> -<g id="node12" class="node"> -<title>11</title> -<ellipse fill="none" stroke="black" cx="253" cy="-449.79" rx="31" ry="31"/> -<text text-anchor="middle" x="253" y="-453.39" font-family="Times,serif" font-size="13.00">f12</text> -<text text-anchor="middle" x="253" y="-439.39" font-family="Times,serif" font-size="13.00">(11)</text> -</g> -<!-- 10->11 --> -<g id="edge11" class="edge"> -<title>10->11</title> -<path fill="none" stroke="black" d="M263.82,-528.15C262.13,-516.05 260.15,-501.93 258.36,-489.09"/> -<polygon fill="black" stroke="black" points="261.09,-488.4 257.21,-480.87 255.55,-489.18 261.09,-488.4"/> -<text text-anchor="middle" x="273.5" y="-501.9" font-family="Times,serif" font-size="10.00">f3=1</text> -</g> -<!-- 20 --> -<g id="node13" class="node"> -<title>20</title> -<ellipse fill="none" stroke="black" cx="333" cy="-449.79" rx="31" ry="31"/> -<text text-anchor="middle" x="333" y="-453.39" font-family="Times,serif" font-size="13.00">f4</text> -<text text-anchor="middle" x="333" y="-439.39" font-family="Times,serif" font-size="13.00">(20)</text> -</g> -<!-- 10->20 --> -<g id="edge12" class="edge"> -<title>10->20</title> -<path fill="none" stroke="black" d="M283.73,-532.06C292.53,-517.55 303.59,-499.31 312.99,-483.79"/> -<polygon fill="black" stroke="black" points="315.43,-485.17 317.18,-476.88 310.64,-482.27 315.43,-485.17"/> -<text text-anchor="middle" x="315.5" y="-501.9" font-family="Times,serif" font-size="10.00">f3=0</text> -</g> -<!-- 24 --> -<g id="node14" class="node"> -<title>24</title> -<ellipse fill="none" stroke="black" cx="461" cy="-449.79" rx="31" ry="31"/> -<text text-anchor="middle" x="461" y="-453.39" font-family="Times,serif" font-size="13.00">f5</text> -<text text-anchor="middle" x="461" y="-439.39" font-family="Times,serif" font-size="13.00">(24)</text> -</g> -<!-- 23->24 --> -<g id="edge13" class="edge"> -<title>23->24</title> -<path fill="none" stroke="black" d="M461,-527.86C461,-515.87 461,-501.93 461,-489.22"/> -<polygon fill="black" stroke="black" points="463.8,-489.09 461,-481.09 458.2,-489.09 463.8,-489.09"/> -<text text-anchor="middle" x="476.5" y="-501.9" font-family="Times,serif" font-size="10.00">f9=23</text> -</g> -<!-- 27 --> -<g id="node15" class="node"> -<title>27</title> -<ellipse fill="none" stroke="black" cx="574" cy="-449.79" rx="31" ry="31"/> -<text text-anchor="middle" x="574" y="-453.39" font-family="Times,serif" font-size="13.00">f4</text> -<text text-anchor="middle" x="574" y="-439.39" font-family="Times,serif" font-size="13.00">(27)</text> -</g> -<!-- 23->27 --> -<g id="edge14" class="edge"> -<title>23->27</title> -<path fill="none" stroke="black" d="M483.33,-536.83C501.2,-519.87 526.39,-495.97 545.68,-477.66"/> -<polygon fill="black" stroke="black" points="547.84,-479.47 551.72,-471.93 543.99,-475.4 547.84,-479.47"/> -<text text-anchor="middle" x="536.5" y="-501.9" font-family="Times,serif" font-size="10.00">f9=22</text> -</g> -<!-- 13 --> -<g id="node16" class="node"> -<title>13</title> -<ellipse fill="none" stroke="black" cx="182" cy="-340.56" rx="31" ry="31"/> -<text text-anchor="middle" x="182" y="-344.16" font-family="Times,serif" font-size="13.00">f5</text> -<text text-anchor="middle" x="182" y="-330.16" font-family="Times,serif" font-size="13.00">(13)</text> -</g> -<!-- 11->13 --> -<g id="edge15" class="edge"> -<title>11->13</title> -<path fill="none" stroke="black" d="M230,-428.79C221.66,-420.68 212.65,-410.84 206,-400.68 201.45,-393.73 197.51,-385.77 194.2,-378.01"/> -<polygon fill="black" stroke="black" points="196.7,-376.72 191.12,-370.34 191.5,-378.81 196.7,-376.72"/> -<text text-anchor="middle" x="227.5" y="-392.68" font-family="Times,serif" font-size="10.00">f12=162</text> -</g> -<!-- 12 --> -<g id="node17" class="node"> -<title>12</title> -<polygon fill="none" stroke="black" points="275,-362.56 231,-362.56 231,-318.56 275,-318.56 275,-362.56"/> -<text text-anchor="middle" x="253" y="-344.16" font-family="Times,serif" font-size="13.00">1</text> -<text text-anchor="middle" x="253" y="-330.16" font-family="Times,serif" font-size="13.00">(12)</text> -</g> -<!-- 11->12 --> -<g id="edge16" class="edge"> -<title>11->12</title> -<path fill="none" stroke="black" d="M253,-418.64C253,-403.72 253,-385.79 253,-370.98"/> -<polygon fill="black" stroke="black" points="255.8,-370.67 253,-362.67 250.2,-370.67 255.8,-370.67"/> -<text text-anchor="middle" x="274.5" y="-392.68" font-family="Times,serif" font-size="10.00">f12=163</text> -</g> -<!-- 21 --> -<g id="node18" class="node"> -<title>21</title> -<polygon fill="none" stroke="black" points="339,-362.56 295,-362.56 295,-318.56 339,-318.56 339,-362.56"/> -<text text-anchor="middle" x="317" y="-344.16" font-family="Times,serif" font-size="13.00">1</text> -<text text-anchor="middle" x="317" y="-330.16" font-family="Times,serif" font-size="13.00">(21)</text> -</g> -<!-- 20->21 --> -<g id="edge17" class="edge"> -<title>20->21</title> -<path fill="none" stroke="black" d="M328.54,-418.92C326.3,-403.86 323.58,-385.67 321.35,-370.72"/> -<polygon fill="black" stroke="black" points="324.12,-370.28 320.17,-362.78 318.58,-371.1 324.12,-370.28"/> -<text text-anchor="middle" x="341.5" y="-392.68" font-family="Times,serif" font-size="10.00">f4=19</text> -</g> -<!-- 22 --> -<g id="node19" class="node"> -<title>22</title> -<polygon fill="none" stroke="black" points="401,-362.56 357,-362.56 357,-318.56 401,-318.56 401,-362.56"/> -<text text-anchor="middle" x="379" y="-344.16" font-family="Times,serif" font-size="13.00">0</text> -<text text-anchor="middle" x="379" y="-330.16" font-family="Times,serif" font-size="13.00">(22)</text> -</g> -<!-- 20->22 --> -<g id="edge18" class="edge"> -<title>20->22</title> -<path fill="none" stroke="black" d="M347.09,-421.8C350.48,-414.98 353.99,-407.61 357,-400.68 361.27,-390.85 365.48,-379.9 369.07,-370.12"/> -<polygon fill="black" stroke="black" points="371.71,-371.05 371.8,-362.57 366.45,-369.14 371.71,-371.05"/> -<text text-anchor="middle" x="376.5" y="-392.68" font-family="Times,serif" font-size="10.00">f4=18</text> -</g> -<!-- 25 --> -<g id="node20" class="node"> -<title>25</title> -<polygon fill="none" stroke="black" points="463,-362.56 419,-362.56 419,-318.56 463,-318.56 463,-362.56"/> -<text text-anchor="middle" x="441" y="-344.16" font-family="Times,serif" font-size="13.00">1</text> -<text text-anchor="middle" x="441" y="-330.16" font-family="Times,serif" font-size="13.00">(25)</text> -</g> -<!-- 24->25 --> -<g id="edge19" class="edge"> -<title>24->25</title> -<path fill="none" stroke="black" d="M453.91,-419.35C452.53,-413.19 451.15,-406.74 450,-400.68 448.15,-390.97 446.44,-380.32 445.01,-370.76"/> -<polygon fill="black" stroke="black" points="447.76,-370.2 443.83,-362.69 442.22,-371.01 447.76,-370.2"/> -<text text-anchor="middle" x="465.5" y="-392.68" font-family="Times,serif" font-size="10.00">f5=15</text> -</g> -<!-- 26 --> -<g id="node21" class="node"> -<title>26</title> -<polygon fill="none" stroke="black" points="525,-362.56 481,-362.56 481,-318.56 525,-318.56 525,-362.56"/> -<text text-anchor="middle" x="503" y="-344.16" font-family="Times,serif" font-size="13.00">0</text> -<text text-anchor="middle" x="503" y="-330.16" font-family="Times,serif" font-size="13.00">(26)</text> -</g> -<!-- 24->26 --> -<g id="edge20" class="edge"> -<title>24->26</title> -<path fill="none" stroke="black" d="M472.81,-420.88C475.55,-414.27 478.41,-407.24 481,-400.68 484.86,-390.89 488.92,-380.12 492.48,-370.48"/> -<polygon fill="black" stroke="black" points="495.21,-371.17 495.35,-362.7 489.95,-369.24 495.21,-371.17"/> -<text text-anchor="middle" x="500.5" y="-392.68" font-family="Times,serif" font-size="10.00">f5=14</text> -</g> -<!-- 28 --> -<g id="node22" class="node"> -<title>28</title> -<ellipse fill="none" stroke="black" cx="574" cy="-340.56" rx="31" ry="31"/> -<text text-anchor="middle" x="574" y="-344.16" font-family="Times,serif" font-size="13.00">f6</text> -<text text-anchor="middle" x="574" y="-330.16" font-family="Times,serif" font-size="13.00">(28)</text> -</g> -<!-- 27->28 --> -<g id="edge21" class="edge"> -<title>27->28</title> -<path fill="none" stroke="black" d="M574,-418.64C574,-406.64 574,-392.7 574,-380"/> -<polygon fill="black" stroke="black" points="576.8,-379.86 574,-371.86 571.2,-379.86 576.8,-379.86"/> -<text text-anchor="middle" x="589.5" y="-392.68" font-family="Times,serif" font-size="10.00">f4=15</text> -</g> -<!-- 31 --> -<g id="node23" class="node"> -<title>31</title> -<ellipse fill="none" stroke="black" cx="654" cy="-340.56" rx="31" ry="31"/> -<text text-anchor="middle" x="654" y="-344.16" font-family="Times,serif" font-size="13.00">f13</text> -<text text-anchor="middle" x="654" y="-330.16" font-family="Times,serif" font-size="13.00">(31)</text> -</g> -<!-- 27->31 --> -<g id="edge22" class="edge"> -<title>27->31</title> -<path fill="none" stroke="black" d="M592.15,-424.46C603.53,-409.21 618.3,-389.41 630.56,-372.98"/> -<polygon fill="black" stroke="black" points="633.1,-374.26 635.64,-366.17 628.61,-370.91 633.1,-374.26"/> -<text text-anchor="middle" x="632.5" y="-392.68" font-family="Times,serif" font-size="10.00">f4=14</text> -</g> -<!-- 15 --> -<g id="node24" class="node"> -<title>15</title> -<ellipse fill="none" stroke="black" cx="141" cy="-231.34" rx="31" ry="31"/> -<text text-anchor="middle" x="141" y="-234.94" font-family="Times,serif" font-size="13.00">f5</text> -<text text-anchor="middle" x="141" y="-220.94" font-family="Times,serif" font-size="13.00">(15)</text> -</g> -<!-- 13->15 --> -<g id="edge23" class="edge"> -<title>13->15</title> -<path fill="none" stroke="black" d="M171.12,-311.11C166.08,-297.92 160.03,-282.1 154.68,-268.11"/> -<polygon fill="black" stroke="black" points="157.26,-267.02 151.79,-260.55 152.03,-269.02 157.26,-267.02"/> -<text text-anchor="middle" x="175.5" y="-283.45" font-family="Times,serif" font-size="10.00">f5=2</text> -</g> -<!-- 14 --> -<g id="node25" class="node"> -<title>14</title> -<polygon fill="none" stroke="black" points="234,-253.34 190,-253.34 190,-209.34 234,-209.34 234,-253.34"/> -<text text-anchor="middle" x="212" y="-234.94" font-family="Times,serif" font-size="13.00">1</text> -<text text-anchor="middle" x="212" y="-220.94" font-family="Times,serif" font-size="13.00">(14)</text> -</g> -<!-- 13->14 --> -<g id="edge24" class="edge"> -<title>13->14</title> -<path fill="none" stroke="black" d="M190.2,-310.26C194.44,-295.09 199.61,-276.62 203.85,-261.47"/> -<polygon fill="black" stroke="black" points="206.64,-261.9 206.1,-253.44 201.25,-260.39 206.64,-261.9"/> -<text text-anchor="middle" x="210.5" y="-283.45" font-family="Times,serif" font-size="10.00">f5=3</text> -</g> -<!-- 29 --> -<g id="node30" class="node"> -<title>29</title> -<polygon fill="none" stroke="black" points="538,-253.34 494,-253.34 494,-209.34 538,-209.34 538,-253.34"/> -<text text-anchor="middle" x="516" y="-234.94" font-family="Times,serif" font-size="13.00">0</text> -<text text-anchor="middle" x="516" y="-220.94" font-family="Times,serif" font-size="13.00">(29)</text> -</g> -<!-- 28->29 --> -<g id="edge29" class="edge"> -<title>28->29</title> -<path fill="none" stroke="black" d="M553.1,-317.31C546.63,-309.56 539.91,-300.53 535,-291.45 529.9,-282.03 525.89,-271.01 522.9,-261.05"/> -<polygon fill="black" stroke="black" points="525.59,-260.28 520.72,-253.35 520.21,-261.81 525.59,-260.28"/> -<text text-anchor="middle" x="553.5" y="-283.45" font-family="Times,serif" font-size="10.00">f6=111</text> -</g> -<!-- 30 --> -<g id="node31" class="node"> -<title>30</title> -<polygon fill="none" stroke="black" points="600,-253.34 556,-253.34 556,-209.34 600,-209.34 600,-253.34"/> -<text text-anchor="middle" x="578" y="-234.94" font-family="Times,serif" font-size="13.00">1</text> -<text text-anchor="middle" x="578" y="-220.94" font-family="Times,serif" font-size="13.00">(30)</text> -</g> -<!-- 28->30 --> -<g id="edge30" class="edge"> -<title>28->30</title> -<path fill="none" stroke="black" d="M575.12,-309.41C575.68,-294.5 576.35,-276.57 576.9,-261.76"/> -<polygon fill="black" stroke="black" points="579.71,-261.54 577.21,-253.44 574.11,-261.33 579.71,-261.54"/> -<text text-anchor="middle" x="595.5" y="-283.45" font-family="Times,serif" font-size="10.00">f6=110</text> -</g> -<!-- 32 --> -<g id="node32" class="node"> -<title>32</title> -<polygon fill="none" stroke="black" points="671,-253.34 627,-253.34 627,-209.34 671,-209.34 671,-253.34"/> -<text text-anchor="middle" x="649" y="-234.94" font-family="Times,serif" font-size="13.00">0</text> -<text text-anchor="middle" x="649" y="-220.94" font-family="Times,serif" font-size="13.00">(32)</text> -</g> -<!-- 31->32 --> -<g id="edge31" class="edge"> -<title>31->32</title> -<path fill="none" stroke="black" d="M651.2,-309.56C650.72,-303.56 650.29,-297.31 650,-291.45 649.51,-281.7 649.25,-271.03 649.11,-261.48"/> -<polygon fill="black" stroke="black" points="651.91,-261.39 649.02,-253.42 646.31,-261.45 651.91,-261.39"/> -<text text-anchor="middle" x="671.5" y="-283.45" font-family="Times,serif" font-size="10.00">f13=345</text> -</g> -<!-- 33 --> -<g id="node33" class="node"> -<title>33</title> -<polygon fill="none" stroke="black" points="737,-253.34 693,-253.34 693,-209.34 737,-209.34 737,-253.34"/> -<text text-anchor="middle" x="715" y="-234.94" font-family="Times,serif" font-size="13.00">1</text> -<text text-anchor="middle" x="715" y="-220.94" font-family="Times,serif" font-size="13.00">(33)</text> -</g> -<!-- 31->33 --> -<g id="edge32" class="edge"> -<title>31->33</title> -<path fill="none" stroke="black" d="M674.66,-317.18C681.12,-309.42 687.88,-300.42 693,-291.45 698.35,-282.07 702.85,-271.14 706.35,-261.25"/> -<polygon fill="black" stroke="black" points="709.03,-262.08 708.94,-253.6 703.72,-260.28 709.03,-262.08"/> -<text text-anchor="middle" x="719.5" y="-283.45" font-family="Times,serif" font-size="10.00">f13=344</text> -</g> -<!-- 16 --> -<g id="node26" class="node"> -<title>16</title> -<ellipse fill="none" stroke="black" cx="105" cy="-122.11" rx="31" ry="31"/> -<text text-anchor="middle" x="105" y="-125.71" font-family="Times,serif" font-size="13.00">f13</text> -<text text-anchor="middle" x="105" y="-111.71" font-family="Times,serif" font-size="13.00">(16)</text> -</g> -<!-- 15->16 --> -<g id="edge25" class="edge"> -<title>15->16</title> -<path fill="none" stroke="black" d="M130.04,-201.95C127.65,-195.49 125.18,-188.64 123,-182.23 120.57,-175.06 118.12,-167.36 115.83,-159.96"/> -<polygon fill="black" stroke="black" points="118.44,-158.93 113.43,-152.09 113.09,-160.56 118.44,-158.93"/> -<text text-anchor="middle" x="138.5" y="-174.23" font-family="Times,serif" font-size="10.00">f5=15</text> -</g> -<!-- 19 --> -<g id="node27" class="node"> -<title>19</title> -<polygon fill="none" stroke="black" points="198,-144.11 154,-144.11 154,-100.11 198,-100.11 198,-144.11"/> -<text text-anchor="middle" x="176" y="-125.71" font-family="Times,serif" font-size="13.00">0</text> -<text text-anchor="middle" x="176" y="-111.71" font-family="Times,serif" font-size="13.00">(19)</text> -</g> -<!-- 15->19 --> -<g id="edge26" class="edge"> -<title>15->19</title> -<path fill="none" stroke="black" d="M150.38,-201.6C155.38,-186.28 161.52,-167.48 166.53,-152.12"/> -<polygon fill="black" stroke="black" points="169.22,-152.91 169.04,-144.44 163.89,-151.17 169.22,-152.91"/> -<text text-anchor="middle" x="175.5" y="-174.23" font-family="Times,serif" font-size="10.00">f5=14</text> -</g> -<!-- 17 --> -<g id="node28" class="node"> -<title>17</title> -<polygon fill="none" stroke="black" points="94,-44 50,-44 50,0 94,0 94,-44"/> -<text text-anchor="middle" x="72" y="-25.6" font-family="Times,serif" font-size="13.00">0</text> -<text text-anchor="middle" x="72" y="-11.6" font-family="Times,serif" font-size="13.00">(17)</text> -</g> -<!-- 16->17 --> -<g id="edge27" class="edge"> -<title>16->17</title> -<path fill="none" stroke="black" d="M85.49,-97.53C80.45,-90.12 75.7,-81.62 73,-73 70.99,-66.59 70.11,-59.49 69.85,-52.7"/> -<polygon fill="black" stroke="black" points="72.65,-52.34 69.82,-44.34 67.05,-52.35 72.65,-52.34"/> -<text text-anchor="middle" x="94.5" y="-65" font-family="Times,serif" font-size="10.00">f13=199</text> -</g> -<!-- 18 --> -<g id="node29" class="node"> -<title>18</title> -<polygon fill="none" stroke="black" points="160,-44 116,-44 116,0 160,0 160,-44"/> -<text text-anchor="middle" x="138" y="-25.6" font-family="Times,serif" font-size="13.00">1</text> -<text text-anchor="middle" x="138" y="-11.6" font-family="Times,serif" font-size="13.00">(18)</text> -</g> -<!-- 16->18 --> -<g id="edge28" class="edge"> -<title>16->18</title> -<path fill="none" stroke="black" d="M114.63,-92.47C118.95,-79.64 124.03,-64.53 128.35,-51.69"/> -<polygon fill="black" stroke="black" points="131.03,-52.51 130.93,-44.03 125.72,-50.72 131.03,-52.51"/> -<text text-anchor="middle" x="146.5" y="-65" font-family="Times,serif" font-size="10.00">f13=198</text> -</g> -</g> -</svg> diff --git a/pages/application/layout_application.py b/pages/application/layout_application.py index 28c6fb6..4972774 100644 --- a/pages/application/layout_application.py +++ b/pages/application/layout_application.py @@ -1,7 +1,6 @@ -from dash import dcc, html, Input, Output, callback +from dash import dcc, html, Input, Output import dash import dash_bootstrap_components as dbc -import visdcc import pandas as pd import numpy as np from os import listdir @@ -9,68 +8,23 @@ from os.path import isfile, join import importlib from pages.application.utils_data import extract_data - -def create_tab_application(data): - model = Model(data) - view = View(model) - - @callback( - Output('ml_datasets_choice', 'options'), - Input('ml_model_choice', 'value') - ) - def update_ml_type(value): - model.update_ml_model(value) - view.update_ml_model() - return model.datasets - - @callback( - Output('ml_instances_choice', 'options'), - Output('model_layout', 'children'), - Input('ml_datasets_choice', 'value'), - Input('ml_instances_choice', 'value') - ) - def update_dataset(value_dataset, value_instance): - if model.ml_model!='': - ctx = dash.callback_context - - if ctx.triggered: - dropdown = button_id = ctx.triggered[0]['prop_id'].split('.')[0] - if dropdown == 'ml_datasets_choice': - model.update_dataset(value_dataset) - model_layout = view.update_dataset() - return model.instances, model_layout - - else : - model.update_instance(value_instance) - model_layout = view.update_instance() - return model.instances, model_layout - else : - return [], html.Div(children=[]) - - return view.create_tab() - +from pages.application.DecisionTree.DecisionTreeComponent import DecisionTreeComponent SIDEBAR_STYLE = { - "width": "20rem", - "margin-bottom": "5rem", - "background-color": "#f8f9fa", } CONTENT_STYLE = { - "padding": "2rem 1rem", } - class Model(): def __init__(self, data): - names_models, dict_data_models, dict_data_instances, dict_components, dict_expl = extract_data(data) + names_models, dict_data_models, dict_data_instances, dict_components = extract_data(data) self.dict_data_models = dict_data_models self.dict_data_instances = dict_data_instances self.dict_components = dict_components - self.dict_expl = dict_expl self.ml_models = names_models self.ml_model = '' @@ -83,8 +37,6 @@ class Model(): self.component_class = '' - self.explicability_algorithm = '' - def update_ml_model(self, ml_model_update): self.ml_model = ml_model_update @@ -92,8 +44,6 @@ class Model(): self.component_class = self.dict_components[self.ml_model] - self.explicability_algorithm = self.dict_expl[self.ml_model] - def update_dataset(self, dataset_update): self.dataset = dataset_update @@ -111,41 +61,28 @@ class View(): self.datasets_menu_models = dcc.Dropdown(self.model.datasets, id='ml_datasets_choice') self.instances_menu = dcc.Dropdown(self.model.instances, id='ml_instances_choice') - self.sidebar = html.Div([ - html.Label("Choose the Machine Learning algorithm :"), - html.Br(), - self.ml_menu_models, - html.Hr(), - html.Label("Choose the dataset : "), - html.Br(), - self.datasets_menu_models, - html.Hr(), - html.Label("Choose the instance to explain : "), - html.Br(), - self.instances_menu], - style=SIDEBAR_STYLE) - self.component = '' - self.model_layout = html.Div( - id="model_layout", - style= CONTENT_STYLE) - - def create_tab(self): - return dcc.Tab(label='Application on Explainable AI', children=[ - html.Div(children=[ - self.sidebar, - self.model_layout - ]) - ]) - - def update_ml_model(self): - cls = getattr(importlib.import_module("pages.application.DecisionTree."+self.model.component_class), self.model.component_class) - self.component = cls() + self.sidebar = dbc.Col([ + html.Label("Choose the Machine Learning algorithm :"), + html.Br(), + self.ml_menu_models, + html.Hr(), + html.Label("Choose the dataset : "), + html.Br(), + self.datasets_menu_models, + html.Hr(), + html.Label("Choose the instance to explain : "), + html.Br(), + self.instances_menu], width=2) + self.layout = dbc.Row([self.sidebar, + dbc.Col(html.Div(id = "graph", children=" "), width=5), + dbc.Col(html.Div(id = "explanation", children=" "), width=3)]) + self.tab = dcc.Tab(label='Application on Explainable AI', children=self.layout) def update_dataset(self): - self.component.update(self.model.dataset) - return html.Div(children=[self.component.network]) + class_ = globals()[self.model.component_class] + self.component = class_(self.model.dataset) + def update_instance(self): - self.component.update_with_explicability(self.model.instance) - return html.Div(children=[self.component.network]) \ No newline at end of file + self.component.update_with_explicability(self.model.dataset, self.model.instance) \ No newline at end of file diff --git a/pages/application/utils_data.py b/pages/application/utils_data.py index f2a814c..7b7a5fe 100644 --- a/pages/application/utils_data.py +++ b/pages/application/utils_data.py @@ -9,21 +9,19 @@ from os.path import isfile, join import importlib def extract_data(data): - names_models = [data['data'][i]['ml_type'] for i in range (len(data))] + names_models = [data[i]['ml_type'] for i in range (len(data))] dict_data_models = {} dict_data_instances = {} dict_components = {} - dict_expl = {} for i in range (len(data)) : - ml_type = data['data'][i]['ml_type'] - dict_components[ml_type] = data['data'][i]['component'] - dict_expl[ml_type] = data['data'][i]['explicability_algorithm'] - dict_data_models[ml_type] = [f for f in listdir(data['data'][i]['trained_models'])] - dict_data_instances = {} + ml_type = data[i]['ml_type'] + dict_components[ml_type] = data[i]['component'] + dict_data_models[ml_type] = [f for f in listdir(data[i]['trained_models'])] + dict_dataset_instances = {} for j in range (len(dict_data_models[ml_type])) : dataset = dict_data_models[ml_type][j] - dict_data_instances[dataset] = [f for f in listdir(data['data'][i]['instances']+dataset) if isfile(join(data['data'][i]['instances']+dataset, f))] - dict_data_instances[ml_type] = dict_data_instances + dict_dataset_instances[dataset] = [f for f in listdir(data[i]['instances']+dataset) if isfile(join(data[i]['instances']+dataset, f))] + dict_data_instances[ml_type] = dict_dataset_instances - return names_models, dict_data_models, dict_data_instances, dict_components, dict_expl \ No newline at end of file + return names_models, dict_data_models, dict_data_instances, dict_components \ No newline at end of file diff --git a/pages/layout.py b/pages/layout.py deleted file mode 100644 index 41742ca..0000000 --- a/pages/layout.py +++ /dev/null @@ -1,20 +0,0 @@ -from dash import dcc -from dash import html -import visdcc -import pandas as pd -import numpy as np - -from pages.application.layout_application import create_tab_application - - -def create_tabs(data): - tabs = dcc.Tabs([ - dcc.Tab(label='Course on Explainable AI', children=[]), - create_tab_application(data), - ]) - return tabs - -def create_layout(data): - return html.Div([ - html.H1('FXToolKit'), - create_tabs(data) ]) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 860116d..9e95cab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,6 @@ numpy>=1.16.2 pandas>=0.24.2 scikit-learn>=0.20.3 scipy>=1.2.1 -visdcc +dash_bootstrap_components +dash_interactive_graphviz +importlib -- GitLab