{"id":2141,"date":"2012-02-28T05:42:14","date_gmt":"2012-02-28T10:42:14","guid":{"rendered":"http:\/\/www.thejuliagroup.com\/blog\/?p=2141"},"modified":"2012-02-28T05:42:14","modified_gmt":"2012-02-28T10:42:14","slug":"learning-advanced-sas-from-a-macro-part-4","status":"publish","type":"post","link":"https:\/\/www.thejuliagroup.com\/blog\/learning-advanced-sas-from-a-macro-part-4\/","title":{"rendered":"Learning Advanced SAS from a Macro: Part 4"},"content":{"rendered":"<p>Continuing to learn Advanced SAS from the <a href=\"http:\/\/www.lexjansen.com\/pharmasug\/2006\/publichealthresearch\/pr05.pdf\">Propensity Score Matching with Calipers macro from Feng, Yu &amp; X<\/a>u ,\u00a0we take our data set we created by doing a principal components analysis on the cases (experimental) group, using the coefficients from that analysis to score every record in our control group, concatenating the cases and controls, performing a cluster analysis on the whole group that adds a variable &#8220;distance&#8221; which is the distance from the seed of the one cluster. \u00a0The data set we ended up with after all this statistical slam-dunking was named mahalanobis_to_point.<\/p>\n<p>I was thinking about the FASTCLUS and I realized something. The PROC APPEND appends all of the records that fall within the calipers to the reference point data set. In FASTCLUS , the first complete record is going to be the seed . So, that first record, which is the seed is from one group and all of the other records are from your matching group. \u00a0(Yes, Feng et al. said exactly that in their paper but I needed to see how it worked for myself.)<\/p>\n<p>So, what now ? First, we&#8217;re going \u00a0to use SQL to select all the records from the\u00a0mahalanobis_to_point that are not in the &amp;refdata data set. That is, we are going to pull out the case we&#8217;re trying to match so we don&#8217;t end up with the silly situation where something is matched with itself.<\/p>\n<p><strong>I&#8217;m not sure if I can call PROC SQL a nifty thing.<\/strong><\/p>\n<p>I guess it is a good thing in the sense that homework and canned spinach are a good thing. Anyway, here we throw in some practice with PROC SQL.<\/p>\n<p><code>proc sql;<\/code><\/p>\n<p>create table mahalanobis_to_point<\/p>\n<p>as select a.*<br \/>\nfrom mahalanobis_to_point a, &amp;refdata b<br \/>\nwhere a.&amp;id^=b.&amp;id;<br \/>\nquit;<\/p>\n<p>Next, we sort all of the records by distance, we SET by distance, which will be in the sorted order, and we take the the first record, because that is the the one with the smallest distance from the seed, i.e., the case we&#8217;re trying to match.<br \/>\n<code>proc sort data=mahalanobis_to_point;<br \/>\nby distance;<br \/>\ndata mahalanobis_to_point;<br \/>\nset mahalanobis_to_point;<br \/>\nby distance;<br \/>\nif _n_=1;<br \/>\nrun;<\/code><\/p>\n<p>We add those two records into a data set named case_ctril and give them both the value of &amp;i (the loop counter) for their newid variable, which we can now use to link the case and it&#8217;s control group match. We don&#8217;t want to write over their old id variable because that is usually needed. \u00a0The need to apply a newid is why we can&#8217;t (as you might be tempted at first glance) combine these into the case_ctrl_together data set in one step.<\/p>\n<p>At our next step we add these two records, the case and its matched control, \u00a0into the case_ctrl_together data set we are building. We pull the record for the matched case out of the temporary data set with cases. We are going to keep going around in the loop until all of the cases are pulled out, either because they were matched here, <a href=\"http:\/\/www.thejuliagroup.com\/blog\/?p=2102\">they were matched previously (because there was only one match) or because there is not a control record that matches within the defined caliper<\/a>.<br \/>\n<code>data case_ctrl;<br \/>\nset &amp;refdata(keep=&amp;id) mahalanobis_to_point(keep=&amp;id);<br \/>\nnewid=&amp;i;<br \/>\nrun;<br \/>\ndata case_ctrl_together;<br \/>\nset case_ctrl_together case_ctrl;<br \/>\nif &amp;id=. then delete;<br \/>\nrun;<\/code><\/p>\n<p><code><br \/>\nproc sql;<br \/>\ncreate table case_temp<br \/>\nas select a.*<br \/>\nfrom case_temp a, mahalanobis_to_point b<br \/>\nwhere a.&amp;id^=b.&amp;id;<br \/>\nquit;<br \/>\n*** Exclude patients in case group which are selected in the Mahalanobis macro ***;<br \/>\n%mend Mahalanobis;<\/code><\/p>\n<p>You may be excused for having forgotten by now that this macro was executed inside of a loop inside of another loop inside of a macro, but it was. Having cranked through the macro as many times as necessary we hit a %END statement, and go searching for another case-control match. When we have matched all of our cases, we&#8217;ll hit our second %END statement for the last time and exit the outer loop.<\/p>\n<p><code>%end;<br \/>\n%end;<br \/>\n***Create test dataset that contains patients from case_control_together, and all<br \/>\nvariables and propensity scores as well***;<\/code><\/p>\n<p>This final PROC SQL is going to select out only the matched records into a data set (table) named test from the original data set we had created at the very beginning as output from the PROC LOGISTIC with our propensity scores (remember propensity scores? That&#8217;s what this macro was about, matching by propensity scores.)<\/p>\n<p>It was a lot of work to get this and I wanted it saved as a permanent data set just in case there was some problem later in the program and I accidentally did something stupid like save over this data set after it took 40 minutes for the program to run and create it. So, I added a DATA step to save it off somewhere safely away in a permanent SAS data set.<br \/>\n<code><br \/>\nproc sql;<br \/>\ncreate table test<br \/>\nas select a.*, b.newid<br \/>\nfrom propen a, case_ctrl_together b<br \/>\nwhere a.&amp;id=b.&amp;id<br \/>\norder by newid,&amp;yvar;<br \/>\nquit;<br \/>\ndata study.calipers ;<br \/>\nset test ;<br \/>\nrun ;<br \/>\n%mend match;<br \/>\n<\/code><br \/>\n<strong> Was it worth it?<\/strong><\/p>\n<p>So, there you have it, how to do propensity score matching with calipers in 5,000 words or more over four posts. Even given all of that, I am not so sure it is worth it, other than the educational value of going through the macro &#8211; and there IS considerable educational value. However, quintiles is far simpler and often works to reduce the bias a great deal. The GREEDY match macro, is also far simpler (though not as simple as quintiles) and has worked really well with the data I&#8217;ve been using. I&#8217;m not convinced that the improvement in precision is worth the tradeoff in complexity. Yeah, it&#8217;s a cool macro and all, but you need someone to understand it and maintain it. When disseminating the results of your research you&#8217;re going to need to explain it to people like a hospital board, a city council. Increasing the probability of difference between case and control from .76 to .91 \u00a0and reducing the r-square from .002 to .00018 &#8211; well, I&#8217;m just not sure it is a non-trivial improvement in a lot of cases.<\/p>\n<p>That, however, is a philosophical discussion for another day.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Continuing to learn Advanced SAS from the Propensity Score Matching with Calipers macro from Feng, Yu &amp; Xu ,\u00a0we take our data set we created by doing a principal components analysis on the cases (experimental) group, using the coefficients from that analysis to score every record in our control group, concatenating the cases and controls,&#8230;<\/p>\n","protected":false},"author":5,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[9,11,8],"tags":[],"class_list":["post-2141","post","type-post","status-publish","format-standard","hentry","category-software","category-statistics","category-technology"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.thejuliagroup.com\/blog\/wp-json\/wp\/v2\/posts\/2141","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.thejuliagroup.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.thejuliagroup.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.thejuliagroup.com\/blog\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/www.thejuliagroup.com\/blog\/wp-json\/wp\/v2\/comments?post=2141"}],"version-history":[{"count":4,"href":"https:\/\/www.thejuliagroup.com\/blog\/wp-json\/wp\/v2\/posts\/2141\/revisions"}],"predecessor-version":[{"id":2149,"href":"https:\/\/www.thejuliagroup.com\/blog\/wp-json\/wp\/v2\/posts\/2141\/revisions\/2149"}],"wp:attachment":[{"href":"https:\/\/www.thejuliagroup.com\/blog\/wp-json\/wp\/v2\/media?parent=2141"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.thejuliagroup.com\/blog\/wp-json\/wp\/v2\/categories?post=2141"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.thejuliagroup.com\/blog\/wp-json\/wp\/v2\/tags?post=2141"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}