BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
chzieg01
Fluorite | Level 6

Dear All,

 

I am learning PROC REPORT and am having trouble with arrays when I run my array programming in a compute block.

What I am trying to do is color code/traffic light cells of ‘one’ variable given the value of ‘another’ variable. In total I will have around 200 variables representing the ‘one’ variable and 200 variables representing the ‘another’ variable.

 

Below is code that I was able to get to work not using arrays:

PROC REPORT data=xxx ;

  column   role Indvars  N x1 x1cc x2 x2cc x3 x3cc x4 x4cc x5 x5cc x6 x6cc x7 x7cc x8 x8cc                       

                                          x9 x9cc x10 x10cc;
  define Indvars / display  left width=25 ;
  define N / display  format=3. width=3;
  define x1 /  display   format=4.2 width=4; 

  define x1cc / noprint display ;
  define x2 /  display   format=4.2 width=4;   Define x2cc   / noprint display ;

  define x3 /  display   format=4.2 width=4;  Define x3cc / noprint display ;
  define x4 /  display   format=4.2 width=4;   Define x4cc   / noprint display ;

  define x5 /  display   format=4.2 width=4;  Define x5cc / noprint display ;
  define x6 /  display   format=4.2 width=4;   Define x6cc   / noprint display ;

  define x7 /  display   format=4.2 width=4;  Define x7cc / noprint display ;
  define x8 /  display   format=4.2 width=4;   Define x8cc   / noprint display ;

  define x9   display   format=4.2 width=4;  Define x9cc / noprint display ;
  define x10 /  display   format=4.2 width=4;   Define x10cc   / noprint display ;

  define role /  group order=internal  'Role' width=15;
  break after role / skip ;

  compute x1cc  ;
           if x1cc  = 1 then do;

           call define('x1' ,  'style', 'style={background=red');

           end;
           if x1cc = 2 then do;  call define('x1' ,  'style', 'style={background=green');  end;
           if x1cc = 3 then do; call define('x1' ,  'style', 'style={background=pink'); end;
           if x1cc = 4 then do; call define('x1' ,  'style', 'style={background=lightgreen');  end;
           if x1cc =  5 then do; call define('x1' ,  'style', 'style={background=yellow'); end;
  endcomp;    
run;

 

Next is my code when using array programming inside PROC REPORT:

 

Define dummy / computed noprint ; /*This is also specified on the column

                                                                    statement*/

 

  compute dummy;

  array xx (10) x1 x2 x3 x4 x5 x6 x7 x8 x9 x10;

  array cc (10) x1cc x2cc x3cc x4cc x5cc x6cc x7cc x8cc x9cc   

                x10cc;

  do i=1 to 10;

  compute cc  ;

  if cc(i) = 1 then do;

  call define('xx(i) ,  'style', 'style={background=red');

  end;

 

  compute cc  ;

  if cc(i) = 2 then do;

  call define(xx(i) ,  'style', 'style={background=green');

  end;

 

  compute cc  ;

  if cc(i) = 3 then do;

  call define(xx(i) ,  'style', 'style={background=pink');

  end;

 

  compute cc  ;

  if cc(i) = 4 then do;

  call define(xx(i) ,  'style', 'style={background=lightgreen');

  end;

 

  compute cc  ;

  if cc(i) = 5 then do;

  call define(xx(i) ,  'style', 'style={background=yellow');

  end;

 EndComp; 

RUN;

 

When I run the program with arrays I get the following error message:

 

“NOTE 49-169: The meaning of an identifier after a quoted string might change in a future SAS release.  Inserting white space between a quoted string and the succeeding identifier is recommended.

 

I have googled this note and have found a lot of questions regarding it, but still do not understand what is going on. After I get this note it also will not allow other PROCs to be run unless I exit SAS. Also, notice below I created a dummy variable. I did this because I seen that other code online had this, but I am not sure why this was done, i.e., what is the purpose of the dummy variable?

Any insight that someone is willing to share is greatly appreciated.

 

Sincerely,

 

Craig

1 ACCEPTED SOLUTION

Accepted Solutions
Cynthia_sas
SAS Super FREQ
Hi:
I'm glad you were helped by my code example and explanation. No need to write anything up. I've sent my manager a link to the Forum posting (technology is wonderful for things like that!)

Cynthia

View solution in original post

14 REPLIES 14
ballardw
Super User

Very simple: Arrays do not exist in Proc Report or most other procedures .

Correction: I thought OP was expecting the ARRAY from the data step to carry through to report.

You have to explicitly name each variable in Proc report code except in places where variable lists such as x1 - x10 might be valid.

Or

Use the syntax correctly with out unbalanced quotes in the call define and such.

FreelanceReinh
Jade | Level 19

Hi Craig,

 

I think this NOTE 49-169 and the other symptoms you describe are due to the unbalanced (opening) single quote in the first CALL DEFINE statement

if cc(i) = 1 then do;

  call define('xx(i) ,  'style', 'style={background=red');

 

And I believe that ARRAY statements are among the SAS language elements which are available in compute blocks of PROC REPORT. (I haven't used this feature, but it's mentioned in Carpenter's Complete Guide to the SAS REPORT Procedure, p. 182.)

chzieg01
Fluorite | Level 6

Dear ballardw,

 

First, think you for your help last week (I was able to use the retain statement to get the variables I need).

 

Second, has Array processing been taken out of PROC REPORT or was it never apart of PROC REPORT? Below is information from sas.com:   http://support.sas.com/kb/43/765.html

 

which states:

 

"When a PROC REPORT COMPUTE block is executed, the DATA step compiler is called. The PROC REPORT COMPUTE block can handle most of the DATA step functionality, including a variation of array processing. The sample code on the Full Code tab illustrates how to apply a character and a numeric array within a COMPUTE block. A dummy column is added to the COLUMN statement and is used as the COMPUTE block to handle the array processing.

 

I have also seen examples online here which uses arrays in PROC REPORT and PDFs from SAS conferences which state array programming can be done in PROC REPORT, but this might all be outdated.

 

Perhaps I should try macros.

 

Sincerely and thanks for your response,

 

Craig

 
Cynthia_sas
SAS Super FREQ
Hi, you did not supply any data for your program, so it's impossible to run.

A comment...typically, the first argument to CALL DEFINE is a quoted string or an expression that resolves to the column name being highlighted. Only _COL_ and _ROW_ do NOT need to be quoted.

Cynthia
chzieg01
Fluorite | Level 6

Dear FreelanceReinhard and Cynthia_SAS,

 

Thank you for your responses.  FreelanceReinhard, your suggestion stopped the problem of the original note: NOTE 49-169. But I am still getting an error message:

 

compute xxcc;
       -------
       180
ERROR 180-322: Statement is not valid or it is used out of proper order.

ERROR 68-185: The function xxcc is unknown, or cannot be accessed.

ERROR: There was 1 unclosed DO block.

 

Cynthia_SAS, I changed the Call Defines to:

 

call define('disparage(q)' ,  'style',  'style={background=red');

 

and

 

call define('disparage'(q) ,  'style',  'style={background=red');

 

but got the same error message as mentioned above.

 

Cynthia_SAS, at your suggestion I created a sas program file which contains the creation of the dataset using input/card statement and attached it (let me know if this is not the proper way of doing this).  I would appreciate you, or anybody else that's interested, looking at it.

 

Sincerely,

 

Craig

Cynthia_sas
SAS Super FREQ

Hi:
Your COMPUTE block has to be either for an item that exists on the COLUMN statement OR has to be a COMPUTE block associated with a break point. I did not see an XXCC variable on your COLUMN statement.

I'm in meetings this afternoon, but I'll look at your program as I have the chance.

 

 But, I did take this screen shot of your earlier code because I meant to also say that you can't "nest" compute blocks like this:

no_nest.png

Cynthia

chzieg01
Fluorite | Level 6

Hi Cynthia_sas,

 

I am not sure what you mean by:

 

"Your COMPUTE block has to be either for an item that exists on the COLUMN statement OR has to be a COMPUTE block associated with a break point. I did not see an XXCC variable on your COLUMN statement."

 

If I list XXCC on the column statement how is this integrated with the array programming? I thought the arrays go into the DUMMY variable.  

 

You also mentioned "I meant to also say that you can't "nest" compute blocks like this:"  How can they be nested? I attempted

if-else if; statements:

 

  do i=1 to 10;
  compute cc  ;
  if cc(i) = 1 then do;
  call define('xx(i)' ,  'style', 'style={background=red');

  else if cc(i) = 2 then do;
  call define('xx(i)' ,  'style', 'style={background=green');
  else if cc(i) = 3 then do;
  call define('xx(i)' ,  'style', 'style={background=pink');
  else if cc(i) = 4 then do;
  call define('xx(i)' ,  'style', 'style={background=lightgreen');
  else if cc(i) = 5 then do;
  call define('xx(i)' ,  'style', 'style={background=yellow');
  end;
endcomp;

 

ERROR 180-322: Statement is not valid or it is used out of proper order.

ERROR 160-185: No matching IF-THEN clause.

ERROR: There were 5 unclosed DO blocks.

 

And:

  do i=1 to 10;
  compute cc  ;
  if cc(i) = 1 then do;
  call define('xx(i)' ,  'style', 'style={background=red');
  end;
  else if cc(i) = 2 then do;
  call define('xx(i)' ,  'style', 'style={background=green');
  end;
  else if cc(i) = 3 then do;
  call define('xx(i)' ,  'style', 'style={background=pink');
  end;
  else if cc(i) = 4 then do;
  call define('xx(i)' ,  'style', 'style={background=lightgreen');
  end;
  else if cc(i) = 5 then do;
  call define('xx(i)' ,  'style', 'style={background=yellow');
  end;
endcomp;

 

and got the following:


ERROR: There was 1 unclosed DO block.

NOTE: The preceding messages refer to the COMPUTE block for dummy.
NOTE: Will not run due to compilation errors.

 

Have a good weekend!!!

ballardw
Super User

When asking about errors please copy the code (entire Proc or data step, not just part) and error messages from the log as one operation.

Paste the copied lines into a code box opened using the forum's {I} icon at the top of the message window. The error messages such as

ERROR 180-322: Statement is not valid or it is used out of proper order.

often have an indicator as to where SAS determined the error. However without the code we can't see that. And if you paste into the main window then the formatting is removed and the indicators appear in the wrong place because the forum message windows remove a lot of white space characters like blanks and tabs.

 

 

And read the messages. SAS is pretty good about providing meaningful error messages in most cases. For instance:

  do i=1 to 10;
  compute cc  ;
  if cc(i) = 1 then do;
  call define('xx(i)' ,  'style', 'style={background=red');
  end;
  else if cc(i) = 2 then do;
  call define('xx(i)' ,  'style', 'style={background=green');
  end;
  else if cc(i) = 3 then do;
  call define('xx(i)' ,  'style', 'style={background=pink');
  end;
  else if cc(i) = 4 then do;
  call define('xx(i)' ,  'style', 'style={background=lightgreen');
  end;
  else if cc(i) = 5 then do;
  call define('xx(i)' ,  'style', 'style={background=yellow');
  end;
endcomp;

and got the following:
ERROR: There was 1 unclosed DO block.

 

Where is the end of the DO block started by do I=1 to 10?

 

Cynthia_sas
SAS Super FREQ

Hi:

  PROC REPORT does NOT use the ARRAY names anywhere but inside the COMPUTE block. Your understanding of why you are using a dummy variable and how PROC REPORT processes the COMPUTE block is not clear. PROC REPORT can ONLY have a COMPUTE block for an item that exists on the COLUMN statement. Or, as an example of using a "location"...you had this:

BREAK AFTER ROLE/SKIP;

However, SKIP is ignored by ODS destinations because it is a LISTING only option. But you can have this to get a blank line underneath each ROLE group:

COMPUTE AFTER ROLE;

   line ' ';

ENDCOMP;

 

AFTER is a LOCATION. The COMPUTE block is going to write an empty line AFTER the whole group for each ROLE. As an example of locations and COMPUTE, see page 9, output figure 6 of this paper: https://support.sas.com/resources/papers/proceedings17/SAS0431-2017.pdf  Here's a teaser for what you can do in COMPUTE blocks using LOCATIONS:

pg9_output6.png

With a location such as AFTER or BEFORE in the COMPUTE block, you can add extra lines to PROC REPORT output.

 

To "touch" a specific column on a report, you must use the name that appears on the COLUMN statement. You have 10 variables whose colors you want to change. In a brute force coding technique you could do this:

brute_force.png

 

 

but you'd then need 10 COMPUTE blocks, one for each "pair" of variables x?cc and x?. Note that in my COLUMN statements, I put each x?cc variable to the left of the x? variable. That's because of PROC REPORT's "left-to-right" rule. If, in the brute force method, I need to test one variable inside the COMPUTE block for another variable, then the color variable has to appear on the COLUMN statement before the variable I want to test. This is a timing issue. Here's an example using SASHELP.CLASS:

left_right_rule.png

Note the position of NAME relative to AGE on the COLUMN statement. The IF statement in the COMPUTE block for AGE will fail because at the point in time when PROC REPORT puts AGE on the report row, it has not yet put NAME on the report row, so PROC REPORT has no visibility of the value for NAME in order to do the IF test. On the other hand, in the COMPUTE block for WEIGHT or the COMPUTE block for DUMMY, NAME can be tested because it has already been put on the report row.

 

Your thought to use an array was right, but your use of the COMPUTE block and trying to nest the COMPUTEs was incorrect. To make your approach work, you would have had to do this in the COMPUTE block for DUMMY:

array_no_fmt.png

 

It's better than the brute force code, but can still be simplified. The key in making the arrays work, even with hard-coded values for the CALL DEFINE, was to use the VNAME function. The arguments to the CALL DEFINE have to all be quoted strings or expressions that resolve to a character value. with the statement:

thisvar = vname(xx(i));

 

  I am using the VNAME function to get the name of the variable in XX(I). That way, I can use the THISVAR temporary variable in the CALL DEFINE statement inside the DO loop. So the first time through the look, THISVAR will hold the value X1 and the second time through the loop, THISVAR will hold the value X2, etc. Now, THISVAR is a variable that can be used in the CALL DEFINE. Then, if you want to hard-code the colors, you need a set of cascading IF/ELSE:

       if cc(i) = 1 then do;
          call define(thisvar,'style','style={background=red}' );
       end;
       else if cc(i) = 2 then do;
          call define(thisvar,'style','style={background=green}' );
       end;
       else if cc(i) = 3 then do;
          call define(thisvar,'style','style={background=pink}' );
       end;
       else if cc(i) = 4 then do;
          call define(thisvar,'style','style={background=lightgreen}' );
       end;
       else if cc(i) = 5 then do;
          call define(thisvar,'style','style={background=yellow}' );
       end;

 

However, that can all be simplified with the use of a user-defined format to create another temporary variable to hold the style string for background color. Let's assume that I'm going to build this format called XCLR to make a look-up list for every value of the X?CC variables:

proc format;
  value xclr 1='red'
             2='green'
             3='pink'
             4='lightgreen'
             5='yellow';
run;

 

Now, I can create another temporary variable called SVAR in the compute block that is going to be used to build the value of the style string:

 

Here's the revised COMPUTE block for DUMMY:

  compute dummy;
    length thisvar $3 svar $30;
    array xx (10) x1 x2 x3 x4 x5 x6 x7 x8 x9 x10;
    array cc (10) x1cc x2cc x3cc x4cc x5cc x6cc x7cc x8cc x9cc x10cc;
    dummy=1;
    do i=1 to 10;
        svar = catt('style={background=',put(cc(i),xclr.),'}');
        thisvar = vname(xx(i));
        call define(thisvar ,  'style', svar );
    end;
  endcomp;

 

Here's the final program...done as a screen shot to save real estate. Also got rid of all the LISTING only options, such as SKIP and WIDTH. I also used a COMPUTE AFTER ROLE to do the equivalent of the SKIP. And, I changed the usage of ROLE to ORDER to avoid the error message in the log. Since all the other variables were a usage of DISPLAY, the correct usage for ROLE was ORDER, not GROUP.

array_with_fmt.png

 

Hope this helps explain things. Complete code for program #1 is below. Did not bother with "brute force" or program #2 because program #1 is less code.

 

Cynthia

 

proc format;
  value xclr 1='red'
             2='green'
             3='pink'
             4='lightgreen'
             5='yellow';
run;
  
** remember to NOPRINT x?cc variables after testing;
title '1) with arrays and simplified with format';
PROC REPORT data=xxx ;
  column   role Indvars N x1cc x1 x2cc x2 x3cc x3 x4cc x4 
                          x5cc x5 x6cc x6 x7cc x7 x8cc x8                      
                          x9cc x9 x10cc x10 dummy;
  define role /  order order=internal  'Role'  ;
  define Indvars / display  left   ;
  define N / display  format=3.  ;
  define x1 /  display   format=4.2 ; 
  define x1cc /   display  ;
  define x2 /  display   format=4.2  ;   
  Define x2cc   /  display   ;
  define x3 /  display   format=4.2  ;  
  Define x3cc /  display   ;
  define x4 /  display   format=4.2  ;   
  Define x4cc   /  display  ;
  define x5 /  display   format=4.2  ;  
  Define x5cc /  display  ;
  define x6 /  display   format=4.2  ;   
  Define x6cc   /  display  ;
  define x7 /  display   format=4.2  ;  
  Define x7cc /  display   ;
  define x8 /  display   format=4.2  ;   
  Define x8cc   /  display  ;
  define x9 /  display   format=4.2 ;  
  Define x9cc /  display   ;
  define x10 /  display   format=4.2  ;   
  Define x10cc   /  display    ;
  Define dummy / computed noprint ; 
  compute after role ;
    line ' ';
  endcomp;
  compute dummy;
    length thisvar $3 svar $30;
    array xx (10) x1 x2 x3 x4 x5 x6 x7 x8 x9 x10;
    array cc (10) x1cc x2cc x3cc x4cc x5cc x6cc x7cc x8cc x9cc x10cc;
    dummy=1;
    do i=1 to 10;
        svar = catt('style={background=',put(cc(i),xclr.),'}');
        thisvar = vname(xx(i));
        call define(thisvar ,  'style', svar );
    end;
  endcomp;
run;

 

Tom
Super User Tom
Super User

@Cynthia_sas

WOW!

 

One nice thing about this solution is that the use of a single compute block on the last (dummy) column means that the column that is being used to drive the color can appear after the column it is coloring.

 

Cynthia_sas
SAS Super FREQ
Hi, Tom:
Yes, that is true. But since I generally start off with "brute force" method just to be sure I've got a desired output to aim for when I switch to the dummyvar method, I generally am still in the habit of coding them to the left.

Cynthia
chzieg01
Fluorite | Level 6
Dear Cynthia_SAS,

Thank you for much for this code and the explanation. Let me digest this and I will reply with more input later.
chzieg01
Fluorite | Level 6

Dear Cynthia_SAS, Tom, ballardw, and FreelanceReinhard,

 

I want to thank you all for helping me, especially Cynthia_SAS for all of her efforts. I believe I understand the long form array method that cynthia_sas described to color code my report and also believe I understand the simplified method using the format method to hold the style string for background color - I was able to generate my report on 'real' variables and data.  The vname function is used to determine which variable a specific array element is referencing; I would have not got this or many other things without your help. Likewise, I feel I understand 'dummy' variables in compute blocks as being a placeholder that is defined last in the column statement, so that the 'actual last variable's' response choice are not the choices reported for each row in the report as this could cause possible 'stair stepping' in the report.

 

I also played around with the line statement.  I tried to create footnotes using the line statement. They looked fine in SAS's results viewer window, but when I used ods rtf, they got messed up (this is what took most of my time in reporting back to you guys). I went ahead and used a footnote statement and it works fine.

 

cynthia_sas I appreciate you working on this on Saturday. If you give me the name, title and email address of your supervisor I would be glad to write a brief email to him acknowledging your efforts.

 

Sincerely.

Cynthia_sas
SAS Super FREQ
Hi:
I'm glad you were helped by my code example and explanation. No need to write anything up. I've sent my manager a link to the Forum posting (technology is wonderful for things like that!)

Cynthia

sas-innovate-2024.png

Join us for SAS Innovate April 16-19 at the Aria in Las Vegas. Bring the team and save big with our group pricing for a limited time only.

Pre-conference courses and tutorials are filling up fast and are always a sellout. Register today to reserve your seat.

 

Register now!

How to Concatenate Values

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.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 14 replies
  • 2956 views
  • 5 likes
  • 5 in conversation