--- %%NOBANNER%% -->
/*------------------<--- Start of Description -->--------------------\
| The purpose of this macro is to match 1 or more controls(from a |
| total of M) for each of N cases. The controls may be matched to |
| the cases by one or more factors(X's). The control selected for a |
| particular case(i) will be the control(j) closest to the case in |
| terms of Dij. Dij is just the weighted sum of the absolute |
| differences between the case and control matching factors. I.e., |
| Dij= SUM{W.k*ABS(X.ik-X.jk)}, where the sum is over the number |
| of matching factors X(with index k) and W.k = the weight assigned |
| to matching factor k and X.ik = the value of variable X(k) for |
| subject i. |
| The control(j) selected for a case(i) is that with the smallest |
| Dij which is less than or equal DMAX(and which is compatible with |
| the DMAXK option below), where DMAX is defined by the user. In the|
| case of ties, the first one encountered will be used. The higher |
| the user-defined weight, the more likely it is that the case and |
| control will be matched on the factor. Assign large weights |
| (relative to the other weights) to obtain exact matches for two- |
| level factors such as gender. |
| Using the GREEDY method, once a match is made it is never broken.|
| This may result in inefficiencies if a previously matched control |
| would be a better match for the current case than those controls |
| currently available. |
| The OPTIMAL method uses PROC NETFLOW from SAS/OR to find the set |
| of matches that minimizes the sum of Dij over all possible sets of |
| matches. The OPTIMAL method also has an option for a variable |
| number of controls per case. |
| The macro checks for missing values of matching variables and the|
| time variable(if specified) and deletes those observations from the|
| case and control datasets. |
|--------------------<--- End of Description -->---------------------|
|--------------------------------------------------------------------|
|--------------<--- Start of Files or Arguments Needed -->-----------|
| Parameter definitions(R=required parameter): |
| R case=SAS data set of cases. Must contain the IDCA variable and|
| the matching variables. |
| R control=SAS data set of possible controls. Must contain the |
| IDCO variable and the matching variables. Note the macro |
| assumes that the cases and controls are in different |
| data sets. |
| FOR RISK SET MATCHING THIS DATA SET SHOULD INCLUDE BOTH CASES |
| AND CONTROLS. |
| R idca=ID variable for the cases. |
| R idco=ID variable for the controls. |
| time=time variable used to define risk sets. Matches are only |
| valid if the control time > case time. |
| R mvars=list of numeric matching variables common to both case & |
| control data sets. For example, mvars=male age birthyr. |
| R wts=list of non-negative weights corresponding to each matching|
| variable. For example wts=10 2 1 corresponding to male, age|
| and birthyr as in the above example. |
| dmaxk=list of non-negative values corresponding to each |
| matching variable. These numbers are the largest possible|
| absolute differences compatible with a valid match. |
| Cases will NOT be matched to a control if ANY of the |
| INDIVIDUAL matching factor differences are >DMAXK. This |
| optional parameter allows one to form matches of the type|
| male+/-0, age+/-2, birth year+/-5 by specifying |
| DMAXK=0 2 5. Given that a possible control meets the |
| DMAXK criteria, the macro selects the control with the |
| smallest Dij. If this list is shorter that the MVARS |
| list, 1-1 matching will be done until the DMAXK list is |
| exhausted. If this list is longer, the extra DMAXK values|
| are ignored. |
| dmax=largest value of Dij considered to be a valid match. If |
| you want to match exactly on a two-level factor(such as |
| gender coded as 0 or 1) then assign DMAX to be less than |
| the weight for the factor. In the example above, one could|
| use wt=10 for male and dmax=9. Leave DMAX blank if any |
| Dij is a valid match. One would typically NOT use both |
| DMAXK and DMAX. The only advantage to using both, would be|
| to further restrict potential matches that meet the |
| DMAXK criteria. |
| R method= GREEDY or OPTIMAL. See reference below. |
| ncontls=fixed number of controls to match to each case. The |
| default is 1. Using the GREEDY method with multiple |
| controls per case, the algorithm will first match every|
| case to one control and then again match each case to a|
| second control, etc. Controls selected on the first |
| pass will be stronger matches than those selected in |
| later rounds. The output data set contains a variable |
| (cont_n) which indicates on which round the control was|
| selected. This option is ignored if a variable number |
| of controls is to be used with the OPTIMAL method(see |
| MINCONT and MAXCONT parameters below). |
| - Options specific to GREEDY method |
| R seedca=seed value used to randomly sort the cases prior to |
| matching using the GREEDY method. This positive integer|
| must be less than (2**31)-1 and will be used as input |
| to the RANUNI function. The greedy matching algorithm |
| is order dependent which, among other things means that|
| cases matched first will be on average more similar to |
| their controls than those matched last(as the number of|
| control choices will be limited). If the matching order|
| is related to confounding factors (possibly age or |
| calendar time) then biases may result. Therefore it is|
| generally considered good practice when using the |
| GREEDY method to randomly sort both the cases and |
| controls before beginning the matching process. |
| R seedco=seed value used to randomly sort the controls prior to |
| matching using the GREEDY method. This seed value must|
| also be an integer less than (2**31)-1. |
| - Options specific to OPTIMAL method |
| mincont=minimum number of controls per case using the OPTIMAL |
| method with a variable number of controls(see Section |
| 3.3 of Rosenbaum). MINCONT must be >=1. |
| maxcont=maximum number of controls per case using the OPTIMAL |
| method with a variable number of controls(see Section |
| 3.3 of Rosenbaum). |
| MAXCONT must be >= MINCONT and <= M-N+1. |
| maxiter=maximum number of iterations for PROC NETFLOW to use |
| under the OPTIMAL method. Default value is 100000. |
| OUTPUT options applicable to either method |
| print= Option to print data for matched cases. Use PRINT=y to |
| print data and PRINT=n or blank to not print. |
| Default is y. |
| out=name of SAS data set containing the results of the |
| matching process. Unmatched cases are not included. See |
| outnm below. The default name is __out. This data set will|
| have the following layout: |
| Case_id Cont_id Cont_n Dij Delta_caco MVARS_ca MVARS_co|
| 1 67 1 5.2 (Differences & actual |
| 1 78 2 6.1 values for matching factors |
| 2 52 1 2.9 for cases & controls) |
| 2 92 2 3.1 |
| . . . . |
| . . . . |
| outnmca=name of SAS data set containing NON-matched cases. |
| Default name is __nmca . |
| outnmco=name of SAS data set containing NON-matched controls. |
| Default name is __nmco . |
|---------------<--- End of Files or Arguments Needed -->------------|
|--------------------------------------------------------------------|
|----------------<--- Start of Example and Usage -->-----------------|
| Example: 1-1 matching by male(exact), age(+-2) and year(+-5). |
| The wt for male is not relevant, as only exact matches |
| on male will be considered. The weight for age(2) is |
| double that for year(1). |
| A. Optimal method. |
| %match(case=case,control=cont,idca=clinic,idco=clinic, |
| mvars=male age_od yr_od,maxiter=10000, |
| wts=2 2 1, dmaxk=0 2 5,out=mtch, method=optimal); |
| B. Greedy method. |
| %match(case=case,control=cont,idca=clinic,idco=clinic, |
| mvars=male age_od yr_od,wts=2 2 1, dmaxk=0 2 5, |
| out=mtch, method=greedy, seedca=87877, seedco=987973);|
| |
| Usae: %MATCH(CASE=,CONTROL=,IDCA=,IDCO=,MVARS=,WTS=,DMAXK=,DMAX=, |
| NCONTLS=1, TIME=, METHOD=,SEEDCA=,SEEDCO=, |
| MAXITER=100000,PRINT=y, OUT=__OUT,OUTNMCA=__NMCA, |
| OUTNMCO=__NMCO,MINCONT=,MAXCONT=); |
| References: Bergstralh, EJ and Kosanke JL(1995). Computerized |
| matching of controls. Section of Biostatistics |
| Technical Report 56. Mayo Foundation. |
| Paul R. Rosenbaum. Optimal matching for observational|
| studies. JASA, 84(408), pp. 1024-1032, 1989. |
\-------------------<--- End of Example and Usage -->---------------*/
%MACRO MATCH(CASE=,CONTROL=,IDCA=,IDCO=,MVARS=,WTS=,DMAXK=,DMAX=,
NCONTLS=1, TIME=,
METHOD=,SEEDCA=,SEEDCO=,MAXITER=100000,PRINT=y,
OUT=__OUT,OUTNMCA=__NMCA,OUTNMCO=__NMCO,MINCONT=,MAXCONT=);
/*--------------------------------------------\
| Author: Jon Kosanke and Erik Bergstralh; |
| Created: April 25, 1995; |
| Purpose: match 1 or more controls(from a |
| total of M) for each of N cases; |
\--------------------------------------------*/
%LET BAD=0;
%IF %LENGTH(&CASE)=0 %THEN %DO; %PUT ERROR: NO CASE DATASET SUPPLIED; %LET BAD=1; %END;
%IF %LENGTH(&CONTROL)=0 %THEN %DO; %PUT ERROR: NO CONTROL DATASET SUPPLIED; %LET BAD=1; %END;
%IF %LENGTH(&IDCA)=0 %THEN %DO; %PUT ERROR: NO IDCA VARIABLE SUPPLIED; %LET BAD=1; %END;
%IF %LENGTH(&IDCO)=0 %THEN %DO; %PUT ERROR: NO IDCO VARIABLE SUPPLIED; %LET BAD=1; %END;
%IF %LENGTH(&MVARS)=0 %THEN %DO; %PUT ERROR: NO MATCHING VARIABLES SUPPLIED; %LET BAD=1; %END;
%IF %LENGTH(&WTS)=0 %THEN %DO; %PUT ERROR: NO WEIGHTS SUPPLIED; %LET BAD=1; %END;
%IF %UPCASE(&METHOD)=GREEDY %THEN %DO;
%IF %LENGTH(&SEEDCA)=0 %THEN %DO; %PUT ERROR: NO SEEDCA VALUE SUPPLIED; %LET BAD=1; %END;
%IF %LENGTH(&SEEDCO)=0 %THEN %DO; %PUT ERROR: NO SEEDCO VALUE SUPPLIED; %LET BAD=1; %END;
%END;
%IF %LENGTH(&OUT)=0 %THEN %DO; %PUT ERROR: NO OUTPUT DATASET SUPPLIED; %LET BAD=1; %END;
%IF %UPCASE(&METHOD)^=GREEDY & %UPCASE(&METHOD)^=OPTIMAL %THEN %DO;
%PUT ERROR: METHOD MUST BE GREEDY OR OPTIMAL; %LET BAD=1;
%END;
%IF (&MINCONT= AND &MAXCONT^= ) OR (&MINCONT^= AND &MAXCONT= ) %THEN %DO;
%PUT ERROR: MINCONT AND MAXCONT MUST BOTH BE SPECIFIED; %LET BAD=1;
%END;
%LET NVAR=0;
%DO %UNTIL(%SCAN(&MVARS,&NVAR+1,' ')= ); %LET NVAR=%EVAL(&NVAR+1); %END;
%LET NWTS=0;
%DO %UNTIL(%QSCAN(&WTS,&NWTS+1,' ')= ); %LET NWTS=%EVAL(&NWTS+1); %END;
%IF &NVAR^= &NWTS %THEN %DO;
%PUT ERROR: #VARS MUST EQUAL #WTS;
%LET BAD=1;
%END;
%LET NK=0;
%IF %QUOTE(&DMAXK)^= %THEN %DO %UNTIL(%QSCAN(&DMAXK,&NK+1,' ')= );
%LET NK=%EVAL(&NK+1);
%END;
%IF &NK>&NVAR %THEN %LET NK=&NVAR;
%DO I=1 %TO &NVAR; %LET V&I=%SCAN(&MVARS,&I,' '); %END;
%IF &NWTS>0 %THEN %DO;
DATA _NULL_;
%DO I=1 %TO &NWTS;
%LET W&I=%SCAN(&WTS,&I,' ');
IF &&W&I<0 THEN DO;
PUT 'ERROR: WEIGHTS MUST BE NON-NEGATIVE'; CALL SYMPUT('BAD','1');
END;
%END;
RUN;
%END;
%IF &NK>0 %THEN %DO;
DATA _NULL_;
%DO I=1 %TO &NK;
%LET K&I=%SCAN(&DMAXK,&I,' ');
IF &&K&I<0 THEN DO;
PUT 'ERROR: DMAXK VALUES MUST BE NON-NEGATIVE'; CALL SYMPUT('BAD','1');
END;
%END;
RUN;
%END;
%MACRO DIJ;
%DO I=1 %TO &NVAR-1; &&W&I*ABS(__CA&I-__CO&I) + %END;
&&W&NVAR*ABS(__CA&NVAR-__CO&NVAR);
%MEND DIJ;
%MACRO MAX1;
%IF &DMAX^= %THEN %DO; & __D<=&DMAX %END;
%DO I=1 %TO &NK; & ABS(__CA&I-__CO&I)<=&&K&I %END;
%MEND MAX1;
%MACRO MAX2;
%IF &DMAX= & &NK=0 %THEN %DO;
%IF &time^= %then %do; if __cotime>__catime then %end;
output;
%end;
%IF &DMAX^= & &NK=0 %THEN %DO;
IF _COST_<=&DMAX %if &time^= %then %do; & __cotime>__catime %end; THEN OUTPUT;
%END;
%IF &DMAX= & &NK>0 %THEN %DO;
IF ABS(__CA1-__CO1)<=&K1
%DO I=2 %TO &NK; & ABS(__CA&I-__CO&I)<=&&K&I %END;
%if &time^= %then %do; & __cotime>__catime %end;
THEN OUTPUT;
%END;
%IF &DMAX^= & &NK>0 %THEN %DO;
IF _COST_<=&DMAX
%DO I=1 %TO &NK; & ABS(__CA&I-__CO&I)<=&&K&I %END;
%if &time^= %then %do; & __cotime>__catime %end;
THEN OUTPUT;
%END;
%MEND MAX2;
%MACRO LBLS;
%DO I=1 %TO &NVAR; __CA&I="&&V&I/CASE" __CO&I="&&V&I/CONTROL"
__DIF&I="&&V&I/ABS. DIFF " __WT&I="&&V&I/WEIGHT"
%END;
%MEND LBLS;
%MACRO VBLES;
%DO I=1 %TO &NVAR; __DIF&I %END;
%DO I=1 %TO &NVAR; __CA&I __CO&I %END;
%MEND VBLES;
%MACRO GREEDY;
%GLOBAL BAD2;
DATA __CASE; SET &CASE;
%DO I=1 %TO &NVAR; %LET MISSTEST=%SCAN(&MVARS,&I,' '); IF &MISSTEST=. THEN DELETE; %END;
%IF &TIME^= %THEN %DO; IF &TIME=. THEN DELETE; %END;
DATA __CASE; SET __CASE END=EOF;
KEEP __IDCA __CA1-__CA&NVAR __R &mvars
%if &time^= %then %do; __catime %end;;
__IDCA=&IDCA;
%if &time^= %then %do; __catime=&time; %end;
%DO I=1 %TO &NVAR; __CA&I=&&V&I; %END;
SEED=&SEEDCA; __R=RANUNI( SEED );
IF EOF THEN CALL SYMPUT('NCA',_N_);
PROC SORT; BY __R __IDCA;
DATA __CONT; SET &CONTROL;
%DO I=1 %TO &NVAR; %LET MISSTEST=%SCAN(&MVARS,&I,' '); IF &MISSTEST=. THEN DELETE; %END;
%IF &TIME^= %THEN %DO; IF &TIME=. THEN DELETE; %END;
DATA __CONT; SET __CONT END=EOF;
KEEP __IDCO __CO1-__CO&NVAR __R &mvars
%if &time^= %then %do; __cotime %end;;
__IDCO=&IDCO;
%if &time^= %then %do; __cotime=&time; %end;
%DO I=1 %TO &NVAR; __CO&I=&&V&I; %END;
SEED=&SEEDCO;
__R=RANUNI( SEED );
IF EOF THEN CALL SYMPUT('NCO',_N_);
RUN;
%LET BAD2=0;
%IF &NCO < %EVAL(&NCA*&NCONTLS) %THEN %DO; %LET BAD2=1;
%PUT ERROR: NOT ENOUGH CONTROLS TO MAKE REQUESTED MATCHES;
%END;
%IF &BAD2=0 %THEN %DO;
PROC SORT; BY __R __IDCO;
DATA __MATCH;
KEEP __IDCA __CA1-__CA&NVAR __DIJ __MATCH __CONT_N
%if &time^= %then %do; __catime __cotime %end;;
ARRAY __USED(&NCO) $ 1 _TEMPORARY_;
DO __I=1 TO &NCO; __USED(__I)='0'; END;
DO __I=1 TO &NCONTLS;
DO __J=1 TO &NCA;
SET __CASE POINT=__J;
__SMALL=.; __MATCH=.;
DO __K=1 TO &NCO;
IF __USED(__K)='0' THEN DO;
SET __CONT POINT=__K; __D=%DIJ
IF __d^=. & (__SMALL=. | __D<__SMALL) %MAX1
%if &time^= %then %do; & __cotime > __catime %end;
THEN DO; __SMALL=__D; __MATCH=__K; __DIJ=__D; __CONT_N=__I; END;
END;
END;
IF __MATCH^=. THEN DO;
__USED(__MATCH)='1';
OUTPUT;
END;
END;
END;
STOP;
DATA &OUT;
SET __MATCH;
SET __CONT POINT=__MATCH;
KEEP __IDCA __IDCO __CONT_N __DIJ __CA1-__CA&NVAR
__CO1-__CO&NVAR __DIF1-__DIF&NVAR __WT1-__WT&NVAR
%if &time^= %then %do; __catime __cotime %end;;
LABEL __IDCA="&IDCA/CASE" __IDCO="&IDCO/CONTROL"
%if &time^= %then %do; __catime="&time/CASE" __cotime="&time/CONTROL" %end;
__CONT_N='CONTROL/NUMBER' __DIJ='DISTANCE/D_IJ' %LBLS;
%DO I=1 %TO &NVAR; __DIF&I=abs(__CA&I-__CO&I); __WT&I=&&W&I; %END;
%END;
%MEND GREEDY;
%MACRO OPTIMAL;
%GLOBAL BAD2;
DATA __CASE; SET &CASE;
%DO I=1 %TO &NVAR; %LET MISSTEST=%SCAN(&MVARS,&I,' '); IF &MISSTEST=. THEN DELETE; %END;
%IF &TIME^= %THEN %DO; IF &TIME=. THEN DELETE; %END;
DATA __CASE; SET __CASE END=EOF;
KEEP __IDCA __CA1-__CA&NVAR &mvars
%if &time^= %then %do; __catime %end;;
__IDCA=&IDCA;
%if &time^= %then %do; __catime=&time; %end;
%DO I=1 %TO &NVAR; __CA&I=&&V&I; %END;
IF EOF THEN CALL SYMPUT('NCA',_N_);
DATA __CONT; SET &CONTROL;
%DO I=1 %TO &NVAR; %LET MISSTEST=%SCAN(&MVARS,&I,' '); IF &MISSTEST=. THEN DELETE; %END;
%IF &TIME^= %THEN %DO; IF &TIME=. THEN DELETE; %END;
DATA __CONT; SET __CONT END=EOF;
KEEP __IDCO __CO1-__CO&NVAR &mvars
%if &time^= %then %do; __cotime %end;;
__IDCO=&IDCO;
%if &time^= %then %do; __cotime=&time; %end;
%DO I=1 %TO &NVAR; __CO&I=&&V&I; %END;
IF EOF THEN CALL SYMPUT('NCO',_N_);
RUN;
%LET BAD2=0;
%IF &NCO < %EVAL(&NCA*&NCONTLS) %THEN %DO; %LET BAD2=1;
%PUT ERROR: NOT ENOUGH CONTROLS TO MAKE REQUESTED MATCHES;
%END;
%IF &BAD2=0 %THEN %DO;
DATA __DIST1;
SET __CASE;
LENGTH __FROM __TO $ 80;
DO I=1 TO &NCO;
SET __CONT POINT=I;
_COST_=%DIJ; __FROM=left(__IDCA); __TO=left(trim(__IDCO) || '_co'); _CAPAC_=1;
IF _COST_^=. THEN DO; %MAX2 END;
END;
DATA __GOODCO; SET __DIST1; KEEP __IDCO;
PROC SORT; BY __IDCO;
DATA __GOODCO; SET __GOODCO; BY __IDCO; IF FIRST.__IDCO;
data _null_; i=1; set __goodco point=i nobs=n; call symput('newcont',n); stop;
DATA __DIST2;
LENGTH __FROM __TO $ 80;
DO I=1 TO N;
SET __GOODCO POINT=I NOBS=N;
__FROM=left(trim(__IDCO) || '_co'); __TO='SK'; _COST_=0; _CAPAC_=1;
OUTPUT;
END;
STOP;
DATA __GOODCA; SET __DIST1; KEEP __IDCA;
PROC SORT; BY __IDCA;
DATA __GOODCA; SET __GOODCA; BY __IDCA; IF FIRST.__IDCA;
DATA __DIST3;
LENGTH __FROM __TO $ 80;
DO I=1 TO N;
SET __GOODCA POINT=I NOBS=N;
__FROM='SC'; __TO=left(__idca); _COST_=0;
%if &mincont= %then %do; _CAPAC_=&NCONTLS; %end;
%else %do; _capac_=&mincont; %end;
OUTPUT;
END;
%if &mincont^= %then %do;
__from='SC'; __to='EXTRA'; _capac_=&newcont-&mincont*n; _cost_=0;
output;
do i=1 to n;
set __goodca point=i;
__from='EXTRA'; __to=left(__idca); _cost_=0; _capac_=&maxcont-&mincont;
output;
end;
%end;
CALL SYMPUT('NEWCASE',N); STOP;
DATA __DIST; SET __DIST1 __DIST2 __DIST3; %LET DEM=%EVAL(&NEWCASE*&NCONTLS);
PROC NETFLOW MAXIT1=&MAXITER
%if &mincont= %then %do; DEMAND=&DEM %end;
%else %do; demand=&newcont %end;
SOURCENODE='SC' SINKNODE='SK' ARCDATA=__DIST ARCOUT=__MATCH;
TAIL __FROM; HEAD __TO;
DATA __OUT;
SET __MATCH;
IF _FLOW_>0 & __FROM^in ('SC' 'EXTRA') & __TO^='SK'; __DIJ=_FCOST_;
%DO I=1 %TO &NVAR; __DIF&I=abs(__CA&I-__CO&I); __WT&I=&&W&I; %END;
PROC SORT; BY __IDCA __DIJ;
DATA &OUT;
SET __OUT; BY __IDCA;
drop __from -- _status_;
IF FIRST.__IDCA THEN __CONT_N=0;
__CONT_N+1; LABEL __IDCA="&IDCA/CASE" __IDCO="&IDCO/CONTROL"
%if &time^= %then %do; __catime="&time/CASE" __cotime="&time/CONTROL" %end;
__CONT_N='CONTROL/NUMBER' __DIJ='DISTANCE/D_IJ'
%LBLS;
%END;
%MEND OPTIMAL;
%IF &BAD=0 %THEN %DO;
%IF %UPCASE(&METHOD)=GREEDY %THEN %DO; %GREEDY %END;
%ELSE %DO; %OPTIMAL %END;
%IF &BAD2=0 %THEN %DO;
PROC SORT DATA=&OUT; BY __IDCA __CONT_N;
proc sort data=__case; by __IDCA;
data &outnmca; merge __case
&out(in=__inout where=(__cont_n=1)); by __idca;
if __inout=0; **non-matches;
proc sort data=__cont; by __IDCO;
proc sort data=&out; by __IDCO;
data &outnmco; merge __cont
&out(in=__inout); by __idco;
if __inout=0; **non-matched controls;
proc sort data=&out; by __IDCA; **re-sort by case id;
%if %upcase(&print)=Y %then %do;
PROC PRINT data=&out LABEL SPLIT='/';
VAR __IDCA __IDCO __CONT_N
%if &time^= %then %do; __catime __cotime %end;
__DIJ %VBLES;
sum __dij;
title9'Data listing for matched cases and controls';
footnote "match macro: case=&case control=&control idca=&idca idco=&idco";
footnote2 " mvars=&mvars wts=&wts dmaxk=&dmaxk dmax=&dmax ncontls=&ncontls";
%if &time^= %then %do;
footnote3 "time=&time method=&method seedca=&seedca seedco=&seedco";
%end;
%else %do;
footnote3" method=&method seedca=&seedca seedco=&seedco";
%end;
footnote4" out=&out outnmca=&outnmca outnmco=&outnmco";
run;
title9'Summary data for matched cases and controls';
proc means data=&out n mean sum min max; class __cont_n;
var __dij
%if &nvar >=2 %then %do; __dif1-__dif&nvar __ca1-__ca&nvar
%if &time^= %then %do; __catime %end;
__co1-__co&nvar
%if &time^= %then %do; __cotime %end; ;
%end;
%else %do; __dif1 __ca1
%if &time^= %then %do; __catime %end;
__co1
%if &time^= %then %do; __cotime %end;;
%end;
run;
proc means data=&outnmca n mean sum min max; var &mvars;
title9'Summary data for NON-matched cases';
run;
proc means data=&outnmco n mean sum min max; var &mvars;
title9'Summary data for NON-matched controls';
run;
%end;
%END;
%END;
title9; footnote;
run;
%MEND MATCH;