Data Mining Two (The Sequel)

I have been collecting anonymous pharmacokinetic data from rxkinetics.net for the past 13 years. I last analyzed this dataset in March 2016 and published the results in a blog post titled “Data Mining.” On the tenth anniversary of that post, I revisited the data.

Note the dataset is intentionally minimal, including only date, model used, age, gender, height, weight, serum creatinine, creatinine clearance (CrCL), elimination rate (Kel), and volume of distribution (Vd). Also, there is no formal quality control applied to these data. As a result, some entries may be fictional, incorrectly entered, or duplicated. The data were collected anonymously, in accordance with our website privacy policy, from registered users worldwide.

Descriptive stats

Several inferences can be drawn from the descriptive statistics.

In September 2019, I changed the vancomycin model labels from “normal/outlier” to “aggressive/conservative.” Prior to this change, the selection ratio of normal to outlier models was 6.67:1. After the change, the ratio of aggressive to conservative shifted to 0.84:1. As suspected, the original model names were likely misleading for users who were unfamiliar with the distinctions and appropriate use of each model.

The BMI distribution (<30% excess BMI) suggests that a substantial portion of the data may originate from outside the United States.

Males outnumber females approximately 2:1. This likely reflects a data entry bias, as “male” is the default selection, suggesting that users may not be consistently updating the field when entering female patients.

Data analysis

Because most of the data pertained to vancomycin, I filtered the dataset to include only those records. Also, I excluded a small number of clearly implausible data points. This included six cases with BMI > 100 accompanied by unrealistically low height values, likely reflecting data entry error (though clinical scenarios such as amputations cannot be ruled out). There were 624 records with a half-life < 4 hours and 15 with a half-life > 7 days, some of which were associated with relatively normal creatinine clearance. The underlying reason for such results cannot be determined, as the serum concentration values and sampling times used for these calculations were not retained. After removing these extreme values, the final dataset consisted of 25,585 observations.

All record analysis

Regression analysis results: the relationship between Creatinine clearance (CrCL) and vancomycin clearance (VanCL) is defined by the following linear equation:

VanCL = (0.053786 x CrCL) + 0.248681

Correlation (r): 0.6711
• Indicates a solid positive linear relationship between the two variables.
Coefficient of Determination (R^2): 0.4504
• This means about 45% of the variation in VanCL is explained by the change in CrCL
Significance (P-value): < 0.0001
• The relationship is highly statistically significant.
Standard Error (RMSE): 1.7308

The residual plot shows the difference between the actual observed values and the values predicted by our model. Interpretation: A random scatter around the red zero line suggests that the linear model is appropriate for this data.

The histogram of residuals shows how the “errors” (residuals) are distributed. The bell-shaped curve indicates that the errors are normally distributed, which validates the use of linear regression for this dataset.

Comparative Analysis

Next, I broke the data into two groups. Group 1: BMI 30 or less (n=18,329) and Group 2: BMI greater than 30 (n=7,456).

Regression results:
• Group 1: Y = 0.047057X + 0.404398
• Group 2: Y = 0.067640X + 0.032623

Key Differences and Findings

  1. Change in Slope:
    The slope increased significantly from Group 1 to Group 2 (0.047 -> 0.068).
    This suggests that in the second group, for every unit increase in Creatinine clearance, the increase in VanCL is nearly 44% higher than in the first group.
  2. Intercept Shift:
    The intercept dropped significantly (0.40 -> 0.03).
    This implies that at very low renal function (Creatinine clearance near 0), the starting value for VanCL is much lower in Group 2.
  3. Model Strength (R^2):
    Group 2 fits the linear model slightly better than Group 1 (49.8% vs 47.4%).
  4. Error Margin (RMSE):
    Group 2 has a higher RMSE (1.95 vs 1.45), meaning the data points are more widely dispersed around the regression line than Group 1.

Chow Test Results
Results testing the null hypothesis (H0): The coefficients (slope and intercept) are identical for both groups:

  • F-Statistic: 1996.62
  • P-Value: < 0.0000000001 (1.11 x 10^-16)

Since the p-value is essentially zero, the null hypothesis is rejected. This confirms a “structural break” in the data. The way CrCL predicts VanCL in Group 1 is mathematically different from how it predicts VanCL in Group 2.

Because the Chow Test is significant, we cannot use a single linear equation to describe both groups accurately. Predictions would be consistently biased (over-predicting for one group and under-predicting for the other).

The Chow Test confirms that the two groups of data are statistically distinct. The relationship between CrCL and VanCL is not the same for both datasets.

Comparative Population Model Performance

Let’s look at how each of the two population models, C and K, perform with each group: (1) BMI 30 or less (n=18,329) and (2) BMI greater than 30 (n=7,456).. Model C is labeled “conservative” in the app and calculates vancomycin clearance. Model K is labeled “aggressive” in the app and calculates vancomycin Kel and Vd separately.

GroupModel K
(r)
Model C
(r)
Performance
Norm BMI (<= 30)0.7240.689Model K is the stronger predictor.
Excess BMI (> 30)0.6620.706Model C is the stronger predictor.
Model accuracy comparison

Our analysis shows that a one-size-fits-all modeling approach is flawed. For patients with a BMI <= 30, Model K (labeled “aggressive” in the app) is the statistically superior predictor of drug clearance. However, once a patient crosses the BMI threshold of 30, Model C (labeled “conservative” in the app) takes the lead.

Clinicians should be aware that model reliability is not static; it fluctuates based on the patient’s body mass, with all models becoming less reliable as obesity increases. This means that as BMI increases, the ‘safety net’ provided by mathematical formulas becomes significantly thinner, demanding more frequent serum monitoring.

Bayesian Revisited

About 30 years ago, a friend asked if I could add Bayesian analysis to my pharmacokinetic (PK) programs: “Look at the equation—it is very simple.” Yes, deceptively so. By this same logic, Einstein’s Field Equations are also simple.

Of course, in both cases, implementation is exponentially more complex. I chose the Levenberg–Marquardt (LM) Maximum A Posteriori (MAP) estimation because it is a fast and accurate optimizer and is considered the gold standard for therapeutic drug monitoring (TDM). MAP estimation finds the best-fit PK parameters given both observed data and the population model. Another way of putting it, MAP shrinks estimates toward the population mean, and that “shrinkage” is why it is so useful in TDM.

LM uses gradient information—it computes the Jacobian (partial derivatives of the predicted concentration with respect to each parameter) at every iteration and uses that to steer directly toward the minimum. This makes it fast and accurate in well-behaved models.

However, least squares (Error²) penalizes outliers exponentially. Outliers create gradients that can pull the algorithm toward a nonsensical solution, or create local minima that the Jacobian calculation cannot “see” past.

Users get frustrated when our Bayesian analysis software returns a “Bayesian failed” message. In the classic Bayesian scenario, the fallback in the presence of an outlier is effectively a return to the population model. Obviously, that is not safe—hence the “Bayesian failed” message.

The root of the problem is that vancomycin pharmacokinetics vary widely. In addition, failure to converge is more likely with fewer data points. Therefore, a single-point vancomycin analysis involving an outlier fails too often.

I have always been dissatisfied with this outcome, as there was no fallback. Lately, I have been working on a safe alternative when the data fail to converge with LM. Here is a comparison of common methods for implementing Bayesian serum level analysis. Most are designed for population PK analysis, not individual patient TDM.

I developed a small utility to compare the three most clinically useful Bayesian implementations, and they generally show agreement: Bayesian PK Method Comparator

I believe the Nelder–Mead (downhill simplex) method can serve as a useful fallback, provided appropriate guardrails are in place. I am currently evaluating its integration.

Adventures in JavaScript

Yet another coding project that has been sitting on the back burner for years, 14 to be exact. How time flies. My original concept was a slick JavaScript based single page web app that would replace the clunky dotNET version of Antibiotic Kinetics.

Per MSDN: “A Single-Page Application (SPA) is a web app that loads a single HTML page and dynamically updates content via JavaScript as users interact with it, eliminating full-page reloads. It offers a fast, native-like experience (e.g., Gmail, Netflix) by fetching only necessary data (JSON) rather than reloading entire pages.”

JavaScript is relatively easy to learn, but very difficult to master. It has many pitfalls and drawbacks. From it’s aggressive coercion to implied global variables, JavaScript code can be extremely bug prone.

JavaScript is a dynamically and loosely typed language, which means type-related errors might only become apparent during runtime, making debugging more challenging compared to statically typed languages.

I’ve tried so many HTML/JavaScript editors over the years it’s ridiculous. Many I paid good money for, none ever worked for me. Here are a few that I can remember trying, buying, and failing:

  • Antechinus
  • BlueGriffin
  • Dreamweaver
  • Komodo
  • NetBeans
  • NSB App Studio
  • RadPHP

There was an online editor that worked pretty well (up to a point), but so much time has passed I’ve forgotten its name.

This time around I used Visual Studio Code, the price was right (free). It does a nice job of color formatting the code, plus a few bug insights, albeit very few. Because JavaScript is a interpreted language, it does not have a compilation step. You don’t know if you have made a syntax error until you run the entire application. The Chrome console and Claude AI were helpful in sussing out the bugs.

So far I’ve only coded the prospective half of Antibiotic Kinetics. There is no serum level analysis methodology (yet?). I’m calling it Antibiotic Kinetics “Express”, to differentiate from the full dotNET web app, renamed Antibiotic Kinetics “Classic”.

When I paused this project 14 years ago, the jQuery mobile library (now deprecated) was the shiz. I kept it, giving the UI an iPhone classic look with rounded buttons and nice looking dialogs. The retro aesthetic evokes a warm sense of nostalgia:

I think the app icon is a winner too </sarcasm>.

Here is the link to Antibiotic Kinetics Express, enjoy.

Updated Android App Is Live

Download links

A Modern, Adaptive Experience

  • Adaptive Android Layout: Whether you’re on a compact smartphone or a 12-inch tablet, the interface now flows perfectly. No more “stretched” buttons or cramped text; the app feels native to your device.
  • Enhanced Navigation: We fixed the data entry tabs for a smoother workflow—moving from patient data to dosing has never been faster.
  • Instant Help: We’ve updated the documentation and added contextual help links to every screen. If you’re ever unsure about a parameter, the answer is just one tap away.
  • Proactive Error Traps: Our improved error trapping catches typos and illogical data entries before they can generate a result, adding an extra layer of clinical defense.

New Clinical Capabilities

  • Continuous Infusion Support: continuous infusion modeling is now integrated.
  • “Conservative” vs. “Aggressive” Models: We’ve updated our model naming to match the web app. Choose the Conservative model for for patients with comorbidities or the Aggressive model for patients without significant comorbidities.
  • Safety First (1g/hr Cap): To prevent infusion-related reactions (Red Man Syndrome), we have implemented an infusion rate cap at 1g/hr, ensuring patient safety is baked into the math.
  • Streamlined Library: We’ve removed the legacy Netilmicin model to keep the interface focused on the drugs you use most in modern practice.

Targeted AUC Dosing for Vancomycin

Following the latest consensus guidelines, AbPK now features Targeted AUC Dosing.

  • Precision Targeting: Move beyond simple trough goals to target the 400-600 mg*hr/L range (AUC/MIC) to maximize efficacy while significantly reducing the risk of AKI.
  • Improved AUC Logic: We’ve refined the underlying methods to ensure your calculations are robust, even in complex clinical scenarios.
  • Outlier Intelligence: Our improved outlier display highlights data points that don’t fit the model, helping you catch lab errors or “dirty” levels before they impact the patient.

This version 2.3.1 is a significant jump forward, especially with the shift toward AUC-targeted dosing, which is the current standard in clinical pharmacy.

The Struggle and the Catch

I actually finished the new build over a month ago, but it’s been stuck in “Play Store Purgatory” ever since. What should have been a quick, easy update turned into an incredibly frustrating ordeal.

The main roadblock was the “handshake” between versions. Trying to link the legacy listing to the new package was a nightmare of signing certificate mismatches and Google’s refusal to recognize the new build as a direct successor. Every time we thought we had the “upgrade path” cleared, we hit another wall regarding package identity or store listing restrictions.

The part of the ordeal that was particularly surreal was my multiple interactions with Google support. After weeks of trying to do things the “right way,” I reached out to support for help with the migration, only to find myself trapped in a loop of automated, robotic non-answers.

Every time I sent a detailed explanation of the technical hurdles we were facing, I’d get back a canned response that felt like it was written by a brick wall. I received multiple emails stating things like, “We regret informing you that your request is out of scope for our team.” It was incredibly frustrating because they weren’t even addressing the actual problem. Instead of a human looking at the legacy transition issue, I was getting high-level technical jargon used as a “keep out” sign. It became clear that the “Support” team wasn’t there to support developers in solving complex migration problems; they were there to recite a script and close tickets.

The last hope for a seamless upgrade rested on a technical request: a Reset of my App Signing Key.’ This would have allowed me to bridge the gap between the old version and the new architecture. I found the ticket buried under the ‘Help’ section of the Play Console, hoping for a breakthrough. Instead, I found that Google Support had simply closed the ticket. No explanation, no resolution, and no path forward. So, in the end, corporate bureaucracy won out.

Since Google refused to provide a direct upgrade path, we were forced to launch completely separate app listings:

  • AbPK Pro This is the new name for the original “Paid” version which includes Bayesian.
  • AbPK Core This is the new name for the original “Lite” (free) version, which excludes Bayesian.

This means our long-time users can’t just hit an “Update” button to get the new version—and honestly, that really sucks.

Play Store Promo Code

I know the lack of an upgrade path is frustrating. If you previously purchased the Paid version, please email me at ricktharp@rxkinetics.com with your original Google Play receipt, and I will personally send you a promo code for the new Pro version.

AI Santa Claus

Lately, I’ve been diving into the world of artificial intelligence, having long refused to engage with what I assumed was mostly hype and nonsense.

My day began, like many, with a bit of doom-scrolling through Reddit to see what fresh hell the current administration had delivered to everyday Americans. Somewhere in that scroll, I stumbled across a post about Suno AI music. As a lifelong music lover, curiosity took over.

Suno impressed me immediately. I used minimal input, a song title and a style, and selected a male vocalist. Moments later, the result was genuinely jaw-dropping. It’s no surprise an AI-generated country band managed to score a number-one hit this summer.

On a camping trip a few days later I played the song for a younger friend who was just as impressed. He asked if I’d ever tried ChatGPT. I hadn’t, but that quickly changed.

After downloading the app, I put it to the test. I’d already committed to giving a presentation on medicinal plants to our Master Gardeners group — a subject I knew a bit about from pharmacognosy class back in pharmacy school. ChatGPT turned out to be right about 85% of the time, which was impressive on its own. What really hooked me, though, was what came next. After answering a question, it asked if I wanted an image to illustrate the results. That’s when I became a fan. For me, the most time-consuming part of preparing a presentation has always been designing engaging PowerPoint slides.

Just a few days ago, I was talking with a twenty-something at a social gathering when the topic of ChatGPT came up again. He mentioned using it to help write software, which struck a nerve. I have a large Android codebase sitting idle because I’d never been able to migrate it from Eclipse to modern Android Studio. The official Android documentation is nearly incomprehensible — it might as well be written in Chinese — and earlier attempts using the import tool built into Android Studio went nowhere.

So, I updated Android Studio to the latest stable version and asked ChatGPT how to convert my old Eclipse development stack into something Android Studio could read. The process is painfully complex, but ChatGPT walked me through it step by step and double-checked my work along the way. After four solid hours, my old source code finally loaded. Of course it immediately crashed when I ran it.

This time, though, Gemini — now fully integrated into Android Studio — stepped me through the necessary code changes until the app ran again. There’s still plenty left to do, but I am aiming for a January 2026 release.

At this point, calling it “AI Santa Claus” doesn’t feel like much of a stretch.

Addendum: I’ve since learned that Sam Altman, the owner of ChatGPT, and one of the creepiest looking techbros, and who once called tRump “terrible”, now says the orange turd is a “breath of fresh air”. I can no longer support ChatGPT,

Windows 11 = Privacy Nightmare and E-Waste Disaster

Windows 10 support officially ended this week. Just kidding. You may now sign up for another year of free security updates. But it’s not automatic, you have to request it. Go to Settings -> Update & Security. There you will (hopefully) see a link to enroll in ESU.

There are two prerequisites to qualify for ESU. First, you will need a Micro$oft account. Second, you are required to backup your computer to the MS OneDrive cloud storage. You don’t have to back up everything, keep it minimal, otherwise they will charge you for additional storage space. After those two steps you will be able to enroll in ESU. FYI, you may enroll up to 10 PC’s per MS account. Oh but it’s not over yet. After enrolling you are required to login to OneDrive from your PC at least every 30 days in order to continue to receive security updates.

More info here: Windows 10 Extended Security Updates (ESU)

Maybe Micro$oft is seeing the writing on the wall? Many current PC’s do not have the required TPM chip. Or, if they do, most users do not have the knowledge to activate the chip in the BIOS settings. The result for most users is the need to purchase a new machine to run Win11. MS actually recommends this! And what becomes of those old PC’s? If they are recycled and still using Win10 to access the internet, they will become zombie spambots. A few users, finally fed up with MS, may choose to install Linux. Otherwise, this situation will create a mountain of E-waste, 1.6 billion pounds per one estimate.

More info here: Why the end of support for Windows 10 is uniquely troubling

Windows 11 is the most intrusive OS in history. Micro$oft is trying to sell security as the main reason to upgrade, but that is a red herring. The true purpose of Win11 is to force their AI nonsense upon every Windows user. The MS AI companion will “hear what you hear, see what you see, and live life essentially alongside you.” You have no choice, it is madness.

More info here: Windows 11 Is a Lost Cause. Truly Destined for the Garbage.

If you are forced to use Windows 11, no worries, our software will run on Win11. 32-bit compatibility continues under the WOW64 subsystem included with Win11. Countless businesses and consumers rely on 32-bit x86 apps. Micro$oft harvests data detailing what apps people are running and how often. They use that data to inform decisions about what features to drop. I can’t imagine Windows dropping support for x86 apps anytime in the next 20 years.

Update: In a new support article MS has admitted that there are problems on almost every major Windows 11 core feature

APK 3.5.30 update

Yes, it’s been a while. I suppose I’m from the old school who believe “if it ain’t broke, don’t fix it”.

This 30th update of version 3.5 adds a few new security features, adds an import function, and a few user interface tweaks.

Strong password option

There are risks associated with weak passwords and various regulatory bodies require applications to implement a strong password policy. In this update a strong password option was added on the setup dialog.

Selecting the checkbox will thereafter require strong passwords for all users. The password rules, explained within the online help, are as follows:

  • Length: 9 to 12 characters
  • Case: must contain both lower and upper case letters
  • Number: must contain at least one number
  • Symbols: must contain at least one symbol

If implementing this option from a previous version, it is important that all user’s current passwords are cleared. Otherwise they will get stuck in an endless loop of the old password not matching the new rules.

On a side note, regarding periodic password changes that other applications force upon the user. The current guidance is that passwords only require changing when there is evidence of compromise.

ADMIN account

In previous version of APK, sensitive functions were protected with a simple password dialog. Users with knowledge of the password could change settings and modify the databases. In version 30 an ADMIN login is now required to access these sensitive functions.

The program will create an ADMIN account when first started after this update.

Click “Yes” to continue. If updating from an older version, it will ask for the OG password to continue. Next, enter your chosen ADMIN password. It is important to choose a password that follows the secure rules and is one that you will remember. It will be extremely difficult to recover the ADMIN password if lost or forgotten.

Inactivity timer

A requirement of some regulatory agencies is that an application close after a period of inactivity. In the case of APK, there is now an optional hard 5 minute timer. After 5 minutes of inactivity, a dialog will appear with a two minute warning that you will be logged out. After another 5 minutes the same dialog will appear with another two minute warning that the application will shut down. Click the OK button to reset the timer.

The inactivity timer is optional and is enabled on the settings dialog.

It is highly recommended that the timer be switched on in a network environment.

Speaking of network considerations, it is also highly recommended that the automatic check for update be turned off in a network implementation. It will then be up to you to periodically check for an update. In case you were not aware, updates are free of charge in perpetuity.

Bulk data import

This function performs bulk addition of users and/or physicians from a spreadsheet into the APK database. The requirements for the source file are spelled out in the online help. After the import routine completes, the dialog will list those records that were imported and also those that were rejected (usually due to duplicate entries).

While on the subject of users, the user name field has increased from the measly 3 to a more reasonable 12 to accommodate more users.

Prescriber lookup changes

This update simplifies the prescriber lookup function. Now you may type a name (or a partial name) in the physician textbox on the patient data tab, then press the Enter key (or click the lookup button) to display the physician list. The closest match to your search will be highlighted. If no match is found, the entire list is displayed. Double click the highlighted text (or click the copy button) to transfer the prescriber name to the patient information tab.

The add/edit/delete functionality has been removed from this dialog. The prescriber database is now solely under control of the ADMIN.

Please note: prescriber lookup is totally optional, you may still free text the physician name.

Added patient lookup by MRN

This didn’t turn out the way I planned. For reasons unknown the exact search option would not work for MRN. I tried a half dozen different approaches. Finally had to resort to a fuzzy search. The result is the MRN search will return either an exact match or a list within which contains the MRN searched for.

Thank you

That’s all for now. Thanks to all for your continued support and suggestions for improving the program.

Web app update

An issue arose where cookies weren’t being saved which crashed the app. While it worked fine on iPhone, it produced a fatal error on Windows. Then instituted a different save method and now all is well.

And a reminder: always choose the secure https site.

Web update update

Three years ago I switched this web site to a secure server (HTTPS). The main difference between HTTP and HTTPS is that HTTPS has the additional SSL/TLS layer to ensure all data being transferred is encrypted and secure. 

The side effect of the switch was the web update functions of APK and AbPK stopped functioning.

Originally I thought the fix would be to simply change the URL in the updaters from HTTP to HTTPS. But it had no effect so I had to dig into the source code.

mxWebUpdate is freeware offered by Max Components. It is still available on the Torry Delphi Developer Library. However the Max Components website is no longer online, and no way to contact the author. Fortunately the source code was included. The component wraps the Windows Internet Library, so my next step was to delve into the Wininet API.

StackOverflow to the rescue. It was simply a matter of setting the correct flag in the call to HttpOpenRequest.

  • Original code: InternetFlag := INTERNET_FLAG_RELOAD
  • Revised code: InternetFlag := INTERNET_FLAG_SECURE

A simple fix that took me three years to discover. I will be posting an update to AbPK soon, still working on significant improvements to APK, stay tuned.

Ditching Google

I was an early fan of Google, back when their guiding principle was “don’t be evil“. Their search engine was amazing compared to the then state-of-the-art AltaVista. When they came out with Gmail, I became a fan because it filtered spam so much more effectively than the Hotmail I was using at the time.  How times have changed. Not only is their search engine now about as useless as AltaVista, Gmail has turned into a spam machine exponentially worse than Hotmail. And over the years they have dropped many of their most useful (free) online services.  The website, Killed by Google, has kept track of the nearly 300 services in the Google graveyard. I still miss Google Reader.

Google turned off the Chart API a couple of years ago. I had been using it to create Calcium Phosphate solubility curves within my online TPN calculator. I wasn’t aware Google killed it until a visitor to my website informed me. 

They replaced “Google Chart” with an incompatible service called “Google Charts”. Guessing the added “s” stands for Suckers. I looked through the documentation for the new service, but, like all their documents, it reads like Chinese stereo instructions (Yes I’m talking about their Android Developers Guide). 

Somehow, through all the irrelevant google search links for alternatives to Chart, I happened upon a post on stack overflow that promised a “drop-in replacement” for the Google Chart API. Haha, the graph it created was positioned wrong and there were missing data points and parameters, with little to no documentation for correcting. So much for dropping in, next.

I spent quite a bit of time looking into Chart.js before realizing it was just too complex for my needs. Then somehow I stumbled upon Plotly via stack overflow. Aha, easy-peasy, straightforward graphing. W3 schools  provides a most helpful sandbox for testing Plotly code. 

So the TPN calculator page is now fixed. And yes, I realize I have become that grouchy old man who complains about everything.