I saw an interesting contagion-related graph on Twitter and decided to do something like it.

The graph that I saw showed the chance that there is a contagious person in a room of N people, given the percentage of the population who are actively contagious. Since television wasn’t that interesting yesterday, I decided to code up the graph on my tv room iPad. On that machine, I have no keyboard, and program with the thing in my lap, which means that I go quick and dirty, with short names and limited use of upper case. I’ll show the code but not as a point of pride.

Here’s the graph:

graph

I have seen that the current estimate for percentage of contagious people in the US is about 0.05. You can read off those values on the graph as you wish.

Some folks won’t be surprised by these curves, but some may be. What makes the curves rise as rapidly as they do? Well, it’s the way probabilities work. Suppose that ten percent of the population are infected, and suppose we have a room of 20 people. For the room to be infection-free, all twenty of them have to be without infection. The chance that any one of them is uninfected is 0.9, or 1 - 0.1. For all 20 to be free of infection, the probability is 0.9 to the 20th power, because to get the probability of A and B, we multiply the probability of A times the probability of B. 0.9 to the 20th is about 0.12 (!), so the chance that there is someone infected in the room is about 0.88, which is what it shows on the graph.

Some folks may be aware of the math puzzle that goes something like this:

You are in a room with 29 other people. Someone wants to bet that there are two people in the room with the same birthday. What would you consider fair odds?

Well, long story short, in a room of 30 randomly chosen people, the chance is about 70% that there are two people with the same birthday. To get that figure you have to multiply 364/365 times 363/365 … and so on. Same principle, and it means that you can make a lot of money betting people that there is a matching birthday in the room. If you can find enough rooms, and people who want to bet. Don’t quit your day job just yet.

Anyway, here’s the program. I’ll comment on each bit.

-- Positive

function setup()
    sca = vec2(10,500)
    org = vec2(200,HEIGHT/6)
    org = vec2(0,0)
    pr = {3,4,5,10}
    ps = {}
    for i,p in ipairs(pr) do
        table.insert(ps, curve(p/100.0))
    end
end

The setup function defines the graph scale and origin. origin is set to 0,0 after setting it to its initial value, because I changed the program to use the translate function, as we’ll see below, and so I didn’t need the values in the computation.

Then setup creates two tables, pr and ps. pr contains the probabilities to use in the graph (as percent), 3,4,5, and 10, and ps contains the curve values for that probability:

function curve(prob)
    local p = {}
    for n = 1,100 do
        local x = n
        local pb = 1 - (1-prob)^n
        local y = pb
        local v = vec2(x,y)
        table.insert(p, v)
    end
    return p
end

This function does the math. It returns a table indexed from 1 to 100, with the values being the probability of an infected person present, given the input probability prob. The function could be optimized, retaining the n-1st power to use to get nth, but I’m in no hurry, and the program runs in zero time anyway.

-- This function gets called once every frame
function draw()
    translate(200, HEIGHT/6)
    background(255)
    stroke(0)
    titles()
    strokeWidth(1)
    grid()
    strokeWidth(3)
    for i,p in ipairs(ps) do
        polyline(p,pr[i]) 
    end
end

This just draws the picture, with titles, grid, and the lines, drawn with polyline. We’ll glance at each of those.

function titles()
    pushStyle()
    fontSize(60)
    textMode(CORNER)
    text("Chance of Infectious Person in room\ngiven number of people in room and\n(percent) of population actively infected", org.x, org.y+510)
    fontSize(50)
    text("Number of people in room", org.x, org.y-75)
    popStyle()
end

This function is pretty ad hoc. I just adjusted font size and the text origins until I found the picture to be acceptable. No magic here. Note that the code refers to org.x and org.y. But org is now (0,0), so that could be removed. If I were providing this program as a paragon of publishable production programming, I’d clean that up. My purpose was to get the graph, and my purpose now is just to show you how it came about.

function grid()
    for x = 0,100,10 do
        drawlineV(x,0,x,1)
    end
    for y = 0,10 do
        drawlineH(0,y/10,100,y/10)
    end
end

The grid function draws the vertical and horizontal lines of the grid, and adds the numerical values at the ends of the lines. It uses two helper functions, drawlineV and drawlineH:

function drawlineV(x1,y1,x2,y2)
    local x,y
    drawline(x1,y1,x2,y2)
    x,y = sc(x1,y1-0.03)
    text(tostring(x1), x,y)
end

function drawlineH(x1,y1,x2,y2)
    local x,y
    drawline(x1,y1,x2,y2)
    x,y = sc(x1-2.5,y1)
    text(tostring(y1),x,y)
    x,y = sc(x2+2.5,y2)
    text(tostring(y2),x,y)
end

These functions use drawline, below, to draw the actual line. Then they calculate positions for the text and text out the values, using the function sc, below. The calculations there are ad hoc, adjusted until it looked OK.

function drawline(x1,y1,x2,y2)
    line(org.x+sca.x*x1, org.y+sca.y*y1, org.x+sca.x*x2, org.y+sca.y*y2)
end

This function is drawing lines with integer coordinates that we have scaled to match a probability of 0.1 in the vertical, and one person (from 0-100) horizontal. Each parameter is scaled by the x or y value in sca, and offset by org (which is now zero, but in the past wasn’t).

function sc(x,y)
    return org.x+sca.x*x, org.y+sca.y*y
end

This function takes two parameters x and y and returns two values, those parameters scaled and offset. Nothing to see here.

function polyline(pts,pr)
    for i = 2,#pts do
        a = pts[i-1]
        b = pts[i]
        local x1,y1,x2,y2
        x1,y1 = sc(a.x,a.y)
        x2,y2 = sc(b.x,b.y)
        if b.x > 18 and b.x < 23 then
            if b.x == 20 then
                pushStyle()
                text("("..pr.."%)",x2,y2)
                popStyle()
            end
        else
            line(x1,y1,x2,y2)
        end
    end
end

This got semi-interesting when I decided that I wanted to put the curve’s starting probability right on top of the curve. If you ignore the if statements, you can see that the function just draws a scaled line from pts[i-1] to pts[i]. That draws the curve. The if statements turn off the line drawing between x = 19 and x = 22. And when x is 20, it displays the value of pr.

Summary

So there we are. A graph that either makes you really comfortable being in a room full of possibly contagious people, or makes you cautious. How you react is up to you. The numbers are as you see here.

Here’s how I react: At a conference, it’s dead certain that someone there is contagious, probably even if they’re testing daily, certainly if they’re not. In a restaurant, chances are about 66% in favor of contagion. Go twice, you have a 90 percent chance of being in a room with someone contagious.

Me, I’m not going anywhere with anyone. You do you. And may your gods be with you, whatever you decide.