;$Id: p_correlate.pro,v 1.16 2004/01/21 15:54:58 scottm Exp $
;
; Copyright (c) 1994-2004, Research Systems, Inc.  All rights reserved.
;       Unauthorized reproduction prohibited.
;+
; NAME:
;       P_CORRELATE
;
; PURPOSE:
;       This function computes the partial correlation coefficient of a
;       dependent variable and one particular independent variable when
;       the effects of all other variables involved are removed.
;
; CATEGORY:
;       Statistics.
;
; CALLING SEQUENCE:
;       Result = P_correlate(X, Y, C)
;
; INPUTS:
;       X:    An n-element vector of type integer, float or double that
;             specifies the independent variable data.
;
;       Y:    An n-element vector of type integer, float or double that
;             specifies the dependent variable data.
;
;       C:    An array of type integer, float or double that specifies the
;             independent variable data whose effects are to be removed.
;             The columns of this two dimensional array correspond to the
;             n-element vectors of independent variable data.
;
; KEYWORD PARAMETERS:
;    DOUBLE:  If set to a non-zero value, computations are done in
;             double precision arithmetic.
;
; EXAMPLES:
;       Define the data vectors.
;         x0 = [64, 71, 53, 67, 55, 58, 77, 57, 56, 51, 76, 68]
;         x1 = [57, 59, 49, 62, 51, 50, 55, 48, 52, 42, 61, 57]
;         x2 = [ 8, 10,  6, 11,  8,  7, 10,  9, 10,  6, 12,  9]
;
;       Compute the partial correlation of x0 and x1 with the effects of
;       x2 removed. The result should be 0.533469
;         result = p_correlate(x0, x1, reform(x2, 1, n_elements(x2)))
;
;       Compute the partial correlation of x0 and x2 with the effects of
;       x1 removed. The result should be 0.334572
;         result = p_correlate(x0, x2, reform(x1, 1, n_elements(x1)))
;
;       Compute the partial correlation of x1 and x2 with the effects of
;       x0 removed. The result should be 0.457907
;         result = p_correlate(x1, x2, reform(x0, 1, n_elements(x0)))
;
; REFERENCE:
;       APPLIED STATISTICS (third edition)
;       J. Neter, W. Wasserman, G.A. Whitmore
;       ISBN 0-205-10328-6
;
; MODIFICATION HISTORY:
;       Modified by:  GGS, RSI, July 1994
;                     Minor changes to code. New documentation header.
;       Modified by:  GGS, RSI, August 1996
;                     Added DOUBLE keyword.
;                     Modified keyword checking and use of double precision.
;       CT, RSI, Feb 2003: Completely rewrote to correct formula for
;                     multiple columns. Before it would always return
;                     a positive correlation. Now the sign is correct.
;-
function P_Correlate, xVec, yVec, cVec, $
    DOUBLE = doubleIn

    compile_opt idl2

    ON_ERROR, 2  ;Return to caller if an error occurs.

    if (N_PARAMS() ne 3) then $
        MESSAGE, 'Incorrect number of arguments.'

    nDim = SIZE(cVec, /N_DIMENSIONS)
    dimC = SIZE(cVec, /DIMENSIONS)

    nX = N_ELEMENTS(xVec)
    if (nX ne N_ELEMENTS(yVec)) then $
        MESSAGE, 'X and Y must have the same number of elements.'

    if (nDim lt 1) or (nDim gt 2) then MESSAGE, $
        'C must be a vector or a two-dimensional array.'

    twoD = (nDim eq 2) and (dimC[0] gt 1)

    ; Check row dimension of C.
    if (dimC[(nDim eq 2)] ne nX) then $
        MESSAGE, 'Incompatible arrays.'

    ; Check for DOUBLE keyword.
    doDouble = N_ELEMENTS(doubleIn) ? KEYWORD_SET(doubleIn) : $
        ((SIZE(xVec,/TYPE) eq 5) or (SIZE(yVec,/TYPE) eq 5) $
            or (SIZE(cVec,/TYPE) eq 5))

    ; From "Partial Correlation Coefficients", Gerard E. Dallal
    ; http://www.tufts.edu/~gdallal/partial.htm
    ;
    ; Let Y and X be the variables of primary interest
    ; and let C1...Cp be the variables held fixed.
    ; First, calculate the residuals after regressing Y on C1...Cp.
    ; These are the parts of Y that cannot be predicted by C1...Cp.
    ; Then, calculate the residuals after regressing X on C1...Cp.
    ; These are the parts of X that cannot be predicted by C1...Cp.
    ; The partial correlation coefficient between Y and X adjusted for
    ; C1...Cp is the correlation between these two sets of residuals.

    if (not twoD) then begin   ; single column

        ; For efficiency for a single C column, we just compute
        ; the partial correlation directly, without going thru regress.
        pXY = CORRELATE(xVec, yVec, DOUBLE=doDouble)
        pXC = CORRELATE(xVec, cVec, DOUBLE=doDouble)
        pYC = CORRELATE(yVec, cVec, DOUBLE=doDouble)

        result = ((pXC ne 1) and (pYC ne 1)) ? $
            (pxY - pXC*pYC)/SQRT((1 - pXC^2)*(1 - pYC^2)) : $
            (doDouble ? 0d : 0.0)

    endif else begin    ; multiple columns

        ; Regress Y on C and compute residuals.
        dummy = REGRESS(cVec, yVec, YFIT=ycFit, DOUBLE=doDouble)
        yresid = yVec - TEMPORARY(ycFit)
        ; Regress X on C and compute residuals.
        dummy = REGRESS(cVec, xVec, YFIT=xcFit, DOUBLE=doDouble)
        xresid = xVec - TEMPORARY(xcFit)
        ; Correlate the residuals.
        result = CORRELATE(yresid, xresid)

    endelse

    return, result

end

