V1
Browse files
app.py
CHANGED
@@ -1,4 +1,249 @@
|
|
1 |
import streamlit as st
|
|
|
|
|
|
|
2 |
|
3 |
-
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
+
import altair as alt
|
3 |
+
import numpy as np
|
4 |
+
import pandas as pd
|
5 |
|
6 |
+
st.markdown(
|
7 |
+
"""
|
8 |
+
<style>
|
9 |
+
@font-face {
|
10 |
+
font-family: 'Tangerine';
|
11 |
+
font-style: normal;
|
12 |
+
font-weight: 400;
|
13 |
+
src: url(https://fonts.gstatic.com/s/tangerine/v12/IurY6Y5j_oScZZow4VOxCZZM.woff2) format('woff2');
|
14 |
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
15 |
+
}
|
16 |
+
|
17 |
+
html, body, [class*="css"] {
|
18 |
+
font-family: 'Public Sans', sans-serif;
|
19 |
+
# font-size: 1rem;
|
20 |
+
}
|
21 |
+
</style>
|
22 |
+
|
23 |
+
""",
|
24 |
+
unsafe_allow_html=True,
|
25 |
+
)
|
26 |
+
|
27 |
+
|
28 |
+
# Define params
|
29 |
+
st.subheader("Configuration")
|
30 |
+
col1, col2 = st.columns(2)
|
31 |
+
|
32 |
+
# Chances of developing symptoms (per day)
|
33 |
+
with col1:
|
34 |
+
symptoms_chance = st.slider(
|
35 |
+
'Chances of developing symptoms if infected (per day)', min_value=0.0, max_value=1.0, value=0.5, step=0.01)
|
36 |
+
|
37 |
+
# Days spent inf asympt
|
38 |
+
with col1:
|
39 |
+
mean_days_inf_asympt = st.slider(
|
40 |
+
'Mean number of days as infectious asymptomatic (without routine testing)', min_value=1, max_value=14, value=4, step=1)
|
41 |
+
base_p00 = 1-(1/mean_days_inf_asympt)
|
42 |
+
base_p01 = (1-symptoms_chance)*(1/mean_days_inf_asympt)
|
43 |
+
base_p03 = (symptoms_chance)*(1/mean_days_inf_asympt)
|
44 |
+
|
45 |
+
# Days spent inf asympt
|
46 |
+
with col2:
|
47 |
+
mean_days_inf_sympt = st.slider(
|
48 |
+
'Mean number of days as infectious symptomatic (when testing on symptoms only)', min_value=1, max_value=14, value=2, step=1)
|
49 |
+
base_p11 = 1-(1/mean_days_inf_sympt)
|
50 |
+
base_p12 = (1/mean_days_inf_sympt)
|
51 |
+
|
52 |
+
# Wearable efficiency
|
53 |
+
efficiency = st.radio(
|
54 |
+
"Performance of device",
|
55 |
+
('Standard', 'Conservative'))
|
56 |
+
# with col2:
|
57 |
+
# wear_efficiency = st.slider(
|
58 |
+
# 'Sensitivity of device', min_value=0.0, max_value=1.0, value=0.2, step=0.01) # 👈 this is a widget
|
59 |
+
|
60 |
+
|
61 |
+
|
62 |
+
# # Calculate
|
63 |
+
# test_efficiency = np.linspace(1, 30, 30)
|
64 |
+
# days_inf = np.zeros((len(test_efficiency)))
|
65 |
+
# temp_df = []
|
66 |
+
# for tau_count, t_e in enumerate(test_efficiency):
|
67 |
+
# tau = 1/t_e
|
68 |
+
# pi = wear_efficiency
|
69 |
+
# # Transition matrix
|
70 |
+
# p = np.array([
|
71 |
+
# [base_p00*(1-tau)*(1-pi), base_p01*(1-tau) *
|
72 |
+
# (1-pi), 1-(1-tau)*(1-pi), base_p03*(1-tau)*(1-pi)],
|
73 |
+
# [0, base_p11*(1-tau)*(1-pi), base_p12*(1+tau+pi-tau*pi), 0.0],
|
74 |
+
# [0, 0, 1.0, 0.0],
|
75 |
+
# [0, 0, 0.0, 1.0]
|
76 |
+
# ])
|
77 |
+
|
78 |
+
# m1 = 1/(1-p[0,0])
|
79 |
+
# m2 = 1/(1-p[1,1])
|
80 |
+
# p2 = p[0,1]/(p[0,1]+p[0,2]+p[0,3])
|
81 |
+
# days_inf[int(tau_count)] = m1 + p2*m2
|
82 |
+
# routine_tests_required = 30 * days_inf[2]
|
83 |
+
|
84 |
+
# Cost case
|
85 |
+
sens_list_standard = {0.0: 0.0,
|
86 |
+
0.005: 0.05,
|
87 |
+
0.014: 0.1,
|
88 |
+
0.021: 0.15,
|
89 |
+
0.05: 0.295,
|
90 |
+
0.1: 0.434,
|
91 |
+
0.2: 0.6,
|
92 |
+
0.3: 0.72,
|
93 |
+
0.4: 0.79,
|
94 |
+
0.5: 0.86,
|
95 |
+
0.6: 0.9,
|
96 |
+
0.7: 0.925,
|
97 |
+
0.8: 0.97,
|
98 |
+
0.9: 0.99,
|
99 |
+
1.0: 1.0}
|
100 |
+
|
101 |
+
sens_list_conservative = {
|
102 |
+
0: 0,
|
103 |
+
0.012: 0.050,
|
104 |
+
0.026: 0.105,
|
105 |
+
0.049: 0.149,
|
106 |
+
0.072: 0.198,
|
107 |
+
0.096: 0.248,
|
108 |
+
0.120: 0.297,
|
109 |
+
0.146: 0.347,
|
110 |
+
0.184: 0.396,
|
111 |
+
0.222: 0.446,
|
112 |
+
0.255: 0.495,
|
113 |
+
0.300: 0.545,
|
114 |
+
0.349: 0.594,
|
115 |
+
0.401: 0.644,
|
116 |
+
0.467: 0.693,
|
117 |
+
0.547: 0.743,
|
118 |
+
0.621: 0.792,
|
119 |
+
0.699: 0.842,
|
120 |
+
0.787: 0.891,
|
121 |
+
0.868: 0.941,
|
122 |
+
1: 1
|
123 |
+
}
|
124 |
+
|
125 |
+
if efficiency == 'Standard':
|
126 |
+
sens_list = sens_list_standard
|
127 |
+
else:
|
128 |
+
sens_list = sens_list_conservative
|
129 |
+
|
130 |
+
def roc_func(x):
|
131 |
+
return sens_list[x]
|
132 |
+
|
133 |
+
def roc_random(x):
|
134 |
+
return x
|
135 |
+
|
136 |
+
test_efficiency = np.array([7, 30, 10000])
|
137 |
+
# FPR = np.linspace(0, 1, 11)
|
138 |
+
# FPR = [0.0, 0.005, 0.016, 0.021, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
|
139 |
+
# FPR = list(sens_list.keys())
|
140 |
+
# days_inf = np.zeros((len(test_efficiency), len(FPR)))
|
141 |
+
|
142 |
+
# for tau_count, t_e in enumerate(test_efficiency):
|
143 |
+
# tau = 1/t_e
|
144 |
+
# for fi_count, fi in enumerate(FPR):
|
145 |
+
# pi = roc_func(fi)
|
146 |
+
# alpha = tau + pi - (tau*pi)
|
147 |
+
# m1 = 4/(1+3*alpha)
|
148 |
+
# m2 = 2/(1 + alpha)
|
149 |
+
# p2 = 1/2 * (1 - alpha) / (1 + 3 * alpha)
|
150 |
+
# days_inf[int(tau_count), int(fi_count)] = m1 + p2*m2
|
151 |
+
|
152 |
+
# Calculate
|
153 |
+
test_efficiency = np.array([7, 30, 10000])
|
154 |
+
FPR = list(sens_list.keys())
|
155 |
+
days_inf = np.zeros((len(test_efficiency), len(FPR)))
|
156 |
+
temp_df = []
|
157 |
+
for tau_count, t_e in enumerate(test_efficiency):
|
158 |
+
tau = 1/t_e
|
159 |
+
for fi_count, fi in enumerate(FPR):
|
160 |
+
pi = roc_func(fi)
|
161 |
+
# Transition matrix
|
162 |
+
p = np.array([
|
163 |
+
[base_p00*(1-tau)*(1-pi), base_p01*(1-tau) *
|
164 |
+
(1-pi), 1-(1-tau)*(1-pi), base_p03*(1-tau)*(1-pi)],
|
165 |
+
[0, base_p11*(1-tau)*(1-pi), base_p12*(1+tau+pi-tau*pi), 0.0],
|
166 |
+
[0, 0, 1.0, 0.0],
|
167 |
+
[0, 0, 0.0, 1.0]
|
168 |
+
])
|
169 |
+
|
170 |
+
m1 = 1/(1-p[0, 0])
|
171 |
+
m2 = 1/(1-p[1, 1])
|
172 |
+
p2 = p[0, 1]/(p[0, 1]+p[0, 2]+p[0, 3])
|
173 |
+
days_inf[int(tau_count), int(fi_count)] = m1 + p2*m2
|
174 |
+
routine_tests_required = 30 * days_inf[2]
|
175 |
+
# print(routine_tests_required)
|
176 |
+
|
177 |
+
# No wearable case
|
178 |
+
no_wearables = []
|
179 |
+
tau = 1/10000
|
180 |
+
for fi_count, fi in enumerate(FPR):
|
181 |
+
pi = roc_random(fi)
|
182 |
+
# Transition matrix
|
183 |
+
p = np.array([
|
184 |
+
[base_p00*(1-tau)*(1-pi), base_p01*(1-tau) *
|
185 |
+
(1-pi), 1-(1-tau)*(1-pi), base_p03*(1-tau)*(1-pi)],
|
186 |
+
[0, base_p11*(1-tau)*(1-pi), base_p12*(1+tau+pi-tau*pi), 0.0],
|
187 |
+
[0, 0, 1.0, 0.0],
|
188 |
+
[0, 0, 0.0, 1.0]
|
189 |
+
])
|
190 |
+
|
191 |
+
m1 = 1/(1-p[0, 0])
|
192 |
+
m2 = 1/(1-p[1, 1])
|
193 |
+
p2 = p[0, 1]/(p[0, 1]+p[0, 2]+p[0, 3])
|
194 |
+
no_wearables.append(m1 + p2*m2)
|
195 |
+
|
196 |
+
cost = np.array(FPR)*30
|
197 |
+
no_wearable_cost = cost
|
198 |
+
# for i in range(len(test_efficiency)):0
|
199 |
+
|
200 |
+
wearable_cost = (1-(1-np.array(FPR))*(1-1/test_efficiency[2]))*30
|
201 |
+
wearable_days_inf = days_inf[2]
|
202 |
+
|
203 |
+
# Create chart
|
204 |
+
chart_data = pd.DataFrame(
|
205 |
+
{'Tests required per month': no_wearable_cost,
|
206 |
+
'Routine testing': no_wearables,
|
207 |
+
'Wearable-triggered testing': wearable_days_inf})
|
208 |
+
|
209 |
+
# st.line_chart(chart_data)
|
210 |
+
|
211 |
+
chart_data_melted = chart_data.melt('Tests required per month')
|
212 |
+
print(chart_data_melted)
|
213 |
+
chart = (
|
214 |
+
alt.Chart(
|
215 |
+
data=chart_data_melted,
|
216 |
+
title="",
|
217 |
+
height=400,
|
218 |
+
)
|
219 |
+
.mark_line()
|
220 |
+
# .encode(
|
221 |
+
# x=alt.X('Tests required per month',
|
222 |
+
# scale=alt.Scale(domain=[0, 30])),
|
223 |
+
# y=alt.Y('Average case infectious days',
|
224 |
+
# scale=alt.Scale(domain=[0, 6])),
|
225 |
+
# # color=alt.value("#162d88"),
|
226 |
+
# color=alt.Color("name:N"),
|
227 |
+
# strokeWidth=alt.value(6),
|
228 |
+
# )
|
229 |
+
.encode(
|
230 |
+
x='Tests required per month',
|
231 |
+
y=alt.Y('value:Q', axis=alt.Axis(title='Average case infectious days')),
|
232 |
+
# y='value:Q',
|
233 |
+
color='variable:N',
|
234 |
+
strokeWidth=alt.value(6)
|
235 |
+
)
|
236 |
+
.configure_axis(
|
237 |
+
labelFontSize=20,
|
238 |
+
titleFontSize=20
|
239 |
+
)
|
240 |
+
|
241 |
+
)
|
242 |
+
|
243 |
+
st.subheader("Outcome")
|
244 |
+
st.altair_chart(chart, use_container_width=True)
|
245 |
+
|
246 |
+
# col1, col2, col3 = st.columns(3)
|
247 |
+
# col1.metric("Tests required per month", int(routine_tests_required), "1.2")
|
248 |
+
# col2.metric("Tests saved", "9", "-8%")
|
249 |
+
# col3.metric("Humidity", "86%", "4%")
|