Skip to content

Instantly share code, notes, and snippets.

@pietersv
Last active June 12, 2020 12:44
Show Gist options
  • Save pietersv/2855695b0c4f79a8ce242f14655a8adc to your computer and use it in GitHub Desktop.
Save pietersv/2855695b0c4f79a8ce242f14655a8adc to your computer and use it in GitHub Desktop.
Calculate rim weights

Rim weighting algorithm

The function below calculates a single respondent-level weight variable such that when applied as a weight, the marginal distribution of multiple elements yields the desired proportions. This can be useful to ensure that the results of a survey sample reflect the population distribution.

Algorithms

Add this function within a Protobi data process:

/**
 * Calculate a single respondent-level  weight such that when weighting is applied 
 * multiple variables yield the desired marginal distribution
 * @param protobi   Protobi instance with elements and data preset 
 * @param name      Name of weight variable to calculate within protobi.data()
 * @param targets   Object specifying target marginal distributions for one or more elements
 *    { <key> : { <val>: <proportion>, <val>:<proportion> } }
 */
Protobi.calculate_rim_weights = function(protobi, name, targets)  {
        
    var rows = protobi.data() // assume rows have already been set
    
    var weights = {}
    rows.forEach(function(row) {
        row.weight = 1;
    })
    protobi.setGlobalWeight(name)

    
    // iterate 10 times. We could get fancier about convergence but good enough
    for (var i=0; i<10; i++) {
        // iterate over each attribute with weighting criteria
        _.each(targets, function(vals, key) {
            var marginal = protobi.getMarginal(key);
            weights[key] = weights[key] || {}

            for (var val in vals) {
                var tgt = vals[val] 
                var wgt = _.get(weights, [key, val]) || 1
                _.set(weights, [key, val], wgt * tgt / marginal.getPercent(val));
            }

            // now apply these weights so available for the next variable
            rows.forEach(function(row, idx) {
                row[name] = 1;
                _.each(targets, function(vals, key) {
                    var dim = protobi.getDimension(key)
                    var val = dim.getValue(row)
                    if (dim.groupFn) val = dim.groupFn(val)
                    var factor = _.get(weights, [key, val]) || 1
                    row[name] *= factor
                })
            })
        })
    }
    
    // normalize final weights
    var sum_weight = rows.reduce(function(sum, row) {return sum + row[name]}, 0)
    rows.forEach(function(row) {row[name] =rows.length * row[name] / sum_weight; })
    return weights;
        
}

Example

The below example demonstrates how to apply the above function in data process. This program retrieves the data and elements for the project, calculates the weights and saves the data.

Protobi.get_tables(["main", "OE"], function(err, data) {
    if (err) return callback(err);
    
    Protobi.get_elements(function(err, protobi) {
        if (err) return callback(err);
    
        var rows= data["main"] //primary data file
        protobi.setData(rows);
        
        Protobi.calculate_rim_weights(
            protobi, 
            'weight', {
            "s3":{
               "1":0.49,
               "2":0.51
            },
            "s2":{
                "0":0.84,
                "1":0.16
            },
            "region":{
                "Northeast":0.17,
                "Midwest":0.21,
                "West":0.24,
                "South":0.38,
            }
        })
    return callback (null, rows)
    })
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment