Wednesday, March 25, 2020

Into the Rapids

To every thing there is a season, and a time to every purpose under the heaven.
—Ecclesiastes 3:1

Update, March 26

No, I couldn’t help myself; this blog post reflects an update done March 26.

I am trying to kick the Covid-19 data modeling habit. It’s just not good for my mental health, or probably yours, at this point. If you have been taking the magnitude of this disaster seriously, keep doing so. What more, really, is there useful to say?

Anyhow, in a moment of weakness I put this evening’s Johns Hopkins data into the model-parameter evolver and turned the crank. And out came the latest update to the Nightmare Plot, which I am putting right here at the top, and leaving the one with yesterday’s where it is.

March 26, 2020: Reported U.S. Covid-19 cases vs days since 1/22

Today’s number was 83,836 vs the 82,716 I’d predicted yesterday. The model was 1.4% off, and on the optimistic side this time. The residuals of modeled vs actual data do not fan out and are not significantly outside a normal distribution. A previous run of the same model with parameters evolved to fit data from the day before yesterday predicted 89,614, which was pessimistic by 6.9% over the course of two days’ extrapolation.

The universe is not yet honoring my request to be proved wrong with the curve bending down soon.

Back to March 25

Since the previous blog post, I did some work on my modeling of reported U.S. Covid-19 cases, ran the scary new example for my evolutionary parameter-finding algorithm on that evening’s data from Johns Hopkins, and posted a plot of the results on Facebook. An updated one with today’s data is here (click on it for the full-size version):

March 25, 2020: Reported U.S. Covid-19 cases vs days since 1/22

After discussing some technical details1 of the day’s work, I gave this

brief summary about what the model is predicting for reported numbers of reported Covid-19 cases in the U.S.: Tomorrow, around 66,000. Day after that, around 84,000. Reaching 100,000 the day after that (3/27). A million U.S. reported cases around April 7, doubling every 4-5 days for a while until the virus starts to run out of people to infect.

As I write this, the Worldometer Coronavirus page reports 65,797 cases in the United States.2 I don’t need to tell you how close that is to what the model was predicting yesterday.

The astute observer will be able to visually extend the red line and note that my model predicts more U.S. cases than people in the U.S. around the middle of May. Obviously, that’s not going to happen. It’s a limitation of my modeling that it doesn’t account for population of the country or region.

I’ve limited the amount of extrapolation I bother to show or talk about, since extrapolations can be uncertain even when the underlying model is well understood theoretically. But the obviously impossible prediction beyond the right edge of the plot does have a practical meaning. And it is an important one: I currently don’t see any sign of a slowdown appearing in the data. I wish I did.

Any refinements made to the model to account for such real-world practicalities as a country’s population would deviate from what I’ve been saying about what it actually does: simply predict what will happen if the reported-case data continues as it has, for about two weeks now. (Yesterday, there were slightly fewer newly reported cases than the day before, an obvious anomaly in the data.)

So, what I am seeing today is what I saw days ago already, and it terrifies me. The upward march of numbers continues, along a faint path increasingly visible on a cold gray mountain full of death.

Model Parameters

A couple of points are worth mentioning about model parameters. There is still no plausible upper bound to L, the limiting number of reported cases for the logistic-growth part of the model. Weirdly, there seems to be no plausible lower bound, either; some parameter combinations in the final population have almost no logistic component. Not something I’m very comfortable with, but there is clearly an exponential component to this, and the best few parameter combinations in the final population have L values ranging from 80 million to approaching the full U.S. population.

Here is the final population of both parameters L and r for the logistic-growth component of the model, plotted against SSE (sum of squared error, after the square-root transform is applied to modeled and actual data values):

Logistic growth parameters for March 26 data

The power-law with exponential decay component has its own parameter weirdness worth noting, too. The value of the power n in the function xd(t)=a*(t-ts)^n*exp(-(t-ts)/t0) is considerably lower than what Ziff and Ziff found for a power-law model by itself.3 Indeed, its best-fit values seems to be far less than even linear. Honestly, I’m not sure what to make of that. A very gradual increase in the number of newly reported cases each day over time, perhaps due to improved testing? Again, interpretation is left up to you.

Power-law parameters for March 26 data

Finally, the poor oddball constant term b is still there, just barely, adding its tiny fixed number of new cases per day. Against my own intuition, leaving this term out noticeably worsens the goodness of fit. I believe its utility is in reducing the SSE contribution made by early data points in the time series, allowing a better fit for the exponential and power-law components when things finally start ramping up.

Constant parameter for March 26 data

If you are comfortable with Python and data modeling, I encourage you to clone yourself a copy of the repo for ADE on GitHub, install the package with “pip install -e ade”, modified as needed.4 Run the newly installed ade-examples script, and then run “./covid19.py US” from inside the “~/ade-examples” directory that the script creates. Then you can look at the parameter values that get evolved using the pv command, like this: “pv -r 1.5 covid19.dat”.

The Fantasy of Normalcy

This afternoon [March 25], I had to go out for three unavoidable errands. I hope this is the last time I have to do that for a little while. One of my stops in the hot zone that our world is fast becoming was to the local grocery store. I sat in the parking lot and pulled on a fresh pair of latex gloves, and paused putting my respirator on my head for a moment, because I was seeing something disturbing.

It wasn’t that things were different, it’s that things were too much the same. Here I was, with every reason to believe that, on this day next week, there would be 300,000 Americans reported as infected with an insidious hidden disease that kills one out of every hundred or so who get it, a disease that puts over a dozen of those into the hospital gasping for breath. That there would likely be over a million reporting it the very week after, with the numbers still climbing fast and many more people not yet or not ever getting their own infections included in these numbers.

So I sat there a while with my well-used dirt-stained herbicide respirator5 in my lap, preparing to encounter in all likelihood at least one person carrying this thing around inside that store. Today’s person feeling fine though I sure have been tired the last couple days is next week’s positive test result.

I put on the mask, stepped outside, picked up my three plastic snap-lid storage bins and carried them into the store past the incredulous faces of people just walking around getting stuff. I stood apart from others and waited as they got their shopping carts and then got mine. I put the three plastic tubs in the cart, waited for the old man–looks like you’ve got about an 85% chance6 of surviving a case of this, gramps–to move along more than 6 feet away from me. I headed straight back to the department with the things that we decided we really couldn’t do without, pausing to give everyone a wide berth and just not giving a fuck what they thought about my respirator mask or gloves.

There were two checkout lines, both full. I just stood there projecting an invisible sphere of stay the fuck away from me and the whole man-from-mars appearance. Those two social distancers worked wonderfully except for one not-young woman who I had to outright tell, “Please move away.” And I don’t think she was even into me.

My muffled Martian voice came through the mask, “I’d like to box up my own.” The terrified-looking cashier handed me my stuff as she scanned it and I put it in my plastic tubs, with a loud snap each time I shut the lids. Those tubs never touched the floor.

No, I’m not a rewards club member. Thanks for that receipt that I’m going to crumple in my gloved hands and throw away. Have a nice day.

I unlocked the Jeep and got out a plastic can of sanitizer wipes. I opened the back, and picked up one tub at a time from the cart, wiping it down before setting it into the Jeep. I closed the back and then sanitizer-wiped all of the handles I’d touched with my gloved hands. Then I opened the door again, took off my windbreaker, and stripped down to my swim trunks that I was wearing instead of underwear. The windbreaker and my pants and shoes went into a plastic tub all their own.

I carefully removed each glove, being careful not to touch anything but the edge dangling loose around my wrist, and dropped them into the tub with the dirty clothes. Yes, folks, there is a 50-something-year-old man wearing swim trunks in March in your friendly neighborhood grocery store parking lot. Snap. Lid closed.

Then I put on a fresh pair of pants that were in the Jeep along with a fresh pair of shoes, and finally then sat in the driver’s seat.

The other two stops were much easier. Our local pharmacy clearly understands what’s happening better than the folks at the grocery store, because they had a sign outside inviting customers to take curbside delivery of their medications. And that’s what I did. The pharmacy clerk waved from the passenger side of my Jeep, I unrolled the window and extended an open Ziploc bag for him to drop the prescription bag into. We urged each other to take care and I didn’t even feel like I needed a glove on the hand that held the bag (mine, that enclosed his).

You’re probably wondering what happened with the groceries that were inside those plastic tubs. Answer: bleach solution and rags, followed by plain water and more rags to rinse off the bleach. There were a couple of nonperishables that we aren’t going to need for a while, and with no freezing temperatures in the forecast for a few days, they’re going to stay outside.

Here’s how I explained all of this to my college-age daughter, who I believe has gotten just a little bit less scornful of her doomer father’s pronouncements in recent days: Tonight, I will not lie awake wishing I hadn’t been so insanely careful.

Here’s what remains with me, though, instead of anxiety about having picked up Covid-19 this afternoon. (I really don’t think so.) I am dealing with the angry dreadful realization that my conservative, backwoods community either doesn’t know or doesn’t care about what is heading their way. All those people, casually walking around Safeway, one pair of gloves to be seen anywhere besides my own. And then there was me, marching in there with a cart of plastic tubs, wearing that neon-pink industrial respirator mask and surgical gloves like something you’d be scared of even without knowing that a pandemic was just starting to cripple your country.

I’ll say it again: Today’s person feeling mostly fine is next week’s positive test result. And next week I am expecting7 there to be around 300,000 of those positive test results. Going beyond the model for a moment and just talking out of my ass, I’d bet there will be twice that many walking around feeling mostly fine if starting to worry that maybe this whole Coronavirus thing isn’t a Democrat hoax after all.8 And they will spawn, mostly without knowing, the following week’s new ever-larger batch of positive test results.

This is as good a time as any to repeat, yet again, that I have no expertise in biology, medicine, or infectious disease. I’m just a retired engineer who has always had a passion for modeling interesting data. I’m currently about to release a free, open-source Python package that does some really cool modeling of power MOSFETs. See the disclaimer stuff in my previous posts.

Also worth repeating: The model is just the integration of a first-order differential equation that can be extrapolated to predict reported U.S. cases of Covid-19 if the reported cases continue as they have for the past two weeks. That said, I feel obligated to share one layman’s thought that gives me hope.

Perhaps the testing is accelerating faster than the virus’s replication and thus the model is being overly pessimistic in terms of real cases. In this cheery scenario, the testing is really taking off lately, lots more people being tested every day, and so yes there are more reported cases. But then the testing will start just humming along efficiently and the number of reported cases won’t keep inflating itself so fast.

There is, unfortunately, a dark alternative scenario: The testing is unable to keep up with the true replication of the virus. Yes, an accelerating number of new tests is finally appearing, but it’s not keeping up with how fast people are getting infected. Hospitals get overwhelmed, doctors stop bothering to test. And my stupid scary model winds up underestimating how fast we actually are getting to Armageddon.

This, gentle reader, is why (1) I have stuck to just predicting numbers of reported cases and leaving the rest to you, and (2) why I am eager to conclude this little modeling project. For my own mental well-being in an anxious time.

Farewell

After I took my shower and finally felt really clean, after the doorknobs were all wiped down with sanitizer wipes and the food was put away, I started doing “just a little more” work on my modeling code. Then I took a break from it and saw tonight’s total for US reported cases: 65,797. That’s 99.6% of what yesterday’s model and data had predicted.9

There is a weird unsettled feeling involved with seeing a predictive tool one has developed–not without criticism from a few self-proclaimed experts on Reddit–make a prediction so uncannily close to the mark.10 It’s a slowly roiling fog of horror with jarring bright spots of pride, and the guilty unease at that fact that I am seeing lights in there at all at a time like this.

This has happened before, on March 23. Then I wrote on Facebook,

the Worldometer Coronavirus update currently lists 43,734 U.S. cases and my model had predicted 43,639 reported U.S. cases for today’s (still nonexistent) Johns Hopkins numbers. This sort of uncannily close tracking with the data leaves a weird sense of anxious conflicted satisfaction in my mind.

It would be so much better for everyone, including myself, if I were instead being proven wrong. Feeling naive and embarrassed would be a pretty small price to pay to see the curve bending downwards faster than all my modeling predicted. Unfortunately, it still isn’t.

Take that, I find myself thinking of the random Internet guy who pooh-poohed my modeling as naive, unfounded, something only someone not properly educated in the relevant area would do. And then I look at the parts of the plot further to the right and realize what continuing to be right means.

So, let me say something for the record, to critics and fans and that little semi-living armored glob of RNA that has found a really effective way for it to propagate copies of itself: I’m totally fine with my model being full of shit for everything that happens from now on. Really, I can deal with it. I get it–it’s completely naive to try to extrapolate the integration of a nonlinear differential equation from cases already reported to what cases might be reported in the future. A charming if silly and perhaps even a little egomaniacal exercise on the part of a retired engineer with an obsession for developing nonlinear models for complicated things.

When the fancy theoretical factors that I didn’t even bother trying to understand finally emerge in a few days (hell, how about tomorrow?) and that curve finally gets its long-awaited downward bend, I will sit and watch movies and feel sorry for myself and the embarrassing way I’ve never quite managed to grow up and leave things to the experts. But it’s okay, and when we top out at maybe a couple hundred thousand U.S. cases and then the kids go back to school, I will immerse myself in weightlifting or something and trying to forget what an idiot I can be sometimes.

Next month, over the Sunday morning breakfast table with some friends, I’m looking forward to having a laugh about how silly were all being. Please be gentle with my own ego when it happens, and fervently hope that it really does.

Meanwhile, I’m done here. The time window for these sorts of projections to make a difference is closing fast, even for those who are paying attention to them. It may have closed already. Those people walking around the grocery store will not have their fates altered by something I write tonight, or whether I decide right now that it’s best for my own sanity not to trouble myself to write about this anymore.

Be well. And, for God’s sake, please do not vote for this same idiot narcissist failure of a President to stay in the White House next year. If that needs any explanation at all in your mind at this point, you need to pay attention to a lot more than just my amateurish attempt at Covid-19 modeling.

Notes


  1. Instead of weighting later samples higher in the sum-of-squared error calculation for the fitness function, I applied a square root transform to the number of new cases per day each day. That somewhat mitigates the effect of exponential growth to put much more emphasis on the most recent days’ data while allowing me to study the residuals of modeled vs actual daily new cases. 

  2. The Johns Hopkins total came in at 65,778. Running the model again when the new data became available after the original writing of this blog post did not result in different enough projections to warrant anything but a couple of footnotes and of course an update to the Nightmare Plot. 

  3. Anna L. Ziff and Robert M. Ziff (“Fractal kinetics of COVID-19 pandemic (with update 3/1/20). They fit to an exponent just a bit greater than 3. Perhaps it shouldn’t be surprising that the exponent would fit much lower than that with in combination with a logistic growth component, but I still wouldn’t have expected n (what Ziff and Ziff call x) to be down in the cube/​quad root range. 

  4. The last argument ade can be changed to whatever directory your cloned repo is in where the setup.py file for the package resides. 

  5. A typical Spring (not this one!) has me outside spraying noxious invasive weeds with a solution of 2,4D herbicide. The idea of a mere plant being “invasive” seems a bit quaint right now, though I’ll surely keep going after my remaining holdouts of spotted knapweed and St. John’s wort for many a Spring to come. 

  6. Based on some stats provided here, the death rate for people 80+ years old is 14.8%. And, by the way, gramps, it is 60% higher for men than for women. 

  7. See my previous posts for context and disclaimer. Of course you knew I’d eventually say that somewhere. 

  8. Yes, the fucking moron really did call it that: “The Democrats are politicizing the coronavirus . . . This is their new hoax” (Feb. 28, 2020). 

  9. The Nightmare Plot from my March 22 was predicting just over 70,000 for 3/25, a bit higher than it is. This is in accordance with the overall trend (a good one!) of the model being proved slightly pessimistic in its extrapolations. But, before you go celebrating some history of erroneous pessimism on my part, the plot from my March 19 blog post optimistically predicted (with an earlier regressive model of cumulative case numbers, rather than the differential equation approach I now prefer) only around 51,000 cases. 

  10. See my Reddit posts yesterday and today, referencing this blog post. Now, if I post a comment to that blog referencing this footnote, do I have to update this footnote to reflect that? And then post another Reddit comment? And so on?