Upload 7 files
Browse files- pages/1-explanationpage.py +103 -0
- pages/2_SHAP.py +196 -0
- pages/3_DecisionTree.py +264 -0
- pages/4_counterfactual.py +215 -0
- pages/5_visualMap.py +179 -0
- pages/6_finalpage.py +47 -0
- pages/7_thankyou.py +13 -0
pages/1-explanationpage.py
ADDED
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from uuid import uuid4
|
3 |
+
from streamlit_extras.switch_page_button import switch_page
|
4 |
+
import random
|
5 |
+
import pandas as pd
|
6 |
+
import xgboost as xgb
|
7 |
+
import copy
|
8 |
+
|
9 |
+
header1, header2, header3 = st.columns([1,2,1])
|
10 |
+
body1, body2, body3 =st.columns([1,2,1])
|
11 |
+
footer1, footer2, footer3 =st.columns([1,2,1])
|
12 |
+
|
13 |
+
if 'nextPage' not in st.session_state:
|
14 |
+
st.session_state.nextPage = random.randint(0, len(st.session_state.pages)-1)
|
15 |
+
# st.write(st.session_state.nextPage)
|
16 |
+
|
17 |
+
|
18 |
+
@st.cache_data
|
19 |
+
def loadData():
|
20 |
+
train_df = pd.read_csv('assets/train_df.csv')
|
21 |
+
test_df = pd.read_csv('assets/test_df.csv')
|
22 |
+
test_with_names = pd.read_csv('assets/test_with_names.csv')
|
23 |
+
# test_with_names.drop('PassengerId', axis=1, inplace=True)
|
24 |
+
X_train = train_df.drop("Survived", axis=1)
|
25 |
+
Y_train = train_df["Survived"]
|
26 |
+
X_test = test_df.drop('PassengerId', axis=1)
|
27 |
+
X_test_names = test_with_names.copy()
|
28 |
+
title_df = pd.DataFrame({'Title indices': [1,2,3,4,5],
|
29 |
+
'Title': ['Mr', 'Miss', 'Mrs', 'Master', 'Rare'] })
|
30 |
+
gender_df = pd.DataFrame({'Gender indices': [0,1],
|
31 |
+
'Sex': ['Male', 'Female'] })
|
32 |
+
ports_df = pd.DataFrame({'Ports indices': [0,1,2],
|
33 |
+
'Embarked': ['Southampton', 'Cherbourg', 'Quenstown'] })
|
34 |
+
return X_train, Y_train, X_test, X_test_names, title_df, gender_df, ports_df, train_df
|
35 |
+
|
36 |
+
if 'X_train' not in st.session_state:
|
37 |
+
st.session_state.X_train, st.session_state.Y_train, st.session_state.X_test, st.session_state.X_test_names, st.session_state.title_df, st.session_state.gender_df, st.session_state.ports_df, st.session_state.train_df = loadData()
|
38 |
+
# st.dataframe(st.session_state.X_train)
|
39 |
+
# st.session_state.X_train, st.session_state.Y_train, st.session_state.X_test, st.session_state.X_test_names= loadData()
|
40 |
+
|
41 |
+
|
42 |
+
|
43 |
+
|
44 |
+
with header2:
|
45 |
+
st.title("Who survived and why?")
|
46 |
+
# st.dataframe(st.session_state.X_train)
|
47 |
+
# st.write("For debugging:")
|
48 |
+
# st.write(st.session_state.participantID)
|
49 |
+
# X_train, Y_train, X_test= loadData()
|
50 |
+
|
51 |
+
with body2:
|
52 |
+
st.header("The Titanic")
|
53 |
+
st.markdown("In the year 1912, the Titanic left from Southampton to New York City, but it never arrived. On April 15, it crashed into an iceberg and sunk. Of the estimated 2,224 passengers and crew aboard, more than 1,500 died, making it the deadliest sinking of a single ship up to that time. ")
|
54 |
+
st.image('assets/titanic.jpg')
|
55 |
+
st.header('Explanation experiment')
|
56 |
+
st.markdown('''In this experiment we will show you two different profiles of passengers.
|
57 |
+
Using Machine Learning (ML) we will show a prediction whether they would have survived the disaster.
|
58 |
+
This prediction is accompanied by each time a different type of explanation.''')
|
59 |
+
st.markdown("After seeing 2 profiles, you will be asked to evaluate the explanation you have just seen.")
|
60 |
+
|
61 |
+
st.subheader('Demographic information')
|
62 |
+
st.markdown("Before you start with the study we would like to ask you to first answer these questions")
|
63 |
+
|
64 |
+
|
65 |
+
|
66 |
+
with footer2:
|
67 |
+
with st.form("demographic_form", clear_on_submit=True):
|
68 |
+
gender = st.radio("How do you identify your gender", ('Female', 'Male', 'Non-binary', 'Other', 'Prefer not to say'))
|
69 |
+
age = st.radio("How old are you?", ('18-25', '26-35', '36-45', '46-55', '56-65', '66-75', '75+'))
|
70 |
+
educationlevel = st.radio("What is your highest level of education?",
|
71 |
+
('elementary school', 'high school', 'MBO', 'HBO', 'University'))
|
72 |
+
st.markdown('**AI literacy**')
|
73 |
+
st.markdown('Please rate to what extent you have the skills/knowledge listed below. 0 means that he ability is hardly or not at all pronounced, whereas a value of 10 means that the ability is very well or almost perfectly pronounced')
|
74 |
+
q1 = st.slider('I know the most important concepts of the topic "artificial intelligence"', 0, 10)
|
75 |
+
q2 = st.slider("I know definitions of artificial intelligence", 0, 10 )
|
76 |
+
q3 = st.slider("I can assess what the limitations and opportunities of using an AI are", 0, 10)
|
77 |
+
q4 = st.slider("I can assess what advantages and disadvantages the use of an artificial intelligence entails", 0, 10)
|
78 |
+
q5 = st.slider("I can think of new uses for AI.", 0, 10)
|
79 |
+
q6 = st.slider("I can imagine possible future uses of AI", 0, 10)
|
80 |
+
|
81 |
+
st.markdown('''On the next page you will see a profile of one of the passengers of the Titanic,
|
82 |
+
a prediction of whether they would have survived and an explanation for why the model made this prediction. Have a look at this and then generate a new profile by clicking on the button.
|
83 |
+
You can look at 2 profiles, next you will be asked to evaluate the explanation.
|
84 |
+
These steps will be repeated in total 4 times after which you will be asked some final questions. ''')
|
85 |
+
|
86 |
+
submitted = st.form_submit_button("Start the experiment")
|
87 |
+
|
88 |
+
if submitted:
|
89 |
+
st.session_state.oocsi.send('EngD_HAII_demographics', {
|
90 |
+
'participant_ID': st.session_state.participantID,
|
91 |
+
'gender': gender,
|
92 |
+
'age': age,
|
93 |
+
'educationLevel': educationlevel,
|
94 |
+
'q1': q1,
|
95 |
+
'q2': q2,
|
96 |
+
'q3': q3,
|
97 |
+
'q4': q4,
|
98 |
+
'q5': q5,
|
99 |
+
'q6': q6,
|
100 |
+
})
|
101 |
+
# if st.button("Start the experiment "):
|
102 |
+
|
103 |
+
switch_page(st.session_state.pages[st.session_state.nextPage])
|
pages/2_SHAP.py
ADDED
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
from oocsi_source import OOCSI
|
5 |
+
from uuid import uuid4
|
6 |
+
from streamlit_extras.switch_page_button import switch_page
|
7 |
+
import random
|
8 |
+
import shap
|
9 |
+
from IPython.display import display_html
|
10 |
+
import xgboost as xgb
|
11 |
+
import matplotlib.pyplot as plt
|
12 |
+
|
13 |
+
# st.markdown("""<style>
|
14 |
+
# .stSlider {
|
15 |
+
# padding-bottom: 20px;
|
16 |
+
# }
|
17 |
+
# </style> """,
|
18 |
+
# unsafe_allow_html=True)
|
19 |
+
|
20 |
+
#Delete this page from the array of pages to visit, this way it cannot be visited twice
|
21 |
+
if 'profile1' not in st.session_state:
|
22 |
+
st.session_state.pages.remove("SHAP")
|
23 |
+
st.session_state.profile1= 'deleted'
|
24 |
+
if (len(st.session_state.pages)>0):
|
25 |
+
st.session_state.nextPage1 = random.randint(0, len(st.session_state.pages)-1)
|
26 |
+
st.session_state.lastQuestion= 'no'
|
27 |
+
else:
|
28 |
+
st.session_state.lastQuestion= 'yes'
|
29 |
+
|
30 |
+
|
31 |
+
if 'index1' not in st.session_state:
|
32 |
+
st.session_state.index1= 0
|
33 |
+
|
34 |
+
if 'profileIndex' not in st.session_state:
|
35 |
+
st.session_state.profileIndex= st.session_state.profileIndices[st.session_state.index1]
|
36 |
+
|
37 |
+
header1, header2, header3 = st.columns([1,2,1])
|
38 |
+
characteristics1, characteristics2, characteristics3 = st.columns([1,2,1])
|
39 |
+
prediction1, prediction2, prediction3 =st.columns([1,2,1])
|
40 |
+
explanation1, explanation2, explanation3 = st.columns([1,5,1])
|
41 |
+
footer1, footer2, footer3 =st.columns([1,2,1])
|
42 |
+
evaluation1, evaluation2, evaluation3 = st.columns([1,2,1])
|
43 |
+
|
44 |
+
|
45 |
+
|
46 |
+
|
47 |
+
name= st.session_state.X_test_names.loc[st.session_state.profileIndex, "Name"]
|
48 |
+
|
49 |
+
@st.cache_resource
|
50 |
+
def trainModel(X_train,Y_train):
|
51 |
+
model = xgb.XGBClassifier().fit(X_train, Y_train)
|
52 |
+
return model
|
53 |
+
|
54 |
+
|
55 |
+
@st.cache_resource
|
56 |
+
def getSHAPvalues(_model,X_train, Y_train, X_test):
|
57 |
+
# compute SHAP values
|
58 |
+
explainer = shap.Explainer(_model, X_test)
|
59 |
+
shap_values = explainer(X_test)
|
60 |
+
return shap_values
|
61 |
+
|
62 |
+
|
63 |
+
|
64 |
+
|
65 |
+
def shapPlot(X_test, _shap_values):
|
66 |
+
return shap.plots.waterfall(shap_values[st.session_state.profileIndex])
|
67 |
+
|
68 |
+
with header2:
|
69 |
+
st.header('Explanation - SHAP Values')
|
70 |
+
st.markdown(''' The SHAP value algorithm (SHapley Additive exPlanations) is a way to reverse-engineer the output of any predictive machine learning model.
|
71 |
+
the technique helps to understand the decision took by a complex model. The classical models will typically answer the question 'how much' whereas the SHAP
|
72 |
+
model will focus on the 'why'.
|
73 |
+
|
74 |
+
Finally, the representation of the SHAP value will show how much each feature are contributing to the final prediction made by the model. For the Titanic dataset, each feature
|
75 |
+
will analyse each the contribution of each will be presented for different persons explaining the reason why this person survived the shipwreck or not.
|
76 |
+
''')
|
77 |
+
st.subheader(name, anchor='top')
|
78 |
+
# st.write("For debugging:")
|
79 |
+
# st.write(st.session_state.participantID)
|
80 |
+
XGBmodel= trainModel(st.session_state.X_train, st.session_state.Y_train)
|
81 |
+
|
82 |
+
with characteristics2:
|
83 |
+
# initialize list of lists
|
84 |
+
data = st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1)
|
85 |
+
# Create the pandas DataFrame
|
86 |
+
df = pd.DataFrame(data, columns=st.session_state.X_test.columns)
|
87 |
+
st.dataframe(df)
|
88 |
+
|
89 |
+
|
90 |
+
|
91 |
+
with prediction2:
|
92 |
+
# st.header("Prediction")
|
93 |
+
prediction = XGBmodel.predict(st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1))
|
94 |
+
probability = XGBmodel.predict_proba(st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1))
|
95 |
+
|
96 |
+
if prediction == 0:
|
97 |
+
prob = round((probability[0][0]*100),2)
|
98 |
+
st.markdown("The model predicts with {}% probability that {} will :red[**not survive**]".format(prob, name) )
|
99 |
+
|
100 |
+
else:
|
101 |
+
prob = round((probability[0][1]*100),2)
|
102 |
+
st.markdown("The model predicts with {}% probability that {} will :green[**survive**]".format(prob, name) )
|
103 |
+
|
104 |
+
|
105 |
+
with explanation2:
|
106 |
+
st.subheader("Explanation")
|
107 |
+
# with st.spinner("Please be patient, we are generating a new explanation"):
|
108 |
+
shap_values= getSHAPvalues(XGBmodel, st.session_state.X_train, st.session_state.Y_train, st.session_state.X_test)
|
109 |
+
st.set_option('deprecation.showPyplotGlobalUse', False)
|
110 |
+
fig = shap.plots.waterfall(shap_values[st.session_state.profileIndex])
|
111 |
+
st.pyplot(fig, bbox_inches='tight')
|
112 |
+
data_indices = pd.concat([d.reset_index(drop=True) for d in [st.session_state.ports_df, st.session_state.title_df, st.session_state.gender_df]], axis=1)
|
113 |
+
# st.dataframe(st.session_state.ports_df)
|
114 |
+
# st.dataframe(st.session_state.title_df)
|
115 |
+
# st.dataframe(st.session_state.gender_df)
|
116 |
+
st.dataframe(data_indices)
|
117 |
+
|
118 |
+
with footer2:
|
119 |
+
|
120 |
+
if (st.session_state.index1 < len(st.session_state.profileIndices)-1):
|
121 |
+
if st.button("New profile"):
|
122 |
+
|
123 |
+
st.session_state.index1 = st.session_state.index1+1
|
124 |
+
st.session_state.profileIndex = st.session_state.profileIndices[st.session_state.index1]
|
125 |
+
st.experimental_rerun()
|
126 |
+
else:
|
127 |
+
def is_user_active():
|
128 |
+
if 'user_active1' in st.session_state.keys() and st.session_state['user_active1']:
|
129 |
+
return True
|
130 |
+
else:
|
131 |
+
return False
|
132 |
+
if is_user_active():
|
133 |
+
# st.markdown("You have reached the end of the profiles")
|
134 |
+
# if st.button("Continue to evaluation"):
|
135 |
+
# st.write(" ")
|
136 |
+
with st.form("my_form1", clear_on_submit=True):
|
137 |
+
st.subheader("Evaluation")
|
138 |
+
st.write("These questions only ask for your opinion about this specific explanation")
|
139 |
+
q1 = st.select_slider(
|
140 |
+
'**1**- From the explanation, I **understand** how the algorithm works:',
|
141 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
142 |
+
|
143 |
+
q2 = st.select_slider(
|
144 |
+
'**2**- This explanation of how the algorithm works is **satisfying**:',
|
145 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
146 |
+
|
147 |
+
q3 = st.select_slider(
|
148 |
+
'**3**- This explanation of how the algorithm works has **sufficient detail**:',
|
149 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
150 |
+
|
151 |
+
q4 = st.select_slider(
|
152 |
+
'**4**- This explanation of how the algorithm works seems **complete**:',
|
153 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
154 |
+
|
155 |
+
q5 = st.select_slider(
|
156 |
+
'**5**- This explanation of how the algorithm works **tells me how to use it**:',
|
157 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
158 |
+
|
159 |
+
q6 = st.select_slider(
|
160 |
+
'**6**- This explanation of how the algorithm works is **useful to my goals**:',
|
161 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
162 |
+
|
163 |
+
q7 = st.select_slider(
|
164 |
+
'**7**- This explanation of the algorithm shows me how **accurate** the algorithm is:',
|
165 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
166 |
+
|
167 |
+
q8 = st.select_slider(
|
168 |
+
'**8**- This explanation lets me judge when I should **trust and not trust** the algorithm:',
|
169 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
170 |
+
|
171 |
+
# Every form must have a submit button.
|
172 |
+
submitted = st.form_submit_button("Submit")
|
173 |
+
if submitted:
|
174 |
+
st.write("question 1", q1)
|
175 |
+
st.session_state.oocsi.send('EngD_HAII', {
|
176 |
+
'participant_ID': st.session_state.participantID,
|
177 |
+
'type of explanation': 'SHAP',
|
178 |
+
'q1': q1,
|
179 |
+
'q2': q2,
|
180 |
+
'q3': q3,
|
181 |
+
'q4': q4,
|
182 |
+
'q5': q5,
|
183 |
+
'q6': q6,
|
184 |
+
'q7': q7,
|
185 |
+
'q8': q8,
|
186 |
+
|
187 |
+
})
|
188 |
+
if (st.session_state.lastQuestion =='yes'):
|
189 |
+
switch_page('finalPage')
|
190 |
+
else:
|
191 |
+
st.session_state.profileIndex =st.session_state.profileIndices[0]
|
192 |
+
switch_page(st.session_state.pages[st.session_state.nextPage1])
|
193 |
+
else:
|
194 |
+
if st.button('Continue to evaluation'):
|
195 |
+
st.session_state['user_active1']=True
|
196 |
+
st.experimental_rerun()
|
pages/3_DecisionTree.py
ADDED
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
from oocsi_source import OOCSI
|
5 |
+
from uuid import uuid4
|
6 |
+
from streamlit_extras.switch_page_button import switch_page
|
7 |
+
import random
|
8 |
+
import dtreeviz
|
9 |
+
import xgboost as xgb
|
10 |
+
from dtreeviz.trees import dtreeviz
|
11 |
+
from sklearn.tree import DecisionTreeClassifier
|
12 |
+
import graphviz as graphviz
|
13 |
+
from sklearn.datasets import make_moons
|
14 |
+
import base64
|
15 |
+
|
16 |
+
# import os
|
17 |
+
# os.environ["PATH"] += os.pathsep + 'D:/Program Files (x86)/Graphviz2.38/bin/'
|
18 |
+
|
19 |
+
# st.markdown("""<style>
|
20 |
+
# .stSlider {
|
21 |
+
# padding-bottom: 20px;
|
22 |
+
# }
|
23 |
+
# </style> """,
|
24 |
+
# unsafe_allow_html=True)
|
25 |
+
|
26 |
+
#Delete this page from the array of pages to visit, this way it cannot be visited twice
|
27 |
+
if 'profile2' not in st.session_state:
|
28 |
+
st.session_state.pages.remove("DecisionTree")
|
29 |
+
st.session_state.profile2= 'deleted'
|
30 |
+
if (len(st.session_state.pages)>0):
|
31 |
+
st.session_state.nextPage2 = random.randint(0, len(st.session_state.pages)-1)
|
32 |
+
st.session_state.lastQuestion= 'no'
|
33 |
+
else:
|
34 |
+
st.session_state.lastQuestion= 'yes'
|
35 |
+
|
36 |
+
|
37 |
+
if 'index2' not in st.session_state:
|
38 |
+
st.session_state.index2= 0
|
39 |
+
|
40 |
+
|
41 |
+
if 'profileIndex' not in st.session_state:
|
42 |
+
st.session_state.profileIndex= st.session_state.profileIndices[st.session_state.index2]
|
43 |
+
|
44 |
+
name= st.session_state.X_test_names.loc[st.session_state.profileIndex, "Name"]
|
45 |
+
|
46 |
+
|
47 |
+
|
48 |
+
header1, header2, header3 = st.columns([1,2,1])
|
49 |
+
characteristics1, characteristics2, characteristics3 = st.columns([1,2,1])
|
50 |
+
prediction1, prediction2, prediction3 =st.columns([1,2,1])
|
51 |
+
explanation1, explanation2, explanation3 = st.columns([1,2,1])
|
52 |
+
footer1, footer2, footer3 =st.columns([1,2,1])
|
53 |
+
evaluation1, evaluation2, evaluation3 = st.columns([1,2,1])
|
54 |
+
|
55 |
+
|
56 |
+
@st.cache_resource
|
57 |
+
def loadData():
|
58 |
+
train_df = pd.read_csv('assets/train_df.csv')
|
59 |
+
test_df = pd.read_csv('assets/test_df.csv')
|
60 |
+
X_train = train_df.drop("Survived", axis=1)
|
61 |
+
Y_train = train_df["Survived"]
|
62 |
+
X_test = test_df.drop("PassengerId", axis=1).copy()
|
63 |
+
return X_train, Y_train, X_test
|
64 |
+
|
65 |
+
@st.cache_resource
|
66 |
+
def trainModel(X_train,Y_train):
|
67 |
+
model = xgb.XGBClassifier().fit(X_train, Y_train)
|
68 |
+
return model
|
69 |
+
|
70 |
+
# @st.cache_resource
|
71 |
+
def createTree(_model, X_train, Y_train, X_test):
|
72 |
+
# X, y = make_moons(n_samples=20, noise=0.25, random_state=3)
|
73 |
+
# treeclf = DecisionTreeClassifier(random_state=0)
|
74 |
+
# treeclf.fit(X, y)
|
75 |
+
# viz_model= dtreeviz(treeclf, X, y, target_name="Classes",
|
76 |
+
# feature_names=["f0", "f1"], class_names=["c0", "c1"])
|
77 |
+
# clf = DecisionTreeClassifier(max_depth=3)
|
78 |
+
# clf.fit(X_train, Y_train)
|
79 |
+
|
80 |
+
# Y_pred = clf.predict(X_test)
|
81 |
+
# acc_decision_tree2 = round(clf.score(X_train, Y_train) * 100, 2)
|
82 |
+
# viz_model = dtreeviz(clf,
|
83 |
+
# X_train, Y_train,
|
84 |
+
# feature_names=X_train.columns,
|
85 |
+
# target_name='Survived',
|
86 |
+
# class_names=['Dead', 'Alive'],
|
87 |
+
# X=X_test.iloc[1]
|
88 |
+
# )
|
89 |
+
viz_model = dtreeviz(_model,
|
90 |
+
X_train, Y_train,
|
91 |
+
tree_index=0,
|
92 |
+
feature_names=list(X_train.columns),
|
93 |
+
target_name='Survived',
|
94 |
+
class_names=['Dead', 'Alive'],
|
95 |
+
X=X_test.iloc[st.session_state.profileIndex],
|
96 |
+
#depth_range_to_display=(0, 2),
|
97 |
+
show_just_path=True,
|
98 |
+
# orientation ='LR',
|
99 |
+
)
|
100 |
+
#path = "/assets/images/prediction_path" + str(st.session_state.profileIndex) +".svg"
|
101 |
+
viz_model.save("/assets/images/prediction_path.svg")
|
102 |
+
return viz_model
|
103 |
+
|
104 |
+
def render_svg(svg):
|
105 |
+
"""Renders the given svg string."""
|
106 |
+
b64 = base64.b64encode(svg.encode('utf-8')).decode("utf-8")
|
107 |
+
html = r'<img src="data:image/svg+xml;base64,%s"/>' % b64
|
108 |
+
st.write(html, unsafe_allow_html=True)
|
109 |
+
|
110 |
+
|
111 |
+
with header2: #header2
|
112 |
+
st.header("Explanation - Decision Tree")
|
113 |
+
st.markdown('''Decision Tree models are a non-parametric supervised learning method
|
114 |
+
commonly used for classification and regression.
|
115 |
+
They are constructed using two kinf of elements: Nodes and branches. At each node (intersection),
|
116 |
+
one of the data features is evaluated to split the observations into different paths.
|
117 |
+
|
118 |
+
|
119 |
+
At typical decision example is shown in the graph below.
|
120 |
+
''')
|
121 |
+
|
122 |
+
st.image('assets/Decision_tree.jpg',caption = 'Example of a decision tree')
|
123 |
+
|
124 |
+
st.markdown(''' The Root Node starts the graph. It is usually the variable that splits the more lcearly the data.
|
125 |
+
Then, intermediate nodes are vsisble were different varaibales are evaluated but no final prediction is made yet.
|
126 |
+
Finally, leaf nodes are present where the predicrtions (numerical of categoriacl) are made.
|
127 |
+
|
128 |
+
For the Titanic dataset, the prediction will be whether the studied person survived the shipwreck.
|
129 |
+
''')
|
130 |
+
|
131 |
+
st.subheader(name)
|
132 |
+
XGBmodel= trainModel(st.session_state.X_train, st.session_state.Y_train)
|
133 |
+
# st.write("For debugging:")
|
134 |
+
# st.write(st.session_state.participantID)
|
135 |
+
|
136 |
+
with characteristics2:
|
137 |
+
# initialize list of lists
|
138 |
+
data = st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1)
|
139 |
+
# Create the pandas DataFrame
|
140 |
+
df = pd.DataFrame(data, columns=st.session_state.X_test.columns)
|
141 |
+
st.dataframe(df)
|
142 |
+
|
143 |
+
|
144 |
+
with prediction2:
|
145 |
+
prediction = XGBmodel.predict(st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1))
|
146 |
+
probability = XGBmodel.predict_proba(st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1))
|
147 |
+
if prediction == 0:
|
148 |
+
prob = round((probability[0][0]*100),2)
|
149 |
+
st.markdown("The model predicts with {}% probability that {} will :red[**not survive**]".format(prob, name) )
|
150 |
+
else:
|
151 |
+
prob = round((probability[0][1]*100),2)
|
152 |
+
st.markdown("The model predicts with {}% probability that {} will :green[**survive**]".format(prob, name) )
|
153 |
+
|
154 |
+
with explanation2:
|
155 |
+
st.subheader("Visualization - Decision Tree")
|
156 |
+
# st.markdown('''Decision Tree model are a non-parametric supervised learning method
|
157 |
+
# commonly used for classification and regression.
|
158 |
+
# They are constructed using two kinf of elements: Nodes and branches. At each node (intersection),
|
159 |
+
# one of the data features is evaluated to split the observations into different paths.
|
160 |
+
|
161 |
+
|
162 |
+
# At typical decision example is shown in the graph below.
|
163 |
+
# ''')
|
164 |
+
|
165 |
+
# st.image('assets/Decision_tree.jpg')
|
166 |
+
|
167 |
+
# st.markdown(''' The Root Node starts the graph. It is usually the variable that splits the more lcearly the data.
|
168 |
+
# Then, intermediate nodes are vsisble were different varaibales are evaluated but no final prediction is made yet.
|
169 |
+
# Finally, leaf nodes are present where the predicrtions (numerical of categoriacl) are made.
|
170 |
+
|
171 |
+
# For the Titanic dataset, the prediction will be whether the studied person survived the shipwreck.
|
172 |
+
# ''')
|
173 |
+
|
174 |
+
|
175 |
+
|
176 |
+
with st.spinner("Please be patient, we are generating a new explanation"):
|
177 |
+
viz_model = createTree(XGBmodel, st.session_state.X_train, st.session_state.Y_train, st.session_state.X_test)
|
178 |
+
# st.image("/assets/images/prediction_path.svg", width =200, use_column_width=True)
|
179 |
+
#viz_model.view()
|
180 |
+
# read in svg prediction path and display
|
181 |
+
path = "/assets/images/prediction_path" + str(st.session_state.profileIndex) +".svg"
|
182 |
+
# st.success("Done!")
|
183 |
+
with open("/assets/images/prediction_path.svg", "r") as f:
|
184 |
+
svg = f.read()
|
185 |
+
render_svg(svg)
|
186 |
+
|
187 |
+
st.text("")
|
188 |
+
|
189 |
+
with footer2:
|
190 |
+
if (st.session_state.index2 < len(st.session_state.profileIndices)-1):
|
191 |
+
if st.button("New profile"):
|
192 |
+
st.session_state.index2 = st.session_state.index2+1
|
193 |
+
st.session_state.profileIndex = st.session_state.profileIndices[st.session_state.index2]
|
194 |
+
st.experimental_rerun()
|
195 |
+
else:
|
196 |
+
def is_user_active():
|
197 |
+
if 'user_active2' in st.session_state.keys() and st.session_state['user_active2']:
|
198 |
+
return True
|
199 |
+
else:
|
200 |
+
return False
|
201 |
+
if is_user_active():
|
202 |
+
# if st.button("Continue to evaluation"):
|
203 |
+
# st.write(" ")
|
204 |
+
with st.form("my_form2", clear_on_submit=True):
|
205 |
+
st.subheader("Evaluation")
|
206 |
+
st.write("These questions only ask for your opinion about this specific explanation")
|
207 |
+
q1 = st.select_slider(
|
208 |
+
'**1**- From the explanation, I **understand** how the algorithm works:',
|
209 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
210 |
+
|
211 |
+
q2 = st.select_slider(
|
212 |
+
'**2**- This explanation of how the algorithm works is **satisfying**:',
|
213 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
214 |
+
|
215 |
+
q3 = st.select_slider(
|
216 |
+
'**3**- This explanation of how the algorithm works has **sufficient detail**:',
|
217 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
218 |
+
|
219 |
+
q4 = st.select_slider(
|
220 |
+
'**4**- This explanation of how the algorithm works seems **complete**:',
|
221 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
222 |
+
|
223 |
+
q5 = st.select_slider(
|
224 |
+
'**5**- This explanation of how the algorithm works **tells me how to use it**:',
|
225 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
226 |
+
|
227 |
+
q6 = st.select_slider(
|
228 |
+
'**6**- This explanation of how the algorithm works is **useful to my goals**:',
|
229 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
230 |
+
|
231 |
+
q7 = st.select_slider(
|
232 |
+
'**7**- This explanation of the algorithm shows me how **accurate** the algorithm is:',
|
233 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
234 |
+
|
235 |
+
q8 = st.select_slider(
|
236 |
+
'**8**- This explanation lets me judge when I should **trust and not trust** the algorithm:',
|
237 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
238 |
+
|
239 |
+
# Every form must have a submit button.
|
240 |
+
submitted = st.form_submit_button("Submit")
|
241 |
+
if submitted:
|
242 |
+
# st.write("question 1", q1)
|
243 |
+
st.session_state.oocsi.send('EngD_HAII', {
|
244 |
+
'participant_ID': st.session_state.participantID,
|
245 |
+
'type of explanation': 'Decision tree',
|
246 |
+
'q1': q1,
|
247 |
+
'q2': q2,
|
248 |
+
'q3': q3,
|
249 |
+
'q4': q4,
|
250 |
+
'q5': q5,
|
251 |
+
'q6': q6,
|
252 |
+
'q7': q7,
|
253 |
+
'q8': q8,
|
254 |
+
|
255 |
+
})
|
256 |
+
if (st.session_state.lastQuestion =='yes'):
|
257 |
+
switch_page('finalPage')
|
258 |
+
else:
|
259 |
+
st.session_state.profileIndex =st.session_state.profileIndices[0]
|
260 |
+
switch_page(st.session_state.pages[st.session_state.nextPage2])
|
261 |
+
else:
|
262 |
+
if st.button('Continue to evaluation'):
|
263 |
+
st.session_state['user_active2']=True
|
264 |
+
st.experimental_rerun()
|
pages/4_counterfactual.py
ADDED
@@ -0,0 +1,215 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
import copy
|
5 |
+
from oocsi_source import OOCSI
|
6 |
+
from uuid import uuid4
|
7 |
+
from streamlit_extras.switch_page_button import switch_page
|
8 |
+
import random
|
9 |
+
# import shap
|
10 |
+
import dice_ml
|
11 |
+
from dice_ml.utils import helpers
|
12 |
+
import xgboost as xgb
|
13 |
+
import matplotlib.pyplot as plt
|
14 |
+
from sklearn.ensemble import RandomForestClassifier
|
15 |
+
|
16 |
+
|
17 |
+
# st.markdown("""<style>
|
18 |
+
# .stSlider {
|
19 |
+
# padding-bottom: 20px;
|
20 |
+
# }
|
21 |
+
# </style> """,
|
22 |
+
# unsafe_allow_html=True)
|
23 |
+
|
24 |
+
# st.session_state.Y_train
|
25 |
+
# st.session_state.X_test
|
26 |
+
# st.session_state.X_test_names
|
27 |
+
|
28 |
+
|
29 |
+
#Delete this page from the array of pages to visit, this way it cannot be visited twice
|
30 |
+
if 'profile3' not in st.session_state:
|
31 |
+
st.session_state.pages.remove("counterfactual")
|
32 |
+
st.session_state.profile3= 'deleted'
|
33 |
+
if (len(st.session_state.pages)>0):
|
34 |
+
st.session_state.nextPage3 = random.randint(0, len(st.session_state.pages)-1)
|
35 |
+
st.session_state.lastQuestion= 'no'
|
36 |
+
else:
|
37 |
+
st.session_state.lastQuestion= 'yes'
|
38 |
+
|
39 |
+
|
40 |
+
if 'index3' not in st.session_state:
|
41 |
+
st.session_state.index3= 0
|
42 |
+
|
43 |
+
|
44 |
+
if 'profileIndex' not in st.session_state:
|
45 |
+
st.session_state.profileIndex= st.session_state.profileIndices[st.session_state.index3]
|
46 |
+
|
47 |
+
header1, header2, header3 = st.columns([1,2,1])
|
48 |
+
characteristics1, characteristics2, characteristics3 = st.columns([1,2,1])
|
49 |
+
prediction1, prediction2, prediction3 =st.columns([1,2,1])
|
50 |
+
explanation1, explanation2, explanation3 = st.columns([1,10,1])
|
51 |
+
footer1, footer2, footer3 =st.columns([1,2,1])
|
52 |
+
evaluation1, evaluation2, evaluation3 = st.columns([1,2,1])
|
53 |
+
|
54 |
+
|
55 |
+
|
56 |
+
name= st.session_state.X_test_names.loc[st.session_state.profileIndex, "Name"]
|
57 |
+
|
58 |
+
|
59 |
+
@st.cache_resource
|
60 |
+
def trainModel(X_train,Y_train):
|
61 |
+
model_1 = RandomForestClassifier().fit(X_train, Y_train) ## Random forest because XGBoost doesn't work with counterfactuals
|
62 |
+
return model_1
|
63 |
+
|
64 |
+
|
65 |
+
@st.cache_resource
|
66 |
+
def getcounterfactual_values(_model,X_prediction, X_train):
|
67 |
+
# compute counterfactual values
|
68 |
+
train_df = pd.read_csv('assets/train_df.csv')
|
69 |
+
continous_col=["Age", 'Fare', 'Siblings_spouses', 'Title', 'Parents_children','relatives' ]
|
70 |
+
# test_df_counter = X_test.copy()
|
71 |
+
# test_df_counter['Survived'] = X_prediction
|
72 |
+
dice_data = dice_ml.Data(dataframe=train_df,continuous_features=continous_col, outcome_name='Survived')
|
73 |
+
dice_model= dice_ml.Model(model=_model, backend="sklearn")
|
74 |
+
explainer = dice_ml.Dice(dice_data, dice_model, method="random")
|
75 |
+
return explainer
|
76 |
+
|
77 |
+
|
78 |
+
|
79 |
+
def Counterfactualsplot(X_test, explainer):
|
80 |
+
e1 = explainer.generate_counterfactuals(
|
81 |
+
X_test[1:2],total_CFs=4, desired_class="opposite",
|
82 |
+
features_to_vary = ['Age','Pclass', 'Sex','Siblings_spouses', 'Parents_children', 'Embarked', 'relatives', 'Title'] ) ## Deck, Fare
|
83 |
+
e1.cf_examples_list[0].final_cfs_df.to_csv(path_or_buf=rf'assets\counterfactuals_{name}.csv', index=False)
|
84 |
+
counter_csv = pd.read_csv(f'assets\counterfactuals_{name}.csv')
|
85 |
+
return st.dataframe(counter_csv, width=10000)
|
86 |
+
|
87 |
+
with header2:
|
88 |
+
st.header("Explanation - Counterfactuals")
|
89 |
+
st.markdown('''A counterfactual explanation describes a situation where if a specific event had not occurred, the conclusion would have been different
|
90 |
+
and a specific outcome would not have occurred. In machine learning, counterfactuals are used to explain prediction of individuals instances. The prediction
|
91 |
+
of the model will be analysed and certain conditions/features that created this prediction will be modified to obtain an different outcome for the model.''')
|
92 |
+
|
93 |
+
st.markdown('''As displayed in the graph below, the relation betwwen the inputs andthe prediciton is modified by the feature values that creates a simple causal
|
94 |
+
relationshhip betwen inputs and predictions.
|
95 |
+
''')
|
96 |
+
|
97 |
+
st.image('assets/counterfactual.jpg', caption = 'Causal relation between inputs and predictions', use_column_width = 'always' )
|
98 |
+
|
99 |
+
st.markdown('''A counterfactual explanation of a prediction will then describe the smallest amount of change that is necessary to make to change the output
|
100 |
+
prediction to a predefine one.''')
|
101 |
+
st.subheader(name, anchor='top')
|
102 |
+
# st.write("For debugging:")
|
103 |
+
# st.write(st.session_state.participantID)
|
104 |
+
random_forest= trainModel(st.session_state.X_train, st.session_state.Y_train)
|
105 |
+
|
106 |
+
with characteristics2:
|
107 |
+
# initialize list of lists
|
108 |
+
data = st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1)
|
109 |
+
# Create the pandas DataFrame
|
110 |
+
df = pd.DataFrame(data, columns=st.session_state.X_test.columns)
|
111 |
+
st.dataframe(df)
|
112 |
+
|
113 |
+
|
114 |
+
with prediction2:
|
115 |
+
# st.header("Prediction")
|
116 |
+
prediction = random_forest.predict(st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1))
|
117 |
+
prediction_all = random_forest.predict(st.session_state.X_test.values)
|
118 |
+
probability = random_forest.predict_proba(st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1))
|
119 |
+
if prediction == 0:
|
120 |
+
prob = round((probability[0][0]*100),2)
|
121 |
+
st.markdown("The model predicts with {}% probability that {} will :red[**not survive**]".format(prob, name) )
|
122 |
+
else:
|
123 |
+
prob = round((probability[0][1]*100),2)
|
124 |
+
st.markdown("The model predicts with {}% probability that {} will :green[**survive**]".format(prob, name) )
|
125 |
+
|
126 |
+
with explanation2:
|
127 |
+
st.subheader("Explanation")
|
128 |
+
st.markdown("counterfactual, more text here")
|
129 |
+
|
130 |
+
|
131 |
+
|
132 |
+
# with st.spinner("Please be patient, we are generating a new explanation"):
|
133 |
+
explainer= getcounterfactual_values(random_forest, prediction_all, st.session_state.X_test)
|
134 |
+
st.set_option('deprecation.showPyplotGlobalUse', False)
|
135 |
+
e1=Counterfactualsplot(st.session_state.X_test, explainer)
|
136 |
+
data_indices = pd.concat([d.reset_index(drop=True) for d in [st.session_state.ports_df, st.session_state.title_df, st.session_state.gender_df]], axis=1)
|
137 |
+
st.dataframe(data_indices)
|
138 |
+
|
139 |
+
with footer2:
|
140 |
+
if (st.session_state.index3 < len(st.session_state.profileIndices)-1):
|
141 |
+
if st.button("New profile"):
|
142 |
+
st.session_state.index3 = st.session_state.index3+1
|
143 |
+
st.session_state.profileIndex = st.session_state.profileIndices[st.session_state.index3]
|
144 |
+
st.experimental_rerun()
|
145 |
+
else:
|
146 |
+
def is_user_active():
|
147 |
+
if 'user_active3' in st.session_state.keys() and st.session_state['user_active3']:
|
148 |
+
return True
|
149 |
+
else:
|
150 |
+
return False
|
151 |
+
if is_user_active():
|
152 |
+
# if st.button("Continue to evaluation"):
|
153 |
+
# st.write(" ")
|
154 |
+
with st.form("my_form3", clear_on_submit=True):
|
155 |
+
st.subheader("Evaluation")
|
156 |
+
st.write("These questions only ask for your opinion about this specific explanation")
|
157 |
+
q1 = st.select_slider(
|
158 |
+
'**1**- From the explanation, I **understand** how the algorithm works:',
|
159 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
160 |
+
|
161 |
+
q2 = st.select_slider(
|
162 |
+
'**2**- This explanation of how the algorithm works is **satisfying**:',
|
163 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
164 |
+
|
165 |
+
q3 = st.select_slider(
|
166 |
+
'**3**- This explanation of how the algorithm works has **sufficient detail**:',
|
167 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
168 |
+
|
169 |
+
q4 = st.select_slider(
|
170 |
+
'**4**- This explanation of how the algorithm works seems **complete**:',
|
171 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
172 |
+
|
173 |
+
q5 = st.select_slider(
|
174 |
+
'**5**- This explanation of how the algorithm works **tells me how to use it**:',
|
175 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
176 |
+
|
177 |
+
q6 = st.select_slider(
|
178 |
+
'**6**- This explanation of how the algorithm works is **useful to my goals**:',
|
179 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
180 |
+
|
181 |
+
q7 = st.select_slider(
|
182 |
+
'**7**- This explanation of the algorithm shows me how **accurate** the algorithm is:',
|
183 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
184 |
+
|
185 |
+
q8 = st.select_slider(
|
186 |
+
'**8**- This explanation lets me judge when I should **trust and not trust** the algorithm:',
|
187 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
188 |
+
|
189 |
+
# Every form must have a submit button.
|
190 |
+
submitted = st.form_submit_button("Submit")
|
191 |
+
if submitted:
|
192 |
+
# st.write("question 1", q1)
|
193 |
+
st.session_state.oocsi.send('EngD_HAII', {
|
194 |
+
'participant_ID': st.session_state.participantID,
|
195 |
+
'type of explanation': 'counterfactual',
|
196 |
+
'q1': q1,
|
197 |
+
'q2': q2,
|
198 |
+
'q3': q3,
|
199 |
+
'q4': q4,
|
200 |
+
'q5': q5,
|
201 |
+
'q6': q6,
|
202 |
+
'q7': q7,
|
203 |
+
'q8': q8,
|
204 |
+
|
205 |
+
})
|
206 |
+
if (st.session_state.lastQuestion =='yes'):
|
207 |
+
switch_page('finalPage')
|
208 |
+
else:
|
209 |
+
st.session_state.profileIndex =st.session_state.profileIndices[0]
|
210 |
+
switch_page(st.session_state.pages[st.session_state.nextPage3])
|
211 |
+
else:
|
212 |
+
if st.button('Continue to evaluation'):
|
213 |
+
st.session_state['user_active3']=True
|
214 |
+
st.experimental_rerun()
|
215 |
+
|
pages/5_visualMap.py
ADDED
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
from oocsi_source import OOCSI
|
5 |
+
from uuid import uuid4
|
6 |
+
from streamlit_extras.switch_page_button import switch_page
|
7 |
+
import random
|
8 |
+
import shap
|
9 |
+
import xgboost as xgb
|
10 |
+
import matplotlib.pyplot as plt
|
11 |
+
import streamlit.components.v1 as components
|
12 |
+
|
13 |
+
|
14 |
+
#Delete this page from the array of pages to visit, this way it cannot be visited twice
|
15 |
+
if 'profile4' not in st.session_state:
|
16 |
+
st.session_state.pages.remove("visualMap")
|
17 |
+
st.session_state.profile4= 'deleted'
|
18 |
+
if (len(st.session_state.pages)>0):
|
19 |
+
st.session_state.nextPage4 = random.randint(0, len(st.session_state.pages)-1)
|
20 |
+
st.session_state.lastQuestion= 'no'
|
21 |
+
else:
|
22 |
+
st.session_state.lastQuestion= 'yes'
|
23 |
+
|
24 |
+
|
25 |
+
if 'index4' not in st.session_state:
|
26 |
+
st.session_state.index4= 0
|
27 |
+
|
28 |
+
if 'profileIndex' not in st.session_state:
|
29 |
+
st.session_state.profileIndex= st.session_state.profileIndices[st.session_state.index4]
|
30 |
+
|
31 |
+
|
32 |
+
|
33 |
+
header1, header2, header3 = st.columns([1,2,1])
|
34 |
+
characteristics1, characteristics2, characteristics3 = st.columns([1,2,1])
|
35 |
+
prediction1, prediction2, prediction3 =st.columns([1,2,1])
|
36 |
+
explanationheader1,explanationheader2, explanationheader3 = st.columns([1,2,1])
|
37 |
+
explanation1, explanation2, explanation3 = st.columns([1,6,1])
|
38 |
+
footer1, footer2, footer3 =st.columns([1,2,1])
|
39 |
+
evaluation1, evaluation2, evaluation3 = st.columns([1,2,1])
|
40 |
+
|
41 |
+
|
42 |
+
|
43 |
+
name= st.session_state.X_test_names.loc[st.session_state.profileIndex, "Name"]
|
44 |
+
|
45 |
+
|
46 |
+
@st.cache_resource
|
47 |
+
def trainModel(X_train,Y_train):
|
48 |
+
model = xgb.XGBClassifier().fit(X_train, Y_train)
|
49 |
+
return model
|
50 |
+
|
51 |
+
|
52 |
+
@st.cache_resource
|
53 |
+
def getSHAPvalues(_model,X_train, Y_train, X_test):
|
54 |
+
# compute SHAP values
|
55 |
+
explainer = shap.Explainer(_model, X_test)
|
56 |
+
shap_values = explainer(X_test)
|
57 |
+
return shap_values
|
58 |
+
|
59 |
+
|
60 |
+
|
61 |
+
|
62 |
+
def shapPlot(X_test, _shap_values):
|
63 |
+
return shap.plots.waterfall(shap_values[st.session_state.profileIndex])
|
64 |
+
|
65 |
+
|
66 |
+
with header2:
|
67 |
+
st.header('Visual Method for XAI')
|
68 |
+
st.markdown('''In this part, a new method of Explainability was implemented using more visual techniques for communicating of the model
|
69 |
+
predictions and the features influence. The values showed when clicked on each feature (title, Age, deck, ...) were obtained using the SHAP algorithm.
|
70 |
+
Let yourself play with it and tell us how easy it was to understand the model prediciton and the influence of the features!
|
71 |
+
|
72 |
+
''')
|
73 |
+
st.markdown("Click on the image to see how each attribute contributed and hover over them to see the SHAP values")
|
74 |
+
# st.subheader(name, anchor='top')
|
75 |
+
st.write("For debugging:")
|
76 |
+
st.write(st.session_state.participantID)
|
77 |
+
XGBmodel= trainModel(st.session_state.X_train, st.session_state.Y_train)
|
78 |
+
|
79 |
+
with characteristics2:
|
80 |
+
# initialize list of lists
|
81 |
+
data = st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1)
|
82 |
+
# Create the pandas DataFrame
|
83 |
+
df = pd.DataFrame(data, columns=st.session_state.X_test.columns)
|
84 |
+
# st.dataframe(df)
|
85 |
+
|
86 |
+
|
87 |
+
|
88 |
+
with prediction2:
|
89 |
+
st.subheader("Prediction")
|
90 |
+
prediction = XGBmodel.predict(st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1))
|
91 |
+
probability = XGBmodel.predict_proba(st.session_state.X_test.iloc[st.session_state.profileIndex].values.reshape(1, -1))
|
92 |
+
if prediction == 0:
|
93 |
+
prob = round((probability[0][0]*100),2)
|
94 |
+
st.markdown("The model predicts with {}% probability that {} will :red[**not survive**]".format(prob, name) )
|
95 |
+
else:
|
96 |
+
prob = round((probability[0][1]*100),2)
|
97 |
+
st.markdown("The model predicts with {}% probability that {} will :green[**survive**]".format(prob, name) )
|
98 |
+
|
99 |
+
# with explanationheader2:
|
100 |
+
# st.subheader("Explanation")
|
101 |
+
# st.markdown("Click on the image to see how each attribute contributed and hover over them to see the SHAP values")
|
102 |
+
|
103 |
+
with explanation2:
|
104 |
+
components.iframe("https://observablehq.com/embed/d177ef99668b6553@1065?cells=viewof+button%2Cchart2", scrolling=False, height=683)
|
105 |
+
|
106 |
+
|
107 |
+
with footer2:
|
108 |
+
|
109 |
+
def is_user_active():
|
110 |
+
if 'user_active4' in st.session_state.keys() and st.session_state['user_active4']:
|
111 |
+
return True
|
112 |
+
else:
|
113 |
+
return False
|
114 |
+
# if st.button('press here to edit'):
|
115 |
+
if is_user_active():
|
116 |
+
with st.form("my_form4", clear_on_submit=True):
|
117 |
+
st.subheader("Evaluation")
|
118 |
+
st.write("These questions only ask for your opinion about this specific explanation")
|
119 |
+
q1 = st.select_slider(
|
120 |
+
'**1**- From the explanation, I **understand** how the algorithm works:',
|
121 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
122 |
+
|
123 |
+
q2 = st.select_slider(
|
124 |
+
'**2**- This explanation of how the algorithm works is **satisfying**:',
|
125 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
126 |
+
|
127 |
+
q3 = st.select_slider(
|
128 |
+
'**3**- This explanation of how the algorithm works has **sufficient detail**:',
|
129 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
130 |
+
|
131 |
+
q4 = st.select_slider(
|
132 |
+
'**4**- This explanation of how the algorithm works seems **complete**:',
|
133 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
134 |
+
|
135 |
+
q5 = st.select_slider(
|
136 |
+
'**5**- This explanation of how the algorithm works **tells me how to use it**:',
|
137 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
138 |
+
|
139 |
+
q6 = st.select_slider(
|
140 |
+
'**6**- This explanation of how the algorithm works is **useful to my goals**:',
|
141 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
142 |
+
|
143 |
+
q7 = st.select_slider(
|
144 |
+
'**7**- This explanation of the algorithm shows me how **accurate** the algorithm is:',
|
145 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
146 |
+
|
147 |
+
q8 = st.select_slider(
|
148 |
+
'**8**- This explanation lets me judge when I should **trust and not trust** the algorithm:',
|
149 |
+
options=['totally disagree', 'disagree', 'neutral' , 'agree', 'totally agree'])
|
150 |
+
|
151 |
+
# Every form must have a submit button.
|
152 |
+
submitted = st.form_submit_button("Submit")
|
153 |
+
if submitted:
|
154 |
+
#st.write("question 1", q1)
|
155 |
+
st.session_state.oocsi.send('EngD_HAII', {
|
156 |
+
'participant_ID': st.session_state.participantID,
|
157 |
+
'type of explanation': 'visualmap',
|
158 |
+
'q1': q1,
|
159 |
+
'q2': q2,
|
160 |
+
'q3': q3,
|
161 |
+
'q4': q4,
|
162 |
+
'q5': q5,
|
163 |
+
'q6': q6,
|
164 |
+
'q7': q7,
|
165 |
+
'q8': q8,
|
166 |
+
|
167 |
+
})
|
168 |
+
if (st.session_state.lastQuestion =='yes'):
|
169 |
+
switch_page('finalPage')
|
170 |
+
else:
|
171 |
+
st.session_state.profileIndex =st.session_state.profileIndices[0]
|
172 |
+
switch_page(st.session_state.pages[st.session_state.nextPage4])
|
173 |
+
else:
|
174 |
+
if st.button('Continue to evaluation'):
|
175 |
+
st.session_state['user_active4']=True
|
176 |
+
st.experimental_rerun()
|
177 |
+
|
178 |
+
|
179 |
+
|
pages/6_finalpage.py
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import streamlit.components.v1 as components
|
3 |
+
from streamlit_extras.switch_page_button import switch_page
|
4 |
+
from oocsi_source import OOCSI
|
5 |
+
|
6 |
+
header1, header2, header3 = st.columns([1,4,1])
|
7 |
+
image1, image2, image3 = st.columns([1,50,1])
|
8 |
+
body1, body2, body3 =st.columns([1,2,1])
|
9 |
+
|
10 |
+
|
11 |
+
with header2:
|
12 |
+
st.title("Comparing the different methods")
|
13 |
+
st.markdown("This is the final section of this experiment, please rate and compare the different methods")
|
14 |
+
|
15 |
+
with image2:
|
16 |
+
st.image('assets/images/overview methods.png')
|
17 |
+
|
18 |
+
with body2:
|
19 |
+
with st.form("my_form"):
|
20 |
+
st.write("As a final evaluation, please rate the different types of explanations (0-10). This is a general grade that you you would give to the different explanation methods.")
|
21 |
+
|
22 |
+
|
23 |
+
shap = st.slider('SHAP', 0, 10)
|
24 |
+
dt = st.slider('Decision tree', 0, 10)
|
25 |
+
counterfactual = st.slider("Counterfactual", 0, 10)
|
26 |
+
visualmap = st.slider("Visual map", 0, 10)
|
27 |
+
favourite = st.radio("What was your favourite type of epxlanation?", ('SHAP', 'Decision tree', 'Counterfactual', 'Visual map'))
|
28 |
+
why = st.text_area('Please explain why', "")
|
29 |
+
# Every form must have a submit button.
|
30 |
+
|
31 |
+
submitted = st.form_submit_button("Submit")
|
32 |
+
if submitted:
|
33 |
+
st.session_state.oocsi.send('EngD_HAII_comparison', {
|
34 |
+
'participant_ID': st.session_state.participantID,
|
35 |
+
'shap': shap,
|
36 |
+
'decisiontree': dt,
|
37 |
+
'counterfactual': counterfactual,
|
38 |
+
'visualmap': visualmap,
|
39 |
+
'favourite': favourite,
|
40 |
+
'why': why
|
41 |
+
})
|
42 |
+
st.balloons()
|
43 |
+
switch_page('thankyou')
|
44 |
+
# Execute your app
|
45 |
+
# embed streamlit docs in a streamlit app
|
46 |
+
|
47 |
+
|
pages/7_thankyou.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import streamlit.components.v1 as components
|
3 |
+
from streamlit_extras.switch_page_button import switch_page
|
4 |
+
from oocsi_source import OOCSI
|
5 |
+
|
6 |
+
header1, header2, header3 = st.columns([1,2,1])
|
7 |
+
body1, body2, body3 =st.columns([1,2,1])
|
8 |
+
|
9 |
+
|
10 |
+
with header2:
|
11 |
+
st.balloons()
|
12 |
+
st.title("Thank you for completing this survey")
|
13 |
+
st.write("You can now close this tab")
|