Skip to article frontmatterSkip to article content

Researcher view for exploring data

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)