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 +21 -0
- aces/__init__.py +11 -0
- aces/__main__.py +185 -0
- aces/aggregate.py +1027 -0
- aces/config.py +1519 -0
- aces/configs/__init__.py +58 -0
- aces/configs/aces.yaml +68 -0
- aces/configs/data/defaults.yaml +5 -0
- aces/configs/data/sharded.yaml +11 -0
- aces/configs/data/single_file.yaml +9 -0
- aces/constraints.py +167 -0
- aces/expand_shards.py +79 -0
- aces/extract_subtree.py +342 -0
- aces/predicates.py +756 -0
- aces/query.py +129 -0
- aces/types.py +315 -0
- aces/utils.py +65 -0
- es_aces-0.0.0.dist-info/LICENSE +21 -0
- es_aces-0.0.0.dist-info/METADATA +399 -0
- es_aces-0.0.0.dist-info/RECORD +23 -0
- es_aces-0.0.0.dist-info/WHEEL +5 -0
- es_aces-0.0.0.dist-info/entry_points.txt +3 -0
- es_aces-0.0.0.dist-info/top_level.txt +1 -0
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()
|