Module phc.easy.codeable
Expand source code
import math
import re
from functools import reduce
import pandas as pd
from phc.easy.util import (
concat_dicts,
join_underscore,
prefix_dict_keys,
without_keys,
)
def system_to_column(system):
"Convert system name (potentially URL) to a readable column name"
return re.subn(r"https?:\/\/", "", system)[0]
def value_string_to_dict(codeable):
"Convert dictionary of url and valueString to flat dictionary"
return {system_to_column(codeable["url"]): codeable["valueString"]}
def merge_codeable_and_prefix(codeable, prefix):
if isinstance(codeable, dict):
return prefix_dict_keys(codeable, prefix)
else:
return concat_dicts(codeable, prefix)
def get_value_codeable_concept_items(concept):
"Extracts dictionaries in a valueCodeableConcept"
base_concept = without_keys(concept, ["coding"])
if "coding" not in concept:
return [base_concept]
return [
{
**base_concept,
**(
merge_codeable_and_prefix(
*flatten_and_find_prefix(coding_value, "coding")
)
),
}
for coding_value in concept["coding"]
]
def flatten_nested_dicts(codeable_dict):
NESTED_TYPES = [
("valueCodeableConcept", get_value_codeable_concept_items),
("extension", lambda x: x),
]
base_dict = without_keys(codeable_dict, [key for key, _ in NESTED_TYPES])
def reduce_nested(acc, nested_type):
key, func = nested_type
if key in codeable_dict:
return [
*acc,
*[
{
**base_dict,
**(
merge_codeable_and_prefix(
*flatten_and_find_prefix(result, key)
)
),
}
for result in func(codeable_dict[key])
],
]
return acc
flattened = reduce(reduce_nested, NESTED_TYPES, [])
if len(flattened) == 0:
# At least include the top-level dictionary attributes
return [base_dict]
return flattened
def flatten_and_find_prefix(codeable_dict, prefix):
"""
Convert a codeable_dict type to a flattened dictionary or list of
dictionaries for simple prefixing
"""
if "tag" in codeable_dict:
return (
[without_keys(codeable_dict, ["tag"]), *codeable_dict["tag"]],
join_underscore([prefix, "tag"]),
)
if "url" in codeable_dict:
return (
flatten_nested_dicts(without_keys(codeable_dict, ["url"])),
join_underscore(
[prefix, "url_", system_to_column(codeable_dict["url"]) + "_"]
),
)
if "system" in codeable_dict:
return (
without_keys(codeable_dict, ["system"]),
join_underscore(
[
prefix,
"system_",
system_to_column(codeable_dict["system"]) + "_",
]
),
)
if "type" in codeable_dict and "value" in codeable_dict:
types = codeable_dict["type"]["coding"]
return (
[{**t, **without_keys(codeable_dict, ["type"])} for t in types],
join_underscore(["type", "coding", prefix]),
)
return (codeable_dict, prefix)
def generic_codeable_to_dict(codeable, prefix=""):
"Convert dict/list/str contains code data to a flat dictionary"
if isinstance(codeable, float) and math.isnan(codeable):
return {}
if isinstance(codeable, list):
return concat_dicts(
[generic_codeable_to_dict(d, prefix) for d in codeable]
)
if not isinstance(codeable, dict):
return {prefix: codeable}
codeable, prefix = flatten_and_find_prefix(codeable, prefix)
# Recurse pre-processed value is not a dictionary (but a list for example)
if not isinstance(codeable, dict):
return generic_codeable_to_dict(codeable, prefix)
def prefixer(dictionary):
return prefix_dict_keys(dictionary, prefix)
# TODO: Add test case for valueString
# if 'valueString' in codeable:
# return prefixer(value_string_to_dict(codeable))
# TODO: Add test case for single value that is a url
# keys = codeable.keys()
# if len(keys) == 1 and 'url' in codeable:
# return prefixer({key: system_to_column(codeable[key]) + '+'})
result = prefixer(
concat_dicts(
[generic_codeable_to_dict(v, k) for k, v in codeable.items()]
)
)
return result
class Codeable:
@staticmethod
def expand_column(codeable_col: pd.Series):
"""Convert a pandas dictionary column with codeable data into a data frame
Attributes
----------
codeable_col : pd.Series
A pandas column that contains codeable data (FHIR resources)
"""
return pd.DataFrame(map(generic_codeable_to_dict, codeable_col.values))
Functions
def flatten_and_find_prefix(codeable_dict, prefix)
-
Convert a codeable_dict type to a flattened dictionary or list of dictionaries for simple prefixing
Expand source code
def flatten_and_find_prefix(codeable_dict, prefix): """ Convert a codeable_dict type to a flattened dictionary or list of dictionaries for simple prefixing """ if "tag" in codeable_dict: return ( [without_keys(codeable_dict, ["tag"]), *codeable_dict["tag"]], join_underscore([prefix, "tag"]), ) if "url" in codeable_dict: return ( flatten_nested_dicts(without_keys(codeable_dict, ["url"])), join_underscore( [prefix, "url_", system_to_column(codeable_dict["url"]) + "_"] ), ) if "system" in codeable_dict: return ( without_keys(codeable_dict, ["system"]), join_underscore( [ prefix, "system_", system_to_column(codeable_dict["system"]) + "_", ] ), ) if "type" in codeable_dict and "value" in codeable_dict: types = codeable_dict["type"]["coding"] return ( [{**t, **without_keys(codeable_dict, ["type"])} for t in types], join_underscore(["type", "coding", prefix]), ) return (codeable_dict, prefix)
def flatten_nested_dicts(codeable_dict)
-
Expand source code
def flatten_nested_dicts(codeable_dict): NESTED_TYPES = [ ("valueCodeableConcept", get_value_codeable_concept_items), ("extension", lambda x: x), ] base_dict = without_keys(codeable_dict, [key for key, _ in NESTED_TYPES]) def reduce_nested(acc, nested_type): key, func = nested_type if key in codeable_dict: return [ *acc, *[ { **base_dict, **( merge_codeable_and_prefix( *flatten_and_find_prefix(result, key) ) ), } for result in func(codeable_dict[key]) ], ] return acc flattened = reduce(reduce_nested, NESTED_TYPES, []) if len(flattened) == 0: # At least include the top-level dictionary attributes return [base_dict] return flattened
def generic_codeable_to_dict(codeable, prefix='')
-
Convert dict/list/str contains code data to a flat dictionary
Expand source code
def generic_codeable_to_dict(codeable, prefix=""): "Convert dict/list/str contains code data to a flat dictionary" if isinstance(codeable, float) and math.isnan(codeable): return {} if isinstance(codeable, list): return concat_dicts( [generic_codeable_to_dict(d, prefix) for d in codeable] ) if not isinstance(codeable, dict): return {prefix: codeable} codeable, prefix = flatten_and_find_prefix(codeable, prefix) # Recurse pre-processed value is not a dictionary (but a list for example) if not isinstance(codeable, dict): return generic_codeable_to_dict(codeable, prefix) def prefixer(dictionary): return prefix_dict_keys(dictionary, prefix) # TODO: Add test case for valueString # if 'valueString' in codeable: # return prefixer(value_string_to_dict(codeable)) # TODO: Add test case for single value that is a url # keys = codeable.keys() # if len(keys) == 1 and 'url' in codeable: # return prefixer({key: system_to_column(codeable[key]) + '+'}) result = prefixer( concat_dicts( [generic_codeable_to_dict(v, k) for k, v in codeable.items()] ) ) return result
def get_value_codeable_concept_items(concept)
-
Extracts dictionaries in a valueCodeableConcept
Expand source code
def get_value_codeable_concept_items(concept): "Extracts dictionaries in a valueCodeableConcept" base_concept = without_keys(concept, ["coding"]) if "coding" not in concept: return [base_concept] return [ { **base_concept, **( merge_codeable_and_prefix( *flatten_and_find_prefix(coding_value, "coding") ) ), } for coding_value in concept["coding"] ]
def merge_codeable_and_prefix(codeable, prefix)
-
Expand source code
def merge_codeable_and_prefix(codeable, prefix): if isinstance(codeable, dict): return prefix_dict_keys(codeable, prefix) else: return concat_dicts(codeable, prefix)
def system_to_column(system)
-
Convert system name (potentially URL) to a readable column name
Expand source code
def system_to_column(system): "Convert system name (potentially URL) to a readable column name" return re.subn(r"https?:\/\/", "", system)[0]
def value_string_to_dict(codeable)
-
Convert dictionary of url and valueString to flat dictionary
Expand source code
def value_string_to_dict(codeable): "Convert dictionary of url and valueString to flat dictionary" return {system_to_column(codeable["url"]): codeable["valueString"]}
Classes
class Codeable
-
Expand source code
class Codeable: @staticmethod def expand_column(codeable_col: pd.Series): """Convert a pandas dictionary column with codeable data into a data frame Attributes ---------- codeable_col : pd.Series A pandas column that contains codeable data (FHIR resources) """ return pd.DataFrame(map(generic_codeable_to_dict, codeable_col.values))
Static methods
def expand_column(codeable_col: pandas.core.series.Series)
-
Convert a pandas dictionary column with codeable data into a data frame
Attributes
codeable_col
:pd.Series
- A pandas column that contains codeable data (FHIR resources)
Expand source code
@staticmethod def expand_column(codeable_col: pd.Series): """Convert a pandas dictionary column with codeable data into a data frame Attributes ---------- codeable_col : pd.Series A pandas column that contains codeable data (FHIR resources) """ return pd.DataFrame(map(generic_codeable_to_dict, codeable_col.values))