Data Mining

Users of rxkinetics.net have been contributing to a large data set for the past three years. The web app has been saving anonymous data from serum level analysis during this time. See this blog post: The BBVM project part II.

It is important to state up front that there is no quality control over this anonymous data. Some of it could have been entered incorrectly. Also there is no way to know how much is real patient data versus someone experimenting with the web app.

The other problem with this type of data collection is that many don’t “run the numbers” when the results are within target range. Therefore, outliers tend to become more prominent in the data set.

With those caveats in mind, let’s examine the data.

All BMI < 19 BMI 19-30 BMI > 30
Total 2937 180 1808 939
Male 1686 89 1074 523
Female 1251 91 734 416

Let’s delve into the statistics from the two largest groups.

BMI 19-30

Descriptive stats

Age BMI CrCl Vd L/kg TBW VD L/kg ABW
Min 15 19.04 8.0 0.38 0.38
Max 100 30.04 120 0.92 1.21
Mean 60.6 24.8 77.3 0.67 0.72
Median 63 24.6 72.0 0.69 0.73
Mode 65 23.2 120 0.70 0.70
S.D. 18.48 2.96 30.97 0.09 0.11

The raw Vd histogram follows a normal distribution (as a large sample should).
Vd_Raw

But the histogram for Vd L/kg total body weight (TBW) has a noticeable negative (left) skew.
Vd_TBW

Because Vancomycin does not distribute well into adipose tissue let’s look at Vd L/kg of adjusted body weight (ABW), where ABW = LBW + 40% of the difference between TBW and LBW (i.e., the weight we used to dose Vancomycin “back in the old days”).
Vd_Adj_BW

The histogram of Vd L/kg ABW is noticeably more normal than TBW (but not ideal).

Regression of CL vs CrCl results in a higher r^2 value when we use Normalized CrCl vs LBW-based CrCl, 0.528 vs 0.524. (CrCl Methods explained)
Clearance_vs_LBW_CrCl

Clearance_vs_NormCrCl

BMI > 30

Descriptive stats

Age BMI CrCl Vd L/kg TBW VD L/kg ABW
Min 17 30.1 10 0.37 0.45
Max 95 77.7 120 0.91 1.40
Mean 59.2 37.9 73.6 0.67 0.89
Median 60 35.4 71 0.69 0.89
Mode 57 32.4 120 0.70 0.59
S.D. 15.59 8.08 30.41 0.11 0.16

As with the previous group, in obese patients the histogram for Vd L/kg of ABW has a more normal distribution than TBW.

Large_Vd_Raw

Large_Vd_TBW

Large_Vd_ABW

The r^2 improvement is even greater in obese patients when using normalized CrCl for the Kel regression, 0.533 vs 0.412.

Large_Clearance_vs_LBW_CrCl

Large_Clearance_vs_NormCrCl

Summary
Again, since the data is not verified this analysis is debatable. However it does pose a couple of questions:

  • Should we use adjusted body weight when modeling Vancomycin volume of distribution?
  • Should we use normalized creatinine clearance when modeling Vancomycin elimination rate?

Complex non-steady-state analysis

This is a feature that I have wanted to add to my PK programs for quite sometime. In the real world doses don’t get hung on time and dosages get changed before levels are drawn.

Complex is the keyword. This was incredibly complex to code, both the logic of data entry and analysis. It has taken weeks of intense work to complete. Currently in the testing phase, I hope to bring it to production soon.
Complex_nonSS

When you select “Complex non-steady-state analysis” the program displays two grids, one for entering doses and the other for serum levels.
Complex_nonSS_data
You may enter up to five doses of various amounts, given at different rates and at different times. You may enter up to two serum levels. Of course there are limitations (which the data entry routines catch). For example, you cannot enter a date/time for a level that conflicts with a dose date/time.

If the first serum level is drawn before the first dose it will be considered a baseline level. This is also something that I have wanted to add for years. It will be most useful when performing a follow-up pk consult on a long-term patient.

Another feature added from my to-do list is the ability to select an infusion rate for your recommended dose.
complex_recommend

You will still see Bayesian fails. This new data entry methodology will not prevent them from happening. That is the nature of Bayesian and the highly variable nature of Vancomycin kinetics.

Even people who have used my programs for years fail to comprehend what Bayesian analysis involves. The first thing to realize is that Bayesian analysis begins with your population model. It then attempts to fit your measured levels to incremental variations of your population model. In classic Bayesian, if the data cannot be fitted to the model within reasonable statistical limits, then the data is thrown out. I do not believe that rule should be applied to clinical pharmacokinetics. So I leave it up to you to decide how to proceed by showing the “Bayesian analysis failed” dialog.
Complex_fail
Essentially this dialog is telling you that your measured level differs too much from the model predicted level. You can find out what level the population model predicted by visiting the prospective tab. If you measured 6 but the model predicted 24, then never the twain shall meet. Either (1) you have chosen the wrong model for your patient, or (2) the measured level is wrong. That is your decision, not one to be made by a computer program, period.

KinPlot Update

Pulled an all nighter to update both the Windows and the Web versions of KinPlot. This is a simple tool for learning pharmacokinetic concepts by depicting the effects that various pk parameters have on serum levels, leading to a better understanding of drug regimen design.

I had a request for IV push modeling from a Cornell professor. The simplest solution was to utilize a 6 minute infusion to approximate IV push administration (the math is easier with decimals, i.e., 6 min = 0.1 hr).

Since they use Macs I needed to update the Web App as well.
KinPlot_web

Web version here.

The Windows version adds a few more bells and whistles: the ability to model oral pk and to save/load graphs.
kinplot_win_23

Windows version here.

Enjoy.

APK 3.1.20

I’ve spent much of my ‘free’ time during the last month been pouring over error reports received during the past year. Unfortunately most of the reports are vague and completely untraceable as to their cause. Focusing on the most commonly reported errors I’ve tried to deduce their causes, but I’m likely to be completely off base.

TfrmMain, TfrmPrint: EConvertError

‘Not a valid floating point value’. All of these error reports came from version .18. This was fixed in version .19 by changing all calls to StrToFloat to TryStrToFloat. Come on folks, updates are free, please keep your copies up-to-date.

TfrmMain: Range check error
There were several Range errors which I believe originate in the Topaz database engine (no trace available). I re-compiled Topaz with Range checking turned off, hopefully that will tone down the frequency of these error reports.

TfrmPrint: Multiple errors

Multiple errors were reported originating in the TfrmPrint class. Procedures flagged include: SaveConsult, SaveLevels, SavePatient, ValidPatData, GraphPrep, and ReDrawSerumLevelPlot. Hopefully recompiling Topaz will fix most of the Save errors. But, to add another layer of safety, I’ve added try..try..except..finally blocks to each procedure and changed the SerumLevel array from fixed to dynamic.

TfrmMonitor: No default printer selected
This was reported twice. I cannot imagine how or why someone would not have a default printer selected in Windows. Regardless, I’ve added this tiny bit of code to hopefully catch that condition (unfortunately I have no way of testing this):
if (Printer.PrinterIndex > 0)then
begin
// Print the consult
end;

TfrmPKPD: Division by Zero
The function AUC was flagged in one report. I cannot imagine how this procedure gets called without valid parameters, but, once again, I added try..try..except..finally block that will prevent the program from crashing.

TfrmLevels; EConvertError
‘is not a valid integer value.’ Sounding like a broken record, I simply cannot imagine how this procedure gets called without valid parameters, regardless I changed StrToInt to TryStrToInt in hopes of fixing this impossible error.

TfrmMain: Pack date invalid
This error came up twice, with no traceable cause. Because the user is able to change date settings in Windows at will, I assumed an incompatible date format is causing the issue. I added some code to force the short date format into the standard USA: ‘MM/dd/yyyy’. Originally I had declared the OutputBuffer within the var block of the function, but that was causing a memory leak on exit, even after adding a StrDispose in the cleanup (finally) block:
procedure TfrmMain.SavePackDate();
var
APKini : TIniFile;
strDateFormat : string;
strTemp : string;
OutputBuffer: PChar;
SelectedLCID: LCID;
begin
try
// Determine local short date format
OutputBuffer := StrAlloc(255);
SelectedLCID := GetUserDefaultLCID;
GetLocaleInfo(SelectedLCID, LOCALE_SSHORTDATE, OutputBuffer, 255);
strTemp := string(OutputBuffer);
// if first letter is d then set to 'dd/MM/yyyy' else 'MM/dd/yyyy'
if Copy(strTemp, 1, 1) = 'd' then
strDateFormat := 'dd/MM/yyyy'
else
strDateFormat := 'MM/dd/yyyy';
try
APKini := TIniFile.Create(PathName + 'apk.ini');
try
APKini.WriteString('dbMaint', 'Pack date', FormatDateTime(strDateFormat, Now));
finally
APKini.Free;
end;
except
on E:Exception do begin
MessageDlg('Error saving pack date',
E.Message,
mtError, [mbOK], 690, dckActiveForm);
end;
end;
finally
// Free up the string list memory
StrDispose(OutputBuffer);
end;
end;

After I moved the code into an inline function there were no more memory leaks:
procedure TfrmMain.SavePackDate();
var
APKini : TIniFile;
strDateFormat : string;
strTemp : string;
function ShortDateFormat: string;
var
OutputBuffer: array[0..255] of Char;
SelectedLCID: LCID;
begin
SelectedLCID := GetUserDefaultLCID;
GetLocaleInfo(SelectedLCID, LOCALE_SSHORTDATE, OutputBuffer, 255);
Result := OutputBuffer;
end;
begin
try
// Determine local short date format
strTemp := ShortDateFormat;
// if first letter is d then set to 'dd/MM/yyyy' else 'MM/dd/yyyy'
if Copy(strTemp, 1, 1) = 'd' then
strDateFormat := 'dd/MM/yyyy'
else
strDateFormat := 'MM/dd/yyyy';
APKini := TIniFile.Create(PathName + 'apk.ini');
try
APKini.WriteString('dbMaint', 'Pack date', FormatDateTime(strDateFormat, Now));
finally
APKini.Free;
end;
except
on E:Exception do begin
MessageDlg('Error saving pack date',
E.Message,
mtError, [mbOK], 690, dckActiveForm);
end;
end;
end;

Population analysis improvements
I did find some time to make several improvements in this version of APK. The main focus of improvements this go round is the population analysis function. Probably the biggest change is removal of the 500 record limit, it now uses dynamic arrays so the number of records that may be analyzed is virtually unlimited. I added text to display the current model for comparison to the population analysis and a regression line of the current Kel or CL calculation.

apk_analysis_report

APK Population Analysis Report

Finally, I added a median Vd calculation, which is surprisingly complex:

function MedianVd: double;
var
n,i,middle : integer;
TempDouble : double;
TempArray : array of Double;
begin
// Set Length of TempArray
SetLength(TempArray,TotalPatients);
// Copy first column of RegressTable array
for i := 0 to TotalForArray do begin
TempArray[i] := RegressTable[i,0];
end;
// use truncated selection sort to find median
middle := (TotalForArray+1) div 2;
for i := 0 to middle do begin
for n := 1 to TotalForArray-i do begin
if TempArray[n] > TempArray[n-1] then
begin
TempDouble := TempArray[n];
TempArray[n] := TempArray[n-1];
TempArray[n-1] := TempDouble
end
end
end;
// find median
try
if odd(high(TempArray)) then
// when high(TempArray) is odd, there are an even number of elements in array.
// define median as average of two middle values.
result := (TempArray[middle] + TempArray[middle-1]) / 2
else
// when high(TempArray) is even, there are an odd number of elements in array.
// median is the middle value.
result := TempArray[middle];
finally
// Free memory used for TempArray
SetLength(TempArray,0);
end;
end;

As soon as I finish testing I will release this version into the wild. Enjoy.

And here’s my current favorite song (based on a true story), “Who’s the judge to decide how much this world should punish”:

FreeKin update

It’s a FreeKin update yo’.

I can’t believe it’s been 12 years since I wrote this FreeKin modeler program. It was originally written in Delphi 5, which I no longer have installed on my PC. However, I was pleasantly surprised when Delphi XE2 imported it without a hitch.

I made a few FreeKin tweaks here and there:

  • Increased the size of the fonts and the dialogs.
  • Added a save to xls function.
  • Improved the report function.
  • Wrote a new help file (RTFM).
  • Miscellaneous improvements to the interface flow.

I doubt if anyone else has ever used this FreeKin modeler program, but I love it. Not just for the double entendre name, but also for the simplicity and no nonsense practicality.

Enjoy FreeKin, I do!

Main FreeKin Screen

Main FreeKin Screen

What’s old is new again

There was an excellent session at the Midyear entitled “Infectious Diseases Update: Using Guidelines to Optimize Treatment”. Dr. Michael Ryback spoke about the “Implications of the Vancomycin Consensus Guidelines”. He pointed out the shortcomings of the 2009 guidelines, specifically those patient populations who were omitted from the guidelines, obese patients being the largest omission (pun intended).

Dr. Ryback is on the panel currently revising these guidelines and he spoke about some of the changes the panel are considering. Most interesting to me was the (possible) recommendation to draw two post-dose serum levels following the loading dose.
first_dose_levels

To which I say a resounding yes. Considering the large interpatient variability in Vancomycin pk, why “guess” what a patient’s dosing requirements might be? Why wait 2-3 days to find out if your recommended dose is therapeutic or toxic? And then fiddle around for days with dose changes and more levels. With two levels following the loading you can nail down the patient’s vancomycin pk right off the bat. Home run.

Usually when I mention this as a solution to a younger pharmacist, I either get that “you’re crazy” look or a blank stare. Apparently these things have to come down from a mountain, carved into stone tablets. Or, at the very least, from someone with a half dozen initials behind their name. Sheep go to heaven, while goats go to hell (or at least exiled to the graveyard shift).

Regardless, this type of pk analysis is actually the original Sawchuck and Zaske method for gentamicin dosing, from 1977. All but forgotten except for a few devotees.

Kinetic model for gentamicin dosing with the use of individual patient parameters.

The “First dose 2- or 3-point” method of serum level analysis has been an option in all of my pk programs from the very beginning, some 30 years ago. Here’s a screen shot from the latest AbPK for Windows:
abpk_first_dose

I certainly hope this recommendation makes it to the new Vancomycin consensus document. Or, at least, it opens the eyes of some of the braver sheep.

APK update

Sometimes it’s good to have a day off in the middle of the work week. I finished up something that I’ve had on my to-do list for years: a distribution plot for population analysis of Vol dist. The plot compares the 3-sigma normal distribution curve (green line) vs actual data points (red bars). Although it’s helpful to have descriptive statistics, nothing beats a picture:
Vd density plot

The population analysis report had to be re-arranged to fit the new plot on the page . I believe it is an easier read now. Here’s a comparison of new vs old:
APK_pop_analysis_new
APK_pop_analysis_old

Other improvements to the population analysis tool include:

  • BMI analysis option
  • CL vs Kel regression option
  • Automatically create/save graphs

While testing all these changes with real-world data I noticed a handful of Vd values in the consult table that were Zero. Further investigation revealed that these were *huge* patients with Vd’s > 100 liters. When I originally set up the APK database format over twenty years ago I did not forsee the U.S. obesity epidemic. Nowadays it is not unusual for us to see patients who are 300, 400, 500+ pounds, a sad commentary on the state of our country’s health. Also, I didn’t realize if the database engine encounters a number exceeding the defined field, it just skips it! It doesn’t flag an error, it just puts in a zero… WTF? Anyway, this update includes a change to the consults table, increasing the width of the Vd field from 4 to 5. This will accommodate patients up to 700 kg. If that limit is exceeded, please stop this planet and let me off.

Enjoy.

APK update

APK for Windows version 3.5.16 is available. Highlights include:

  • Added a point prediction tool.
  • Improved the ‘Outlier’ message dialog.
  • Added a goodness-of-fit indicator to the Bayesian results dialog.
  • Improved the ‘Non-steady-state’ message dialog.
  • Improved the serum level graph print-out.
  • Updated the help file.

Point prediction tool
Provides serum level prediction at any point within the dosing regimen. I find this useful for timing off-schedule doses and for evaluating non-steady-state levels.
apk_predict

Improved outlier message
Added detail to the Bayesian outlier message. Here is the old message:

And here is the improved message:

Goodness-of-fit indicator
A goodness-of-fit indicator was added to the Bayesian results dialog. This parameter is used to evaluate the reliability of the serum level analysis. The following parameters are included in the test:

  • Initial SS < 5
  • Final SS < 0.5
  • Final Vd SD < Model Vd SD
  • Final Kel SD < Model Kel SD
  • Predicted level = Measured level

If all 5 parameters are true, the measurement-to-model fit is excellent; 4/5 is a good fit; 3/5 is a fair fit; less than 3 is a poor fit.

Improved ‘Non-steady-state’ dialog
The previous dialog was wordy and vague:

The new dialog is simpler with clear choices:

APK update

Today I finished the latest update of the APK for Windows program to version 3.5.15.

Highlights include:

  • Added a “virtual log paper” tool.
  • Added automatic peak time calculation based on dist time.
  • Changed model editor to prevent name change.
  • Fixed gender choice on population analysis.
  • Fixed non-steady-state dialog.
  • Improved consult print-out: added method used, model selected.
  • Updated help file.

What I’m most proud of in this version is the new “virtual log paper” function. So many times over the years I’ve had to bring out the 3-cycle log paper to plot data for an outlier.

FYI, this is the 42nd time the program has been updated since it was first conceived in 1999. The original idea behind the APK program was to strip Kinetics down to one-compartment models only and add support for pediatric dosing. The executable was (and still is) small enough to run off a floppy drive.

But it has grown in complexity over the years. I decided to have some fun comparing code metrics from the original release to this one:

Forms: increased from 20 to 33
Code files: increased from 20 to 44
Functions: increased from 124 to 348
Events: increased from 202 to 2385 (not a typo!)
Lines of code: increased from 10,690 to 67,742.

In addition to the code base, the application includes an exhaustive help file with 81 topics, 85 images, and 14,260 lines of text.

To quote the Dude: “A lot of ins, a lot of outs, a lot of what-have-yous, and, a lot of strands to keep in my head, man. A lot of strands in old Duder’s head.”

So, forgive me if I forget your name next time we meet.

Half-life calculator

Finished up work on the latest iteration of the new program, Half-life calculator.

Summary of changes:

  • Changed serum level entry to a grid.
  • Up to 10 level/time pairs can be entered.
  • Added logarithmic graph of plot points and regression line
  • Added report function
  • Added built-in help.
  • Added save/load of serum level profiles
  • Added toolbar.

Main dialog:

Graph:

Report:

Just wish I could think of a “catchy” name for it 🙂