I have written a SAS/IML module to extend the SERIES subroutine to produce SGPLOT graphics using the SGPLOT SERIES statement and additional optional parameters. The PLOT_SERIES module accepts up to 6 pairs of (x, y) variables for plotting on the same set of axes. The series may be of differing lengths. Each series may be specified independently of the others using particular SGPLOT SERIES statements. You may specify PROC SGPLOT-specific statements also, and other statements are possible as well, such as XAXIS and YAXIS, &c.
The code for the PLOT_SERIES subroutine is included below.
start plot_series( p1, p2=, p3=, p4=, p5=, p6=, p7=, p8=, p9=, p10=, p11=, p12=,
procopt=,
s1opt=, s2opt=, s3opt=, s4opt=, s5opt=, s6opt=,
otheropts=
) ;
/* purpose: create 2-D plot of X vs Y as a series plot using SGPLOT called from within SAS/IML
* multiple variables may be plotted on the same set of axes
* up to 6 different pairs of variables may be specified, e.g., x1, y1, ..., x6, y6
*
* series-specific options may be specified by keyword parameters, e.g., s1opt='group=variable datalabel markers'
* each series may be specified independently of others by using s[i]opt= optional parameter, i=1:6
*
* other options, e.g., XAXIS, YAXIS are specified using the keyword parameter otheropts=
*
* parameters:
* p_i ::= parameters of the plot_series() module, i = 1:12
* procopt ::= PROC SGPLOT optional arguments
* s[i]opt ::= SGPLOT SERIES optional statements (keyword parameters) in quotes, i = 1:6
* otheropts ::= other options, e.g., XAXIS, YAXIS
*
* there may be up to six pairs of numeric x, numeric y comprising max of six series on same set of axes
*
* syntax:
* call plot_series{ x, y ) ;
* call plot_series{ x, y, x1, y1, x2, y2, ..., x5, y5 ) ; *** plot_series 6 different series in a single graph ***
* call plot{series( x, y, x1, y1, x2, y2, ..., x5, y5 ) s1opt='group=groupID' ; *** specify SGPLOT GROUP option for series 1 ***
* call plot_series{ x, y ) procopt='noautolegend noborder' ; *** turn off legend, border around graphs ***
* call plot_series{ x, y ) otheropts='XAXIS GRID ; YAXIS GRID' ; *** specify that grids be drawn on x, y axes ***
*
* examples:
* ods graphics on / height=6in width=6in ;
* proc iml ;
* *** adapted from SAS ODS Graphics Procedures Guide, SGPLOT Procedure
* *** Example 3: Plotting Three Series
*
* filename = 'sashelp.stocks' ;
*
* use ( filename ) var { date close low high } where( date >= "01jan2002"d & stock = "IBM" ) ;
* read all var { date close low high } ;
* close ( filename ) ;
*
* date1 = date ; date2 = date ; *** duplication necessary since IML does not allow repeated variable names in an argument list ***
*
* call plot_series( date, close, date1, low, date2, high ) ;
*
* quit ;
* ods graphics off ;
*================================================================================
* ods graphics on / width=4in height=4in ;
*
* title "Power Generation (Gigawatt Hours)" ;
* proc iml ;
* *** adapted from SAS ODS Graphics Procedures Guide, SGPLOT Procedure, Overview of the SGPLOT Procedure ***
*
* filename = 'sashelp.electric' ;
*
* use ( filename ) var { year coal naturalgas customer } where( year >= 2001 & customer = "Residential" ) ;
* read all var { year coal naturalgas } ;
* close ( filename ) ;
* * year1 = year ; *** duplication necessary since IML does not allow repeated variable names in an argument list *** *
* call plot_series( year , coal
* , year1, naturalgas
* )
* s1opt='datalabel' *** keyword parameter ***
* , s2opt='datalabel y2axis' *** ditto ***
* ;
* quit ;
* title ;
*
* ods graphics off ;
*/
/*============================================================================*/
/* parse parameter string for parm name (indep var, dep var), type (numeric, char)
/*============================================================================*/
parm_list = 'p1' : 'p12' ; /* create vector of parameter types to control SGPLOT statement generation */
/* parse parameter list to extract parm names, types */
parm_type = '' ;
do i = 1 to ncol( parm_list ) ;
call execute( 'arg_type = type(' || parm_list[ i ] || ') ;' ) ;
if arg_type = 'N' then arg_value = parentname( strip( parm_list[ i ] )) ; else
if arg_type = 'U' then arg_value = '.' ; else
if arg_type = 'C' then call error( 'plot_series', 'Parameters to be plotted must be numeric only' ) ;
if arg_value = ' ' then call error( 'plot_series', 'Missing or duplicated parameter in position' + char( i )) ;
parm_value = parm_value || arg_value ;
parm_type = rowcatc( parm_type || arg_type ) ;
end ;
n_parms = max( loc( parm_value ^= '.' )) ; /* determine # of parameters in plot_series() call */
parm_value = parm_value[ , 1 : n_parms ] ; /* strip off unused parameters */
parm_type = substr( parm_type, 1, n_parms ) ;
/* create "long" form of data matrix. distinguish btwn parameter pairs by group_num */
do i = 1 to n_parms by 2 ;
val_x = colvec( value( parm_list[ i ] )) ; /* x */
val_y = colvec( value( parm_list[ i + 1 ] )) ; /* y */
group_pts = nrow( val_x ) ;
group_num = repeat( i, group_pts ) ; /* group_num is join key for char var name, numeric (x,y) pair */
group_mat = group_mat // ( group_num || val_x || val_y ) ;
group_dim = group_dim || group_pts ;
end ;
group_ID = group_mat[ , 1 ] ; /* vector of group IDs for selecting from "long" mat by group */
max_dim = max( group_dim ) ;
/*============================================================================*/
/* create "wide" SAS dataset containing values of variables to be plotted
/*============================================================================*/
series_mat = j( max_dim, n_parms, . ) ;
/* convert "long" matrix to "wide" matrix */
uniq_group_ID = unique( group_ID ) ;
do i = 1 to ncol( uniq_group_ID ) ;
series_col = uniq_group_ID[ i ] ;
ndx = loc( series_col = group_ID )` ;
series_mat[ 1 : group_dim[ i ], series_col ] = group_mat[ ndx, 2 ] ; /* x */
series_mat[ 1 : group_dim[ i ], series_col + 1 ] = group_mat[ ndx, 3 ] ; /* y */
end ;
/* create variable names in output dataset
* write "wide" matrix to dataset
*/
series_names = parm_value ; /* switch to graphics terminology */
mattrib series_mat colname=series_names ;
create sgplot_data from series_mat[ colname=series_names ] ;
append from series_mat ;
close sgplot_data ;
/* use SAS/MACRO "call symput()" function to transmit PROC SGPLOT arguments to global environment */
if ^isEmpty( procopt ) then call symput( 'PROCOPT' , procopt ) ; else call symput( 'PROCOPT' , '' ) ;
if ^isEmpty( otheropts ) then call symput( 'OTHEROPTS', cat( otheropts, ';' )) ; else call symput( 'OTHEROPTS', '' ) ;
do i = 1 to 6 ;
series_opt = cats( 'S', char( i ), 'OPT' ) ;
call symput( series_opt, '' ) ;
end ;
do i = 1 to n_parms / 2;
buffer = 'series x=' + series_names[ 2 # i - 1 ] + ' y=' + series_names[ 2 # i ] ;
series_num = char( i ) ; series_opt = cats( 'S', series_num, 'OPT' ) ;
if ^isEmpty( value( series_opt )) then buffer = cat( buffer, '/ ', value( series_opt )) ;
buffer = buffer + ';' ;
call symput( series_opt, buffer ) ;
end ;
submit ;
proc sgplot data=sgplot_data &PROCOPT ;
%if %length( &S1OPT ) %then %do ; &S1OPT ; %end ;
%if %length( &S2OPT ) %then %do ; &S2OPT ; %end ;
%if %length( &S3OPT ) %then %do ; &S3OPT ; %end ;
%if %length( &S4OPT ) %then %do ; &S4OPT ; %end ;
%if %length( &S5OPT ) %then %do ; &S5OPT ; %end ;
%if %length( &S6OPT ) %then %do ; &S6OPT ; %end ;
%if %length( &OTHEROPTS ) %then %do ; &OTHEROPTS ; %end ;
run ;
endsubmit ;
finish plot_series ;
Here are some examples of use:
%macro CIRC ;
title 'circular functions' ;
twopi = 2 # constant( 'pi' ) ;
theta_01 = do( 0, twopi, .01 # twopi ) ; /* 101 points */
theta_02 = do( 0, twopi, .02 # twopi ) ; /* 51 points */
x1 = theta_01 ;
x2 = theta_02 ;
y1 = 2 # theta_01 ;
z1 = theta_01 ; z2 = theta_02 ;
sin_01 = sin( theta_01 ) ;
cos_01 = cos( theta_01 ) ; cos_01a = cos_01 ;
sin_02 = sin( theta_02 ) ;
cos_02 = cos( theta_02 ) ;
sin_cos_01 = sin_01 # cos_01 ;
ods graphics on / height=5in width=5in ;
run plot_series( x1, sin_01 ) ; /* simple x,y plot */
run plot_series( x1, sin_01, z1, cos_01 ) ; /* plot two variables on same axis */
run plot_series( x1, sin_01, z2, cos_02 ) ; /* plot differing-length series on same graph */
run plot_series( x2, sin_02, y1, cos_01, z1, sin_cos_01, sin_01, cos_01a ) /* use keyword parameters */
procopt='noborder' s1opt='datalabel' s2opt='x2axis'
;
ods graphics off ;
/* graph logarithmic spiral */
theta3 = do( 0, 8 # twopi, .02 # twopi ) ;
sin3 = sin( theta3 ) ;
cos3 = cos( theta3 ) ;
/* equation of logarithmic spiral is r = exp( a + b # theta ) */
a = -1 ; /* right-handed spiral */
b = 0.1749 ;
r = exp( a + b # theta3 ) ;
spiral_x = r # cos3 ;
spiral_y = r # sin3 ;
title 'Logarithmic Spiral' ;
ods graphics on / height=6in width=6in ;
run plot_series( spiral_x, spiral_y )
s1opt ='arrowheadpos=end arrowheadshape=barbed arrowheadscale=1.25'
otheropts='xaxis display=(nolabel) grid ; yaxis display=(nolabel) grid ;'
;
title ;
ods graphics off ;
title ;
%mend CIRC ;
%macro POWER ;
title "Power Generation (Gigawatt Hours)" ;
filename = 'sashelp.electric' ;
use ( filename ) var { year coal naturalgas customer } where( year >= 2001 & customer = "Residential") ;
read all var { year coal naturalgas } ;
close ( filename ) ;
year_dup = year ; /* duplication required since IML does not allow repeated occurrences of same variable in a module's argument list */
ods graphics on ;
call plot_series( year, coal
, year_dup, naturalgas
) ;
ods graphics off ;
title ;
%mend POWER ;
%macro STOCK ;
title "Stock Trends" ;
filename = 'sashelp.stocks' ;
use ( filename ) var { date close low high stock } where( date >= "01jan2000"d & stock = "IBM" ) ;
read all var { date close low high stock } ;
close ( filename ) ;
date1 = date ;
date2 = date ;
ods graphics on / width=6in height=6in ;
call plot_series( date, close, date1, low, date2, high ) ;
ods graphics off ;
title ;
%mend STOCK ;
%CIRC ;
%POWER ;
%STOCK ;
There is a caveat that must be observed when using PROC_SERIES: IML does not allow repeated parameters in an argument list because parameters in a module are called by reference, e.g., a parameter's values may be changed by the module, so if there is a sequence of parameters such as ( x, y, x, z ) then the second x is a duplication of the first one and PLOT_SERIES cannot refer to it by name since the parentname() function returns a blank value. The workaround is to duplicate x by creating a copy of x, e.g., x1 so that all of the names in the argument list are unique. Then we have the list ( x, y, x1, z ) which satisfies the IML parser.
The PLOT_SERIES code can be adapted to other PROC SGPLOT statements because the mechanism for invoking PROC SGPLOT via the IML SUBMIT/ENDSUBMIT bracket is easily modified to accommodate the requirements of other graphical displays, e.g., HBAR or SCATTER.
... View more