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
attribute()
: Returns aNodeMultipleValuesWithIndexOperand()
to query on the values of the nodes for that attribute.max()
: Returns aNodeSingleValueWithIndexOperand()
holding the node index and value pair holding the maximum value for that attribute.query_nodes()
: Retrieves information on the nodes from the MedRecord given the query.
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
in_group()
: Query nodes that belong to that group.index()
: Returns aNodeIndicesOperand
representing the indices of the nodes queried.contains()
: Query node indices containing that argument.attribute()
: Returns aNodeMultipleValuesWithIndexOperand
to query on the values of the nodes for that attribute.lowercase()
: Converts the values that are strings to lowercase.trim()
: Removes leading and trailing whitespacing from the values.equal_to()
: Query values equal to that value.has_attribute()
: Query nodes that have that attribute.mean()
: Returns aNodeSingleValueWithoutIndexOperand
containing the mean of those values.subtract()
: Subtract the argument from the single value operand.less_than()
: Query values that are less than that value.query_nodes()
: Retrieves information on the nodes from the MedRecord given the query.
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:
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:
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)