HugBot commited on
Commit
cfc7962
Β·
verified Β·
1 Parent(s): 88ffb40

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +135 -160
app.py CHANGED
@@ -1,23 +1,18 @@
1
- # -*- coding: utf-8 -*-
2
-
3
-
4
  import numpy as np
5
  import matplotlib.pyplot as plt
6
- import ipywidgets as widgets
7
- from ipywidgets import FloatSlider, VBox, HBox, Layout, HTML
8
- from IPython.display import display
9
 
10
  # ── style ──────────────────────────────────────────────────────────────────
11
- BG = '#f8fafc'
12
- PANEL = '#ffffff'
13
- GRID = '#e2e8f0'
14
- ACCENT = '#2563eb'
15
- C_INC = '#d97706'
16
- C_ZS = '#7c3aed'
17
- C_RR = '#059669'
18
- C_TEXT = '#1e293b'
19
- C_DIM = '#64748b'
20
- C_BORDER= '#cbd5e1'
21
 
22
  plt.rcParams.update({
23
  'figure.facecolor': BG, 'axes.facecolor': PANEL,
@@ -32,153 +27,133 @@ EP_RANGE = np.linspace(0, 15, 600)
32
  def calc_paf(episodes, incidence, zscore, rr):
33
  return 1 - 1 / np.exp(episodes * incidence * zscore * rr)
34
 
35
- def make_slider(desc, mn, mx, val, step, color):
36
- return FloatSlider(
37
- value=val, min=mn, max=mx, step=step,
38
- description=desc, continuous_update=True,
39
- layout=Layout(width='520px'),
40
- style={'description_width': '160px', 'handle_color': color}
41
- )
42
-
43
- # ── widgets ────────────────────────────────────────────────────────────────
44
- s_ep = make_slider('Episodes', 0, 15, 4.2, 0.1, ACCENT)
45
- s_inc = make_slider('Pathogen Incidence', 0.01, 1, 0.3, 0.01, C_INC)
46
- s_zs = make_slider('Ξ”z per Episode', 0.01, 2, 0.2, 0.01, C_ZS)
47
- s_rr = make_slider('Relative Risk (RR)', 1.01, 5, 1.5, 0.05, C_RR)
48
-
49
- btn_reset = widgets.Button(
50
- description='Reset Defaults',
51
- layout=Layout(width='160px', height='34px'),
52
- style={'button_color': '#e2e8f0'}
53
- )
54
-
55
- info_box = HTML()
56
- out = widgets.Output()
57
-
58
  # ── plot function ──────────────────────────────────────────────────────────
59
  def draw(ep, inc, zs, rr):
60
  paf_curve = calc_paf(EP_RANGE, inc, zs, rr) * 100
61
  cur_paf = calc_paf(ep, inc, zs, rr)
62
  exponent = ep * inc * zs * rr
63
 
64
- with out:
65
- out.clear_output(wait=True)
66
- fig, axes = plt.subplots(1, 2, figsize=(15, 6),
67
- gridspec_kw={'width_ratios': [3, 1]})
68
- fig.patch.set_facecolor(BG)
69
-
70
- # ── left: curve ───────────────────────────────────────────────────
71
- ax = axes[0]
72
- ax.set_facecolor(PANEL)
73
- for spine in ax.spines.values():
74
- spine.set_color(C_BORDER)
75
-
76
- # shadow effect
77
- ax.plot(EP_RANGE, paf_curve, color=ACCENT, linewidth=5, alpha=0.15)
78
- ax.plot(EP_RANGE, paf_curve, color=ACCENT, linewidth=2.2)
79
-
80
- # crosshairs
81
- ax.axvline(ep, color=ACCENT, linestyle='--', linewidth=1.2, alpha=0.6)
82
- ax.axhline(cur_paf*100, color=ACCENT, linestyle='--', linewidth=1.2, alpha=0.3)
83
-
84
- # dot at current point
85
- ax.scatter([ep], [cur_paf*100], color=ACCENT, s=80, zorder=5, edgecolors='white', linewidths=1.5)
86
-
87
- # shaded area under curve up to current episode
88
- mask = EP_RANGE <= ep
89
- ax.fill_between(EP_RANGE[mask], paf_curve[mask], color=ACCENT, alpha=0.08)
90
-
91
- ax.set_xlim(0, 15)
92
- ax.set_ylim(0, 102)
93
- ax.set_xlabel('Episodes', fontsize=11, labelpad=8)
94
- ax.set_ylabel('PAF (%)', fontsize=11, labelpad=8)
95
- ax.set_title('Population Attributable Fraction',
96
- color=C_TEXT, fontsize=13, pad=12, fontweight='normal')
97
- ax.grid(True, linewidth=0.8)
98
-
99
- # annotation
100
- ax.annotate(
101
- f' {cur_paf*100:.1f}%',
102
- xy=(ep, cur_paf*100),
103
- xytext=(min(ep + 1, 13), cur_paf*100 + 4),
104
- color=ACCENT, fontsize=15, fontweight='bold',
105
- arrowprops=dict(arrowstyle='->', color=ACCENT, lw=1.2)
106
- )
107
-
108
- # formula watermark
109
- ax.text(0.98, 0.05,
110
- 'PAF = 1 βˆ’ exp(βˆ’ep Γ— inc Γ— Ξ”z Γ— RR)',
111
- transform=ax.transAxes, fontsize=15,
112
- color=C_DIM, ha='right', va='bottom')
113
-
114
- # ── right: breakdown panel ────────────────────────────────────────
115
- ax2 = axes[1]
116
- ax2.set_facecolor('#f1f5f9')
117
- for spine in ax2.spines.values():
118
- spine.set_color(C_BORDER)
119
- ax2.set_xticks([]); ax2.set_yticks([])
120
-
121
- rows = [
122
- ('episodes', f'{ep:.2f}', ACCENT),
123
- ('incidence', f'{inc:.3f}', C_INC),
124
- ('Ξ”z / ep.', f'{zs:.3f}', C_ZS),
125
- ('RR', f'{rr:.3f}', C_RR),
126
- ('─' * 10, '─' * 6, C_BORDER),
127
- ('exponent', f'{exponent:.4f}', '#475569'),
128
- ('exp(βˆ’x)', f'{1/np.exp(exponent):.4f}','#475569'),
129
- ('─' * 10, '─' * 6, C_BORDER),
130
- ('PAF', f'{cur_paf*100:.2f}%', ACCENT),
131
- ]
132
-
133
- ax2.text(0.5, 0.97, 'BREAKDOWN', transform=ax2.transAxes,
134
- ha='center', fontsize=15, color=ACCENT, fontweight='bold')
135
-
136
- y_start = 0.88
137
- for i, (k, v, c) in enumerate(rows):
138
- y = y_start - i * 0.095
139
- ax2.text(0.08, y, k, transform=ax2.transAxes,
140
- fontsize=12, color=C_DIM, va='center')
141
- ax2.text(0.92, y, v, transform=ax2.transAxes,
142
- fontsize=12, color=c, va='center', ha='right', fontweight='bold')
143
-
144
- ax2.set_title('Parameters', color=C_TEXT, fontsize=15, pad=8)
145
-
146
- plt.tight_layout(pad=1.5)
147
- plt.show()
148
-
149
- info_box.value = f"""
150
- <div style="background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;
151
- padding:8px 18px;font-family:monospace;font-size:15px;
152
- color:#64748b;display:inline-block;margin-top:6px;">
153
- episodes&nbsp;<b style="color:{ACCENT}">{ep:.2f}</b> &nbsp;Β·&nbsp;
154
- incidence&nbsp;<b style="color:{C_INC}">{inc:.3f}</b> &nbsp;Β·&nbsp;
155
- Ξ”z&nbsp;<b style="color:{C_ZS}">{zs:.3f}</b> &nbsp;Β·&nbsp;
156
- RR&nbsp;<b style="color:{C_RR}">{rr:.3f}</b> &nbsp;&nbsp;β†’&nbsp;&nbsp;
157
- <b style="color:{ACCENT};font-size:15px;">PAF = {cur_paf*100:.2f}%</b>
158
- </div>"""
159
-
160
- def on_change(change):
161
- draw(s_ep.value, s_inc.value, s_zs.value, s_rr.value)
162
-
163
- def on_reset(b):
164
- s_ep.value=5.0; s_inc.value=0.3; s_zs.value=1.2; s_rr.value=2.5
165
-
166
- for s in [s_ep, s_inc, s_zs, s_rr]:
167
- s.observe(on_change, names='value')
168
- btn_reset.on_click(on_reset)
169
-
170
- # ── layout & display ───────────────────────────────────────────────────────
171
- header = HTML("""
172
- <div style="background:#eff6ff;border:1px solid #bfdbfe;border-radius:10px;
173
- padding:12px 20px;margin-bottom:12px;font-family:monospace;">
174
- <span style="color:#1e293b;font-size:18px;">Population Attributable Fraction Visualizer</span>
175
- </div>""")
176
-
177
- controls = VBox([
178
- HBox([s_ep, s_inc]),
179
- HBox([s_zs, s_rr]),
180
- HBox([btn_reset, info_box])
181
- ], layout=Layout(padding='10px'))
182
-
183
- display(VBox([header, controls, out]))
184
- draw(s_ep.value, s_inc.value, s_zs.value, s_rr.value)
 
 
 
 
 
 
 
1
  import numpy as np
2
  import matplotlib.pyplot as plt
3
+ import gradio as gr
 
 
4
 
5
  # ── style ──────────────────────────────────────────────────────────────────
6
+ BG = '#f8fafc'
7
+ PANEL = '#ffffff'
8
+ GRID = '#e2e8f0'
9
+ ACCENT = '#2563eb'
10
+ C_INC = '#d97706'
11
+ C_ZS = '#7c3aed'
12
+ C_RR = '#059669'
13
+ C_TEXT = '#1e293b'
14
+ C_DIM = '#64748b'
15
+ C_BORDER = '#cbd5e1'
16
 
17
  plt.rcParams.update({
18
  'figure.facecolor': BG, 'axes.facecolor': PANEL,
 
27
  def calc_paf(episodes, incidence, zscore, rr):
28
  return 1 - 1 / np.exp(episodes * incidence * zscore * rr)
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  # ── plot function ──────────────────────────────────────────────────────────
31
  def draw(ep, inc, zs, rr):
32
  paf_curve = calc_paf(EP_RANGE, inc, zs, rr) * 100
33
  cur_paf = calc_paf(ep, inc, zs, rr)
34
  exponent = ep * inc * zs * rr
35
 
36
+ fig, axes = plt.subplots(1, 2, figsize=(15, 6),
37
+ gridspec_kw={'width_ratios': [3, 1]})
38
+ fig.patch.set_facecolor(BG)
39
+
40
+ # ── left: curve ───────────────────────────────────────────────────────
41
+ ax = axes[0]
42
+ ax.set_facecolor(PANEL)
43
+ for spine in ax.spines.values():
44
+ spine.set_color(C_BORDER)
45
+
46
+ # shadow + main curve
47
+ ax.plot(EP_RANGE, paf_curve, color=ACCENT, linewidth=5, alpha=0.15)
48
+ ax.plot(EP_RANGE, paf_curve, color=ACCENT, linewidth=2.2)
49
+
50
+ # crosshairs
51
+ ax.axvline(ep, color=ACCENT, linestyle='--', linewidth=1.2, alpha=0.6)
52
+ ax.axhline(cur_paf*100, color=ACCENT, linestyle='--', linewidth=1.2, alpha=0.3)
53
+
54
+ # dot at current point
55
+ ax.scatter([ep], [cur_paf*100], color=ACCENT, s=80, zorder=5,
56
+ edgecolors='white', linewidths=1.5)
57
+
58
+ # shaded area under curve up to current episode
59
+ mask = EP_RANGE <= ep
60
+ ax.fill_between(EP_RANGE[mask], paf_curve[mask], color=ACCENT, alpha=0.08)
61
+
62
+ ax.set_xlim(0, 15)
63
+ ax.set_ylim(0, 102)
64
+ ax.set_xlabel('Episodes', fontsize=11, labelpad=8)
65
+ ax.set_ylabel('PAF (%)', fontsize=11, labelpad=8)
66
+ ax.set_title('Population Attributable Fraction',
67
+ color=C_TEXT, fontsize=13, pad=12, fontweight='normal')
68
+ ax.grid(True, linewidth=0.8)
69
+
70
+ # annotation
71
+ ax.annotate(
72
+ f' {cur_paf*100:.1f}%',
73
+ xy=(ep, cur_paf*100),
74
+ xytext=(min(ep + 1, 13), cur_paf*100 + 4),
75
+ color=ACCENT, fontsize=15, fontweight='bold',
76
+ arrowprops=dict(arrowstyle='->', color=ACCENT, lw=1.2)
77
+ )
78
+
79
+ # formula watermark
80
+ ax.text(0.98, 0.05,
81
+ 'PAF = 1 βˆ’ exp(βˆ’ep Γ— inc Γ— Ξ”z Γ— RR)',
82
+ transform=ax.transAxes, fontsize=11,
83
+ color=C_DIM, ha='right', va='bottom')
84
+
85
+ # ── right: breakdown panel ────────────────────────────────────────────
86
+ ax2 = axes[1]
87
+ ax2.set_facecolor('#f1f5f9')
88
+ for spine in ax2.spines.values():
89
+ spine.set_color(C_BORDER)
90
+ ax2.set_xticks([])
91
+ ax2.set_yticks([])
92
+
93
+ rows = [
94
+ ('episodes', f'{ep:.2f}', ACCENT),
95
+ ('incidence', f'{inc:.3f}', C_INC),
96
+ ('Ξ”z / ep.', f'{zs:.3f}', C_ZS),
97
+ ('RR', f'{rr:.3f}', C_RR),
98
+ ('─' * 10, '─' * 6, C_BORDER),
99
+ ('exponent', f'{exponent:.4f}', '#475569'),
100
+ ('exp(βˆ’x)', f'{1/np.exp(exponent):.4f}', '#475569'),
101
+ ('─' * 10, '─' * 6, C_BORDER),
102
+ ('PAF', f'{cur_paf*100:.2f}%', ACCENT),
103
+ ]
104
+
105
+ ax2.text(0.5, 0.97, 'BREAKDOWN', transform=ax2.transAxes,
106
+ ha='center', fontsize=13, color=ACCENT, fontweight='bold')
107
+
108
+ y_start = 0.88
109
+ for i, (k, v, c) in enumerate(rows):
110
+ y = y_start - i * 0.095
111
+ ax2.text(0.08, y, k, transform=ax2.transAxes,
112
+ fontsize=11, color=C_DIM, va='center')
113
+ ax2.text(0.92, y, v, transform=ax2.transAxes,
114
+ fontsize=11, color=c, va='center', ha='right', fontweight='bold')
115
+
116
+ ax2.set_title('Parameters', color=C_TEXT, fontsize=13, pad=8)
117
+
118
+ plt.tight_layout(pad=1.5)
119
+ return fig
120
+
121
+ # ── Gradio UI ─────────────────────────────────────────────────────────────
122
+ with gr.Blocks(theme=gr.themes.Soft(), title="PAF Visualizer") as demo:
123
+
124
+ gr.HTML("""
125
+ <div style="background:#eff6ff;border:1px solid #bfdbfe;border-radius:10px;
126
+ padding:12px 20px;margin-bottom:12px;font-family:monospace;">
127
+ <span style="color:#1e293b;font-size:20px;font-weight:600;">
128
+ Population Attributable Fraction Visualizer
129
+ </span><br>
130
+ <span style="color:#64748b;font-size:13px;">
131
+ PAF = 1 βˆ’ exp(βˆ’episodes Γ— incidence Γ— Ξ”z Γ— RR)
132
+ </span>
133
+ </div>""")
134
+
135
+ with gr.Row():
136
+ with gr.Column():
137
+ ep = gr.Slider(0, 15, value=4.2, step=0.1, label="Episodes")
138
+ inc = gr.Slider(0.01, 1, value=0.3, step=0.01, label="Pathogen Incidence")
139
+ zs = gr.Slider(0.01, 2, value=0.2, step=0.01, label="Ξ”z per Episode")
140
+ rr = gr.Slider(1.01, 5, value=1.5, step=0.05, label="Relative Risk (RR)")
141
+
142
+ reset_btn = gr.Button("Reset Defaults", variant="secondary")
143
+
144
+ plot = gr.Plot(label="")
145
+
146
+ # live update on any slider change
147
+ for slider in [ep, inc, zs, rr]:
148
+ slider.change(fn=draw, inputs=[ep, inc, zs, rr], outputs=plot)
149
+
150
+ # reset button
151
+ def reset():
152
+ return 5.0, 0.3, 1.2, 2.5
153
+
154
+ reset_btn.click(fn=reset, outputs=[ep, inc, zs, rr])
155
+
156
+ # draw on load
157
+ demo.load(fn=draw, inputs=[ep, inc, zs, rr], outputs=plot)
158
+
159
+ demo.launch()