A procedure for adjusting a table of data cells such that they add up to selected totals for both the columns and rows of the table
Aaron D. Schroeder

Starting table and target margins

seedTable = matrix(c(6,6,3,8,10,10,9,10,9,3,14,8),ncol=3,byrow=TRUE)
goalRowMargs = c(20,30,35,15)
goalColMargs = c(35,40,25)

Support Functions

Equal Margins

Function to check for equality of margins

equalMarginals = function(rowMs, colMs){sum(rowMs) == sum(colMs)}

No Zeros

Function to fill cells with vlaue 0 with 0.001

noZeros = function(aMatrix){replace(aMatrix, aMatrix == 0, .0001)}

Cell Functions

Functions to calculate cell values during an iteration

rowCellAdj = function(prevCellVal, prevRowSum, goalRowSum){prevCellVal/prevRowSum*goalRowSum}
colCellAdj = function(prevCellVal, prevColSum, goalColSum){prevCellVal/prevColSum*goalColSum}

Function for a single iteration of IPF

Returns a list of two tables, a row adjusted table and a column adjusted table

iterTable = function(inTable, goalRowMargs, goalColMargs){
    # adjust rows
    rowAdjTable = matrix(nrow = nrow(inTable), ncol = ncol(inTable))
    for (r in 1:nrow(inTable)){
        prevRowSum = sum(inTable[r,])
        goalRowSum = goalRowMargs[r]
        for (c in 1:ncol(inTable)){
            prevCellVal = inTable[r,c]
            newCellVal = rowCellAdj(prevCellVal, prevRowSum, goalRowSum)
            rowAdjTable[r,c] = newCellVal
        }
    }
    # adjust columns
    colAdjTable = matrix(nrow = nrow(rowAdjTable), ncol = ncol(rowAdjTable))
    for (c in 1:ncol(rowAdjTable)){
        prevColSum = sum(rowAdjTable[,c])
        goalColSum = goalColMargs[c]
        for (r in 1:nrow(rowAdjTable)){
            prevCellVal = rowAdjTable[r,c]
            newCellVal = rowCellAdj(prevCellVal, prevColSum, goalColSum)
            colAdjTable[r,c] = newCellVal
        }
    }
    resultTables = list()
    resultTables[[1]] = rowAdjTable
    resultTables[[2]] = colAdjTable
    resultTables
}

IPF function

Sets intial values and iteration parameters - returns a proportionally-fitted table

ipf = function(seedTable, goalRowMargs, goalColMargs, accuracy = .0001, maxiter = 50){
    if (equalMarginals(goalRowMargs, goalColMargs)){
        seedTable = noZeros(seedTable)
        iter = 0
        checkDif = 1
        i = iterTable(seedTable, goalRowMargs, goalColMargs)
        while((checkDif > accuracy) && (iter < maxiter)){
            i = iterTable(i[[2]], goalRowMargs, goalColMargs)
            rowMaxDif = max(abs(rowSums(i[[2]]) - goalRowMargs))
            colMaxDif = max(abs(colSums(i[[1]]) - goalColMargs))
            checkDif = max(rowMaxDif, colMaxDif)
            iter = iter + 1
        }
        round(addmargins(i[[2]]), 2)
    }
}
ipf(seedTable, goalRowMargs, goalColMargs)

9.14 7.75 3.1120.00
10.3010.92 8.7730.00
13.3412.57 9.0935.00
2.21 8.76 4.0215.00
35 40 25100