Attributes & Values#

One of the main strengths of the query engine is the possibility of diving deeper into the MedRecords’ attributes and values. We can access them by using different return types - in the Query Engine Introduction, we mainly used indices as the return type for each query.

1.  Inspecting Attributes Names#

Each node can have a variety of different attributes, each one of the holding an assigned MedRecordValue. We can look at the attributes of each node by using the method attributes():

def query_node_attribute_names(node: NodeOperand) -> NodeAttributesTreeOperand:
    node.in_group("patient")

    return node.attributes()


medrecord.query_nodes(query_node_attribute_names)
{'pat_3': ['gender', 'age'], 'pat_2': ['age', 'gender'], 'pat_4': ['gender', 'age'], 'pat_1': ['age', 'gender'], 'pat_5': ['age', 'gender']}
Methods used in the snippet
  • in_group() : Query nodes that belong to that group.

  • attributes() : Query the attribute names of each node.

  • query_nodes() : Retrieves information on the nodes from the MedRecord given the query.

You can also do operations on them, like checking how many attributes each node has, thanks to the count() method:

def query_node_attributes_count(
    node: NodeOperand,
) -> NodeMultipleAttributesWithIndexOperand:
    node.in_group("patient")
    attributes = node.attributes()

    return attributes.count()


medrecord.query_nodes(query_node_attributes_count)
{'pat_2': 2, 'pat_1': 2, 'pat_5': 2, 'pat_4': 2, 'pat_3': 2}
Methods used in the snippet
  • in_group() : Query nodes that belong to that group.

  • attributes() : Query the attribute names of each node.

  • count() : Query how many attributes each node has.

  • query_nodes() : Retrieves information on the nodes from the MedRecord given the query.

2.  Inspecting Attributes Values#

As said before we can look for specific values within our MedRecord, using the method attribute(). For instance, we can search for the maximum age in our patients, and we will get the node ID of the patient with the highest age, and what that age is.

def query_node_max_age(
    node: NodeOperand,
) -> NodeSingleValueWithIndexOperand:
    age = node.attribute("age")

    return age.max()


medrecord.query_nodes(query_node_max_age)
('pat_3', 96)
Methods used in the snippet

3.  Advanced Query Operations#

In case, for instance, that you do not know whether there are different ways to assign the gender attribute across the MedRecord (with leading/trailing whitespaces or formatted in lower/uppercase), you can modify the value of the attributes of a node/edge inside the query.

Note

It is important to note that modifying these values does not change the actual value of the attributes within the MedRecord: it just changes the value of those variables in the query.

You can also perform mathematical calculations like mean(), median() or min() and assign them to a variable. Also, you can keep manipulating the operand, like in the following example, where we are subtracting 5 years from the mean_age to query on that value.

In the result, we can see the only patient whose age is less than five years under the mean age and what that value is.

def query_node_male_patient_under_mean(
    node: NodeOperand,
) -> tuple[NodeMultipleValuesWithIndexOperand, NodeSingleValueWithoutIndexOperand]:
    node.in_group("patient")
    node.index().contains("pat")

    gender = node.attribute("gender")
    gender.lowercase()  # Converts the string to lowercase
    gender.trim()  # Removes leading and trailing whitespaces
    gender.equal_to("m")

    node.has_attribute("age")
    mean_age = node.attribute("age").mean()
    mean_age.subtract(5)  # Subtract 5 from the mean age
    node.attribute("age").less_than(mean_age)

    return node.attribute("age"), mean_age


medrecord.query_nodes(query_node_male_patient_under_mean)
[{'pat_4': 19}, 27.666666666666664]
Methods used in the snippet

Note

Query methods used for changing the operands cannot be assigned to variables for further querying, since their return type is None. The following code snippet shows an example, where the variable gender_lowercase evaluates to None. An AttributeError is thrown as a consequence when trying to further query with the equal_to querying method:

def query_operand_assigned(node: NodeOperand) -> NodeIndicesOperand:
    gender_lowercase = node.attribute(
        "gender"
    ).lowercase()  # Assigning the querying method to a variable
    gender_lowercase.equal_to("m")

    return node.index()


medrecord.query_nodes(query_operand_assigned)

Error:

PanicException: Call must succeed: PyErr { type: , value: AttributeError("'NoneType' object has no attribute 'equal_to'"), traceback: Some() }

The concatenation of querying methods also throws an error:

def query_operands_concatenated(node: NodeOperand) -> NodeIndicesOperand:
    gender = node.attribute("gender")
    gender.lowercase().trim()  # Concatenating the querying methods
    gender.equal_to("m")

    return node.index()


medrecord.query_nodes(query_operands_concatenated)

Error:

PanicException: Call must succeed: PyErr { type: , value: AttributeError("'NoneType' object has no attribute 'trim'"), traceback: Some() }

Correct implementation:

def query_correct_implementation(node: NodeOperand) -> NodeIndicesOperand:
    gender = node.attribute("gender")
    gender.lowercase()
    gender.trim()
    gender.equal_to("m")

    return node.index()


medrecord.query_nodes(query_correct_implementation)
['pat_4', 'pat_5', 'pat_1']

4.  Full example Code#

The full code examples for this chapter can be found here:

from medmodels import MedRecord
from medmodels.medrecord.querying import (
    NodeAttributesTreeOperand,
    NodeIndicesOperand,
    NodeMultipleAttributesWithIndexOperand,
    NodeMultipleValuesWithIndexOperand,
    NodeOperand,
    NodeSingleValueWithIndexOperand,
    NodeSingleValueWithoutIndexOperand,
)

medrecord = MedRecord().from_simple_example_dataset()


def query_node_attribute_names(node: NodeOperand) -> NodeAttributesTreeOperand:
    node.in_group("patient")

    return node.attributes()


medrecord.query_nodes(query_node_attribute_names)


def query_node_attributes_count(
    node: NodeOperand,
) -> NodeMultipleAttributesWithIndexOperand:
    node.in_group("patient")
    attributes = node.attributes()

    return attributes.count()


medrecord.query_nodes(query_node_attributes_count)


def query_node_max_age(
    node: NodeOperand,
) -> NodeSingleValueWithIndexOperand:
    age = node.attribute("age")

    return age.max()


medrecord.query_nodes(query_node_max_age)


# Advanced node query
def query_node_male_patient_under_mean(
    node: NodeOperand,
) -> tuple[NodeMultipleValuesWithIndexOperand, NodeSingleValueWithoutIndexOperand]:
    node.in_group("patient")
    node.index().contains("pat")

    gender = node.attribute("gender")
    gender.lowercase()  # Converts the string to lowercase
    gender.trim()  # Removes leading and trailing whitespaces
    gender.equal_to("m")

    node.has_attribute("age")
    mean_age = node.attribute("age").mean()
    mean_age.subtract(5)  # Subtract 5 from the mean age
    node.attribute("age").less_than(mean_age)

    return node.attribute("age"), mean_age


medrecord.query_nodes(query_node_male_patient_under_mean)


# Incorrect implementation because the querying methods are assigned to a variable
def query_operand_assigned(node: NodeOperand) -> NodeIndicesOperand:
    gender_lowercase = node.attribute(
        "gender"
    ).lowercase()  # Assigning the querying method to a variable
    gender_lowercase.equal_to("m")

    return node.index()


medrecord.query_nodes(query_operand_assigned)


# Incorrect implementation because the querying methods are concatenated
def query_operands_concatenated(node: NodeOperand) -> NodeIndicesOperand:
    gender = node.attribute("gender")
    gender.lowercase().trim()  # Concatenating the querying methods
    gender.equal_to("m")

    return node.index()


medrecord.query_nodes(query_operands_concatenated)


# Correct implementation
def query_correct_implementation(node: NodeOperand) -> NodeIndicesOperand:
    gender = node.attribute("gender")
    gender.lowercase()
    gender.trim()
    gender.equal_to("m")

    return node.index()


medrecord.query_nodes(query_correct_implementation)