meta-edc 1.0.5__py3-none-any.whl → 1.0.7__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.
- meta_ae/admin/modeladmin_mixins.py +0 -1
- meta_analytics/dataframes/__init__.py +3 -0
- meta_analytics/dataframes/constants.py +1 -1
- meta_analytics/dataframes/enrolled/__init__.py +0 -1
- meta_analytics/dataframes/get_eos_df.py +15 -2
- meta_analytics/dataframes/get_glucose_df.py +149 -0
- meta_analytics/dataframes/get_glucose_fbg_df.py +27 -0
- meta_analytics/dataframes/get_glucose_fbg_ogtt_df.py +22 -0
- meta_analytics/dataframes/glucose_endpoints/endpoint_by_date.py +106 -120
- meta_analytics/dataframes/glucose_endpoints/glucose_endpoints_by_date.py +36 -227
- meta_analytics/dataframes/utils.py +18 -4
- meta_analytics/notebooks/hiv_regimens.ipynb +425 -0
- meta_analytics/notebooks/monitoring_report.ipynb +1561 -0
- meta_analytics/notebooks/pharmacy.ipynb +971 -0
- meta_analytics/utils.py +81 -0
- {meta_edc-1.0.5.dist-info → meta_edc-1.0.7.dist-info}/METADATA +5 -4
- {meta_edc-1.0.5.dist-info → meta_edc-1.0.7.dist-info}/RECORD +41 -27
- {meta_edc-1.0.5.dist-info → meta_edc-1.0.7.dist-info}/WHEEL +1 -1
- meta_edc-1.0.7.dist-info/licenses/AUTHORS.rst +8 -0
- meta_prn/models/end_of_study.py +0 -2
- meta_prn/models/loss_to_followup.py +0 -2
- meta_prn/models/pregnancy_notification.py +0 -2
- meta_reports/migrations/0054_auto_20250422_2003.py +81 -0
- meta_reports/migrations/0055_alter_glucosesummary_table.py +17 -0
- meta_reports/migrations/0056_auto_20250422_2214.py +54 -0
- meta_reports/migrations/0057_auto_20250422_2224.py +54 -0
- meta_reports/migrations/0058_auto_20250422_2232.py +54 -0
- meta_reports/models/dbviews/glucose_summary/unmanaged_model.py +13 -1
- meta_reports/models/dbviews/glucose_summary/view_definition.py +8 -5
- meta_subject/form_validators/glucose_form_validator.py +16 -1
- meta_subject/forms/study_medication_form.py +5 -3
- meta_subject/migrations/0221_auto_20250402_1913.py +42 -0
- meta_subject/migrations/0222_alter_historicalstudymedication_stock_codes_and_more.py +46 -0
- meta_subject/models/blood_results/blood_results_hba1c.py +0 -1
- meta_subject/models/blood_results/blood_results_ins.py +0 -1
- meta_subject/models/blood_results/blood_results_lft.py +0 -1
- meta_subject/models/delivery.py +0 -2
- meta_subject/models/followup_examination.py +0 -2
- meta_analytics/dataframes/enrolled/get_glucose_df.py +0 -122
- /meta_edc-1.0.5.dist-info/AUTHORS → /meta_analytics/dataframes/glucose_endpoints/utils.py +0 -0
- {meta_edc-1.0.5.dist-info → meta_edc-1.0.7.dist-info/licenses}/LICENSE +0 -0
- {meta_edc-1.0.5.dist-info → meta_edc-1.0.7.dist-info}/top_level.txt +0 -0
@@ -8,6 +8,9 @@ from .constants import (
|
|
8
8
|
endpoint_columns,
|
9
9
|
)
|
10
10
|
from .get_eos_df import get_eos_df
|
11
|
+
from .get_glucose_df import get_glucose_df
|
12
|
+
from .get_glucose_fbg_df import get_glucose_fbg_df
|
13
|
+
from .get_glucose_fbg_ogtt_df import get_glucose_fbg_ogtt_df
|
11
14
|
from .get_last_imp_visits_df import get_last_imp_visits_df
|
12
15
|
from .glucose_endpoints import EndpointByDate, GlucoseEndpointsByDate
|
13
16
|
from .screening import get_glucose_tested_only_df, get_screening_df
|
@@ -1 +0,0 @@
|
|
1
|
-
from .get_glucose_df import get_glucose_df
|
@@ -13,14 +13,27 @@ def get_eos_df() -> pd.DataFrame:
|
|
13
13
|
df_eos = get_eos("meta_prn.endofstudy")
|
14
14
|
df_visit = get_subject_visit("meta_subject.subjectvisit")
|
15
15
|
df_last_visit = (
|
16
|
-
df_visit.groupby(["subject_identifier", "
|
16
|
+
df_visit.groupby(["subject_identifier", "site_id"])
|
17
17
|
.agg({"endline_visit_code": "max", "endline_visit_datetime": "max"})
|
18
18
|
.reset_index()
|
19
19
|
)
|
20
|
-
df_last_visit = df_last_visit.rename(columns={"site": "site_id"})
|
20
|
+
# df_last_visit = df_last_visit.rename(columns={"site": "site_id"})
|
21
21
|
|
22
22
|
df_eos = df_eos.merge(
|
23
23
|
df_last_visit, on="subject_identifier", how="left", suffixes=("", "_y")
|
24
24
|
)
|
25
25
|
df_eos = df_eos.drop(columns=["site_id_y"])
|
26
|
+
df_visit_grp = (
|
27
|
+
df_visit.groupby(by=["subject_identifier"])[["baseline_datetime", "visit_datetime"]]
|
28
|
+
.max()
|
29
|
+
.reset_index()
|
30
|
+
)
|
31
|
+
df_visit_grp["followup_days"] = (
|
32
|
+
df_visit_grp["visit_datetime"] - df_visit_grp["baseline_datetime"]
|
33
|
+
).dt.days
|
34
|
+
df_eos = df_eos.merge(
|
35
|
+
df_visit_grp[["subject_identifier", "followup_days"]],
|
36
|
+
on="subject_identifier",
|
37
|
+
how="left",
|
38
|
+
).reset_index(drop=True)
|
26
39
|
return df_eos
|
@@ -0,0 +1,149 @@
|
|
1
|
+
import numpy as np
|
2
|
+
import pandas as pd
|
3
|
+
from django_pandas.io import read_frame
|
4
|
+
from edc_appointment.constants import MISSED_APPT # noqa
|
5
|
+
from edc_pdutils.dataframes import get_eos, get_subject_consent, get_subject_visit
|
6
|
+
|
7
|
+
from meta_subject.models import Glucose, GlucoseFbg
|
8
|
+
|
9
|
+
|
10
|
+
def get_glucose_df() -> pd.DataFrame:
|
11
|
+
subject_visit_df = (
|
12
|
+
get_subject_visit("meta_subject.subjectvisit")
|
13
|
+
.rename(columns={"id": "subject_visit_id"})
|
14
|
+
.query("appt_timing!=@MISSED_APPT")
|
15
|
+
)
|
16
|
+
df_glucose_fbg = read_frame(GlucoseFbg.objects.all(), verbose=False).rename(
|
17
|
+
columns={"fasting": "fasted", "subject_visit": "subject_visit_id"},
|
18
|
+
)
|
19
|
+
df_glucose_fbg["fasting_hrs"] = np.nan
|
20
|
+
df_glucose_fbg["fasting_hrs"] = df_glucose_fbg["fasting_duration_delta"].apply(
|
21
|
+
lambda x: x.total_seconds() / 3600
|
22
|
+
)
|
23
|
+
df_glucose_fbg.loc[
|
24
|
+
:,
|
25
|
+
["ogtt_value", "ogtt_units", "ogtt_datetime"],
|
26
|
+
] = [np.nan, None, pd.NaT]
|
27
|
+
df_glucose_fbg["source"] = "meta_subject.glucosefbg"
|
28
|
+
df_glucose_fbg = pd.merge(
|
29
|
+
subject_visit_df[
|
30
|
+
[
|
31
|
+
"subject_identifier",
|
32
|
+
"site_id",
|
33
|
+
"visit_code",
|
34
|
+
"visit_datetime",
|
35
|
+
"baseline_datetime",
|
36
|
+
"subject_visit_id",
|
37
|
+
]
|
38
|
+
],
|
39
|
+
df_glucose_fbg[[col for col in df_glucose_fbg.columns if "site_id" not in col]],
|
40
|
+
on="subject_visit_id",
|
41
|
+
how="left",
|
42
|
+
)
|
43
|
+
|
44
|
+
df_glucose = read_frame(Glucose.objects.all(), verbose=False).rename(
|
45
|
+
columns={"subject_visit": "subject_visit_id", "fasting": "fasted"}
|
46
|
+
)
|
47
|
+
df_glucose["fasting_hrs"] = np.nan
|
48
|
+
df_glucose["fasting_hrs"] = df_glucose["fasting_duration_delta"].apply(
|
49
|
+
lambda x: x.total_seconds() / 3600
|
50
|
+
)
|
51
|
+
df_glucose["source"] = "meta_subject.glucose"
|
52
|
+
|
53
|
+
df_glucose = pd.merge(
|
54
|
+
subject_visit_df[
|
55
|
+
[
|
56
|
+
"subject_identifier",
|
57
|
+
"site_id",
|
58
|
+
"visit_code",
|
59
|
+
"visit_datetime",
|
60
|
+
"baseline_datetime",
|
61
|
+
"subject_visit_id",
|
62
|
+
]
|
63
|
+
],
|
64
|
+
df_glucose[[col for col in df_glucose.columns if "site_id" not in col]],
|
65
|
+
on="subject_visit_id",
|
66
|
+
how="left",
|
67
|
+
)
|
68
|
+
|
69
|
+
keep_cols = [
|
70
|
+
"subject_identifier",
|
71
|
+
"site_id",
|
72
|
+
"visit_code",
|
73
|
+
"visit_datetime",
|
74
|
+
"baseline_datetime",
|
75
|
+
"subject_visit_id",
|
76
|
+
"fasted",
|
77
|
+
"fasting_hrs",
|
78
|
+
"fbg_value",
|
79
|
+
"fbg_units",
|
80
|
+
"fbg_datetime",
|
81
|
+
"ogtt_value",
|
82
|
+
"ogtt_units",
|
83
|
+
"ogtt_datetime",
|
84
|
+
"source",
|
85
|
+
"revision",
|
86
|
+
"report_datetime",
|
87
|
+
]
|
88
|
+
df = pd.merge(
|
89
|
+
df_glucose[keep_cols],
|
90
|
+
df_glucose_fbg[keep_cols],
|
91
|
+
on="subject_visit_id",
|
92
|
+
how="outer",
|
93
|
+
# indicator=True,
|
94
|
+
suffixes=("", "_2"),
|
95
|
+
)
|
96
|
+
|
97
|
+
for suffix in ["", "_2"]:
|
98
|
+
df[[f"fasting_hrs{suffix}", f"fbg_value{suffix}", f"ogtt_value{suffix}"]] = df[
|
99
|
+
[f"fasting_hrs{suffix}", f"fbg_value{suffix}", f"ogtt_value{suffix}"]
|
100
|
+
].apply(pd.to_numeric)
|
101
|
+
df.loc[
|
102
|
+
(df[f"fbg_units{suffix}"] != "mmol/L (millimoles/L)")
|
103
|
+
& (df[f"fbg_value{suffix}"] >= 0),
|
104
|
+
f"fbg_units{suffix}",
|
105
|
+
] = "mmol/L (millimoles/L)"
|
106
|
+
df.loc[
|
107
|
+
(df[f"ogtt_units{suffix}"] != "mmol/L (millimoles/L)")
|
108
|
+
& (df[f"ogtt_value{suffix}"] >= 0),
|
109
|
+
f"ogtt_units{suffix}",
|
110
|
+
] = "mmol/L (millimoles/L)"
|
111
|
+
|
112
|
+
# reconcile all to single column
|
113
|
+
for col in ["fasted", "fbg_value", "ogtt_value", "fbg_datetime", "ogtt_datetime"]:
|
114
|
+
df.loc[(df[col].isna()) & (df[f"{col}_2"].notna()), col] = df[f"{col}_2"]
|
115
|
+
|
116
|
+
df_consent = get_subject_consent("meta_consent.subjectconsent")
|
117
|
+
df_eos = get_eos("meta_prn.endofstudy")
|
118
|
+
df = df.merge(
|
119
|
+
df_consent[["subject_identifier", "gender", "consent_datetime", "dob"]],
|
120
|
+
on="subject_identifier",
|
121
|
+
how="left",
|
122
|
+
).merge(
|
123
|
+
df_eos[["subject_identifier", "offstudy_datetime", "offstudy_reason"]],
|
124
|
+
on="subject_identifier",
|
125
|
+
how="left",
|
126
|
+
)
|
127
|
+
|
128
|
+
df[[col for col in df.columns if "datetime" in col]] = df[
|
129
|
+
[col for col in df.columns if "datetime" in col]
|
130
|
+
].apply(lambda x: x.dt.tz_localize(None) if x.dtype == "datetime64[ns, UTC]" else x)
|
131
|
+
|
132
|
+
df["visit_days"] = df["baseline_datetime"].rsub(df["visit_datetime"]).dt.days
|
133
|
+
df["fgb_days"] = df["baseline_datetime"].rsub(df["fbg_datetime"]).dt.days
|
134
|
+
df["ogtt_days"] = df["baseline_datetime"].rsub(df["ogtt_datetime"]).dt.days
|
135
|
+
df["visit_days"] = pd.to_numeric(df["visit_days"], downcast="integer")
|
136
|
+
df["fgb_days"] = pd.to_numeric(df["fgb_days"], downcast="integer")
|
137
|
+
df["ogtt_days"] = pd.to_numeric(df["ogtt_days"], downcast="integer")
|
138
|
+
|
139
|
+
df = (
|
140
|
+
df.query(
|
141
|
+
"offstudy_reason != 'Patient fulfilled late exclusion criteria "
|
142
|
+
"(due to abnormal blood values or raised blood pressure at enrolment'"
|
143
|
+
)
|
144
|
+
.copy()
|
145
|
+
.drop(columns=[col for col in df.columns if "_2" in col])
|
146
|
+
.sort_values(by=["subject_identifier", "visit_code"])
|
147
|
+
.reset_index(drop=True)
|
148
|
+
)
|
149
|
+
return df
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
from edc_constants.constants import NO, YES
|
3
|
+
from edc_pdutils.dataframes import get_crf
|
4
|
+
|
5
|
+
from meta_analytics.dataframes.utils import calculate_fasting_hrs
|
6
|
+
|
7
|
+
__all__ = ["get_glucose_fbg_df"]
|
8
|
+
|
9
|
+
|
10
|
+
def get_glucose_fbg_df(subject_identifiers: list[str] | None = None) -> pd.DataFrame:
|
11
|
+
"""Returns a prepared Dataframe of CRF
|
12
|
+
meta_subject.glucosefbg.
|
13
|
+
|
14
|
+
Note: meta_subject.glucosefbg has only FBG measures.
|
15
|
+
"""
|
16
|
+
df = get_crf(
|
17
|
+
model="meta_subject.glucosefbg",
|
18
|
+
subject_identifiers=subject_identifiers or [],
|
19
|
+
subject_visit_model="meta_subject.subjectvisit",
|
20
|
+
)
|
21
|
+
df["source"] = "meta_subject.glucosefbg"
|
22
|
+
df.rename(columns={"fbg_fasting": "fasting"}, inplace=True)
|
23
|
+
df.loc[(df["fasting"] == "fasting"), "fasting"] = YES
|
24
|
+
df.loc[(df["fasting"] == "non_fasting"), "fasting"] = NO
|
25
|
+
df = calculate_fasting_hrs(df)
|
26
|
+
df = df.reset_index(drop=True)
|
27
|
+
return df
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import pandas as pd
|
2
|
+
from edc_pdutils.dataframes import get_crf
|
3
|
+
|
4
|
+
from .utils import calculate_fasting_hrs
|
5
|
+
|
6
|
+
__all__ = ["get_glucose_fbg_ogtt_df"]
|
7
|
+
|
8
|
+
|
9
|
+
def get_glucose_fbg_ogtt_df(subject_identifiers: list[str] | None = None) -> pd.DataFrame:
|
10
|
+
"""Returns a prepared Dataframe of CRF meta_subject.glucose.
|
11
|
+
|
12
|
+
Note: meta_subject.glucose has FBG and OGTT measures.
|
13
|
+
"""
|
14
|
+
df = get_crf(
|
15
|
+
model="meta_subject.glucose",
|
16
|
+
subject_identifiers=subject_identifiers or [],
|
17
|
+
subject_visit_model="meta_subject.subjectvisit",
|
18
|
+
)
|
19
|
+
df["source"] = "meta_subject.glucose"
|
20
|
+
df = calculate_fasting_hrs(df)
|
21
|
+
df = df.reset_index(drop=True)
|
22
|
+
return df
|
@@ -1,3 +1,5 @@
|
|
1
|
+
from dataclasses import dataclass, field
|
2
|
+
|
1
3
|
import numpy as np
|
2
4
|
import pandas as pd
|
3
5
|
from edc_constants.constants import YES
|
@@ -13,6 +15,97 @@ class InvalidCaseList(Exception):
|
|
13
15
|
pass
|
14
16
|
|
15
17
|
|
18
|
+
@dataclass(kw_only=True)
|
19
|
+
class CaseData:
|
20
|
+
df: pd.DataFrame
|
21
|
+
index: int
|
22
|
+
fbg_value: float | None = field(default=None, init=False)
|
23
|
+
fbg_datetime: pd.Timestamp | None = field(default=None, init=False)
|
24
|
+
fasted: str | None = field(default=None, init=False)
|
25
|
+
ogtt_value: float | None = field(default=None, init=False)
|
26
|
+
next_fbg_value: float | None = field(default=None, init=False)
|
27
|
+
next_fbg_datetime: pd.Timestamp | None = field(default=None, init=False)
|
28
|
+
next_fasted: str | None = field(default=None, init=False)
|
29
|
+
next_ogtt_value: float | None = field(default=None, init=False)
|
30
|
+
|
31
|
+
previous_fbg_value: float | None = field(default=None, init=False)
|
32
|
+
previous_fbg_datetime: pd.Timestamp | None = field(default=None, init=False)
|
33
|
+
previous_fasted: str | None = field(default=None, init=False)
|
34
|
+
previous_ogtt_value: float | None = field(default=None, init=False)
|
35
|
+
|
36
|
+
fbg_threshold: float = field(default=7.0, init=False)
|
37
|
+
ogtt_threshold: float = field(default=11.1, init=False)
|
38
|
+
|
39
|
+
def __post_init__(self):
|
40
|
+
self.fbg_value = self.df.loc[self.index, "fbg_value"]
|
41
|
+
self.fbg_datetime = self.df.loc[self.index, "fbg_datetime"]
|
42
|
+
self.ogtt_value = self.df.loc[self.index, "ogtt_value"]
|
43
|
+
self.fasted = self.df.loc[self.index, "fasted"]
|
44
|
+
|
45
|
+
try:
|
46
|
+
self.next_fbg_value = self.df.loc[self.index + 1, "fbg_value"]
|
47
|
+
except KeyError:
|
48
|
+
self.next_fbg_value = np.nan
|
49
|
+
self.next_fbg_datetime = pd.NaT
|
50
|
+
self.next_ogtt_value = np.nan
|
51
|
+
self.next_fasted = np.nan
|
52
|
+
else:
|
53
|
+
self.next_fbg_datetime = self.df.loc[self.index + 1, "fbg_datetime"]
|
54
|
+
self.next_ogtt_value = self.df.loc[self.index + 1, "ogtt_value"]
|
55
|
+
self.next_fasted = self.df.loc[self.index + 1, "fasted"]
|
56
|
+
|
57
|
+
try:
|
58
|
+
self.previous_fbg_value = self.df.loc[self.index - 1, "fbg_value"]
|
59
|
+
except KeyError:
|
60
|
+
self.previous_fbg_value = np.nan
|
61
|
+
self.previous_fbg_datetime = pd.NaT
|
62
|
+
self.previous_ogtt_value = np.nan
|
63
|
+
self.previous_fasted = np.nan
|
64
|
+
else:
|
65
|
+
self.previous_fbg_datetime = self.df.loc[self.index - 1, "fbg_datetime"]
|
66
|
+
self.previous_ogtt_value = self.df.loc[self.index - 1, "ogtt_value"]
|
67
|
+
self.previous_fasted = self.df.loc[self.index - 1, "fasted"]
|
68
|
+
|
69
|
+
def case_two(self) -> bool:
|
70
|
+
""" "FBG >= 7 x 2, first OGTT<=11.1"""
|
71
|
+
if (
|
72
|
+
self.fbg_value >= self.fbg_threshold
|
73
|
+
and self.next_fbg_value >= self.fbg_threshold
|
74
|
+
and 0.0 < self.ogtt_value < self.ogtt_threshold
|
75
|
+
and self.fasted == YES
|
76
|
+
and self.next_fasted == YES
|
77
|
+
and (self.next_fbg_datetime.date() - self.fbg_datetime.date()).days > 6
|
78
|
+
):
|
79
|
+
return True
|
80
|
+
return False
|
81
|
+
|
82
|
+
def case_three(self) -> bool:
|
83
|
+
""" "FBG >= 7 x 2, second OGTT<=11.1"""
|
84
|
+
if (
|
85
|
+
self.fbg_value >= self.fbg_threshold
|
86
|
+
and self.next_fbg_value >= self.fbg_threshold
|
87
|
+
and 0.0 < self.next_ogtt_value < self.ogtt_threshold
|
88
|
+
and self.fasted == YES
|
89
|
+
and self.next_fasted == YES
|
90
|
+
and (self.next_fbg_datetime.date() - self.fbg_datetime.date()).days > 6
|
91
|
+
):
|
92
|
+
return True
|
93
|
+
return False
|
94
|
+
|
95
|
+
def case_two_reversed(self) -> bool:
|
96
|
+
"""Same as case 2, but with the previous FBG reading."""
|
97
|
+
if (
|
98
|
+
self.fbg_value >= self.fbg_threshold
|
99
|
+
and self.previous_fbg_value >= self.fbg_threshold
|
100
|
+
and 0.0 < self.previous_ogtt_value < self.ogtt_threshold
|
101
|
+
and self.fasted == YES
|
102
|
+
and self.previous_fasted == YES
|
103
|
+
and (self.fbg_datetime.date() - self.previous_fbg_datetime.date()).days > 6
|
104
|
+
):
|
105
|
+
return True
|
106
|
+
return False
|
107
|
+
|
108
|
+
|
16
109
|
class EndpointByDate:
|
17
110
|
"""Given all timepoints for a subject, flag the first timepoint
|
18
111
|
where the protocol endpoint is reached.
|
@@ -27,157 +120,50 @@ class EndpointByDate:
|
|
27
120
|
* case 3. FBG >= 7 x 2, second OGTT<11.1
|
28
121
|
|
29
122
|
Additional criteria considered:
|
30
|
-
1. any threshhold FBG must be taken while fasted (
|
123
|
+
1. any threshhold FBG must be taken while fasted (fasted=YES)
|
31
124
|
2. threshhold FBG readings must be consecutive (no
|
32
125
|
readings below threshold in the sequence regardless
|
33
126
|
of fasting)
|
34
127
|
3. at least 7 days between threshhold FBG readings.
|
35
128
|
4. at least one of the two threshold FBG readings must be taken
|
36
129
|
with an OGTT at the same timepoint.
|
37
|
-
|
38
|
-
Note:
|
39
|
-
case 4 is not a protocol endpoint. It considers only FBG and fasting.
|
40
|
-
It looks for two consecutive fasted threshold FBG readings.
|
41
130
|
"""
|
42
131
|
|
43
|
-
valid_case_list = [2, 3, 4]
|
44
|
-
|
45
132
|
def __init__(
|
46
133
|
self,
|
47
134
|
subject_df: pd.DataFrame = None,
|
48
135
|
fbg_threshhold: float = None,
|
49
136
|
ogtt_threshhold: float = None,
|
50
|
-
case_list: list[int] | None = None,
|
51
137
|
):
|
52
138
|
self.row = None
|
53
139
|
self.index = None
|
54
|
-
self.subject_df = subject_df[
|
55
|
-
self.subject_df = self.subject_df.reset_index(drop=True)
|
140
|
+
self.subject_df = subject_df.sort_values(by=["visit_code"]).reset_index(drop=True)
|
56
141
|
self.fbg_threshhold = fbg_threshhold
|
57
142
|
self.ogtt_threshhold = ogtt_threshhold
|
58
|
-
self.case_list = case_list or [2, 3]
|
59
|
-
if [x for x in self.case_list if x not in self.valid_case_list]:
|
60
|
-
raise InvalidCaseList(f"Expected any of {self.valid_case_list}. Got {case_list}.")
|
61
|
-
self.endpoint_cases = {k: v for k, v in endpoint_cases.items() if k in self.case_list}
|
62
143
|
self.evaluate()
|
63
144
|
|
64
145
|
def evaluate(self):
|
65
146
|
for index, _ in self.subject_df.iterrows():
|
66
|
-
|
147
|
+
case_data = CaseData(df=self.subject_df, index=index)
|
148
|
+
if case_data.case_two():
|
149
|
+
self.endpoint_reached(index, case=2, fbg_datetime=case_data.next_fbg_datetime)
|
67
150
|
break
|
68
|
-
elif
|
151
|
+
elif case_data.case_three():
|
152
|
+
self.endpoint_reached(index, case=3, fbg_datetime=case_data.next_fbg_datetime)
|
69
153
|
break
|
70
|
-
elif
|
154
|
+
elif case_data.case_two_reversed():
|
155
|
+
self.endpoint_reached(index, case=2, fbg_datetime=case_data.fbg_datetime)
|
71
156
|
break
|
157
|
+
else:
|
158
|
+
pass
|
72
159
|
|
73
|
-
def endpoint_reached(self, index: int, case: int,
|
160
|
+
def endpoint_reached(self, index: int, case: int, fbg_datetime: pd.Timestamp):
|
74
161
|
"""Update the subject_df"""
|
75
|
-
fbg_datetime = (
|
76
|
-
self.get_next("fbg_datetime", index)
|
77
|
-
if next_is_endpoint
|
78
|
-
else self.get("fbg_datetime", index)
|
79
|
-
)
|
80
162
|
self.subject_df.loc[self.subject_df["fbg_datetime"] == fbg_datetime, "endpoint"] = 1
|
81
163
|
self.subject_df["interval_in_days"] = np.nan
|
82
|
-
try:
|
83
|
-
self.subject_df.loc[
|
84
|
-
self.subject_df["fbg_datetime"] == fbg_datetime, "interval_in_days"
|
85
|
-
] = self.sequential_assessments_in_days(index)
|
86
|
-
except EndpointTdeltaError:
|
87
|
-
pass
|
88
|
-
self.subject_df["interval_in_days"] = pd.to_numeric(
|
89
|
-
self.subject_df["interval_in_days"]
|
90
|
-
)
|
91
164
|
self.subject_df.loc[
|
92
165
|
self.subject_df["fbg_datetime"] == fbg_datetime, "endpoint_type"
|
93
166
|
] = case
|
94
167
|
self.subject_df.loc[
|
95
168
|
self.subject_df["fbg_datetime"] == fbg_datetime, "endpoint_label"
|
96
|
-
] =
|
97
|
-
|
98
|
-
def case_two(self, index: int):
|
99
|
-
"""FBG >= 7 x 2, first OGTT<11.1.
|
100
|
-
|
101
|
-
First FBG must be done with corresponding OGTT.
|
102
|
-
"""
|
103
|
-
reached = (
|
104
|
-
self.get_next("fbg_datetime", index)
|
105
|
-
and self.get("fbg_value", index)
|
106
|
-
and self.get("ogtt_value", index)
|
107
|
-
and self.get("fasting", index)
|
108
|
-
and self.get_next("fbg_value", index)
|
109
|
-
and self.get_next("fasting", index)
|
110
|
-
and self.get("fbg_value", index) >= self.fbg_threshhold
|
111
|
-
and self.get("ogtt_value", index) < self.ogtt_threshhold
|
112
|
-
and self.get("fasting", index) == YES
|
113
|
-
and self.get_next("fbg_value", index) >= self.fbg_threshhold
|
114
|
-
and self.get_next("fasting", index) == YES
|
115
|
-
and (self.get_next("fbg_datetime", index) - self.get("fbg_datetime", index)).days
|
116
|
-
>= 7
|
117
|
-
)
|
118
|
-
if reached:
|
119
|
-
self.endpoint_reached(index, case=2, next_is_endpoint=True)
|
120
|
-
return reached
|
121
|
-
|
122
|
-
def case_three(self, index: int):
|
123
|
-
"""FBG >= 7 x 2, second OGTT<11.1.
|
124
|
-
|
125
|
-
Second FBG must be done with corresponding OGTT.
|
126
|
-
"""
|
127
|
-
reached = (
|
128
|
-
self.get_next("fbg_datetime", index)
|
129
|
-
and self.get("fbg_value", index)
|
130
|
-
and self.get("fasting", index)
|
131
|
-
and self.get_next("fbg_value", index)
|
132
|
-
and self.get_next("ogtt_value", index)
|
133
|
-
and self.get_next("fasting", index)
|
134
|
-
and self.get("fbg_value", index) >= self.fbg_threshhold
|
135
|
-
and self.get("fasting", index) == YES
|
136
|
-
and self.get_next("fbg_value", index) >= self.fbg_threshhold
|
137
|
-
and self.get_next("ogtt_value", index) < self.ogtt_threshhold
|
138
|
-
and self.get_next("fasting", index) == YES
|
139
|
-
and (self.get_next("fbg_datetime", index) - self.get("fbg_datetime", index)).days
|
140
|
-
>= 7
|
141
|
-
)
|
142
|
-
if reached:
|
143
|
-
self.endpoint_reached(index, case=3, next_is_endpoint=True)
|
144
|
-
return reached
|
145
|
-
|
146
|
-
def case_four(self, index: int):
|
147
|
-
"""FBG >= 7 x 2, OGTT not considered
|
148
|
-
|
149
|
-
This is not a protocol endpoint.
|
150
|
-
"""
|
151
|
-
reached = (
|
152
|
-
self.get("fbg_value", index)
|
153
|
-
and self.get("fbg_datetime", index)
|
154
|
-
and self.get("fasting", index)
|
155
|
-
and self.get_next("fbg_value", index)
|
156
|
-
and self.get_next("ogtt_value", index)
|
157
|
-
and self.get_next("fbg_datetime", index)
|
158
|
-
and self.get_next("fasting", index)
|
159
|
-
and self.get("fbg_value", index) >= self.fbg_threshhold
|
160
|
-
and self.get("fasting", index) == YES
|
161
|
-
and self.get_next("fbg_value", index) >= self.fbg_threshhold
|
162
|
-
and self.get_next("fasting", index) == YES
|
163
|
-
and (self.get_next("fbg_datetime", index) - self.get("fbg_datetime", index)).days
|
164
|
-
>= 7
|
165
|
-
)
|
166
|
-
if reached:
|
167
|
-
self.endpoint_reached(index, case=4, next_is_endpoint=True)
|
168
|
-
return reached
|
169
|
-
|
170
|
-
def sequential_assessments_in_days(self, index) -> int:
|
171
|
-
if not self.get_next("fbg_value", index):
|
172
|
-
raise EndpointTdeltaError
|
173
|
-
return (self.get_next("fbg_datetime", index) - self.get("visit_datetime", index)).days
|
174
|
-
|
175
|
-
def get(self, col: str, index: int) -> float | None:
|
176
|
-
try:
|
177
|
-
next_value = self.subject_df.iloc[index : index + 1][col].item()
|
178
|
-
except ValueError:
|
179
|
-
next_value = None
|
180
|
-
return next_value
|
181
|
-
|
182
|
-
def get_next(self, col: str, index: int) -> float | None:
|
183
|
-
return self.get(col, index + 1)
|
169
|
+
] = endpoint_cases[case]
|