How can I calculate weighted standard errors and plot them in a bar plot?

Viewed 1308

I have a data frame of counts. I would like to calculate weighted proportions, plot the proportions, and also plot standard error bars for these weighted proportions.

Sample of my data frame:

head(df[1:4,])
  badge year total b_1 b_2 b_3 b_4 b_5 b_6 b_7 b_8 b_9 b_10
1    15 2014    14   3   2   1   1   1   1   1   1   1    1
2    15 2015   157  13  12  11   8   6   6   6   5   5    5
3    15 2016    15   5   3   1   1   1   1   1   1   1    0
4  2581 2014    13   1   1   1   1   1   1   1   1   1    1

The data contain counts of 911 calls officers respond to in ten different police beats (b_1, b_2,...) in a given year. So officer 15 responds to 14 calls total in 2014, 3 of which were in beat 1, 2 in beat 2, and so on.

Essentially, what I want is to get the overall proportion of calls that occur within each beat. But I want these proportions to be weighted by the total number of calls.

So far, I've been able to calculate this by just adding the values within each b_ column and the total column, and calculating proportions. I have plotted these in a simple bar plot. I am haven't been able to figure out how to calculate standard errors that are weighted by total.

I have no preference for how the data are plotted. I'm mainly interested in getting the right standard errors.

Here is the code I have so far:

sums_by_beat <- apply(df[, grep('b_', colnames(df2))], 2, sum)
props_by_beat <- sums_by_beat / sum(df$total)
# Bar plot of proportions by beat
barplot(props_by_beat, main='Distribution of Calls by Beat', 
        xlab="Nth Most Common Division", ylim=c(0,1), 
        names.arg=1:length(props_by_beat), ylab="Percent of Total Calls")

And a 30-row sample of my data:

df <- structure(list(badge = c(15, 15, 15, 2581, 2581, 2745, 2745, 
3162, 3162, 3162, 3396, 3650, 3650, 3688, 3688, 3688, 3698, 3698, 
3698, 3717, 3717, 3717, 3740, 3740, 3740, 3813, 3873, 3907, 3930, 
4007), year = c(2014, 2015, 2016, 2014, 2015, 2015, 2016, 2014, 
2015, 2016, 2016, 2014, 2015, 2014, 2015, 2016, 2014, 2015, 2016, 
2014, 2015, 2016, 2014, 2015, 2016, 2016, 2015, 2014, 2014, 2014
), total = c(14, 157, 15, 13, 29, 1, 1, 754, 1172, 1039, 14, 
1, 2, 34, 57, 146, 3, 7, 28, 593, 1036, 1303, 461, 952, 1370, 
1, 4, 41, 5, 451), b_1 = c(3, 13, 5, 1, 3, 1, 1, 33, 84, 83, 
2, 1, 2, 5, 10, 14, 2, 7, 7, 39, 72, 75, 42, 69, 81, 1, 1, 7, 
1, 36), b_2 = c(2, 12, 3, 1, 2, 0, 0, 33, 61, 52, 2, 0, 0, 3, 
6, 8, 1, 0, 2, 37, 65, 70, 29, 65, 75, 0, 1, 5, 1, 23), b_3 = c(1, 
11, 1, 1, 2, 0, 0, 32, 57, 45, 2, 0, 0, 3, 5, 8, 0, 0, 2, 34, 
62, 67, 28, 50, 73, 0, 1, 3, 1, 22), b_4 = c(1, 8, 1, 1, 2, 0, 
0, 31, 44, 39, 2, 0, 0, 3, 3, 7, 0, 0, 2, 34, 61, 67, 26, 42, 
72, 0, 1, 3, 1, 21), b_5 = c(1, 6, 1, 1, 1, 0, 0, 30, 42, 37, 
1, 0, 0, 3, 3, 7, 0, 0, 1, 33, 53, 61, 23, 42, 67, 0, 0, 2, 1, 
21), b_6 = c(1, 6, 1, 1, 1, 0, 0, 30, 40, 36, 1, 0, 0, 2, 2, 
6, 0, 0, 1, 32, 53, 61, 22, 41, 63, 0, 0, 2, 0, 21), b_7 = c(1, 
6, 1, 1, 1, 0, 0, 26, 39, 35, 1, 0, 0, 2, 2, 6, 0, 0, 1, 30, 
47, 58, 22, 39, 62, 0, 0, 2, 0, 21), b_8 = c(1, 5, 1, 1, 1, 0, 
0, 26, 39, 33, 1, 0, 0, 2, 2, 6, 0, 0, 1, 30, 47, 58, 21, 38, 
59, 0, 0, 2, 0, 19), b_9 = c(1, 5, 1, 1, 1, 0, 0, 24, 34, 33, 
1, 0, 0, 2, 2, 5, 0, 0, 1, 30, 43, 57, 20, 37, 57, 0, 0, 2, 0, 
15), b_10 = c(1, 5, 0, 1, 1, 0, 0, 23, 34, 32, 1, 0, 0, 1, 2, 
5, 0, 0, 1, 27, 40, 56, 18, 36, 55, 0, 0, 2, 0, 14)), row.names = c(NA, 
30L), class = "data.frame")
2 Answers

There isn't (as far as I know) a built-in R function to calculate the standard error of a weighted mean, but it is fairly straightforward to calculate - with some assumptions that are probably valid in the case you describe.
See, for instance: https://en.wikipedia.org/wiki/Weighted_arithmetic_mean#Standard_error

Standard error of the weighted mean

If the elements used to calculate the weighted mean are samples from populations that all have the same variance v, then the variance of the weighted sample mean is estimated as:

var_m = v^2 * sum( wnorm^2 )   # wnorm = weights normalized to sum to 1

And the standard error of the weighted mean is equal to the square root of the variance.

sem = sqrt( var_m )

So, we need to calculate the sample variance from the weighted data.

Weighted variance

The weighted population variance (or biased sample variance) is calculated as:

pop_v = sum( w * (x-mean)^2 ) / sum( w )

However, if (as in the case you describe), we are working with samples taken from the population, rather then with the population itself, we need to make an adjustment to obtain an unbiased sample variance.
If the weights represent the frequencies of observations underlying each of the elements used to calculate the weighted mean & variance, then the adjustment is:

v = pop_v * sum( w ) / ( sum( w ) -1 )

However, this is not the case here, as the weights are the total frequenceis of 911 calls for each policeman, not the calls for each beat. So in this case the weights correspond to the reliabilities of each element, and the adjustment is:

v = pop_v * sum( w )^2 / ( sum( w )^2 - sum( w^2) ) 

weighted.var and weighted.sem functions

Putting all this together, we can define weighted.var and weighted.sem functions, similar to the base R weighted.mean function (note that several R packages, for instance "Hmisc", already include more-versatile functions to calculate the weighted variance):

weighted.var = function(x,w,type="reliability") {
    m=weighted.mean(x,w)
    if(type=="frequency"){ return( sum(w*(x-m)^2)/(sum(w)-1) ) }
    else { return( sum(w*(x-m)^2)*sum(w)/(sum(w)^2-sum(w^2)) ) }
}
weighted.sem = function(x,w,...) { return( sqrt(weighted.var(x,w,...)*sum(w^2)/sum(w)^2) ) }

applied to 911 call data in the question

In the case of the question, the elements from which we want to calculate the weighted mean and weighted sem correspond to the proportions of calls in each beat, for each policeman.
So (finally...):

props = t(apply(df,1,function(row) row[-(1:3)]/row[3]))
wmean_props = apply(props,2,function(col) weighted.mean(col,w=df[,3]))
wsem_props = apply(props,2,function(col) weighted.sem(col,w=df[,3]))

Aren't your "proportions" actually the mean of the weighted (by total) observations? Then we could simply calculate the weighted colMeans accordingly.

df2 <- df[, grep('b_', colnames(df))]

means.w <- colMeans(df2 / df$total)

For the error bars we could use the quantiles of 1 - alpha/2, i.e. for alpha==.05 we use c(.025, .975). The analytical sds would yield negative values.

q.w <- t(apply(df2 / df$total, 2, quantile, c(.025, .975)))

Now, we store the x-positions that barplot returns invisible,

# Bar plot of proportions by beat
b <- barplot(means.w, main='Distribution of Calls by Beat', 
             xlab="Nth Most Common Division", ylim=c(0,1), 
             names.arg=1:length(means.w), ylab="Percent of Total Calls")

and construct the error bars with arrows.

arrows(b, q.w[,1], b, q.w[,2], length=.02, angle=90, code=3)

enter image description here

Related