-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathROC_curve_testingAnswers.py
281 lines (212 loc) · 12.1 KB
/
ROC_curve_testingAnswers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
import streamlit as st
import random
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# Function to generate a dog's T4 or T3 value and disease status
def generate_dog_data(test_type, breed):
breed_prevalence = {
"Golden Retriever": 0.60,
"Shih-tzu": 0.05,
"Alaskan Malamute": 0.35
}
is_hypothyroid = random.random() < breed_prevalence[breed]
if test_type == 'T4':
if is_hypothyroid:
value = random.uniform(5, 20) # Lower T4 values for hypothyroid dogs
else:
value = random.uniform(15, 50) # Normal to high T4 values for healthy dogs
# Add some noise to simulate imperfect data
value += random.uniform(-5, 5)
# Ensure T4 value is not negative
value = max(0, value)
else: # T3
if is_hypothyroid:
value = random.uniform(10, 40) # Lower T3 values for hypothyroid dogs
else:
value = random.uniform(30, 100) # Normal to high T3 values for healthy dogs
# Add more noise to T3 to simulate higher error rate
value += random.uniform(-10, 10)
# Ensure T3 value is not negative
value = max(0, value)
return round(value, 2), is_hypothyroid
# App title
st.title("Canine Hypothyroidism Diagnostic Simulation")
# Instructions for students
st.header("Instructions for Students")
st.markdown("""
## Canine Hypothyroidism Diagnostic Simulation: Student Guide
### Background
Hypothyroidism is a common endocrine disorder in dogs, characterized by insufficient production of thyroid hormones. Accurate diagnosis is crucial for proper treatment and management of the condition. Two key thyroid hormones used in diagnosing hypothyroidism are:
1. **Thyroxine (T4)**: The primary hormone produced by the thyroid gland.
2. **Triiodothyronine (T3)**: The active form of thyroid hormone, converted from T4.
### Diagnostic Challenges
Diagnosing hypothyroidism can be challenging due to several factors:
1. Overlap in hormone levels between healthy and hypothyroid dogs.
2. Variations in individual dogs' normal hormone ranges.
3. Influence of non-thyroidal factors on hormone levels.
4. Differences in test accuracy between T4 and T3 measurements.
### Simulation Exercise
This simulation allows you to explore the challenges of diagnosing hypothyroidism using either T4 or T3 tests. You'll sample a population of dogs, analyze their hormone levels, and set diagnostic thresholds to classify them as hypothyroid or healthy.
### Instructions
1. **Choose Your Test**: Select either T4 or T3 for testing. Note that T4 is generally considered more reliable for initial screening.
2. **Sample Size**: Decide how many dogs to sample (1-200). Larger samples provide more data but may take longer to analyze.
3. **Generate Data**: Click "Sample Dogs" to create your dataset.
4. **Analyze the Data**:
- Examine the strip plot showing hormone levels for hypothyroid and normal dogs.
- Note the overlap between the two groups and the position of the normal range lines.
5. **Set a Diagnostic Threshold**:
- Use the slider to set a hormone level below which you'll classify dogs as hypothyroid.
- Consider the trade-offs between sensitivity (correctly identifying hypothyroid dogs) and specificity (correctly identifying healthy dogs).
6. **Evaluate Your Threshold**:
- Observe how sensitivity and specificity change as you adjust the threshold.
- Examine the confusion matrix to understand the implications of false positives and false negatives.
7. **Compare T4 and T3**:
- Repeat the process for both hormones.
- Note any differences in the difficulty of setting an accurate threshold.
8. **Reflect on the Challenges**:
- How does the overlap between hypothyroid and normal dogs affect diagnosis?
- What are the implications of false positives vs. false negatives in a clinical setting?
- Why might T4 be preferred over T3 for initial screening?
### Key Concepts to Consider
- **Sensitivity**: The ability of the test to correctly identify dogs with hypothyroidism.
- **Specificity**: The ability of the test to correctly identify dogs without hypothyroidism.
- **False Positives**: Healthy dogs incorrectly diagnosed as hypothyroid.
- **False Negatives**: Hypothyroid dogs incorrectly classified as healthy.
- **Prevalence**: The proportion of hypothyroid dogs in the population (set to 30% in this simulation).
### Normal Ranges (for reference)
- T4: 12-45 nmol/l
- T3: 20-90 ng/dl
Remember, these ranges are guidelines. In practice, individual variation and other factors can complicate diagnosis.
### Conclusion
This simulation demonstrates the complexities of diagnosing hypothyroidism in dogs. In real clinical settings, veterinarians often use multiple tests and consider the dog's overall health and symptoms when making a diagnosis. The exercise highlights the importance of understanding test limitations and the need for careful interpretation of diagnostic results.
### Additional Information on Breed-Specific Prevalence
In this simulation, we've included breed-specific prevalence rates for hypothyroidism:
1. Golden Retrievers: 60% prevalence
2. Shih-tzu: 5% prevalence
3. Alaskan Malamutes: 35% prevalence
Consider how these different prevalence rates might affect your diagnostic approach and interpretation of results.
""")
# Breed selection
st.header("Select Dog Breed")
breed = st.radio("Choose a breed", ('Golden Retriever', 'Shih-tzu', 'Alaskan Malamute'))
# Test type selection
st.header("Which Thyroid Hormone would you like to test?")
test_type = st.radio("Select test type", ('T4', 'T3'))
# Number of dogs to sample
num_dogs = st.slider("Number of dogs to sample", 1, 200, 30)
# Initialize state to keep track of the sampled dogs
if 'dogs_data' not in st.session_state:
st.session_state.dogs_data = None
# Sample dogs when the button is clicked
if st.button("Sample Dogs"):
st.session_state.dogs_data = [generate_dog_data(test_type, breed) for _ in range(num_dogs)]
# Display data and plot if dogs have been sampled
if st.session_state.dogs_data is not None:
# Create a DataFrame from the sampled data
df = pd.DataFrame(st.session_state.dogs_data, columns=[f'{test_type} Value', 'Is Hypothyroid'])
df['Status'] = df['Is Hypothyroid'].map({True: 'Hypothyroid', False: 'Normal'})
# Display the data
st.write(f"Sampled {breed} Data:")
st.dataframe(df)
# Create a strip plot
fig, ax = plt.subplots(figsize=(10, 6))
sns.stripplot(x='Status', y=f'{test_type} Value', data=df, jitter=True, ax=ax)
ax.set_ylabel(f'{test_type} Value ({("nmol/l" if test_type == "T4" else "ng/dl")})')
ax.set_title(f'{test_type} Values of Sampled {breed}s')
if test_type == 'T4':
ax.axhline(y=12, color='green', linestyle='--', label='Lower Normal Limit')
ax.axhline(y=45, color='green', linestyle='--', label='Upper Normal Limit')
else: # T3
ax.axhline(y=20, color='green', linestyle='--', label='Lower Normal Limit')
ax.axhline(y=90, color='green', linestyle='--', label='Upper Normal Limit')
plt.legend()
st.pyplot(fig)
# Threshold selection and evaluation
st.header("Threshold Selection and Evaluation")
if test_type == 'T4':
threshold = st.slider(f"Select {test_type} threshold for hypothyroidism diagnosis", 0.0, 50.0, 12.0, 0.1)
else: # T3
threshold = st.slider(f"Select {test_type} threshold for hypothyroidism diagnosis", 0.0, 100.0, 20.0, 0.1)
df['Diagnosed Hypothyroid'] = df[f'{test_type} Value'] < threshold
true_positives = ((df['Is Hypothyroid'] == True) & (df['Diagnosed Hypothyroid'] == True)).sum()
true_negatives = ((df['Is Hypothyroid'] == False) & (df['Diagnosed Hypothyroid'] == False)).sum()
false_positives = ((df['Is Hypothyroid'] == False) & (df['Diagnosed Hypothyroid'] == True)).sum()
false_negatives = ((df['Is Hypothyroid'] == True) & (df['Diagnosed Hypothyroid'] == False)).sum()
sensitivity = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0
specificity = true_negatives / (true_negatives + false_positives) if (true_negatives + false_positives) > 0 else 0
st.write(f"Sensitivity: {sensitivity:.2f}")
st.write(f"Specificity: {specificity:.2f}")
# Confusion matrix
st.write("Confusion Matrix:")
confusion_matrix = pd.DataFrame({
'Actual Hypothyroid': [true_positives, false_negatives],
'Actual Healthy': [false_positives, true_negatives]
}, index=['Predicted Hypothyroid', 'Predicted Healthy (negative)'])
st.dataframe(confusion_matrix)
# Clear the result when the Clear Results button is clicked
if st.button("Clear Results"):
st.session_state.dogs_data = None
st.write("Results cleared. You can sample again.")
else:
st.write("Click 'Sample Dogs' to generate data and view the plot.")
import numpy as np
from sklearn.metrics import roc_curve, auc
import matplotlib.pyplot as plt
# Add this function to calculate metrics for a range of thresholds
def calculate_metrics(df, test_type, thresholds):
metrics = []
for threshold in thresholds:
df['Diagnosed Hypothyroid'] = df[f'{test_type} Value'] < threshold
true_positives = ((df['Is Hypothyroid'] == True) & (df['Diagnosed Hypothyroid'] == True)).sum()
true_negatives = ((df['Is Hypothyroid'] == False) & (df['Diagnosed Hypothyroid'] == False)).sum()
false_positives = ((df['Is Hypothyroid'] == False) & (df['Diagnosed Hypothyroid'] == True)).sum()
false_negatives = ((df['Is Hypothyroid'] == True) & (df['Diagnosed Hypothyroid'] == False)).sum()
sensitivity = true_positives / (true_positives + false_negatives) if (true_positives + false_negatives) > 0 else 0
specificity = true_negatives / (true_negatives + false_positives) if (true_negatives + false_positives) > 0 else 0
youden_index = sensitivity + specificity - 1
metrics.append({
'Threshold': threshold,
'Sensitivity': sensitivity,
'Specificity': specificity,
'Youden Index': youden_index
})
return pd.DataFrame(metrics)
# Add this after the existing threshold selection code
if st.session_state.dogs_data is not None:
st.header("Automated Metrics Calculation and ROC Curve")
# Generate thresholds
if test_type == 'T4':
thresholds = np.linspace(0, 50, 100)
else: # T3
thresholds = np.linspace(0, 100, 100)
# Calculate metrics
metrics_df = calculate_metrics(df, test_type, thresholds)
# Find optimal threshold (maximum Youden Index)
optimal_threshold = metrics_df.loc[metrics_df['Youden Index'].idxmax()]
st.write("Optimal Threshold:")
st.write(f"Threshold: {optimal_threshold['Threshold']:.2f}")
st.write(f"Sensitivity: {optimal_threshold['Sensitivity']:.2f}")
st.write(f"Specificity: {optimal_threshold['Specificity']:.2f}")
st.write(f"Youden Index: {optimal_threshold['Youden Index']:.2f}")
# Plot ROC curve
fpr, tpr, _ = roc_curve(df['Is Hypothyroid'], -df[f'{test_type} Value'])
roc_auc = auc(fpr, tpr)
fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
ax.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
ax.set_xlim([0.0, 1.0])
ax.set_ylim([0.0, 1.05])
ax.set_xlabel('False Positive Rate')
ax.set_ylabel('True Positive Rate')
ax.set_title(f'Receiver Operating Characteristic (ROC) Curve for {test_type}')
ax.legend(loc="lower right")
# Annotate Youden Index point
youden_point = (1 - optimal_threshold['Specificity'], optimal_threshold['Sensitivity'])
ax.plot(youden_point[0], youden_point[1], 'ro')
ax.annotate(f"J index: {optimal_threshold['Youden Index']:.2f}",
xy=youden_point, xytext=(20, -20),
textcoords='offset points', ha='right', va='bottom',
bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
arrowprops=dict(arrowstyle = '->', connectionstyle='arc3,rad=0'))
st.pyplot(fig)