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%")
|