meta-edc 1.0.7__py3-none-any.whl → 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. meta_ae/action_items.py +10 -2
  2. meta_ae/baker_recipes.py +1 -2
  3. meta_ae/tests/tests/test_actions.py +1 -2
  4. meta_analytics/README.rst +1 -2
  5. meta_analytics/notebooks/anu.ipynb +95 -0
  6. meta_analytics/notebooks/appointment_planning.ipynb +329 -0
  7. meta_analytics/notebooks/arvs.ipynb +103 -0
  8. meta_analytics/notebooks/cleaning/consent_v1_ext.ipynb +227 -0
  9. meta_analytics/notebooks/cleaning/offschedule_eos.ipynb +353 -0
  10. meta_analytics/notebooks/dsmc/renal_dysfunction.ipynb +435 -0
  11. meta_analytics/notebooks/endpoints/meta_endpoints_by_date.ipynb +664 -0
  12. meta_analytics/notebooks/followup_examination.ipynb +141 -0
  13. meta_analytics/notebooks/hba1c.ipynb +136 -0
  14. meta_analytics/notebooks/hiv_regimens.ipynb +122 -118
  15. meta_analytics/notebooks/incidence.ipynb +232 -0
  16. meta_analytics/notebooks/liver.ipynb +389 -0
  17. meta_analytics/notebooks/magreth.ipynb +645 -0
  18. meta_analytics/notebooks/monitoring_report.ipynb +721 -448
  19. meta_analytics/notebooks/pharmacy.ipynb +405 -306
  20. meta_analytics/notebooks/pharmacy_stock_202410.ipynb +306 -0
  21. meta_analytics/notebooks/steering.ipynb +61 -0
  22. meta_analytics/notebooks/undiagnosed/meta3_screening_consort_chart.ipynb +1176 -0
  23. meta_analytics/notebooks/undiagnosed/meta3_screening_undiagnosed.ipynb +519 -0
  24. meta_analytics/notebooks/undiagnosed/meta_screening_table2.ipynb +964 -0
  25. meta_analytics/notebooks/undiagnosed/screen_undiagnosed_or.ipynb +296 -0
  26. meta_analytics/notebooks/undiagnosed/screening.ipynb +273 -0
  27. meta_analytics/notebooks/undiagnosed/screening2.ipynb +958 -0
  28. meta_analytics/notebooks/undiagnosed/screening_undiagnosed_20241002.ipynb +958 -0
  29. meta_analytics/notebooks/ven.ipynb +191 -0
  30. meta_analytics/notebooks/vitals.ipynb +263 -0
  31. meta_edc/settings/debug.py +3 -2
  32. meta_edc/urls.py +1 -0
  33. {meta_edc-1.0.7.dist-info → meta_edc-1.1.1.dist-info}/METADATA +3 -3
  34. {meta_edc-1.0.7.dist-info → meta_edc-1.1.1.dist-info}/RECORD +62 -35
  35. {meta_edc-1.0.7.dist-info → meta_edc-1.1.1.dist-info}/WHEEL +1 -1
  36. meta_labs/reportables.py +14 -11
  37. meta_labs/tests/test_reportables.py +33 -12
  38. meta_pharmacy/notebooks/pharmacy.ipynb +41 -0
  39. meta_prn/admin/offschedule_pregnancy_admin.py +3 -3
  40. meta_prn/admin/onschedule_dm_referral_admin.py +5 -5
  41. meta_prn/form_validators/end_of_study.py +2 -2
  42. meta_prn/migrations/0063_historicaloffstudymedication_singleton_field_and_more.py +37 -0
  43. meta_prn/migrations/0064_auto_20250602_2143.py +18 -0
  44. meta_prn/models/end_of_study.py +2 -0
  45. meta_prn/models/off_study_medication.py +2 -0
  46. meta_reports/admin/last_imp_refill_admin.py +3 -2
  47. meta_screening/eligibility/eligibility_part_three/base_eligibility_part_three.py +59 -47
  48. meta_screening/form_validators/screening_part_three.py +6 -1
  49. meta_screening/tests/meta_test_case_mixin.py +3 -0
  50. meta_screening/tests/tests/test_forms.py +9 -2
  51. meta_screening/tests/tests/test_screening_part_three.py +11 -14
  52. meta_subject/action_items.py +2 -3
  53. meta_subject/choices.py +2 -1
  54. meta_subject/form_validators/delivery_form_validator.py +1 -0
  55. meta_subject/forms/blood_results/blood_results_rft_form.py +60 -3
  56. meta_subject/forms/delivery_form.py +2 -0
  57. meta_subject/migrations/0223_bloodresultsfbc_errors_bloodresultsgludummy_errors_and_more.py +83 -0
  58. meta_subject/migrations/0224_bloodresultsfbc_abnormal_summary_and_more.py +153 -0
  59. meta_subject/tests/tests/test_egfr.py +5 -5
  60. meta_analytics/dataframes/enrolled/__init__.py +0 -0
  61. {meta_edc-1.0.7.dist-info → meta_edc-1.1.1.dist-info}/licenses/AUTHORS.rst +0 -0
  62. {meta_edc-1.0.7.dist-info → meta_edc-1.1.1.dist-info}/licenses/LICENSE +0 -0
  63. {meta_edc-1.0.7.dist-info → meta_edc-1.1.1.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "cells": [
3
3
  {
4
- "metadata": {},
5
4
  "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "0",
7
+ "metadata": {},
8
+ "outputs": [],
6
9
  "source": [
7
10
  "%%capture\n",
8
11
  "import os\n",
@@ -16,39 +19,39 @@
16
19
  "pharmacy_folder = Path(os.environ[\"META_PHARMACY_FOLDER\"])\n",
17
20
  "plus = activate(dotenv_file=env_file)\n",
18
21
  "pd.set_option('future.no_silent_downcasting', True)"
19
- ],
20
- "id": "5c3bc2c5cc22e357",
21
- "outputs": [],
22
- "execution_count": null
22
+ ]
23
23
  },
24
24
  {
25
- "metadata": {},
26
25
  "cell_type": "code",
26
+ "execution_count": null,
27
+ "id": "1",
28
+ "metadata": {},
29
+ "outputs": [],
27
30
  "source": [
28
31
  "from edc_pdutils.dataframes import get_crf, get_subject_visit\n",
29
32
  "from edc_constants.constants import YES\n",
30
33
  "from edc_appointment.analytics import get_appointment_df\n",
31
34
  "from datetime import datetime"
32
- ],
33
- "id": "4067e6257bf1c657",
34
- "outputs": [],
35
- "execution_count": null
35
+ ]
36
36
  },
37
37
  {
38
- "metadata": {},
39
38
  "cell_type": "code",
39
+ "execution_count": null,
40
+ "id": "2",
41
+ "metadata": {},
42
+ "outputs": [],
40
43
  "source": [
41
44
  "cutoff_datetime = datetime(2026,3,1)\n",
42
45
  "df_patienthistory = get_crf(\"meta_subject.patienthistory\", subject_visit_model=\"meta_subject.subjectvisit\")\n",
43
46
  "df_followup_examination = get_crf(\"meta_subject.FollowupExamination\", subject_visit_model=\"meta_subject.subjectvisit\")"
44
- ],
45
- "id": "5a00d10dadc9d7f8",
46
- "outputs": [],
47
- "execution_count": null
47
+ ]
48
48
  },
49
49
  {
50
- "metadata": {},
51
50
  "cell_type": "code",
51
+ "execution_count": null,
52
+ "id": "3",
53
+ "metadata": {},
54
+ "outputs": [],
52
55
  "source": [
53
56
  "replacements = {\n",
54
57
  " \"ABC+ 3TC+ DTG\": \"ABC + 3TC + DTG\",\n",
@@ -65,124 +68,128 @@
65
68
  " df_patienthistory[\"other_current_arv_regimen\"]\n",
66
69
  " .replace(replacements)\n",
67
70
  ")"
68
- ],
69
- "id": "cc2958e795b6c83f",
70
- "outputs": [],
71
- "execution_count": null
71
+ ]
72
72
  },
73
73
  {
74
- "metadata": {},
75
74
  "cell_type": "code",
75
+ "execution_count": null,
76
+ "id": "4",
77
+ "metadata": {},
78
+ "outputs": [],
76
79
  "source": [
77
80
  "df_patienthistory['regimen'] = df_patienthistory[\"current_arv_regimen\"]\n",
78
81
  "df_patienthistory.loc[df_patienthistory[\"current_arv_regimen\"]==\"Other, specify ...\", \"regimen\"] = df_patienthistory[\"other_current_arv_regimen\"]"
79
- ],
80
- "id": "41a0202e91442199",
81
- "outputs": [],
82
- "execution_count": null
82
+ ]
83
83
  },
84
84
  {
85
- "metadata": {},
86
85
  "cell_type": "code",
86
+ "execution_count": null,
87
+ "id": "5",
88
+ "metadata": {},
89
+ "outputs": [],
87
90
  "source": [
88
91
  "df_followup_examination[\"art_new_regimen_other\"] = (\n",
89
92
  " df_followup_examination[\"art_new_regimen_other\"]\n",
90
93
  " .replace(replacements)\n",
91
94
  ")"
92
- ],
93
- "id": "29ee66d690a550b",
94
- "outputs": [],
95
- "execution_count": null
95
+ ]
96
96
  },
97
97
  {
98
- "metadata": {},
99
98
  "cell_type": "code",
99
+ "execution_count": null,
100
+ "id": "6",
101
+ "metadata": {},
102
+ "outputs": [],
100
103
  "source": [
101
104
  "df_followup_examination['regimen'] = pd.NA\n",
102
105
  "df_followup_examination.loc[(df_followup_examination[\"art_change\"]==YES) & (df_followup_examination.art_new_regimen_other.notna()), \"regimen\"] = df_followup_examination[\"art_new_regimen_other\"]\n",
103
106
  "df_followup_examination.loc[(df_followup_examination[\"art_change\"]==YES) & (df_followup_examination.art_new_regimen_other.isna()), \"regimen\"] = \"CHANGE_NOT_REPORTED\""
104
- ],
105
- "id": "3356b70f9415f8cf",
106
- "outputs": [],
107
- "execution_count": null
107
+ ]
108
108
  },
109
109
  {
110
- "metadata": {},
111
110
  "cell_type": "code",
111
+ "execution_count": null,
112
+ "id": "7",
113
+ "metadata": {},
114
+ "outputs": [],
112
115
  "source": [
113
116
  "df_regimen = pd.concat([df_patienthistory[[\"subject_identifier\", \"visit_datetime\", \"regimen\"]], df_followup_examination[[\"subject_identifier\", \"visit_datetime\", \"regimen\"]]])\n",
114
117
  "df_regimen[\"regimen\"] = df_regimen[\"regimen\"].replace({\"Other second line\": \"CHANGE_NOT_REPORTED\"})\n",
115
118
  "df_regimen[\"regimen\"] = pd.Categorical(df_regimen[\"regimen\"], categories=list(df_regimen.query(\"regimen.notna()\").regimen.unique()), ordered=False)\n",
116
119
  "df_regimen = df_regimen.sort_values([\"subject_identifier\", \"visit_datetime\"])\n",
117
120
  "df_regimen = df_regimen.reset_index(drop=True)"
118
- ],
119
- "id": "bfa119a47ab4827e",
120
- "outputs": [],
121
- "execution_count": null
121
+ ]
122
122
  },
123
123
  {
124
- "metadata": {},
125
124
  "cell_type": "code",
125
+ "execution_count": null,
126
+ "id": "8",
127
+ "metadata": {},
128
+ "outputs": [],
126
129
  "source": [
127
130
  "df_pivot = df_regimen.pivot_table(values=\"visit_datetime\", columns=\"regimen\", index=\"subject_identifier\", observed=True)\n",
128
131
  "df_pivot = df_pivot.reset_index()"
129
- ],
130
- "id": "10d8f6687f35b5e4",
131
- "outputs": [],
132
- "execution_count": null
132
+ ]
133
133
  },
134
134
  {
135
- "metadata": {},
136
135
  "cell_type": "code",
136
+ "execution_count": null,
137
+ "id": "9",
138
+ "metadata": {},
139
+ "outputs": [],
137
140
  "source": [
138
141
  "subject_identifier = \"105-20-0050-0\"\n",
139
142
  "df_pivot[df_pivot.subject_identifier==subject_identifier].melt().query(\"value.notna() and regimen!='subject_identifier'\").sort_values(\"value\", ascending=False)"
140
- ],
141
- "id": "d3b3a1a80436cc1d",
142
- "outputs": [],
143
- "execution_count": null
143
+ ]
144
144
  },
145
145
  {
146
- "metadata": {},
147
146
  "cell_type": "code",
148
- "source": "df_melt = df_pivot.melt(id_vars=[\"subject_identifier\"]).query(\"value.notna()\")",
149
- "id": "b82ea4629050675e",
147
+ "execution_count": null,
148
+ "id": "10",
149
+ "metadata": {},
150
150
  "outputs": [],
151
- "execution_count": null
151
+ "source": [
152
+ "df_melt = df_pivot.melt(id_vars=[\"subject_identifier\"]).query(\"value.notna()\")"
153
+ ]
152
154
  },
153
155
  {
154
- "metadata": {},
155
156
  "cell_type": "code",
157
+ "execution_count": null,
158
+ "id": "11",
159
+ "metadata": {},
160
+ "outputs": [],
156
161
  "source": [
157
162
  "df_melt[\"max_date\"] = df_melt.groupby(\"subject_identifier\")[\"value\"].transform(\"max\")\n",
158
163
  "df_melt[\"current_regimen\"] = df_melt[df_melt.value==df_melt.max_date][\"regimen\"]"
159
- ],
160
- "id": "46906641b5f8e73b",
161
- "outputs": [],
162
- "execution_count": null
164
+ ]
163
165
  },
164
166
  {
165
- "metadata": {},
166
167
  "cell_type": "code",
167
- "source": "df_current_regimens = df_melt.query(\"current_regimen.notna()\")[[\"subject_identifier\", \"max_date\", \"current_regimen\"]].copy()",
168
- "id": "c297749df4c7397c",
168
+ "execution_count": null,
169
+ "id": "12",
170
+ "metadata": {},
169
171
  "outputs": [],
170
- "execution_count": null
172
+ "source": [
173
+ "df_current_regimens = df_melt.query(\"current_regimen.notna()\")[[\"subject_identifier\", \"max_date\", \"current_regimen\"]].copy()"
174
+ ]
171
175
  },
172
176
  {
173
- "metadata": {},
174
177
  "cell_type": "code",
178
+ "execution_count": null,
179
+ "id": "13",
180
+ "metadata": {},
181
+ "outputs": [],
175
182
  "source": [
176
183
  "df_visit = get_subject_visit(model=\"meta_subject.subjectvisit\")\n",
177
184
  "df_visit = df_visit[df_visit.visit_code==1000.0].copy()"
178
- ],
179
- "id": "ce1f8d3dacba2d99",
180
- "outputs": [],
181
- "execution_count": null
185
+ ]
182
186
  },
183
187
  {
184
- "metadata": {},
185
188
  "cell_type": "code",
189
+ "execution_count": null,
190
+ "id": "14",
191
+ "metadata": {},
192
+ "outputs": [],
186
193
  "source": [
187
194
  "df_appointment = get_appointment_df()\n",
188
195
  "df_appointment_next = (\n",
@@ -192,14 +199,14 @@
192
199
  " .copy()\n",
193
200
  " .reset_index()\n",
194
201
  ")"
195
- ],
196
- "id": "72c7862945e8a25e",
197
- "outputs": [],
198
- "execution_count": null
202
+ ]
199
203
  },
200
204
  {
201
- "metadata": {},
202
205
  "cell_type": "code",
206
+ "execution_count": null,
207
+ "id": "15",
208
+ "metadata": {},
209
+ "outputs": [],
203
210
  "source": [
204
211
  "df_appointment_last = (\n",
205
212
  " df_appointment[df_appointment.appt_datetime<cutoff_datetime][[\"subject_identifier\", \"appt_datetime\", \"visit_code\"]]\n",
@@ -217,38 +224,38 @@
217
224
  " }\n",
218
225
  " )\n",
219
226
  ")"
220
- ],
221
- "id": "cecd662f3a76a4ac",
222
- "outputs": [],
223
- "execution_count": null
227
+ ]
224
228
  },
225
229
  {
226
- "metadata": {},
227
230
  "cell_type": "code",
231
+ "execution_count": null,
232
+ "id": "16",
233
+ "metadata": {},
234
+ "outputs": [],
228
235
  "source": [
229
236
  "df = df_current_regimens.merge(df_visit[[ \"subject_identifier\", \"baseline_datetime\", \"endline_visit_datetime\", \"endline_visit_code\"]], on=\"subject_identifier\", how=\"left\")\n",
230
237
  "df = df.reset_index(drop=True)\n",
231
238
  "df[\"changed\"] = False\n",
232
239
  "df.loc[df.max_date != df.baseline_datetime, \"changed\"] = True"
233
- ],
234
- "id": "46700ad09717a89f",
235
- "outputs": [],
236
- "execution_count": null
240
+ ]
237
241
  },
238
242
  {
239
- "metadata": {},
240
243
  "cell_type": "code",
244
+ "execution_count": null,
245
+ "id": "17",
246
+ "metadata": {},
247
+ "outputs": [],
241
248
  "source": [
242
249
  "df = df.merge(df_appointment_next[[\"subject_identifier\", \"next_appt_datetime\", \"next_visit_code\"]], on=\"subject_identifier\", how=\"left\")\n",
243
250
  "df = df.merge(df_appointment_last[[\"subject_identifier\", \"last_appt_datetime\", \"last_visit_code\"]], on=\"subject_identifier\", how=\"left\")"
244
- ],
245
- "id": "21a77db6b3d342ad",
246
- "outputs": [],
247
- "execution_count": null
251
+ ]
248
252
  },
249
253
  {
250
- "metadata": {},
251
254
  "cell_type": "code",
255
+ "execution_count": null,
256
+ "id": "18",
257
+ "metadata": {},
258
+ "outputs": [],
252
259
  "source": [
253
260
  "# from last seen to final scheduled appt\n",
254
261
  "df[\"remaining_delta_from_last_seen\"] = df.last_appt_datetime - df.endline_visit_datetime\n",
@@ -269,14 +276,14 @@
269
276
  "df[\"remaining_delta_from_next\"] = df[\"remaining_delta_from_next\"].apply(lambda x: 0 if x.total_seconds()<0 else x)\n",
270
277
  "df[\"remaining_delta_from_next\"] = pd.to_timedelta(df[\"remaining_delta_from_next\"])\n",
271
278
  "df[\"remaining_days_next_to_final\"] = df[\"remaining_delta_from_next\"].dt.days"
272
- ],
273
- "id": "46455533d4c928a5",
274
- "outputs": [],
275
- "execution_count": null
279
+ ]
276
280
  },
277
281
  {
278
- "metadata": {},
279
282
  "cell_type": "code",
283
+ "execution_count": null,
284
+ "id": "19",
285
+ "metadata": {},
286
+ "outputs": [],
280
287
  "source": [
281
288
  "df_final = (\n",
282
289
  " df\n",
@@ -313,14 +320,14 @@
313
320
  "df_final[\"remaining_days_now_to_final\"] = df_final[\"remaining_days_now_to_final\"].astype(\"float64\").fillna(0)\n",
314
321
  "df_final[\"remaining_days_next_to_final\"] = df_final[\"remaining_days_next_to_final\"].astype(\"float64\").fillna(0)\n",
315
322
  "df_final"
316
- ],
317
- "id": "c32993c296d84def",
318
- "outputs": [],
319
- "execution_count": null
323
+ ]
320
324
  },
321
325
  {
322
- "metadata": {},
323
326
  "cell_type": "code",
327
+ "execution_count": null,
328
+ "id": "20",
329
+ "metadata": {},
330
+ "outputs": [],
324
331
  "source": [
325
332
  "# need from now until end of study\n",
326
333
  "df_summary1 = (pd.merge(\n",
@@ -334,14 +341,14 @@
334
341
  " .reset_index()\n",
335
342
  ")\n",
336
343
  "df_summary1"
337
- ],
338
- "id": "3b8b54a63bc67608",
339
- "outputs": [],
340
- "execution_count": null
344
+ ]
341
345
  },
342
346
  {
343
- "metadata": {},
344
347
  "cell_type": "code",
348
+ "execution_count": null,
349
+ "id": "21",
350
+ "metadata": {},
351
+ "outputs": [],
345
352
  "source": [
346
353
  "# need from last seen to end of study\n",
347
354
  "df_summary2 = (pd.merge(\n",
@@ -355,14 +362,14 @@
355
362
  " .reset_index()\n",
356
363
  ")\n",
357
364
  "df_summary2"
358
- ],
359
- "id": "9fcf09fbc781845b",
360
- "outputs": [],
361
- "execution_count": null
365
+ ]
362
366
  },
363
367
  {
364
- "metadata": {},
365
368
  "cell_type": "code",
369
+ "execution_count": null,
370
+ "id": "22",
371
+ "metadata": {},
372
+ "outputs": [],
366
373
  "source": [
367
374
  "# need from next to end of study\n",
368
375
  "df_summary3 = (pd.merge(\n",
@@ -377,14 +384,14 @@
377
384
  ")\n",
378
385
  "\n",
379
386
  "df_summary3"
380
- ],
381
- "id": "9d4bebb0a52a8457",
382
- "outputs": [],
383
- "execution_count": null
387
+ ]
384
388
  },
385
389
  {
386
- "metadata": {},
387
390
  "cell_type": "code",
391
+ "execution_count": null,
392
+ "id": "23",
393
+ "metadata": {},
394
+ "outputs": [],
388
395
  "source": [
389
396
  "with pd.ExcelWriter(\n",
390
397
  " analysis_folder / \"hiv_medication.xlsx\",\n",
@@ -395,10 +402,7 @@
395
402
  " df_summary1.to_excel(writer, sheet_name=\"now to final\", index=False)\n",
396
403
  " df_summary2.to_excel(writer, sheet_name=\"last seen to final\", index=False)\n",
397
404
  " df_summary3.to_excel(writer, sheet_name=\"next to final\", index=False)\n"
398
- ],
399
- "id": "e344164f67b3cc46",
400
- "outputs": [],
401
- "execution_count": null
405
+ ]
402
406
  }
403
407
  ],
404
408
  "metadata": {
@@ -0,0 +1,232 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "0",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "%%capture\n",
11
+ "import os\n",
12
+ "from pathlib import Path\n",
13
+ "import pandas as pd\n",
14
+ "from dj_notebook import activate\n",
15
+ "\n",
16
+ "env_file = os.environ[\"META_ENV\"]\n",
17
+ "reports_folder = Path(os.environ[\"META_REPORTS_FOLDER\"])\n",
18
+ "analysis_folder = Path(os.environ[\"META_ANALYSIS_FOLDER\"])\n",
19
+ "pharmacy_folder = Path(os.environ[\"META_PHARMACY_FOLDER\"])\n",
20
+ "plus = activate(dotenv_file=env_file)\n",
21
+ "pd.set_option('future.no_silent_downcasting', True)"
22
+ ]
23
+ },
24
+ {
25
+ "cell_type": "code",
26
+ "execution_count": null,
27
+ "id": "1",
28
+ "metadata": {},
29
+ "outputs": [],
30
+ "source": [
31
+ "\n",
32
+ "import pdfkit\n",
33
+ "from datetime import date\n",
34
+ "from edc_pdutils.dataframes import get_subject_visit\n",
35
+ "from meta_analytics.dataframes import get_eos_df\n",
36
+ "from meta_analytics.dataframes import GlucoseEndpointsByDate\n",
37
+ "from scipy.stats import chi2\n",
38
+ "from meta_analytics.utils import df_as_great_table\n",
39
+ "from great_tables import md\n"
40
+ ]
41
+ },
42
+ {
43
+ "cell_type": "code",
44
+ "execution_count": null,
45
+ "id": "2",
46
+ "metadata": {},
47
+ "outputs": [],
48
+ "source": [
49
+ "html_data = []\n",
50
+ "cutoff_date = date(2025,3, 31)\n"
51
+ ]
52
+ },
53
+ {
54
+ "cell_type": "code",
55
+ "execution_count": null,
56
+ "id": "3",
57
+ "metadata": {},
58
+ "outputs": [],
59
+ "source": [
60
+ "df_visit = get_subject_visit(\"meta_subject.subjectvisit\")\n",
61
+ "df_visit = df_visit[df_visit.appt_datetime.dt.date<=cutoff_date]\n"
62
+ ]
63
+ },
64
+ {
65
+ "cell_type": "code",
66
+ "execution_count": null,
67
+ "id": "4",
68
+ "metadata": {},
69
+ "outputs": [],
70
+ "source": [
71
+ "cls = GlucoseEndpointsByDate()\n",
72
+ "cls.run()\n",
73
+ "df_endpoint = cls.endpoint_only_df.copy()"
74
+ ]
75
+ },
76
+ {
77
+ "cell_type": "code",
78
+ "execution_count": null,
79
+ "id": "5",
80
+ "metadata": {},
81
+ "outputs": [],
82
+ "source": [
83
+ "def get_df_main(df_visit:pd.DataFrame, lower_days:float|None=None, upper_days:float|None=None):\n",
84
+ " if not lower_days:\n",
85
+ " lower_days = -1\n",
86
+ " cutoff_datetime = df_visit.query(\"@lower_days<followup_days<=@upper_days\").visit_datetime.max()\n",
87
+ " # exclude subjects for this reason\n",
88
+ " offstudy_reasons = ['Patient fulfilled late exclusion criteria (due to abnormal blood values or raised blood pressure at enrolment']\n",
89
+ "\n",
90
+ " df_eos = get_eos_df()\n",
91
+ " df_eos_excluded = (\n",
92
+ " df_eos\n",
93
+ " .query(\"followup_days<@lower_days and followup_days<=@upper_days and offstudy_reason.isin(@offstudy_reasons)\")\n",
94
+ " .copy()\n",
95
+ " .reset_index()\n",
96
+ " )\n",
97
+ " df_visit_final = (\n",
98
+ " df_visit.query(\"@lower_days<followup_days<=@upper_days and reason!='missed'\")\n",
99
+ " .merge(df_eos_excluded[[\"subject_identifier\"]], on=\"subject_identifier\", how=\"left\", suffixes=(\"\", \"_y\"), indicator=True)\n",
100
+ " .query(\"_merge=='left_only'\")\n",
101
+ " .drop(columns=[\"_merge\"])\n",
102
+ " )\n",
103
+ " df_main = (\n",
104
+ " df_visit_final\n",
105
+ " .groupby(by=[\"subject_identifier\"])[[\"baseline_datetime\", \"visit_datetime\", \"followup_days\"]]\n",
106
+ " .max()\n",
107
+ " .reset_index()\n",
108
+ " )\n",
109
+ "\n",
110
+ " df_main = (\n",
111
+ " df_main\n",
112
+ " .merge(\n",
113
+ " df_endpoint.query(\"days_to_endpoint>@lower_days\")[[\"subject_identifier\", \"endpoint_label\", \"endpoint_type\", \"days_to_endpoint\"]],\n",
114
+ " how=\"left\",\n",
115
+ " on=[\"subject_identifier\"])\n",
116
+ " .reset_index(drop=True)\n",
117
+ " )\n",
118
+ " if lower_days>=365.25:\n",
119
+ " df_main[\"followup_days\"] = df_main[\"followup_days\"] - lower_days\n",
120
+ " df_main[\"followup_years\"] = df_main[\"followup_days\"]/365.25\n",
121
+ " return df_main, len(df_main), len(df_main.query(\"@lower_days<days_to_endpoint<=@upper_days and endpoint_label.notna()\"))\n",
122
+ "\n",
123
+ "def get_rate_and_ci(events, person_years_total):\n",
124
+ " lower_ci = (chi2.ppf(0.025, 2 * events) / (2 * person_years_total)) * 1000\n",
125
+ " upper_ci = (chi2.ppf(0.975, 2 * (events + 1)) / (2 * person_years_total)) * 1000\n",
126
+ " return events/person_years_total*1000, lower_ci, upper_ci\n",
127
+ "\n",
128
+ "def get_incidence_data(term:str, lower_days:float, upper_days:float):\n",
129
+ " data = {}\n",
130
+ " df_main, subjects, events = get_df_main(df_visit, lower_days=lower_days, upper_days=upper_days)\n",
131
+ " person_years_total = df_main.followup_years.sum()\n",
132
+ " data.update({term:[person_years_total, subjects, events, *get_rate_and_ci(events, person_years_total)]})\n",
133
+ " return data"
134
+ ]
135
+ },
136
+ {
137
+ "cell_type": "code",
138
+ "execution_count": null,
139
+ "id": "6",
140
+ "metadata": {},
141
+ "outputs": [],
142
+ "source": [
143
+ "incidence_data = {}\n",
144
+ "incidence_data.update(get_incidence_data(\"total\", lower_days=0, upper_days=10000))\n",
145
+ "incidence_data.update(get_incidence_data(\"0-1 years\", lower_days=0, upper_days=365.25))\n",
146
+ "incidence_data.update(get_incidence_data(\"1-2 years\", lower_days=365.25, upper_days=2*365.25))\n",
147
+ "incidence_data.update(get_incidence_data(\"2-3 years\", lower_days=2*365.25, upper_days=3*365.25))\n",
148
+ "incidence_data.update(get_incidence_data(\"3+ years\", lower_days=3*365.25, upper_days=10*365.25))"
149
+ ]
150
+ },
151
+ {
152
+ "cell_type": "code",
153
+ "execution_count": null,
154
+ "id": "7",
155
+ "metadata": {},
156
+ "outputs": [],
157
+ "source": [
158
+ "data = dict(label=[], person_years=[], failures=[], rate=[], lower_ci=[], upper_ci=[])\n",
159
+ "for k in incidence_data:\n",
160
+ " data[\"label\"].append(k)\n",
161
+ "\n",
162
+ "for v in incidence_data.values():\n",
163
+ " data[\"person_years\"].append(v[0])\n",
164
+ " data[\"failures\"].append(v[2])\n",
165
+ " data[\"rate\"].append(v[3])\n",
166
+ " data[\"lower_ci\"].append(v[4])\n",
167
+ " data[\"upper_ci\"].append(v[5])\n",
168
+ "\n",
169
+ "df = pd.DataFrame(data=data)"
170
+ ]
171
+ },
172
+ {
173
+ "cell_type": "code",
174
+ "execution_count": null,
175
+ "id": "8",
176
+ "metadata": {},
177
+ "outputs": [],
178
+ "source": [
179
+ "gt = df_as_great_table(\n",
180
+ " df,\n",
181
+ " title=\"Table 9: Incident Rate per 1000 person years\",\n",
182
+ " subtitle=md(\"using randomisation to diabetes/last seen\"),\n",
183
+ ")\n",
184
+ "gt = gt.fmt_number(columns=[\"person_years\", \"failures\", \"rate\", \"lower_ci\", \"upper_ci\"], decimals=2)\n",
185
+ "gt = (gt\n",
186
+ " .cols_label({\"label\": \"Label\", \"person_years\": \"Person years\", \"failures\": \"Failures\", \"rate\": \"Rate\", \"lower_ci\": \"Lower\", \"upper_ci\": \"Upper\"})\n",
187
+ " .cols_align(align=\"left\", columns=[\"label\"])\n",
188
+ " .cols_align(align=\"center\", columns=[\"person_years\", \"failures\", \"rate\", \"lower_ci\", \"upper_ci\"])\n",
189
+ " .tab_spanner(\n",
190
+ " label=\"95%CI\",\n",
191
+ " columns=[\"lower_ci\", \"upper_ci\"],\n",
192
+ " )\n",
193
+ ")\n",
194
+ "gt.show()\n",
195
+ "html_data.append(gt.as_raw_html())\n"
196
+ ]
197
+ },
198
+ {
199
+ "cell_type": "code",
200
+ "execution_count": null,
201
+ "id": "9",
202
+ "metadata": {},
203
+ "outputs": [],
204
+ "source": [
205
+ "raw_html = \"</BR>\".join(html_data)\n",
206
+ "raw_html = '<!DOCTYPE html>\\n<html lang=\"en\">\\n<head>\\n<meta charset=\"utf-8\"/>\\n</head>\\n<body>\\n' + raw_html + '\\n</body>\\n</html>\\n'\n",
207
+ "pdfkit.from_string(raw_html, str(analysis_folder / \"incident_rate.pdf\"))\n"
208
+ ]
209
+ }
210
+ ],
211
+ "metadata": {
212
+ "kernelspec": {
213
+ "display_name": "Python 3 (ipykernel)",
214
+ "language": "python",
215
+ "name": "python3"
216
+ },
217
+ "language_info": {
218
+ "codemirror_mode": {
219
+ "name": "ipython",
220
+ "version": 3
221
+ },
222
+ "file_extension": ".py",
223
+ "mimetype": "text/x-python",
224
+ "name": "python",
225
+ "nbconvert_exporter": "python",
226
+ "pygments_lexer": "ipython3",
227
+ "version": "3.12.4"
228
+ }
229
+ },
230
+ "nbformat": 4,
231
+ "nbformat_minor": 5
232
+ }