/*-------------------------------------------------------------------* * Name: agree.sas * * Title: Agreement chart for n x n table * * * *-------------------------------------------------------------------* * Author: Michael Friendly * * Created: 13 Mar 1991 10:23:12 * * Revised: 12 Jan 1998 09:15:33 * * Version: 1.1 * * * * From ``Visualizing Categorical Data'', Michael Friendly (2000) * *-------------------------------------------------------------------*/ /*= =Description: The AGREE program is a collection of SAS/IML modules for preparing observer agreement charts which portray the agreement between two raters. =Usage: The modules are typically loaded into the SAS/IML workspace with the %include statement. The required input parameters are specified with IML statements, and the AGREE module is called as follows, proc iml; %include 'path/to/agree.sas'; *-- set global variables, if desired; font = 'hwpsl009'; htext=1.3; *-- data, labels, title, weights; freq = { 5 3 0 0, 3 11 4 0, 2 13 3 4, 1 2 4 14 }; vnames = {'New Orleans Neurologist' 'Winnipeg Neurologist'}; lnames = {'Certain' 'Probable' 'Possible' 'Doubtful'}; title = 'Multiple Sclerosis: New Orleans patients'; w = 1; *-- diagonals only , or ...; w = {1 (8/9)}; *-- diagonals + 1-off; run agree(freq, w, vnames, lnames, title); ==Parameters: The required parameters for the RUN AGREE statement are: * table A square numeric matrix containing the contingency table to be analyzed. * weight A vector of one or more weights used to give ``partial credit'' for disagreements by one or more categories. To ignore all but exact agreements, let weight=1. To take into account agreements one-step apart (with a weight of 5/6), let weight={1 5/6}. * vnames A character vector of two elements, containing the names of the row and column variables. * lnames A character vector containing the names of the row and column categories. If table is $n \times n$, then lnames should contain $n$ elements. * title A character string containing the title for the plot. ==Global input variables: The program uses two global variables to determine the font and character height for text in the agreement chart. * font A character string specifying the font used. The default is Helvetica ('hwpsl009') if a PostScript driver is being used, SWISS otherwise. * htext A numeric value specifying the height of text characters. =*/ *-- Bangdiwala Observer Agreement Chart [SAS SUGI, 1987, 1083-1088]; start agree(freq, w, vnames, lnames, title ) global (font, htext); if type(font ) ^= 'C' then do; call execute('device = upcase("&sysdevic");'); if index(device,'PS') > 0 then font= 'hwpsl009'; /* Helvetica for PS drivers */ else font = 'SWISS'; end; if type(htext ) ^= 'N' then htext=1.2; row_sum = freq[,+]; col_sum = freq[+,]; n = freq[+,+]; k = nrow(freq); reset noname; print (( freq || row_sum ) // ( col_sum || n ) )[r=(lnames ||'Total') c=(lnames ||'Total')]; obs_agr = ssq( vecdiag(freq) ); tot_agr = col_sum * row_sum ; call gstart; call gwindow( (-.15#J(1,2,n)) // (1.1#J(1,2,n)) ); call gset('FONT', font); height = htext; call gstrlen(len,lnames,height); corner= { 0 0 }; fill = 'EMPTY'; *-- construct marginal rectangles and locate row/col labels --; do s = 1 to k; thisbox = corner || row_sum[s] || col_sum[s]; boxes = boxes // thisbox; fill = fill // 'EMPTY'; center = corner +((row_sum[s] || col_sum[s]) - (len[s] || len[s] ))/2; labelx = labelx //(center[1] || (-.06#n) || 0 ) //((-.04#n) || center[2] || 90); labels = labels // lnames[s] // lnames[s]; corner = corner + (row_sum[s] || col_sum[s]); ht = ht // height // height; end; *-- variable names; height = 1.4#htext; call gstrlen(len,vnames,height); center = ((1.0#n) - len)/2; labelx = labelx // ( center[1] || (-.12#n) || 0 ) // ( (-.10#n) || center[2] || 90); labels = labels // vnames[1] // vnames[2]; ht = ht // height // height; *-- surrounding frame, for all observations; boxes = boxes // ( { 0 0 } || n || n ) ; corner= { 0 0 }; *-- construct agreement squares and scores; q = ncol(w) - 1; a = J(q+1,k,0); *-- b indexes distance from main diagonal for agreement; do b = 0 to q; do s = 1 to k; agr = max(1, s-b) : min(k, s+b) ; * cells which agree; dis = 1 : max(1, s-b-1) ; * disagre; box_loc = choose( (s-b-1)>0, (sum(freq[s,dis]) || sum(freq[dis,s])), { 0 0 } ); /* box_size= choose( (s-b) > 0, (sum(freq[s,agr] ... */ if s=1 then corner = {0 0}; else corner = boxes[s,1:2] + box_loc; thisbox = corner || sum(freq[s,agr]) || sum(freq[agr,s]); boxes = boxes // thisbox; if b>0 then a[b+1,s] =thisbox[3] # thisbox[4]; if b=0 then fill = fill // 'SOLID'; else do; if mod(b,2)=1 then dir='L'; else dir='R'; dens = int((b+1) / 2); fill = fill // (dir + char(dens,1)); end; end; end; print 'Bangdiwala agreement scores'; part = diag(w) * A; weights = shape(w,0,1); steps = 0:q; BN = 1 - ( ( tot_agr - obs_agr - part[,+] ) / tot_agr ); reset name; print steps weights[f=8.5] BN[f=8.4]; * print boxes[c={'BotX' 'BotY' 'LenX' 'LenY'}] fill; * print labels labelx[c={X Y ANGLE}] ht; run gboxes( boxes, labels, labelx, fill, ht, title ); call gstop; finish; *-- Draw and label the agreement display --; start gboxes( boxes, labels, labelx, fill, ht, title ) global ( htext ); call gopen('AGREEMT'); *-- locate the 4 corners of each box; ll = boxes[,{1 2}]; lr = boxes[,{1 3}][,+] || boxes[,2] ; ul = boxes[,1] || boxes[,{2 4}][,+] ; ur = boxes[,{1 3}][,+] || boxes[,{2 4}][,+]; xy = ll || ul || ur || lr; max = max(ur[,1]) || max(ur[,2]); do i=1 to nrow(boxes); box = shape(xy[i,], 4); color='BLACK'; pat = fill[i]; call gpoly( box[,1], box[,2], 1, color, pat, color); end; *-- Draw dotted diagonal line to show marginal homogeneity--; call gdrawl( {0 0}, max, 3 , 'RED' ); do f=1 to nrow(labels); lxya = labelx[f,]; labl = labels[f ]; height = ht[f]; call gscript( lxya[,1], lxya[,2], labl, lxya[,3], 0, height); end; height = 1.4#htext; call gstrlen(len, title, height); tx = (max[1] - len)/2; call gscript(tx, max[2]#1.05, title, 0, 0, height); call gshow; finish;