es-aces 0.0.0__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.
aces/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # ACES in Python
2
+
3
+ ```mermaid
4
+ sequenceDiagram
5
+ participant User
6
+ participant ACES
7
+ participant Config
8
+ participant Predicates
9
+ participant Query
10
+
11
+ User->>ACES: Execute command with configuration parameters
12
+ ACES->>Config: Load configuration
13
+ Config-->>ACES: Task Configuration
14
+ ACES->>Predicates: Get predicates dataframe
15
+ Predicates-->>ACES: Predicate DataFrame
16
+ ACES->>Query: Execute query with predicates and task configuration
17
+ Query-->>ACES: Filtered Results
18
+ ACES-->>User: Display & Save Results
19
+ ```
20
+
21
+ Please see the tutorial in the [ACES Documentation](https://eventstreamaces.readthedocs.io/en/latest/notebooks/tutorial.html) for information on how to use ACES in Python.
aces/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """This is the main module of the ACES package.
2
+
3
+ It contains the main functions and classes needed to extract cohorts.
4
+ """
5
+ from importlib.metadata import PackageNotFoundError, version
6
+
7
+ __package_name__ = "es-aces"
8
+ try:
9
+ __version__ = version(__package_name__)
10
+ except PackageNotFoundError:
11
+ __version__ = "unknown"
aces/__main__.py ADDED
@@ -0,0 +1,185 @@
1
+ """Main script for end-to-end cohort extraction."""
2
+
3
+ import sys
4
+ from importlib.resources import files
5
+
6
+ import hydra
7
+ import polars as pl
8
+ import pyarrow as pa
9
+ import pyarrow.parquet as pq
10
+ from loguru import logger
11
+ from meds import label_schema
12
+ from omegaconf import DictConfig
13
+
14
+ config_yaml = files("aces").joinpath("configs/aces.yaml")
15
+
16
+ if len(sys.argv) == 1:
17
+ print("Usage: aces-cli [OPTIONS]")
18
+ print("Try 'aces-cli --help' for more information.")
19
+ print("For more information, visit: https://eventstreamaces.readthedocs.io/en/latest/usage.html")
20
+ sys.exit(1)
21
+
22
+ MEDS_LABEL_MANDATORY_TYPES = {
23
+ "patient_id": pl.Int64,
24
+ }
25
+
26
+ MEDS_LABEL_OPTIONAL_TYPES = {
27
+ "boolean_value": pl.Boolean,
28
+ "integer_value": pl.Int64,
29
+ "float_value": pl.Float64,
30
+ "categorical_value": pl.String,
31
+ "prediction_time": pl.Datetime("us"),
32
+ }
33
+
34
+
35
+ def get_and_validate_label_schema(df: pl.DataFrame) -> pa.Table:
36
+ """Validates the schema of a MEDS data DataFrame.
37
+
38
+ This function validates the schema of a MEDS label DataFrame, ensuring that it has the correct columns
39
+ and that the columns are of the correct type. This function will:
40
+ 1. Re-type any of the mandator MEDS column to the appropriate type.
41
+ 2. Attempt to add the ``numeric_value`` or ``time`` columns if either are missing, and set it to `None`.
42
+ It will not attempt to add any other missing columns even if ``do_retype`` is `True` as the other
43
+ columns cannot be set to `None`.
44
+
45
+ Args:
46
+ df: The MEDS label DataFrame to validate.
47
+
48
+ Returns:
49
+ pa.Table: The validated MEDS data DataFrame, with columns re-typed as needed.
50
+
51
+ Raises:
52
+ ValueError: if do_retype is False and the MEDS data DataFrame is not schema compliant.
53
+
54
+ Examples:
55
+ >>> df = pl.DataFrame({})
56
+ >>> get_and_validate_label_schema(df) # doctest: +NORMALIZE_WHITESPACE
57
+ Traceback (most recent call last):
58
+ ...
59
+ ValueError: MEDS Data DataFrame must have a 'patient_id' column of type Int64.
60
+ MEDS Data DataFrame must have a 'prediction_time' column of type String.
61
+ Datetime(time_unit='us', time_zone=None).
62
+ >>> from datetime import datetime
63
+ >>> df = pl.DataFrame({
64
+ ... "patient_id": pl.Series([1, 3, 2], dtype=pl.UInt32),
65
+ ... "time": [datetime(2021, 1, 1), datetime(2021, 1, 2), datetime(2021, 1, 3)],
66
+ ... "boolean_value": [1, 0, 100],
67
+ ... })
68
+ >>> get_and_validate_label_schema(df)
69
+ pyarrow.Table
70
+ patient_id: int64
71
+ time: timestamp[us]
72
+ boolean_value: bool
73
+ integer_value: int64
74
+ float_value: float
75
+ categorical_value: string
76
+ ----
77
+ patient_id: [[1,3,2]]
78
+ time: [[2021-01-01 00:00:00.000000,2021-01-02 00:00:00.000000,2021-01-03 00:00:00.000000]]
79
+ boolean_value: [[true,false,true]]
80
+ integer_value: [[null,null,null]]
81
+ float_value: [[null,null,null]]
82
+ categorical_value: [[null,null,null]]
83
+ """
84
+
85
+ schema = df.schema
86
+ if "prediction_time" not in schema:
87
+ logger.warning(
88
+ "Output DataFrame is missing a 'prediction_time' column. If this is not intentional, add a "
89
+ "'index_timestamp' (yes, it should be different) key to the task configuration identifying "
90
+ "which window's start or end time to use as the prediction time."
91
+ )
92
+
93
+ errors = []
94
+ for col, dtype in MEDS_LABEL_MANDATORY_TYPES.items():
95
+ if col in schema and schema[col] != dtype:
96
+ df = df.with_columns(pl.col(col).cast(dtype, strict=False))
97
+ elif col not in schema:
98
+ errors.append(f"MEDS Data DataFrame must have a '{col}' column of type {dtype}.")
99
+
100
+ if errors:
101
+ raise ValueError("\n".join(errors))
102
+
103
+ for col, dtype in MEDS_LABEL_OPTIONAL_TYPES.items():
104
+ if col in schema and schema[col] != dtype:
105
+ df = df.with_columns(pl.col(col).cast(dtype, strict=False))
106
+ elif col not in schema:
107
+ df = df.with_columns(pl.lit(None, dtype=dtype).alias(col))
108
+
109
+ extra_cols = [
110
+ c for c in schema if c not in MEDS_LABEL_MANDATORY_TYPES and c not in MEDS_LABEL_OPTIONAL_TYPES
111
+ ]
112
+ if extra_cols:
113
+ err_cols_str = "\n".join(f" - {c}" for c in extra_cols)
114
+ logger.warning(
115
+ "Output contains columns that are not valid MEDS label columns. For now, we are dropping them.\n"
116
+ "If you need these columns, please comment on https://github.com/justin13601/ACES/issues/97\n"
117
+ f"Columns:\n{err_cols_str}"
118
+ )
119
+ df = df.drop(extra_cols)
120
+
121
+ df = df.select(
122
+ "patient_id", "prediction_time", "boolean_value", "integer_value", "float_value", "categorical_value"
123
+ )
124
+
125
+ return df.to_arrow().cast(label_schema)
126
+
127
+
128
+ @hydra.main(version_base=None, config_path=str(config_yaml.parent.resolve()), config_name=config_yaml.stem)
129
+ def main(cfg: DictConfig):
130
+ import os
131
+ from datetime import datetime
132
+ from pathlib import Path
133
+
134
+ from hydra.core.hydra_config import HydraConfig
135
+ from omegaconf import OmegaConf
136
+
137
+ from . import config, predicates, query, utils
138
+
139
+ utils.hydra_loguru_init(f"{HydraConfig.get().job.name}.log")
140
+
141
+ st = datetime.now()
142
+
143
+ # Set output path
144
+ cohort_dir = Path(cfg.cohort_dir)
145
+ cohort_dir.mkdir(exist_ok=True, parents=True)
146
+
147
+ # load configuration
148
+ logger.info(f"Loading config from '{cfg.config_path}'")
149
+ task_cfg = config.TaskExtractorConfig.load(Path(cfg.config_path))
150
+
151
+ logger.info(f"Attempting to get predicates dataframe given:\n{OmegaConf.to_yaml(cfg.data)}")
152
+ predicates_df = predicates.get_predicates_df(task_cfg, cfg.data)
153
+
154
+ # query results
155
+ result = query.query(task_cfg, predicates_df)
156
+ result_is_empty = len(result) == 0
157
+
158
+ # save results to parquet
159
+ os.makedirs(os.path.dirname(cfg.output_filepath), exist_ok=True)
160
+
161
+ if cfg.data.standard.lower() == "meds":
162
+ for in_col, out_col in [
163
+ ("subject_id", "patient_id"),
164
+ ("index_timestamp", "prediction_time"),
165
+ ("label", "boolean_value"),
166
+ ]:
167
+ if in_col in result.columns:
168
+ result = result.rename({in_col: out_col})
169
+ if "patient_id" not in result.columns:
170
+ if not result_is_empty:
171
+ raise ValueError("Output dataframe is missing a 'patient_id' column.")
172
+ else:
173
+ logger.warning("Output dataframe is empty; adding an empty patient ID column.")
174
+ result = result.with_columns(pl.lit(None, dtype=pl.Int64).alias("patient_id"))
175
+ result = result.head(0)
176
+
177
+ result = get_and_validate_label_schema(result)
178
+ pq.write_table(result, cfg.output_filepath)
179
+ else:
180
+ result.write_parquet(cfg.output_filepath, use_pyarrow=True)
181
+ logger.info(f"Completed in {datetime.now() - st}. Results saved to '{cfg.output_filepath}'.")
182
+
183
+
184
+ if __name__ == "__main__":
185
+ main()