More code-orientied exploration of data, view for researchers.
First, list available studiest and patients
from enum import Enum
import pandas as pd
from jupyterhealth_client import Code, JupyterHealthClient
BLOOD_PRESSURE = Code.BLOOD_PRESSURE.value
pd.options.mode.chained_assignment = None
jh_client = JupyterHealthClient()
org_dict = {org['id']: org for org in jh_client.list_organizations()}
# children is not populated
for org in org_dict.values():
parent_id = org['partOf']
if parent_id is not None:
parent = org_dict[parent_id]
parent.setdefault("child_ids", []).append(org['id'])
def print_org(org, indent=''):
print(f"{indent}[{org['id']}] {org['name']}")
for org_id in org.get('child_ids', []):
print_org(org_dict[org_id], indent=" " * len(indent) + " ⮑")
for org_id in org_dict[0]['child_ids']:
print_org(org_dict[org_id])
print("All my studies:")
for study in jh_client.list_studies():
print(f" - [{study['id']}] {study['name']} org:{study['organization']['name']}")
# show all the patients with study data I have access to:
print("Patients with data I have access to:")
for patient in jh_client.list_patients():
consents = jh_client.get_patient_consents(patient['id'])
print(f"[{patient['id']}] {patient['nameFamily']}, {patient['nameGiven']}: {patient['telecomEmail']}")
for study in consents['studies']:
print(f" - [{study['id']}] {study['name']}")
for study in consents['studiesPendingConsent']:
print(f" - (not consented) [{study['id']}] {study['name']}")
if not consents['studies'] and not consents['studiesPendingConsent']:
print(" (no studies)")
Select study and patient¶
At this point, we can edit the code below to select the study and patient we are interested in. This is the same thing the widgets do in the dashboard.
# pick patient id, study id from above
study_id = 30013
patient_id = 40039
df = jh_client.list_observations_df(patient_id=patient_id, study_id=study_id)
df.head()
Reduce data to relevant subset for blood pressure
# reduce data
bp = df.loc[df.resource_type == BLOOD_PRESSURE, ['systolic_blood_pressure_value', 'diastolic_blood_pressure_value', 'effective_time_frame_date_time']]
bp = bp.astype({"systolic_blood_pressure_value": int, "diastolic_blood_pressure_value": int})
bp
Plot over time
bp.plot(x="effective_time_frame_date_time", y=["diastolic_blood_pressure_value", "systolic_blood_pressure_value"])
Compute goals, categories
from functools import partial
class Goal(Enum):
"""Enum for met/unmet
These strings will be used for the legend.
"""
met = "goal"
unmet = "over"
class Category(Enum):
normal = "normal"
elevated = "elevated"
hypertension = "hypertension"
def classify_bp(row):
"""Classify blood pressure"""
# https://www.heart.org/en/health-topics/high-blood-pressure/understanding-blood-pressure-readings
# note from : We can decide to have just normal, elevated and hypertension to begin with
if (
row.diastolic_blood_pressure_value < 80
and row.systolic_blood_pressure_value < 120
):
return Category.normal.value
elif (
row.diastolic_blood_pressure_value < 80
and 120 <= row.systolic_blood_pressure_value < 130
):
return Category.elevated.value
else:
return Category.hypertension.value
def bp_goal(patient_df, goal="140/90"):
"""True/False for blood pressure met goal"""
sys_goal, dia_goal = (int(s) for s in goal.split("/"))
if (patient_df.systolic_blood_pressure_value <= sys_goal) & (
patient_df.diastolic_blood_pressure_value <= dia_goal
):
return Goal.met.value
else:
return Goal.unmet.value
bp["category"] = bp.apply(classify_bp, axis=1)
goal = "110/70"
bp["goal"] = bp.apply(partial(bp_goal, goal="110/70"), axis=1)
bp
Compute fractions by category, goal
bp.category.value_counts(normalize=True)
bp.goal.value_counts(normalize=True)
Test out styling based on therapeutic goal
def bp_goal_style(row):
"""highlight rows exceeding bp goal"""
goal = Goal(row.goal)
if goal == Goal.unmet:
color = "#fdd"
else:
color = None
return [f"background-color:{color}" if color else None] * len(row)
bp[-50:].style.hide().hide(["category", "goal"], axis="columns").apply(bp_goal_style, axis=1)