Source code for isaricanalytics.isaric_data_schema

from __future__ import annotations

__all__ = [
    "transform_to_isaric_data_schema",
]


# -- IMPORTS --

# -- Standard libraries --
import pathlib
import typing
import warnings as warnings_

# -- 3rd party libraries --
import adtl
import pandas as pd

# -- Internal ISARIC libraries --
from arc.arc_api import ArcApiClient, ArcApiClientError
from arc.arc_core import get_arc

import isaricanalytics.isaric_transformations as tf


class IsaricDataSchemaTransformationException(Exception): ...


def _warn_on_non_arc_columns(
    column_set: typing.Iterable, column_type: str, arc_version: str, stacklevel=2
) -> None:
    """Warns on the transform function detecting non-ARC columns in the core or long schema tables."""  # noqa: E501
    columns_str = ", ".join(map(lambda s: f'"{s}"', sorted(column_set)))
    msg = (
        f"\n\nThe following {column_type} table columns are not in ARC "
        f"{arc_version}: {columns_str}\n\nPlease check these columns."
    )
    warnings_.warn(msg, DeprecationWarning, stacklevel=stacklevel)


[docs] def transform_to_isaric_data_schema( parser_file: str | pathlib.Path, data_file: str | pathlib.Path, arc_version: str | None = None, /, *, as_vertex_data: bool = False, ) -> dict[str, pd.DataFrame]: """:py:class:`dict` : ISARIC schema-compliant short/core- and long-format datasets as Pandas dataframes, with optional VERTEX-style output. Parameters ---------- parser_file : str or pathlib.Path The parser TOML file path. data_file : str or pathlib.Path The data file path. arc_version : str or None, default=None Optional ARC version string used to fetch the ARC data dictionary associated with the ARC version; defaults to ``None``. as_vertex_data : bool, default=False Optional indicator of whether to return the data as a VERTEX-style dictionary in the format: :: { "df_map": <one-row-per-patient dataframe>, "daily": <patient observations dataframe>, "dictionary": <ARC data dictionary> } Returns ------- dict A dict which could be in one of two forms: :: { "core": <ISARIC data schema core table>, "long": <ISARIC data schema long table> } or, if ``as_vertex_data``: :: { "df_map": <one-row-per-patient dataframe>, "daily": <patient observations dataframe>, "dictionary": <ARC data dictionary> } Raises ------ IsaricDataSchemaTransformationException In case of an ARC API client exception. """ # noqa : E501 # Call ADTL to parse the data and retrieve the core and long-format tables. ids_tables = adtl.parse(parser_file, data_file, include_transform=tf.__file__) # An ARC version is required to fetch the ARC data dictionary. If no ARC # version is provided by the caller, fetch the latest directly from ARC @ # GitHub - in case of an exception raise it. if not arc_version: try: arc_version = ArcApiClient().get_arc_version_list()[0] except (ArcApiClientError, IndexError) as e: raise IsaricDataSchemaTransformationException( f'Exception fetching ARC version "{arc_version}": ' f"{e}.\n\n Please check that you have provided a valid " "ARC version. If you have then there may be a network- " "related error, so please retry after some time." ) # Fetch the ARC data dictionary associated with the ARC version - in case # of an exception raise it. try: arc_dd = get_arc(arc_version)[0] except (ArcApiClientError, IndexError) as e: raise IsaricDataSchemaTransformationException( "Exception fetching ARC data dictionary for ARC version " f'"{arc_version}": {e}.\n\n Please check that you have provided a ' "valid ARC version. If you have, then please retry after some " "time." ) # Checking transformed columns in the core and long tables against ARC, and # issuing warnings about any non-ARC columns in either. non_arc_core_columns = set(ids_tables["core"].columns).difference( arc_dd["Variable"] ) if non_arc_core_columns: _warn_on_non_arc_columns( non_arc_core_columns, "core", arc_version, stacklevel=2 ) non_arc_long_columns = set(ids_tables["long"].columns).difference( arc_dd["Variable"] ) if non_arc_long_columns: _warn_on_non_arc_columns( non_arc_long_columns, "long", arc_version, stacklevel=2 ) if not as_vertex_data: return ids_tables return { "df_map": ids_tables["core"], "daily": ids_tables["long"], "dictionary": arc_dd, }