Annotated Example of IML Program for Evaluating Coarse Factor Score Estimates
(annotation appears in red)

The IML program is simply appended to your SAS program that reads the data. Here is an example SAS program for a 19-item questionnaire called the Morningness-Eveningness Questionnaire. Most of the 540 cases have been omitted for brevity.

Title "MEQ Data set";
Data MEQDATA;
  input sub meq1 - meq19;
cards;
  1  2  3  2  2  3  3  3  2  3  3  6  3  2  4  3  2  3  3  4
  2  5  3  2  3  3  1  2  1  3  4  6  3  1  3  4  4  5  3  6
  3  3  2  1  1  2  3  1  2  2  3  4  0  1  2  1  2  3  2  0
  4  5  3  2  3  3  1  3  2  4  4  4  2  1  4  4  1  5  3  4
  5  3  2  3  3  4  4  4  1  1  3  4  5  3  4  3  1  3  3  4
  6  2  2  1  2  2  3  2  1  3  2  4  0  1  2  3  2  3  2  0
  7  2  1  3  4  4  2  3  3  2  3  4  2  4  3  3  3  5  3  6
  8  3  3  2  3  4  3  3  2  4  3  6  2  1  3  4  2  5  4  4
  9  3  3  2  3  2  4  1  1  1  3  4  3  1  3  3  1  3  2  2
 10  2  2  2  3  3  1  2  2  2  3  4  2  1  2  2  1  3  3  0
......
538  3  3  3  3  4  4  3  2  3  3  4  2  2  3  4  1  3  2  1
539  4  3  3  2  2  3  2  3  2  3  4  3  1  4  3  2  3  3  2
540  1  1  1  2  2  2  1  2  1  1  4  2  3  2  2  1  3  2  0
;
 

The IML program is now appended:

/* ***************************************************************** */
/*                                                                   */
/*          EVALUATING COARSE ("SIMPLIFIED") FACTOR SCORES           */
/*        --------------------------------------------------         */
/*  The user makes only three changes to the program (marked in the  */
/*  code with numbers) before running:                               */
/*    1. The name of the data set to be analyzed is specified on     */
/*       the SET subcommand. The variables to be factored (only)     */
/*       are listed on the "KEEP" subcommand.                        */
/*    2. Desired options are set on the PROC FACTOR command.         */
/*    3. The simplified factor score coefficient (weight) matrix is  */
/*       changed: # rows is set equal to # items; and # of columns   */
/*       is set equal to # factors. The values in the matrix are     */
/*       then set (usually to -1s, 0s, and 1s).                      */
/*                                                                   */
/*  Note: The item correlation matrix must have an inverse for       */
/*        this program to run without producing an error.            */
/*                                                                   */
/* ***************************************************************** */
options linesize=132 font='Sasfont' 8 nocenter;
data work;
  set MEQDATA;                                                        /* 1 */
  keep meq1 - meq19;

The first change is made to the "data" statement above. Specifically, the "set" command is changed to include the name of the data set I am working on, "set MEQDATA". Also, the names of the variables that I am actually factor analyzing are included on the "keep" command. Notice that the "id" (subject number) has been excluded as it is not included in the factor analysis.
 

proc factor data=work m=ml n=2 r=p power=4 scree score outstat=fact;  /* 2 */

The second change is made to the "proc factor" command. I am here requesting a maximum likelihood factor analysis (m=ml), the extraction of two factors (n=2), and promax rotation (r=p). I have set the power for the rotation to 4, a value recommended in the literature (power=4). Other options can be set, such as principal axis factoring (m=prinit) and varimax rotation (r=v). The "outstat=fact" option should NOT, however, be changed.

One other change is made below.

proc standard data=work mean=0 std=1 replace out=zscores;
data ItmLabel;
  set fact;
  if _type_ = 'SCORE';
proc transpose data=ItmLabel out=ItmLabel;

proc iml;
reset fw=8 spaces=4;

   start corr (x, corr);
      nr=nrow(x);
      sum=x[+,];
      xpx=t(x)*x-t(sum)*sum/nr;
      s=diag(1/sqrt(vecdiag(xpx)));
      corr=s*xpx*s;
   finish corr;

use ItmLabel;
read all var{_name_} into Item;

use fact;
read all into ItemCor where(_type_ = 'CORR');
read all into Pattern where(_type_ = 'PATTERN');
Pattern=T(Pattern);
nr=nrow(Pattern);
nc=ncol(Pattern);
read all into FactCor where(_type_ = 'FCORR');
if type(FactCor) = 'U' then
  FactCor = I(nc);
FactCor=FactCor[1:nc,1:nc];
Struct=Pattern*FactCor;

use zscores;
read all into ZScore;
factor=T(DO(1,nc,1));

/* (Rows = Items / Columns = Factors)   */                            /* 3 */
FSCoef={ 0 1,
         0 1,
         1 0,
         1 0,
         1 0,
         0 0,
         1 0,
         0 0,
         1 0,
         0 0,
         0 0,
         0 0,
         0 0,
         0 0,
         0 0,
         0 0,
         0 0,
         0 0,
         1 1};
 

The FSCoef matrix above is changed to fit the dimensions of the particular factor analysis. In this example 2 factors have been selected above in the "proc factor" command. Furthermore, 19 items were factor analyzed. Hence, the FSCoef matrix must be comprised of 2 columns (# of factors) and 19 rows (# items factor analyzed) for this example.

The 1's in this matrix indicate which items are to be included in the coarse factor score estimates. As can be seen in the first column, items 3, 4, 5, 7, 9 and 19 are summed. For the second factor, items 1, 2, and 19 are summed. The 0's indicate that the item is essentially ignored in the computation of the coarse factor score estimates. As strange as it may seem, as long these coarse factor score estimates are valid, most of the items are not used to compute the scores.

If an item is to be subtracted rather than added in the computational process, a negative 1 should be entered into the FSCoef matrix. Differential weighting may be applied to items as well. For instance, if item 19 is twice as important for computing a factor score on the second factor than items 1 and 2, a value (weight) of 2 can be entered in the FSCoef matrix above rather than a 1.

This final change may require a bit of typing to set up the FSCoef matrix and it is important to include a comma after each row of data and to close the matrix with the bracket and semi-colon ( }; ).  Once the matrix is set up, however, it is easy to change (if needed) and the complete SAS code can then be run.
 

print , , "**** BEGIN OUTPUT FROM PROC IML **** ", , ;
print "Simplified Weights for Items / Factors", Item FSCoef [format=4.0], ,;

MultR=t(Struct)*inv(ItemCor)*Struct;
RSQR=vecdiag(MultR);
MultR=sqrt(vecdiag(MultR));
MinCor=j(nrow(MultR),1,0);
do i=1 to nrow(MultR);
  MinCor[i] = 2 * MultR[i]**2 - 1;
  end;
print "Indeterminacy / Determinacy Indices", "(Multiple R, R-Squared, and Minimum Correlation)"
      ,factor MultR [format=6.3] RSQR [format=6.3] MinCor [format=6.3], , ,;

C=t(FSCoef)*ItemCor*FSCoef;
C=diag(C);
C=sqrt(C);
Univ=t(Struct)*FSCoef*inv(C);
Valid=vecdiag(Univ);
print "Validity Coefficients",
      factor Valid [format=6.3] " compare to MULTR --> " MultR [format=6.3], , ,;

do i=1 to nc;
  Univ[i,i] = .;
  FactCor[i,i] = .;
  end;
print "Univocality Matrix",
      "(Rows = Factor Scores / Columns = Factors)",
      Univ [format=6.3] " compare to FACTCOR --> " FactCor [format=6.3], , ,;

ComScore=ZScore*FSCoef;
run corr(ComScore,ScoreCor);
do i=1 to nc;
  FactCor[i,i] = 1;
  do j=1 to nc;
    if i < j then do;
      ScoreCor[i,j] = .;
      FactCor[i,j] = .;
      end;
    end;
  end;
print "Correlational Accuracy",
       ScoreCor [format=6.3] " compare to FACTCOR --> " FactCor [format=6.3], , ,;
stop;
run;