Having the Proc Report Improvements Suggested in the 2000 SASware Ballot NOW! By David Trenery

Size: px
Start display at page:

Download "Having the Proc Report Improvements Suggested in the 2000 SASware Ballot NOW! By David Trenery"

Transcription

1 Having the Proc Report Improvements Suggested in the 2000 SASware Ballot NOW! By David Trenery * 1

2 2 The current SASware ballot includes the following items for the SAS Report Procedure 49) provide the ability to suppress summary lines when there is only one observation for a group 50) add the ability to dynamically align text from LINE statements with report columns 52) provide the ability to print the word 'CONTINUED' when a report spans pages and to print 'END' on the last page

3 3 1999/2000 SASware ballot Survey Results Overall Rank Item # provide the ability to suppress summary lines when there is only one observation for a group add the ability to dynamically align text from LINE statements with report columns provide the ability to print the word 'CONTINUED' when a report spans pages and to print 'END' on the last page

4 4 Item 49: Provide the ability to suppress summary lines when there is only one observation for a group. Adds clarity to report

5 With and without summary suppression Without: Sector Manager Sales Northeast Alomar Northwest Brown Pelfrey Reveiz 1, , ========== 2, With: Sector Manager Sales Northeast Alomar Northwest Brown Pelfrey Reveiz 1, , ========== 2,

6 With and without summary suppression Without: Sector Manager Sales Northeast Alomar Northwest Brown Pelfrey Reveiz 1, , ========== 2, With: Sector Manager Sales Northeast Alomar Northwest Brown Pelfrey Reveiz 1, , ========== 2,

7 7 Step by step!create a format that writes out numbers as blanks. PROC FORMAT; VALUE _blank_ low-high = ' ';!Find the number of observations in each group by using the N statistic. DEFINE N / noprint;!identify those groups with only one observation. IF N = 1...

8 !Identify summary lines - these have missing values for the variables that have a definition of ORDER or GROUP.!Use the CALL DEFINE statement to change the format for a column. CALL DEFINE (_col_,"format","_blank_.");!inside a compute block conditionally apply the blanking format to summary lines for groups with one observation. IF N = 1 and manager = ' ' and sector = ' ' then CALL DEFINE (_col_,"format","_blank_."); 8

9 Note that the method described here does not actually suppress lines but blanks them out. 9

10 Complete Report code PROC FORMAT; VALUE _blank_ low-high = ' '; PROC REPORT data=grocery headskip nowd; COLUMN sector manager n sales; DEFINE sector / order 'Sector' '--'; DEFINE manager / order 'Manager' '--'; DEFINE sales / analysis sum format=comma10.2 'Sales' '--'; DEFINE N / noprint; BREAK AFTER sector / ol summarize skip suppress; RBREAK AFTER / dol summarize; COMPUTE sales; IF n=1 then DO; IF manager=' ' and sector=' ' then CALL DEFINE(_col_,"format","_blank_."); ELSE CALL DEFINE(_col_,"format","dollar11.2"); ELSE IF manager=' ' and sector=' ' then DO; CALL DEFINE(_col_,"format","dollar11.2"); ENDCOMP; 10

11 Complete Report code PROC FORMAT; VALUE _blank_ low-high = ' '; PROC REPORT data=grocery headskip nowd; COLUMN sector manager n sales; DEFINE sector / order 'Sector' '--'; DEFINE manager / order 'Manager' '--'; DEFINE sales / analysis sum format=comma10.2 'Sales' '--'; DEFINE N / noprint; BREAK AFTER sector / ol summarize skip suppress; RBREAK AFTER / dol summarize; COMPUTE sales; IF n=1 then DO; IF manager=' ' and sector=' ' then CALL DEFINE(_col_,"format","_blank_."); ELSE CALL DEFINE(_col_,"format","dollar11.2"); ELSE IF manager=' ' and sector=' ' then DO; CALL DEFINE(_col_,"format","dollar11.2"); ENDCOMP; 11

12 Complete Report code PROC FORMAT; VALUE _blank_ low-high = ' '; PROC REPORT data=grocery headskip nowd; COLUMN sector manager n sales; DEFINE sector / order 'Sector' '--'; DEFINE manager / order 'Manager' '--'; DEFINE sales / analysis sum format=comma10.2 'Sales' '--'; DEFINE N / noprint; BREAK AFTER sector / ol summarize skip suppress; RBREAK AFTER / dol summarize; COMPUTE sales; IF n=1 then DO; IF manager=' ' and sector=' ' then CALL DEFINE(_col_,"format","_blank_."); ELSE CALL DEFINE(_col_,"format","dollar11.2"); ELSE IF manager=' ' and sector=' ' then DO; CALL DEFINE(_col_,"format","dollar11.2"); ENDCOMP; 12

13 Complete Report code PROC FORMAT; VALUE _blank_ low-high = ' '; PROC REPORT data=grocery headskip nowd; COLUMN sector manager n sales; DEFINE sector / order 'Sector' '--'; DEFINE manager / order 'Manager' '--'; DEFINE sales / analysis sum format=comma10.2 'Sales' '--'; DEFINE N / noprint; BREAK AFTER sector / ol summarize skip suppress; RBREAK AFTER / dol summarize; COMPUTE sales; IF n=1 then DO; IF manager=' ' and sector=' ' then CALL DEFINE(_col_,"format","_blank_."); ELSE CALL DEFINE(_col_,"format","dollar11.2"); ELSE IF manager=' ' and sector=' ' then DO; CALL DEFINE(_col_,"format","dollar11.2"); ENDCOMP; 13

14 Complete Report code e PROC FORMAT; VALUE _blank_ low-high = ' '; PROC REPORT data=grocery headskip nowd; COLUMN sector manager n sales; DEFINE sector / order 'Sector' '--'; DEFINE manager / order 'Manager' '--'; DEFINE sales / analysis sum format=comma10.2 'Sales' '--'; DEFINE N / noprint; BREAK AFTER sector / ol summarize skip suppress; RBREAK AFTER / dol summarize; COMPUTE sales; IF n=1 then DO; IF manager=' ' and sector=' ' then CALL DEFINE(_col_,"format","_blank_."); ELSE CALL DEFINE(_col_,"format","dollar11.2"); ELSE IF manager=' ' and sector=' ' then DO; CALL DEFINE(_col_,"format","dollar11.2"); ENDCOMP; 14

15 15 Item 50: Add the ability to dynamically align text from LINE statements with report columns Essential if LINE statements are to be used in multiple or frequently re-run tables.

16 16 The use of LINE statements LINE!""""""""""""""""""""""""""!# LINE Page 1 ;!# $$$$%&'()*$$$$+,-,.&*$$$$$$$%,/&0 $$ $$ $$ $$%)2(3&,0($$4)-&0$$$$$$$$$56766 $$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$:6766 $$$$$$$$$$$$$%;<(3$$$$$$$$$=6766 $$$$$$$$$$$$$$$$$$$$$$$$$$>66766 $$$$$$$$$$$$$$$$$$$$$$$$$$>96766 $$$$$$$$$$$$$$$$$$$$$$$$$$$?6766 $$$$"""""""""""""""""""""""""" $$$$$$$$$$$$$@,.&$>

17 17 To place text in right place must specify the start position. Page 1 ; Can find the start position thorough trial and error. Can change depending on: LINESIZE CENTER/NOCENTER option Lengths of the variables Formats

18 18 Produce the report twice 1st - find column positions 2nd - use column positions and produce final report

19 Step by step! Create a stored copy of the report with the BOX option. PROC REPORT data=grocery(obs=1) nowd BOX;!The OBS=1 option reduces its size. $$$A B $$$C%DEFGH$$$$$+IJIKDH$$$$$$%ILD%C $$$M N N O $$$C%)2(3&,0(C$%;<(3$$C$$$$$$$$=6C $$$P Q Q R!Use PROC PRINTTO to send the report to a temporary SAS catalog. 19

20 !Read in stored report with DATASTEP DATA cols; INFILE temp length=len; INPUT line $varying%sysfunc(getoption(linesize)). len; col1=index(line,'+'); IF col1>0; linelen=length(left(line))-3; Finds the current setting for LINESIZE CALL SYMPUT('col1', col1); CALL SYMPUT('linelen',linelen); 20

21 !Read in stored report with DATASTEP DATA cols; INFILE temp length=len; INPUT line $varying%sysfunc(getoption(linesize)). len; col1=index(line,'+'); IF col1>0; Corner symbol is + linelen=length(left(line))-3; CALL SYMPUT('col1', col1); CALL SYMPUT('linelen',linelen); 21

22 !Read in stored report with DATASTEP DATA cols; INFILE temp length=len; INPUT line $varying%sysfunc(getoption(linesize)). len; col1=index(line,'+'); IF col1>0; linelen=length(left(line))-3; CALL SYMPUT('col1', col1); CALL SYMPUT('linelen',linelen); Find the length of the BOX 22

23 !Read in stored report with DATASTEP DATA cols; INFILE temp length=len; INPUT line $varying%sysfunc(getoption(linesize)). len; col1=index(line,'+'); IF col1>0; linelen=length(left(line))-3; CALL SYMPUT('col1', col1); CALL SYMPUT('linelen',linelen); * Store as macro variables 23

24 !Find position of each corner DATA _null_; SET cols end=eof; IF eof; colno=0; DO UNTIL(col=0); IF colno => 1 then DO; colname= compress('col' put(colno,3.)); CALL SYMPUT(colname,col); OUTPUT; colno=colno+1; col=index(line,'+'); IF col ne 0 then substr(line,col,1)='#'; 24

25 !Use these macro variables in your LINE statement uline = repeat( 'S', &linelen ); uline $200.; Page 1 ; $$%&'()*$$$$+,-,.&*$$$$$$$%,/&0 $$ $$ $$ $$%)2(3&,0($$4)-&0$$$$$$$$$56766 $$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$ $$$$$$$$$$$$$$$$$$$$$$$$$$$:6766 $$$$$$$$$$$$$%;<(3$$$$$$$$$=6766 $$$$$$$$$$$$$$$$$$$$$$$$$$>66766 $$$$$$$$$$$$$$$$$$$$$$$$$$>96766 $$$$$$$$$$$$$$$$$$$$$$$$$$$?6766 $$ $$@,.&$> 25

26 V8 enhancement The PROC REPORT statement supports this new option: FORMCHAR= defines the characters to use as line-drawing characters in the report. SAS OnlineDoc Version 8 e 26

27 27 Item 52: Provide the ability to print the word 'CONTINUED' when a report spans pages and to print 'END' on the last page Standard practice in many organisations

28 28 Sector Manager Sales Northwest Brown Page 1 (continued) Print Continued when report spans pages %&'()*$$$$$+,-,.&*$$$$$$$%,/&0 $$"""""""""""""""""""""""""""""" $$%)2(3T&0($$F,U/)*$$$$$$$$=8766 $$$$$$$$$$$$$$$$$$$$$$$$$$>86766 $$$$$$$$$$$$$$$$$$$$$$$$$$>96766 $$$$$$$$$$$$$$$$$$$$$$$$$$$=6766 $$$$$$$$$$$$$$$$$>96766 $$$$$$$$$$$$$$$$$$$$$$$$$$$?6766 $$H&V&<W$$$$$$$$$$$$$$$$$$$X6766 $$YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY $$@,.&$8$$$$$$$$$$$Z&-[\ Print End on last page

29 29 Sector Manager Sales Northwest Brown Page 1 (continued) Single line across bottom of page %&'()*$$$$$+,-,.&*$$$$$$$%,/&0 $$"""""""""""""""""""""""""""""" $$%)2(3T&0($$F,U/)*$$$$$$$$=8766 $$$$$$$$$$$$$$$$$$$$$$$$$$>86766 $$$$$$$$$$$$$$$$$$$$$$$$$$>96766 $$$$$$$$$$$$$$$$$$$$$$$$$$$=6766 $$$$$$$$$$$$$$$$$>96766 $$$$$$$$$$$$$$$$$$$$$$$$$$$?6766 $$H&V&<W$$$$$$$$$$$$$$$$$$$X6766 $$YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY $$@,.&$8$$$$$$$$$$$Z&-[\ Except the last which has a double line

30 30 Sector Manager Sales Northwest Brown Page 1 (continued) %&'()*$$$$$+,-,.&*$$$$$$$%,/&0 $$"""""""""""""""""""""""""""""" $$%)2(3T&0($$F,U/)*$$$$$$$$=8766 $$$$$$$$$$$$$$$$$$$$$$$$$$>86766 $$$$$$$$$$$$$$$$$$$$$$$$$$>96766 $$$$$$$$$$$$$$$$$$$$$$$$$$$=6766 $$$$$$$$$$$$$$$$$>96766 $$$$$$$$$$$$$$$$$$$$$$$$$$$?6766 $$H&V&<W$$$$$$$$$$$$$$$$$$$X6766 $$YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY $$@,.&$8$$$$$$$$$$$Z&-[\ Page number

31 31 Method Let the Report Procedure:!determine the number of observations in the dataset!keep track of how many it has processed!if these are equal the end of the report is reached

32 A report with grouping variables 32

33 33 Step by step! Determine the number of observations DEFINE N / 'N' noprint; COMPUTE before; nobs = n; ENDCOMP;

34 34 Step by step! Determine the number of observations DEFINE N / 'N' noprint; COMPUTE before; nobs = n; ENDCOMP; Note that statements inside the COMPUTE BEFORE block are executed at the start of the report creation process.

35 35! Create a COMPUTE AFTER block for the grouping variable that determines the pagination. COMPUTE after sector; ENDCOMP;

36 !Inside this increment the counter COMPUTE after sector; ENDCOMP; group = sum(group,n); This is called group for reasons that will become apparent latter. Note that I use the sum function to increment because the counter is initially missing 36

37 37! Conditionally execute code. COMPUTE after sector; ENDCOMP; group = sum(group,n); IF group = nobs then DO; continued = '(end) '; ELSE DO; continued = '(continued)';!create a variable that has the value (end) or (continued).

38 38! Specify a LINE statement that uses our specially created variables COMPUTE after sector; group = sum(group,n); IF group = nobs then DO; continued = '(end) '; ELSE DO; continued = '(continued)'; continued $200.; ENDCOMP;

39 39 This may seem more logical IF group = nobs then DO; '(end) '; ELSE DO; '(continued)'; But will result in: (end) (continued)

40 A report with ordering variables 40

41 41 Step by step!define a dummy computed variable and add to the end of the COLUMN statement COLUMN... EACHOBS; DEFINE eachobs / computed noprint;

42 42! Determine the number of observations COMPUTE before; nobs = n; ENDCOMP;

43 43! Initialise the counter variable COMPUTE before; nobs = n; order = 0 ENDCOMP;

44 44! Increment the counter variable for each observation COMPUTE eachobs; order + 1; ENDCOMP;

45 45! Create a COMPUTE AFTER block for the ordering variable that determines the pagination. COMPUTE after sector; ENDCOMP;

46 !Inside this decrease the counter by one COMPUTE after sector; ENDCOMP; order = order - 1; Every time this block executes the EACHOBS block also executes. If there are more blocks this may have to be added order = order

47 ! Conditionally execute code. COMPUTE after sector; order = order - 1 ; IF order = nobs then DO; continued = '(end) '; ELSE DO; continued = '(continued)'; ENDCOMP; * 47

48 48 Switching between reports %let type = order; PROC REPORT... DEFINE manager / &type; IF &type = nobs then DO;...

49 49 Switching between reports %let type = order; PROC REPORT... DEFINE manager / &type; IF &type = nobs then DO;...

50 50 Switching between reports %let type=order; PROC REPORT... DEFINE manager / &type; IF &type = nobs then DO;...

51 51 An alternative method for when a convenient paging variable is not available

52 Create a paging variable PROC SORT data=grocery; BY sector manager; DATA grocery2; SET grocery; RETAIN count 0 _page_ 1; count=count+1; IF count > 6 then DO; _page_+1; count=1; %let type=group; 52

53 Create a paging variable PROC SORT data=grocery; BY sector manager; DATA grocery2; SET grocery; RETAIN count 0 _page_ 1; count=count+1; IF count > 6 then DO; _page_+1; count=1; %let type=group; 53

54 Create a paging variable PROC SORT data=grocery; BY sector manager; DATA grocery2; SET grocery; RETAIN count 0 _page_ 1; count=count+1; IF count > 6 then DO; _page_+1; count=1; %let type=group; 54

55 PROC REPORT data=grocery2 headline nowd; COLUMN _page_ sector... n eachobs; DEFINE _page_ / group noprint; DEFINE sector / &type; DEFINE N / 'N' noprint; DEFINE eachobs / computed noprint; COMPUTE before; order=0; nobs=n; ENDCOMP; COMPUTE eachobs; order+1; ENDCOMP; BREAK after _page_ / page; COMPUTE after _page_ ; order=order-1; IF &type=nobs then continued='(end) '; ELSE continued='(continued)'; continued $200.; ENDCOMP; COMPUTE after sector; order=order-1; group=sum(group,n); ENDCOMP; V6 55

56 56 An important V8 enhancement The COMPUTE statement on page 905 supports this new option: _PAGE_ places information at the top or bottom of each page. SAS OnlineDoc Version 8

57 PROC REPORT data=grocery headline nowd; COLUMN _page_ sector... n eachobs; DEFINE _page_ / group noprint; DEFINE sector / &type; DEFINE N / 'N' noprint; DEFINE eachobs / computed noprint; COMPUTE before; order=0; nobs=n; ENDCOMP; COMPUTE eachobs; order+1; ENDCOMP; BREAK after _page_ / page; COMPUTE after _page_ ; order=order-1; IF &type=nobs then continued='(end) '; ELSE continued='(continued)'; continued $200.; ENDCOMP; COMPUTE after sector; order=order-1; group=sum(group,n); ENDCOMP; V8 57

58 The end 58

59 59

60 Having the Proc Report Improvements Suggested in the 2000 SASware Ballot NOW! By David Trenery, Aventis Pharma UK Ltd, Denham Abstract The current SASware ballot includes the following items for the SAS Report Procedure: 49) provide the ability to suppress summary lines when there is only one observation for a group 50) add the ability to dynamically align text from LINE statements with report columns 52) provide the ability to print the word 'CONTINUED' when a report spans pages and to print 'END' on the last page Items 49 and 52 where also in the 1999 SASware ballot, ranked 8 th and 20 th overall. As they are in the ballot you might be excused for thinking that you can not incorporate these features into your reports created using the latest version, Version 8. This is not so. In fact, I will show you how to create reports with these features in Version Introduction Have you ever wished for something and found that you have had it all the time? Well I find this is frequently true of the Report Procedure. An illustration of this are the current SASware ballot items for the Report Procedure. The current SAS software ballot includes the following items for the Report Procedure: 49) provide the ability to suppress summary lines when there is only one observation for a group 50) add the ability to dynamically align text from LINE statements with report columns 52) provide the ability to print the word 'CONTINUED' when a report spans pages and to print 'END' on the last page Items 49 and 52 where also in the 1999 SASware ballot, ranked 8 th and 20 th overall. As they are in the ballot you might be excused for thinking that you can not incorporate these features into your reports created using the latest version, Version 8. This is not so. In fact, I will show you how to create reports with these features in Version The foundations of this paper must be laid to rest at my bosses feet. For they have been invariably very particular and insistent on how they want in their reports matter how impractical it seems at first. Their inflexibly as caused me to explore new ways of doing things in search of making the impractical practical. My work has demanded that I have the abilities requested in these three SASware ballot items, NOW! I will go through how I achieved each of the SASware ballot items in turn. In my mind item 50 is particularly critical as without it the use of LINE statements becomes impractical when a large volume of reports are created or re-run. Examples and data All the examples given use the SASUSER.GROCERY dataset and formats that can be created using SAS Help. To create these - from inside a SAS session start with the Help pull down menu and make the following choices. SAS>Help>Extended Help> SAS System Help>Main menu> Report Writing>REPORT>Examples> Code for Datasets and Permanent Formats

61 Item 49: Provide the ability to suppress summary lines when there is only one observation for a group. Suppressing summary lines when there is only one observation adds to the clarity of the report. Below are two simple tables produced by Proc Report, the upper illustrates the problem and the lower is the desired table. Note that in the upper table first sector Northeast only has one row and the value of its sales (90.00) appears a second time in the summary row (which I have bolded). The table could be improved as illustrated in the lower table by suppressing the summary line when there is only one observation in a group. Without summary suppression: Sector Manager Sales Northeast Alomar Northwest Brown Pelfrey Reveiz 1, , With summary suppression: ========== 2, Sector Manager Sales Northeast Alomar Northwest Brown Pelfrey Reveiz 1, , ========== 2, The solution is extremely easy once you know. Briefly, the CALL DEFINE statement can be used to change formats (the CALL DEFINE statement has had this ability at least since ). Values to be suppressed can have what I call a blanking format applied to them, i.e., a format which writes a value out as a series of blanks. The CALL DEFINE statement can then conditionally applied when both of the following conditions are true: there is one observation per group, and it is a summary line. Step by step. 1 - Create a format that writes out numbers as blanks. PROC FORMAT; VALUE _blank_ low-high = ' '; 2 - Find the number of observations in each group by using the N statistic. DEFINE N / noprint; 3 - Identify those groups with only one observation (these will have N=1). 4 - Identify summary lines - these have missing values for the variables that have a definition of ORDER or GROUP. 5 - Use the CALL DEFINE statement to change the format for a column. Inside a compute block conditionally apply the blanking format to summary lines for groups with one observation. IF N = 1 and manager = ' ' and sector = ' ' then CALL DEFINE (_col_,"format","_blank_."); Note that the method described here does not actually suppress lines but blanks them out. The complete code can be found in the appendix at the end of this paper.

62 Item 50: Add the ability to dynamically align text from LINE statements with report column The ability to dynamically align text is essential if LINE statements are to be used in multiple or frequently re-run tables. To place the text in the right place using a LINE statement you need to specify the start position. I used to find the start position through a time consuming process of trial and error. The problem with this was that if the same report is run again with a different line size specified, or the CENTER/NOCENTER option changed, or the lengths of the variables changed then the whole process of finding the right position must be repeated. This is not practical in an environment where many reports are produced repeatedly. In this environment the functionality provided by the LINE must be abandoned or a method for dynamically finding column positions must be found. In brief, to align text I produce the report twice. The first time with the BOX option for only a single observation, which I send to a SAS catalog. The BOX option places a box around each cell of the report. I use only a single observation because this is all that is needed. I read the stored report back into a SAS dataset to find the bottom line of the box and then the positions of the box dividers. These positions are placed into macro variables and the report is created a second time without the BOX option, with all the observations and the LINE statements with the right start positions. Step by step. 1 - Create a stored copy of the report. This copy has the BOX option on the PROC REPORT statement specified to place a box around the report. I use PROC PRINTTO to send the report to a temporary SAS catalog. By using the OBS=1 option you can reduce the size of the stored report. PROC REPORT data=grocery(obs=1) nowd BOX; 2 - I read the stored report with a DATASTEP. I keep only the line that forms the bottom of the box, i.e., the last line that contains the box corner character. This is a + in most cases but you can confirm this by checking the value of the FORMCHAR option. 3 - Find the length of the box, taking 3 off. One for two outer most corners which extend past the end of the report and an additional one. Store in a macro variable. linelen = length(left(line)) -3; 4 - Find the position of each corner and store in macro variables. Below is the code I use. DATA _null_; colno=0; DO UNTIL(col=0); IF colno => 1 then DO; colname = compress('col' put(colno,3.)); CALL SYMPUT(colname,col); OUTPUT; colno = colno+1; col = index(line,'+'); IF col ne 0 then substr(line,col,1) = '#'; 5 - Use these macro variables in your LINE statement. The code below will draw a line from the left edge to the right edge of the report. uline = repeat( '=', &linelen ); uline $200.; The complete code for this example can be found in the appendix. This ability to dynamically align text with report columns makes use of LINE statements practical in multiple or repeatedly run tables. Without this capability the positions of text by the LINE statement reeds to be checked each time the table is run.

63 Item 52: Provide the ability to print the word 'CONTINUED' when a report spans pages and to print 'END' on the last page The indication of the continuation or end of report is standard practice in many organisations. Below is an example showing the Report Procedure s ability to do this. In addition it has: 1 - A single line across the bottom of each page except for the last, which has a double line. 2 - A page number at the foot of each page of the report. Sector Manager Sales Northwest Brown Page 1 (continued) Sector Manager Sales Southeast Jones Smith Page 2 (continued) Sector Manager Sales Southwest Taylor Reveiz ============================== Page 3 (end) The method entails, letting the Report Procedure determine how many observations there are in the dataset and it keeping a track of how many observations it has processed. These are the same when the end of the report is reached. I have adapted this method to work both summary and ordered list style reports. First I will show how this works in a summary report (one that groups at least one variable), then an ordered list (one with an order variable) report. Then I will synthesise these two methods into one which can easily switch from one to the other. Finally I will show an alternative method which can be applied in more general situations. Step by step for a summary report 1 - Determine the number of observations in the dataset, creating a variable NOBS with this information in it. Note that statements inside the COMPUTE BEFORE block are executed at the start of the report creation process. DEFINE N / 'N' noprint; COMPUTE before; nobs = n; ENDCOMP; 2 - Create a COMPUTE AFTER variablename block, where variablename is the name of the group variable that determines the pagination. In the case of the example it is sector. COMPUTE after sector;...; ENDCOMP; 3 - Inside this block increment the counter. I have called the counter group for reasons that will become apparent latter. Note that I use the sum function to increment because the counter is initially missing. group = sum(group,n); 4 - Inside the same block conditionally execute code. Create a variable that has the value (end) or (continued). IF group = nobs then DO; continued = '(end) '; ELSE DO; continued = '(continued)'; 5 - Specify a LINE statement that uses our specially created variables. continued $200.;

64 Note: It may seem more logical to have conditionally placed end s or continued s in the following manner: IF count = nobs then DO; '(end) '; ELSE DO; '(continued)'; However this will not produce the desired result. LINE statements ignore conditional statements and are always executed. Consequently this code will produce two lines on each page the first with (end) the next with (continued). Step by step for an ordered list style report The order list style report is actually slightly more complex. 1 - Define a computed variable, in the example it is called EACHOBS. Add this variable to the end of the COLUMN statement. This is a dummy variable so make sure that this it is not printed. COLUMN... EACHOBS; DEFINE eachobs / computed noprint; 2 - As the summary report, determine the number of observations in the dataset, creating a variable NOBS with this information in it. 3 - Inside the COMPUTE BEFORE block initialise the counter variable to 0. I have called the counter variable ORDER, why will become apparent latter. For some reason that I haven t fathomed yet the ORDER=SUM(ORDER,1) method does not work without initialising the counter variable. COMPUTE before; order = 0; nobs = n; ENDCOMP; 4 - Increment the counter variable for each observation. Note that this compute block will not only execute for each observation but additionally each time another compute block executes. COMPUTE eachobs; order + 1; ENDCOMP; 5 - As for the summary report, create a COMPUTE AFTER variablename block, where variablename is the name of the group variable that determines the pagination. COMPUTE after sector;...; ENDCOMP; 6 - Inside this block decrease the counter by one. Every time this block executes the COMPUTE eachobs block also executes. The 1 here counterbalances the +1 in the COMPUTE eachobs block. If your report has more compute blocks then the code below may need to be added to them. order = order - 1; 7 - As for the summary report, inside the same block conditionally execute code. 8 - As for the summary report, specify a LINE statement that uses our specially created variables. To switch easily between summary and ordered list reports I have both the GROUP and ORDER counters in my Proc Report code. At the start I set a macro variable indicating the report type to either ORDER or GROUP. The following statements incorporated into my Proc Report code swaps from a summary to ordered list report. %LET type = ORDER; PROC REPORT..; DEFINE manager / &type; IF &type=nobs then DO; The appendix contains the complete example code. I have also added page numbering and conditional underlining to this. An alternative method for when a convenient paging variable is not available The previous method relied on the existence of a convenient variable, which changed for each page. Not all datasets have such a variable. I have not yet found away to place conditional text at the bottom of

65 a page without one. From my understanding of how the Report Procedure works I do not believe it is possible in version A simple solution is to create a paging variable. Below the simple code to do this. But first sort the data in the order it is to appear in the report. DATA grocery2; SET grocery; RETAIN count 0 page 1; count = count + 1; IF count > 7 then DO; page + 1; count = 1; This creates a variable PAGE which increments each time a new page is desired. I have chosen a page size of 7 here purely for illustrative purposes. The creation of such a variable has other advantages. Now the total number of pages in the report is known and can be used in the report. A word of warning, take care when you use the FLOW option on the DEFINE statement it as this may increase the number of lines an observation uses, so it pays to have a little leeway in the page size. Below is an example report. Sector Manager Sales Northwest Brown Reveiz Southeast Smith Jones Page 1 of 2 (continued) Sector Manager Sales Southeast Smith Southwest Taylor ============================== Page 2 of 2 (end) The report code following shows the use of the page variable to: * 1 - Determine the last page. * 2 - Cause a page break. * 3 - Place at the foot of the report the current and total page number. * 4 - Execute any code specific to the last page. PROC REPORT data=grocery2 headline nowd; COLUMN page sector manager sales page = pagemax; DEFINE page / order noprint; * 2; DEFINE sector / order 'Sector'; DEFINE manager / order 'Manager'; DEFINE sales / analysis sum format=comma10.2 'Sales'; DEFINE pagemax / analysis max noprint; * 1; COMPUTE before ; * 1; lastpage = pagemax; ENDCOMP; BREAK after page / page; * 2; COMPUTE after page; pageno = compbl('page ' put(page,3.) ' of ' put(lastpage,3.)); * 3; IF page = lastpage then DO; * 1;... * 4; page $200.; * 3; ENDCOMP; Note that the page variable appears twice in the COLUMN statement. I am using it both as an ordering variable and an analysis variable so I need to have it twice with different aliases.

66 Conclusion I guess that the moral of this paper is that the Report Procedure has some incredible capabilities you just need to discover them. Two things that have come up in this paper that I wish to highlight. These can cause confusion if you don t know about them: 1 - LINE statements always execute. 2 - COMPUTE blocks can execute more often than you suspect A further useful tip, all variables used in a line statement need a format. For character variables I normally use the $200. format. This works for variables that are written out with a shorter length and it saves changing the format if the variable length is increased. Finally here are my suggestions for the next SASware ballot: 1) Conditional execution of LINE statements 2) The ability to set the number of observations on a page 3) The addition END, FIRST., LAST. variables similar to those available in the datastep. References 1 SAS Guide to the REPORT Procedure, Reference, Release Appendix: Example code Item 49: Provide the ability to suppress summary lines when there is only one observation for a group. *--- First create a dataset that has some groups with only one observation ; DATA grocery; SET sasuser.grocery; BY sector notsorted; IF sector in( ne, se ) and not first.sector then DELETE; FORMAT manager $MGRFMT. sector $SCTRFMT.; *-- Create a format that will blank out a number --; PROC FORMAT; VALUE _blank_ low-high =' '; *--- Create report ; PROC REPORT data=grocery headskip nowd; COLUMN sector manager n sales; DEFINE sector / order 'Sector' '--'; DEFINE manager / order 'Manager' '--'; DEFINE sales / analysis sum format=comma10.2 'Sales' '--'; DEFINE N / noprint; BREAK AFTER sector / ol summarize skip suppress; RBREAK AFTER / dol summarize; COMPUTE sales; IF n=1 then DO; IF manager=' ' and sector=' ' then CALL DEFINE(_col_,"format","_blank_."); ELSE CALL DEFINE(_col_,"format","dollar11.2"); ELSE IF manager=' ' and sector=' ' then DO; CALL DEFINE(_col_,"format","dollar11.2"); ENDCOMP; RUN SAS and SASware ballot are registered trademarks of SAS Institute Inc. in the USA and other countries. indicates USA registration. The Author can be contacted at David Trenery Phone: (020) David@Trenery.com

67 Item 50: Add the ability to dynamically align text from LINE statements with report columns *--- Send the report to a SAS catalog ; FILENAME temp catalog 'work.report.colstart.source'; PROC PRINTTO print=temp new; PROC REPORT data=grocery(obs=1) nowd box; COLUMN sector manager sales ; PROC PRINTTO; *--- Find the box ; DATA cols; INFILE temp length=len; INPUT line $varying%sysfunc(getoption(linesize)). len; col1 = index(line,'+'); * Corners are indicated by '+' in my case; IF col1 > 0; * Select only lines with + ; *--- Find the start and length of the report ; DATA _null_; SET cols end=eof; IF eof; * Select the last line; *--- Find the length of the report ; linelen = length(left(line))-3; * Subtract 1 each for the left and right corners and an additional 1; CALL SYMPUT('linelen',linelen); colno = 0; DO UNTIL(col = 0); IF colno => 1 then DO; colname = compress('col' put(colno,3.)); CALL SYMPUT(colname,col); OUTPUT; colno = colno+1; col = index(line,'+'); IF col ne 0 then substr(line,col,1) = '#'; Item 52: Provide the ability to print the word 'CONTINUED' when a report spans pages and to print 'END' on the last page *--- Specify whether the report is a summary (GROUP) or ordered list (ORDER) ; %let type=order; *--- Create the report ; PROC REPORT data=grocery headline nowd; COLUMN sector manager n sales eachobs; DEFINE sector / group 'Sector'; DEFINE manager / &type 'Manager'; DEFINE sales / analysis sum format=comma10.2 'Sales'; DEFINE N / 'N' noprint; DEFINE eachobs / computed noprint; COMPUTE before; order = 0; nobs = n; ENDCOMP; COMPUTE eachobs; order + 1; ENDCOMP; COMPUTE after sector; page + 1; pageno = compbl('page ' put(page,3.)); group = sum(group,n); order = order - 1; * Either GROUP or ORDER is used in comparison; IF &type = nobs then DO; uline = repeat('=',29); continued = '(end) '; ELSE DO; uline = repeat('-',29); continued = '(continued)'; uline $200.; pageno continued $200.; LINE ' '; ENDCOMP; *--- Create report ; PROC REPORT data=grocery nowd headline; COLUMN sector manager sales ; DEFINE sector / group; DEFINE manager / group; DEFINE sales / analysis sum; COMPUTE after; uline=repeat('=', &linelen); uline $200.; ENDCOMP;