Hello folks :
I'm trying to calculate monthly survival rates using the %lexis macro shown in the code snippet below. DX and EXIT are date of diagnosis and date of censoring in days , respectively. Scale is set to 365.24 because time units need to be in years. In order to calculate monthly rate "breaks = %str( 0 to 1 by 0.083333)" was used where 0.083333~1/12
. However, I have to round 0.083333 in the calculation of a variable 'length' to more decimal places to produce a correct time range. The current range has overlapped borders: 0.2-0.2; 0.7-0.7 as shown int he image below. The desired output is either range 1 or range 2 depending on the number of places for rounding. Please see image below.
I don't use macro extensively. Would you please help in rounding of 0.08333 in the macro below to more decimal places to produce a desired time range such as range 1 or 2 shown in red in the image below?
data have;
input id age yydx year8594 dx exit d;
cards;
1 78 1978 0 6854 9365 1
21 76 1976 0 6063 6078 1
;
%macro Lexis ( data = , /* Data set with original data, */
/* defaults to _last_ */
out = , /* Where to put the result, */
/* defaults to &data. */
entry = entry, /* Variable holding the entry date */
exit = exit, /* Variable holding the exit date */
fail = fail, /* Variable holding the exit status */
/* If any of the entry, exit or fail */
/* variables are missing the person is */
/* discarded from the computations. */
breaks = , /* Specification of the cutpoints on */
/* the transformed scale. */
/* Syntax as for a do statement. */
/* The ONLY Mandatory argument. */
cens = 0, /* Code for censoring (may be a variable) */
scale = 1, /* Factor to transform from the scale */
/* of entry and exit to the scale */
/* where breaks and risk are given */
origin = 0, /* Origin of the transformed scale */
risk = risk, /* Variable recieving the risk time */
lrisk = lrisk, /* Variable recieving the log(risk time) */
left = left, /* Variable recieving left endpoint of int */
other = , /* Other dataset statements to be used such */
/* as: %str( format var ddmmyy10. ; ) */
/* or: %str( label risk ="P-years" ; ) */
disc = discrd, /* Dataset holding discarded observations */
/*-------------------------------------------------------------*/
/* Variables for making life-tables and other housekeeping: */
/* These will only appear in the output dataset if given here */
/* The existence of these arguments are tested in the macro so */
/* they cannot have names that are also logical operators such */
/* as: or, and, eq, ne, le, lt, gt. */
/*-------------------------------------------------------------*/
right = , /* Variable recieving right endpoint of int */
lint = , /* Variable recieving interval length */
os_left = , /* Variable recieving left endpoint of int */
os_right = , /* Variable recieving right endpoint of int */
os_lint = , /* Variable recieving interval length */
/* - the latter three on original scale */
cint = , /* Variable recieving censoring indicator */
/* for the current input record */
nint = /* Variable recieving index of follow-up */
/* interval; */
);
%if &breaks.= %then %put ERROR: breaks MUST be specified. ;
%if &data. = %then %let data = &syslast. ;
%if &out. = %then %do ;
%let out=&data. ;
%put
NOTE: Output dataset not specified, input dataset %upcase(&data.) will be overwritten. ;
%end ;
data &disc. &out. ;
set &data. ;
if ( nmiss ( &entry., &exit., &fail., &origin. ) gt 0 )
then do ; output &disc. ;
goto next ;
end ;
* Labelling of variables ;
label &entry. = 'Entry into interval' ;
label &exit. = 'Exit from interval' ;
label &fail. = 'Failure indicator for interval' ;
label &risk. = 'Risktime in interval' ;
label &lrisk. = 'Natural log of risktime in interval' ;
label &left. = 'Left endpoint of interval (transformed scale)' ;
%if &right.^= %then label &right. = 'Right endpoint of interval (transformed scale)' ; ;
%if &lint.^= %then label &lint. = 'Interval width (transformed scale)' ; ;
%if &os_left.^= %then label &os_left. = 'Left endpoint of interval (original scale)' ; ;
%if &os_right.^= %then label &os_right. = 'Right endpoint of interval (original scale)' ; ;
%if &os_lint.^= %then label &os_lint. = 'Interval width (original scale)' ; ;
%if &cint.^= %then label &cint. = 'Indicator for censoring during the interval' ; ;
%if &nint.^= %then label &nint. = 'Sequential index for follow-up interval' ; ;
&other. ;
drop _entry_ _exit_ _fail_
_origin_ _break_
_cur_r _cur_l _int_r _int_l
_first_ _cint_ _nint_;
/*
Temporary variables in this macro:
_entry_ holds entry date on the transformed timescale
_exit_ holds exit date on the transformed timescale
_fail_ holds exit status
_break_ current cut-point
_origin_ origin of the time scale
_cur_l left endpoint of current risk interval
_cur_r right endpoint of current risk interval
_int_l left endpoint of current break interval
_int_r right endpoint of current break interval
_first_ indicator for processing of the first break interval
_cint_ indicator for censoring during the interval
_nint_ sequential index of interval
If a variable with any of these names appear in the input dataset it will
not be present in the output dataset.
*/
_origin_ = &origin. ;
_entry_ = ( &entry. - _origin_ ) / &scale. ;
_exit_ = ( &exit. - _origin_ ) / &scale. ;
_fail_ = &fail. ;
_cur_l = _entry_ ;
_first_ = 1 ;
do _break_ = &breaks. ;
if _first_ then do ;
_nint_=-1;
_cur_l = max ( _break_, _entry_ ) ;
_int_l = _break_ ;
end ;
_nint_ + 1;
_first_ = 0 ;
_int_r = _break_ ;
_cur_r = min ( _exit_, _break_ ) ;
if _cur_r gt _cur_l then do ;
/*
Endpoints of risk interval are put back on original scale.
If any of left or right are specified the corresponding endpoint
of the break-interval are output.
*/
&entry. = _cur_l * &scale. + _origin_ ;
&exit. = _cur_r * &scale. + _origin_ ;
&risk. = _cur_r - _cur_l ;
&lrisk. = log ( &risk. ) ;
&fail. = _fail_ * ( _exit_ eq _cur_r ) +
&cens. * ( _exit_ gt _cur_r ) ;
_cint_ = not( _fail_ ) * ( _exit_ eq _cur_r ) ;
%if &left.^= %then &left. = _int_l ; ;
%if &right.^= %then &right. = _int_r ; ;
%if &lint.^= %then &lint. = _int_r - _int_l ; ;
%if &os_left.^= %then &os_left. = _int_l * &scale. + _origin_ ; ;
%if &os_right.^= %then &os_right. = _int_r * &scale. + _origin_ ; ;
%if &os_lint.^= %then &os_lint. = ( _int_r - _int_l ) * &scale. ; ;
%if &cint.^= %then &cint. = _cint_ ; ;
%if &nint.^= %then &nint. = _nint_ ; ;
output &out. ;
end ;
_cur_l = max ( _entry_, _break_ ) ;
_int_l = _break_ ;
end ;
next: ;
run ;
%mend Lexis;
%lexis (data=have, out=have, breaks = %str( 0 to 1 by 0.083333), origin = dx, entry = dx, exit = exit,
fail = d, scale = 365.24, right = right, risk = y, lrisk = ln_y, lint = length, cint = w, nint = fu);
data have; set have;
range=put(left,4.1) || ' - ' || left(put(right,4.1));
run;
proc print; run;
@PaigeMiller interested in this kinda problem?
To get range2, you multiply left and right by 12 (OR divide by length), then round to an integer
However, I have to round 0.083333 in the calculation of a variable 'length'to more decimal places to produce a correct time range. The current range has overlapped borders: 0.2-0.2; 0.7-0.7 as shown int he image below. Either range 1 or range 2 is fine depending on the number of places for rounding.
I do not understand the relationship between the 0.083333 and the 0.2-0.2. Please explain.
Also, please show us the desired output.
Interval length in the life table is defined by the 'breaks' in the macro. For example, breaks = %str( 0 to 10 by 1) would create 1 year length intervals such as 0-1, 1-2, 2-3, 3-4, 4-5, 5-6, 6-7. 7-8, 8-9, 9-10 for 10 year annual survival rates. Here the unit of transforming scale as they called is one. Therefore, macro variables called left (left endpoint of interval) and right (right endpoint of interval) starts with 0 to 1 for the 1st year survival rate then continues until 9-10.
However,
because underlying unit of time based on the input data (annual population lifetable) is a calendar year, I have to cut one year time unit by 12 which is 0.08333~1/12 and define that length in the breaks which is: breaks=%str(0 to 1 by 0.08333) to cut one-year length to 12 parts. This would ideally create 0-1mo, 1-2mo et,c, 11-12 mo intervals in the final life table I posted below (range 1 or range 2 in red). However, 0.083333 doesn't straight forwardly work as 1 unit as was the case for 1-10 annual rates where transforming scale unit is an integer 1.
When Zero is taken as the left endpoint of the first interval the following image explains why I get 0.2-0.2 and 0.7-0.7 ranges overlapped in the life table. Please see life table I also included in this response.
Macro variable for a variable 'length' is a '&lint' and &lint is created as below in the macro.
%if &left.^= %then &left. = _int_l ; ;
%if &right.^= %then &right. = _int_r ; ;
%if &lint.^= %then &lint. = _int_r - _int_l ;
Does it clarify? My problem is that I don't how to round 0.08333 in the macro. But I conceptually understand the underlying logic.
The desired output is the range 1 or range 2 in the image below depending how many places we decided to round for.
The author's response to my inquiry how to modify his annual rate macro to monthly and weekly is following.
and his complete program for calculating annual rate is here: http://www.pauldickman.com/survival/sas/survival.sas
To get range2, you multiply left and right by 12 (OR divide by length), then round to an integer
That sounds like it. Can you help reflect your suggestion inside the macro please? I understand it but couldn't modify the macro.
I don't think this is a problem with the macro. I think the problem is here
data have; set have;
range=put(left,4.1) || ' - ' || left(put(right,4.1));
run;
and I challenge you to make the proper changes here to fix the problem.
@PaigeMiller do you mean this?
data have1; set have;
range=put(left*12,4.0) || ' - ' || left(put(right*12,4.0));
run;
proc print; run;
This created weekly rates:
%lexis (data=have, out=have_week, breaks = %str( 0 to 1 by 0.0192307692307692), origin = dx, entry = dx, exit = exit,
fail = d, scale = 365.4, right = right, risk = y, lrisk = ln_y, lint = length, cint = w, nint = fu);
data have_week1; set have_week;
range=put(left*52,4.0) || ' - ' || left(put(right*52,4.0));
run;
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.