Skip to content

Instantly share code, notes, and snippets.

@clarkevans
Created February 28, 2018 21:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save clarkevans/f5ca1382a4366b68f92f25cba7772eb6 to your computer and use it in GitHub Desktop.
Save clarkevans/f5ca1382a4366b68f92f25cba7772eb6 to your computer and use it in GitHub Desktop.
A Hypertensive Question
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# A Hypertensive Question"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This notebook walks though a particular hypertensive question against the sample IHM XML data provided on Nov 20th (with timestamp of June 7th):\n",
"\n",
"**Let's test the efficacy of hypertensive drugs. For those patients diagnosed with hypertension, monitor them over a period of 12 months. Find any encounter in the next 6 months that have an anti-hypertensive medication added or intensified. After the medication adjustment, but within 5 days span, was there a blood pressure drop of 5mm?**\n",
"\n",
"In this prototype, for each and every ``query_ihmi`` call, the entire data set is loaded and prepared -- this is extremely slow. In a later version, the combinator library will have it cache the output so it could be reused. Note that this XML file also had to be decoded from IHM concept UUIDs into SNOMED codes for this measure. Also, in this formulation we ignore encounters since they are unreliable in the test data set (unrealistic dates that are not contstrainted to the dates of the requests and observations)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"cd(\"src\")"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"WARNING: Method definition cse(RBT.Query) in module RBT at /home/cce/.julia/v0.5/RBT/src/cse.jl:3 overwritten in module Main at /home/cce/ihm-fhir-ri/src/querycombinators.jl:84.\n"
]
},
{
"data": {
"text/plain": [
"\"Data and source code loaded\""
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"include(\"querycombinators.jl\") # ignore redefinition of \"cse\" warning\n",
"include(\"load-ihmi.jl\") \n",
"include(\"interval.jl\")\n",
"include(\"load-valueset.jl\")\n",
"include(\"load-snomed.jl\")\n",
"\"Data and source code loaded\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**What years to we have synthetic data?**\n",
"\n",
"Since this IHM XML data source uses a \"dummy\" encounter with dates in 1900 and 1901 we need to filter those out. Then we group by the year to figure out what sort of data ranges we have."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"5-element composite vector of {{Int64?}, Int64}:\n",
" ((2011,),384) \n",
" ((2012,),4863)\n",
" ((2013,),5582)\n",
" ((2014,),5540)\n",
" ((2015,),5644)"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"DummyStartTime = ToDateTime(\"1950-01-01T05:00:00\")\n",
"\n",
"query_ihmi(\n",
" Patient >> Encounter\n",
" >> ThenFilter(RecordTime .> DummyStartTime)\n",
" >> ThenGroup(RecordTime >> AsYear)\n",
" >> ThenSelect(It, Count(Encounter))\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's define our measure period to be 2013."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"RBT.Combinator(RBT.#201)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"MeasurePeriod = MakeInterval(\"2013-01-01T00:00\", \"2013-12-31T23:59:59\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**How many patients have had an encounter during this period?**"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"846"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"query_ihmi(\n",
" Patient\n",
" >> ThenFilter(\n",
" Exists(\n",
" Encounter\n",
" >> ThenFilter(During(MeasurePeriod))))\n",
" >> ThenCount\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Let's define a ``PatientSample`` as hypertensive patients during the measure period.**"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"576"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"EssentialHypertension = ToConcept(59621000)\n",
"\n",
"PatientSample = (\n",
" Patient\n",
" >> ThenFilter(\n",
" Exists(\n",
" Encounter\n",
" >> Observation\n",
" >> ThenFilter(StartsBeforeEndOf(MeasurePeriod))\n",
" >> ThenFilter(\n",
" Observable >> IsA(EssentialHypertension))))\n",
")\n",
" \n",
"query_ihmi(\n",
" PatientSample\n",
" >> ThenCount\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The IHM defines a value-set of hypertensive drugs."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"1328"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"AntiHypertensive = ValueSet(\"Anti-hypertensive\")\n",
"\n",
"query_ihmi(\n",
" Count(AntiHypertensive)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**What anti-hypertensives is a test patient on?**\n",
"\n",
"Let's pick a particular test paint in this sample data set; and look at their substances."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6-element composite vector of {Concept, String}:\n",
" (Concept(376508004),\"Product containing hydrochlorothiazide 50 mg/1 each oral tablet\")\n",
" (Concept(318860005),\"Product containing lisinopril 20 mg/1 each oral tablet\") \n",
" (Concept(318858008),\"Product containing lisinopril 5 mg/1 each oral tablet\") \n",
" (Concept(318885001),\"Product containing quinapril 5 mg/1 each oral tablet\") \n",
" (Concept(318434003),\"Product containing atenolol 25 mg/1 each oral tablet\") \n",
" (Concept(318887009),\"Product containing quinapril 20 mg/1 each oral tablet\") "
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"TestPatient = (\n",
" Patient\n",
" >> ThenFilter(Handle .== \"0001487\")\n",
")\n",
"\n",
"query_ihmi(\n",
" TestPatient\n",
" >> Encounter\n",
" >> Request\n",
" >> ThenFilter(\n",
" AnyOf(RequestedAction .== AntiHypertensive))\n",
" >> RequestedAction\n",
" >> ThenUnique\n",
" >> ThenSelect(It, ConceptDescription)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**How could we get active ingredient and dosage?**\n",
"\n",
"From the SNOMED-CT terminology service, the ``PresentationStrength`` and ``ActiveIngredient`` is sometimes available. Unfortunately, it isn't particularly reliable since the strength is stored in the description field (untyped string) for select numbers; and some concepts don't have this information. Further, some drugs have more than one active ingredient and one might assume (but it's not possible to know for sure) that additional drug strengths provided correspond to each ingredient."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(\"Product containing hydrochlorothiazide 50 mg/1 each oral tablet\",\"50\",\"Hydrochlorothiazide\")"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Hydrochlorothiazide_50mg = ToConcept(\"SNOMED-CT\", 376508004)\n",
"\n",
"HasActiveIngredient = ConceptRelationship(127489000) \n",
"HasPresentationStrength = ConceptRelationship(732944001)\n",
"\n",
"query_ihmi(\n",
" Hydrochlorothiazide_50mg\n",
" >> ThenSelect(\n",
" ConceptDescription,\n",
" HasPresentationStrength \n",
" >> ThenExpectOne >> ConceptDescription,\n",
" HasActiveIngredient \n",
" >> ThenExpectOne >> ConceptDescription)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Of the substances, 41 have two active ingredients, 91 have one active ingredient, and the ingredients for 57 are not listed in the terminology server."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(0,41,87,55)"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"HypertensiveSample = (\n",
" PatientSample\n",
" >> Encounter\n",
" >> Request\n",
" >> ThenFilter(\n",
" AnyOf(RequestedAction .== AntiHypertensive))\n",
" >> RequestedAction\n",
" >> ThenUnique \n",
")\n",
"\n",
"query_ihmi(\n",
" ThenSelect(\n",
" Count(HypertensiveSample >> ThenFilter(\n",
" Count(HasPresentationStrength) .> 2 )),\n",
" Count(HypertensiveSample >> ThenFilter(\n",
" Count(HasPresentationStrength) .== 2 )),\n",
" Count(HypertensiveSample >> ThenFilter(\n",
" Count(HasPresentationStrength) .== 1 )),\n",
" Count(HypertensiveSample >> ThenFilter(\n",
" Count(HasPresentationStrength) .< 1 )))\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Define ``Medication`` as administered time, active ingrediant, and dosage.**\n",
"\n",
"For the purposes of this demonstration, let's define a ``Person >> Medication(aValueSet)`` as taking a ``Person`` context and ``aValueSet`` as an argument and returning medication requests that have some corrresponding data in the terminology service. Note that this is bad practice since it ignores drugs that arn't in the terminology service. That says, it does expand dual-substance each requests into 2 distinct medication rows."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"DecodeMedication (generic function with 1 method)"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# There is a bit of magic hidden, a \"MakeMedication\"\n",
"# and a MedicationDataType that works around the \n",
"# current query prototype limitations.\n",
"\n",
"DecodeMedication(aValueSet) = (\n",
" Encounter\n",
" >> Request\n",
" >> ThenFilter(\n",
" AnyOf(RequestedAction .== aValueSet))\n",
" >> Merge(\n",
" MakeMedication(\n",
" Initiation,\n",
" RequestedAction\n",
" >> HasActiveIngredient\n",
" >> ThenTake(1),\n",
" RequestedAction\n",
" >> HasPresentationStrength\n",
" >> ConceptDescription\n",
" >> ThenTake(1)\n",
" ),\n",
" MakeMedication(\n",
" Initiation,\n",
" RequestedAction\n",
" >> HasActiveIngredient\n",
" >> ThenSkip(1) >> ThenTake(1),\n",
" RequestedAction\n",
" >> HasPresentationStrength\n",
" >> ConceptDescription\n",
" >> ThenSkip(1) >> ThenTake(1)\n",
" ))\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**What are the 5 most commonly requested prescriptions for our hypertensive sample?**"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"5-element composite vector of {Int64, String, Float64}:\n",
" (1936,\"Hydrochlorothiazide\",18.2593) \n",
" (1586,\"Amlodipine besilate\",7.46217) \n",
" (1247,\"Lisinopril\",17.8869) \n",
" (780,\"Benazepril hydrochloride\",22.7308)\n",
" (481,\"Valsartan\",242.994) "
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"query_ihmi(\n",
" PatientSample\n",
" >> DecodeMedication(AntiHypertensive)\n",
" >> ThenGroup(Substance)\n",
" >> ThenSort(Count(Complement) >> Desc)\n",
" >> ThenSelect(\n",
" Count(Complement), \n",
" Substance >> ConceptDescription,\n",
" MeanOf(Complement >> Dosage))\n",
" >> ThenTake(5)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**For a particular test patient what medication are they on during the measure period?**"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"10-element composite vector of {DateTime, String, Float64}:\n",
" (2013-04-23T04:00:00,\"Hydrochlorothiazide\",50.0) \n",
" (2013-04-23T04:00:00,\"Hydrochlorothiazide\",50.0) \n",
" (2013-04-23T04:00:00,\"Atenolol\",25.0) \n",
" (2013-07-01T04:00:00,\"Hydrochlorothiazide\",50.0) \n",
" (2013-07-01T04:00:00,\"Quinapril hydrochloride\",5.0) \n",
" (2013-07-01T04:00:00,\"Quinapril hydrochloride\",20.0)\n",
" (2013-12-30T05:00:00,\"Hydrochlorothiazide\",50.0) \n",
" (2013-12-30T05:00:00,\"Lisinopril\",5.0) \n",
" (2013-12-30T05:00:00,\"Atenolol\",25.0) \n",
" (2013-12-30T05:00:00,\"Lisinopril\",20.0) "
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"query_ihmi(\n",
" TestPatient\n",
" >> DecodeMedication(AntiHypertensive)\n",
" >> ThenFilter(StartsDuring(MeasurePeriod))\n",
" >> ThenSort(Initiation)\n",
" >> ThenSelect(\n",
" Initiation,\n",
" Substance >> ConceptDescription,\n",
" Dosage)\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**How do we know when a drug was started or intensified?**\n",
"\n",
"Let's define an intensification correlation between 2 time points. We define ``MedicationIncrease(aValueSet)`` that returns a set of medication requests for a patient where new medication was added or an intensification has occurred. Currently, it doesn't specify what happens when a drug is dropped.\n",
"\n",
"The implementation below is significantly less than ideal since our prototype combinator language only supports uni-directional tree structure; in a version later this spring, we will support graph structures (we did this in a much earlier prototype) so that one could traverse from each request to the current patient and then back down to all peers w/o having to explicitly manage context. In this version we must expressly manage context."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6-element composite vector of {DateTime, String, Float64?, Float64}:\n",
" (2013-04-23T04:00:00,\"Hydrochlorothiazide\",#NULL,50.0) \n",
" (2013-04-23T04:00:00,\"Atenolol\",#NULL,25.0) \n",
" (2013-07-01T04:00:00,\"Quinapril hydrochloride\",#NULL,5.0)\n",
" (2013-07-01T04:00:00,\"Quinapril hydrochloride\",5.0,20.0) \n",
" (2013-12-30T05:00:00,\"Lisinopril\",#NULL,5.0) \n",
" (2013-12-30T05:00:00,\"Lisinopril\",5.0,20.0) "
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Current = Field(:current)\n",
"Previous = Field(:previous)\n",
"\n",
"MedicationIncrease(aValueSet) = (\n",
" Frame(\n",
" DecodeMedication(aValueSet)\n",
" >> ThenSort(Initiation)\n",
" >> ThenSelect(\n",
" :current => It,\n",
" :previous => FindPrevious(Substance)))\n",
" >> ThenFilter(\n",
" (~Exists(Previous >> Dosage)) | \n",
" AnyOf(\n",
" (Current >> Dosage) .>\n",
" (Previous >> Dosage)))\n",
")\n",
"\n",
"query_ihmi(\n",
" TestPatient\n",
" >> MedicationIncrease(AntiHypertensive)\n",
" >> ThenSelect(\n",
" Current >> Initiation,\n",
" (Current >> Substance\n",
" >> ConceptDescription),\n",
" (Previous >> Dosage),\n",
" (Current >> Dosage))\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**How can we detect a drop in blood pressure?**\n",
"\n",
"In a similar way, we could create combinator to track when an observable's value has dropped from the previous instance of the same observable."
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"2-element composite vector of {DateTime?, Float64?, Float64?}:\n",
" (2013-04-27T04:00:00,130.0,110.0)\n",
" (2013-08-20T04:00:00,130.0,110.0)"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"Diastolic = ToConcept(271650006)\n",
"Systolic = ToConcept(271649006)\n",
"\n",
"ObservableDrop(WhichOne, Amount) = (\n",
" Frame(\n",
" Encounter\n",
" >> Observation\n",
" >> ThenSort(Initiation)\n",
" >> ThenFilter(Observable .== WhichOne)\n",
" >> ThenSelect(\n",
" :period => (It >> Period),\n",
" :current => It,\n",
" :previous => \n",
" FindPrevious(MatchAll)))\n",
" >> ThenFilter(\n",
" AnyOf(\n",
" (Current >> (Value >> AsFloat))\n",
" .< \n",
" ((Previous >> (Value >> AsFloat)) \n",
" .- Const(Amount))))\n",
")\n",
"\n",
"query_ihmi(\n",
" TestPatient\n",
" >> ObservableDrop(Systolic, 5)\n",
" >> ThenFilter(StartsDuring(MeasurePeriod))\n",
" >> ThenSelect(\n",
" Initiation,\n",
" Previous >> Value >> AsFloat,\n",
" Current >> Value >> AsFloat)\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"2-element composite vector of {String, DateTime, String, Float64}:\n",
" (\"0001487\",2013-04-23T04:00:00,\"Hydrochlorothiazide\",50.0)\n",
" (\"0001487\",2013-04-23T04:00:00,\"Atenolol\",25.0) "
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"FiveDaysAfter(aStart) = ToInterval( \n",
" # don't measure on same day as intensification\n",
" (aStart >> Initiation >> ThenExpectOne) .+\n",
" Const(Dates.Day(1)) .- Const(Dates.Second(1))\n",
" ,\n",
" # look at 5 days after this time...\n",
" (aStart >> Initiation >> ThenExpectOne) .+ \n",
" Const(Dates.Day(6)) .- Const(Dates.Second(1)))\n",
"\n",
"IntenseSuccess = (\n",
" WithThisPatient(\n",
" MedicationIncrease(AntiHypertensive)\n",
" >> Current\n",
" >> ThenFilter(StartsDuring(MeasurePeriod))\n",
" >> WithThisMedication(\n",
" ThenFilter(\n",
" AnyOf(\n",
" ThisPatient \n",
" >> ObservableDrop(Systolic, 5) \n",
" >> During(\n",
" FiveDaysAfter(ThisMedication)))\n",
" & AnyOf(\n",
" ThisPatient \n",
" >> ObservableDrop(Diastolic, 5)\n",
" >> During(\n",
" FiveDaysAfter(ThisMedication))))\n",
" >> ThenSelect(\n",
" :patient => ThisPatient,\n",
" :medication => ThisMedication,\n",
" ))))\n",
"\n",
"query_ihmi(\n",
" PatientSample\n",
" >> IntenseSuccess\n",
" >> ThenSelect(\n",
" Patient >> Handle,\n",
" Medication >> Initiation,\n",
" Medication >> Substance >> ConceptDescription,\n",
" Medication >> Dosage))"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Julia 0.5.2",
"language": "julia",
"name": "julia-0.5"
},
"language_info": {
"file_extension": ".jl",
"mimetype": "application/julia",
"name": "julia",
"version": "0.5.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment