Hello, can anyone help check the following Macro loop code? I cannot find anything wrong. But it doesn't give me anything after I run the code? Any help will be really appreciated.
%macro loop(year, nfirms);
/* Loop by firm &i for year &year. This will generate a dataset of firm i-j AcctComp for each year. */
data temp1a; set temp1; if year(datadate1)=&year;
proc sort; by gvkey1;
data nfirms; set temp1a;
keep gvkey1;
proc sort nodupkey; by gvkey1;
data nfirms; set nfirms; id = _N_;
data temp1a; merge temp1a nfirms; by gvkey1;
%do i= 1 %to &nfirms;
data est1; set temp1a;
proc sort nodupkey; by gvkey1;
data est2; set temp1a; if id = &i;
PROC SQL;
CREATE TABLE est2 AS SELECT
est2.*, est1.gvkey1 AS gvkey_j, est1.a_i AS a_j, est1.b_i AS b_j
FROM est2 LEFT JOIN est1
ON est2.gvkey1 ~= est1.gvkey1
AND est2.sic2 = est1.sic2;
QUIT;
data est2; set est2;
a_dif = a_i - a_j;
b_dif = b_i - b_j;
drop a_i a_j b_i b_j;
proc sort; by gvkey1 datadate1 fqenddt1 datacqtr1 gvkey_j;
data est2; set est2;
proc sort; by gvkey1 datadate1 fqenddt1 datacqtr1 gvkey_j fqenddt;
data est2; set est2;
error = ABS(a_dif + bhr * b_dif);
proc sort; by gvkey1 datadate1 gvkey_j fqenddt;
PROC UNIVARIATE NOPRINT; by gvkey1 datadate1 gvkey_j;
VAR error ;
OUTPUT OUT = univ N=n MEAN = me_error ;
data univ; set univ;
acctcomp = -1 * me_error*100;
acctcomp = round(acctcomp, 0.001);
rename gvkey1 = gvkey_i;
rename datadate1 = datadate_i;
if not missing (acctcomp);
if 14 <= n <= 16;
drop me_error n;
PROC APPEND BASE = final data = univ; RUN;
%end ;
data QDC.acctcomp&year; set final;
proc sort; by gvkey_i datadate_i gvkey_j;
%mend loop;
Yes.
The code between %Macro and %Mend doesn't actually execute until you call it. You have to call the macro with a % sign and then the name of the macro or it won't do anything.
It should generally look something like this. The values 2019 and 5 are just examples. Of course you should use values that make sense for what you're doing.
%MACRO Loop(year, nfirms);
...macro code goes here...
%MEND Loop;
%Loop(2019, 5);
Jim
Hi, @Winnie1,
Would it be possible for you to post the log? If you can post log, please use the "insert code" option.
It's much easier to read that way.
Thank you,
Jim
1386 %macro loop(year, nfirms); 1387 /* Loop by firm &i for year &year. This will generate a dataset of 1387! firm i-j AcctComp for each year. */ 1388 1389 data temp1a; set temp1; if year(datadate1)=&year; 1390 proc sort; by gvkey1; 1391 1392 data nfirms; set temp1a; 1393 keep gvkey1; 1394 proc sort nodupkey; by gvkey1; 1395 1396 data nfirms; set nfirms; id = _N_; 1397 1398 data temp1a; merge temp1a nfirms; by gvkey1; 1399 1400 %do i= 1 %to &nfirms; 1401 1402 data est1; set temp1a; 1403 proc sort nodupkey; by gvkey1; 1404 1405 data est2; set temp1a; if id = &i; 1406 1407 PROC SQL; 1408 CREATE TABLE est2 AS SELECT 1409 est2.*, est1.gvkey1 AS gvkey_j, est1.a_i AS a_j, est1.b_i AS b_j 1410 FROM est2 LEFT JOIN est1 1411 ON est2.gvkey1 ~= est1.gvkey1 1412 AND est2.sic2 = est1.sic2; 1413 QUIT; 1414 1415 data est2; set est2; 1416 a_dif = a_i - a_j; 1417 b_dif = b_i - b_j; 1418 drop a_i a_j b_i b_j; 1419 1420 proc sort; by gvkey1 datadate1 fqenddt1 datacqtr1 gvkey_j; 1421 1422 data est2; set est2; 1423 1424 proc sort; by gvkey1 datadate1 fqenddt1 datacqtr1 gvkey_j fqenddt; 1425 1426 data est2; set est2; 1427 error = ABS(a_dif + bhr * b_dif); 1428 1429 proc sort; by gvkey1 datadate1 gvkey_j fqenddt; 1430 1431 PROC UNIVARIATE NOPRINT; by gvkey1 datadate1 gvkey_j; 1432 VAR error ; 1433 OUTPUT OUT = univ N=n MEAN = me_error ; 1434 1435 data univ; set univ; 1436 acctcomp = -1 * me_error*100; 1437 acctcomp = round(acctcomp, 0.001); 1438 1439 rename gvkey1 = gvkey_i; 1440 rename datadate1 = datadate_i; 1441 1442 if not missing (acctcomp); 1443 if 14 <= n <= 16; 1444 1445 drop me_error n; 1446 1447 PROC APPEND BASE = final data = univ; RUN; 1448 1449 %end ; 1450 1451 data QDC.acctcomp&year; set final; 1452 1453 proc sort; by gvkey_i datadate_i gvkey_j; 1454 1455 %mend loop;
One suggestion:
1407 PROC SQL; 1408 CREATE TABLE est2 AS SELECT 1409 est2.*, est1.gvkey1 AS gvkey_j, est1.a_i AS a_j, est1.b_i AS b_j 1410 FROM est2 LEFT JOIN est1 1411 ON est2.gvkey1 ~= est1.gvkey1 1412 AND est2.sic2 = est1.sic2; 1413 QUIT;
has at least a chance of being a problem.
When I run a simpler program I get this warning:
9 proc sql; 10 create table work.class as 11 select * 12 from work.class 13 ; WARNING: This CREATE TABLE statement recursively references the target table. A consequence of this is a possible data integrity problem. NOTE: Table WORK.CLASS created, with 19 rows and 5 columns. 14 quit;
General style notes when working with macro code: Always use a run, or quit, to end a procedure or data step.
The nature of the macro language makes it very possible to generate code that does not end steps at the time you expect them too otherwise.
Do not reuse data set names inside a macro. When you reuse the same name then you cannot tell where the contents have problems.
Always explicitly state the input data set by using data=somesetname on procedures. It is very easy to have a macro code branch execute and you forgot which set was intended to be sorted leaving 1) the proper set unsorted and 2) possibly place the wrong order on another set or 3) using some other proc on the wrong set.
Any time you use constructs like
1398 data temp1a; merge temp1a nfirms; by gvkey1;
or Set, Modify or Update as well as merge, you completely replace the input data set. So logic errors can make it impossible to tell whether a set has bad values before or after the merge (or set ).
In this block of code:
1415 data est2; set est2; 1416 a_dif = a_i - a_j; 1417 b_dif = b_i - b_j; 1418 drop a_i a_j b_i b_j; 1419 1420 proc sort; by gvkey1 datadate1 fqenddt1 datacqtr1 gvkey_j; 1421 1422 data est2; set est2; 1423 1424 proc sort; by gvkey1 datadate1 fqenddt1 datacqtr1 gvkey_j fqenddt; 1425 1426 data est2; set est2; 1427 error = ABS(a_dif + bhr * b_dif);
Lines 1420 through 1422 do, for practical purposes, NOTHING.
When you sort data without a need to use by processing, as the three data steps do, then you are wasting potentially lots of time for no purpose.
The whole bit can be accomplished with
data est2; set est2; a_dif = a_i - a_j; b_dif = b_i - b_j; drop a_i a_j b_i b_j; error = ABS(a_dif + bhr * b_dif); run;
General rule of thumb when you find that you need a new variable: go back to a previous data step and add the code there, don't run a data step to add one variable.
And the Log should have included:
The statement that calls the macro
Any messages, notes or errors that appear from running the macro.
The Option Mprint, in addition to providing details of the code generated the call will place the notes and messages in relation to the code that creates them.
Thank you, @ballardw
These things in particular, @Winnie1, would be very helpful as I try to determine what is wrong:
@ballardw wrote:
the Log should have included:
The statement that calls the macro
Any messages, notes or errors that appear from running the macro.
The Option Mprint, in addition to providing details of the code generated the call will place the notes and messages in relation to the code that creates them.
Jim
Thank you, @jimbarbour. I have posted the log. There is no error in the log.
Thank you for posting the log. Can you re-run with these options in effect:
OPTIONS MCOMPILENOTE=NOAUTOCALL;
OPTIONS SYMBOLGEN;
OPTIONS MPRINT;
I'd like to see what the SAS macro facility is seeing.
Before you re-run, you might want to try something really basic: Close your session and re-open it. Don't do this if you have work files that have taken you hours to build, but if you can re-create your work files quickly, then close and re-open your session before re-trying.
And can you tell me a few more things?
How is the macro being called? Can you post the calling code?
What is the value of nfirms and how is it being set? What year(s) are you running for?
%macro loop(year, nfirms);
How many records are in temp1? What is the distribution of years?
data temp1a; set temp1; if year(datadate1)=&year;
Jim
Hi Jim,
Thank you so much for your help. I re ran your code. But it didn't work. It didn't show any result. There are 236,480 records in the dataset and it is from 2014-2019. nfirms is based on the pair of the firm i and j. Is there anyway we can post the dataset so you may understand it better?
Thank you @Winnie1 ,
You can post a few records by exporting them to Excel and then attaching it to a reply.
Can you post the log again? The OPTION statements should have caused SAS to produce diagnostic messages. I want the macro but also the code immediately following the macro. There should be compile and execution log statements.
At a minimum, I'm looking for this and what follows it:
NOTE: The macro LOOP completed compilation without errors. 29 instructions 1784 bytes.
Jim
152 %macro loop(year, nfirms);
153 OPTIONS MCOMPILENOTE=NOAUTOCALL;
154 OPTIONS SYMBOLGEN;
155 OPTIONS MPRINT;
156 /* Loop by firm &i for year &year. This will generate a dataset of
156! firm i-j AcctComp for each year. */
157
158 data temp1a; set temp1; if year(datadate1)=&year;
159 proc sort; by gvkey1;
160
161 data nfirms; set temp1a;
162 keep gvkey1;
163 proc sort nodupkey; by gvkey1;
164
165 data nfirms; set nfirms; id = _N_;
166
167 data temp1a; merge temp1a nfirms; by gvkey1;
168
169 %do i= 1 %to &nfirms;
170
171 data est1; set temp1a;
172 proc sort nodupkey; by gvkey1;
173
174 data est2; set temp1a; if id = &i;
175
176 PROC SQL;
177 CREATE TABLE est2 AS SELECT
178 est2.*, est1.gvkey1 AS gvkey_j, est1.a_i AS a_j, est1.b_i AS b_j
179 FROM est2 LEFT JOIN est1
180 ON est2.gvkey1 ~= est1.gvkey1
181 AND est2.sic2 = est1.sic2;
182 QUIT;
183
184 data est2; set est2;
185 a_dif = a_i - a_j;
186 b_dif = b_i - b_j;
187 drop a_i a_j b_i b_j;
188
189 proc sort; by gvkey1 datadate1 fqenddt1 datacqtr1 gvkey_j;
190
191 data est2; set est2;
192
193 proc sort; by gvkey1 datadate1 fqenddt1 datacqtr1 gvkey_j fqenddt;
194
195 data est2; set est2;
196 error = ABS(a_dif + bhr * b_dif);
197
198 proc sort; by gvkey1 datadate1 gvkey_j fqenddt;
199
200 PROC UNIVARIATE NOPRINT; by gvkey1 datadate1 gvkey_j;
201 VAR error ;
202 OUTPUT OUT = univ N=n MEAN = me_error ;
203
204 data univ; set univ;
205 acctcomp = -1 * me_error*100;
206 acctcomp = round(acctcomp, 0.001);
207
208 rename gvkey1 = gvkey_i;
209 rename datadate1 = datadate_i;
210
211 if not missing (acctcomp);
212 if 14 <= n <= 16;
213
214 drop me_error n;
215
216 PROC APPEND BASE = final data = univ; RUN;
217
218 %end ;
219
220 data QDC.acctcomp&year; set final;
221
222 proc sort; by gvkey_i datadate_i gvkey_j;
223
224 %mend loop;
NOTE: The macro LOOP completed compilation without errors.
30 instructions 1888 bytes.
Excellent! 👍👍
Thank you, @Winnie1.
I need just a bit more from your log, please. The three OPTION statements are diagnostic in nature. They'll make certain things show up in the log. They won't fix anything, but they'll help us determine what is wrong.
OPTIONS MCOMPILENOTE=NOAUTOCALL;
OPTIONS SYMBOLGEN;
OPTIONS MPRINT;
The first option statement produced the statement:
NOTE: The macro LOOP completed compilation without errors.
Now, I need to see another say 20 to 30 lines. I need to see where the LOOP macro is called and what SAS wrote to the log after the call.
Jim
Hi Jim,
Thank you so much for the help! And thank you for your patience with a new user. Here is all logs I have in log window. The code I got is from website posted by others. I guess it worked with others.
306 data QDC.temp1;set temp1;run; NOTE: There were 236480 observations read from the data set WORK.TEMP1. NOTE: The data set QDC.TEMP1 has 236480 observations and 12 variables. NOTE: DATA statement used (Total process time): real time 0.66 seconds cpu time 0.04 seconds 307 308 proc sort data=temp1; by year;run; NOTE: Input data set is already sorted, no sorting done. NOTE: PROCEDURE SORT used (Total process time): real time 0.00 seconds cpu time 0.00 seconds 309 310 %macro loop(year, nfirms); 311 OPTIONS MCOMPILENOTE=NOAUTOCALL; 312 OPTIONS SYMBOLGEN; 313 OPTIONS MPRINT; 314 /* Loop by firm &i for year &year. This will generate a dataset of 314! firm i-j AcctComp for each year. */ 315 316 data temp1a; set temp1; if year(datadate1)=&year; 317 proc sort; by gvkey1; 318 319 data nfirms; set temp1a; 320 keep gvkey1; 321 proc sort nodupkey; by gvkey1; 322 323 data nfirms; set nfirms; id = _N_; 324 325 data temp1a; merge temp1a nfirms; by gvkey1; 326 327 %do i= 1 %to &nfirms; 328 329 data est1; set temp1a; 330 proc sort nodupkey; by gvkey1; 331 332 data est2; set temp1a; if id = &i; 333 334 PROC SQL; 335 CREATE TABLE est2 AS SELECT 336 est2.*, est1.gvkey1 AS gvkey_j, est1.a_i AS a_j, est1.b_i AS b_j 337 FROM est2 LEFT JOIN est1 338 ON est2.gvkey1 ~= est1.gvkey1 339 AND est2.sic2 = est1.sic2; 340 QUIT; 341 342 data est2; set est2; 343 a_dif = a_i - a_j; 344 b_dif = b_i - b_j; 345 drop a_i a_j b_i b_j; 346 347 proc sort; by gvkey1 datadate1 fqenddt1 datacqtr1 gvkey_j; 348 349 data est2; set est2; 350 351 proc sort; by gvkey1 datadate1 fqenddt1 datacqtr1 gvkey_j fqenddt; 352 353 data est2; set est2; 354 error = ABS(a_dif + bhr * b_dif); 355 356 proc sort; by gvkey1 datadate1 gvkey_j fqenddt; 357 358 PROC UNIVARIATE NOPRINT; by gvkey1 datadate1 gvkey_j; 359 VAR error ; 360 OUTPUT OUT = univ N=n MEAN = me_error ; 361 362 data univ; set univ; 363 acctcomp = -1 * me_error*100; 364 acctcomp = round(acctcomp, 0.001); 365 366 rename gvkey1 = gvkey_i; 367 rename datadate1 = datadate_i; 368 369 if not missing (acctcomp); 370 if 14 <= n <= 16; 371 372 drop me_error n; 373 374 PROC APPEND BASE = final data = univ; RUN; 375 376 %end ; 377 378 data QDC.acctcomp&year; set final; 379 380 proc sort; by gvkey_i datadate_i gvkey_j; 381 382 %mend loop; NOTE: The macro LOOP completed compilation without errors. 30 instructions 1888 bytes. 383 run;
Where are you actually using the macro though? Where’s the macro call?
We should see a line like this that calls the macro, so far you’ve just shown the definition.
%loop(2019, 85);
So to call Macro, I should add this line first: %loop(year, nfirms), is that right?
Yes.
The code between %Macro and %Mend doesn't actually execute until you call it. You have to call the macro with a % sign and then the name of the macro or it won't do anything.
It should generally look something like this. The values 2019 and 5 are just examples. Of course you should use values that make sense for what you're doing.
%MACRO Loop(year, nfirms);
...macro code goes here...
%MEND Loop;
%Loop(2019, 5);
Jim
SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!
Learn how use the CAT functions in SAS to join values from multiple variables into a single value.
Find more tutorials on the SAS Users YouTube channel.
Ready to level-up your skills? Choose your own adventure.