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

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? 

desired range.png

 

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?

1 ACCEPTED SOLUTION

Accepted Solutions
PaigeMiller
Diamond | Level 26

To get range2, you multiply left and right by 12 (OR divide by length), then round to an integer

--
Paige Miller

View solution in original post

9 REPLIES 9
PaigeMiller
Diamond | Level 26

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.

--
Paige Miller
Cruise
Ammonite | Level 13

@PaigeMiller 

 

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.

 

showcase to Paige.png

 

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.

 

desired range.png

 

 

Cruise
Ammonite | Level 13

@PaigeMiller 

The author's response to my inquiry how to modify his annual rate macro to monthly and weekly is following.

dickman.png

 

and his complete program for calculating annual rate is here: http://www.pauldickman.com/survival/sas/survival.sas

 

PaigeMiller
Diamond | Level 26

To get range2, you multiply left and right by 12 (OR divide by length), then round to an integer

--
Paige Miller
Cruise
Ammonite | Level 13

That sounds like it. Can you help reflect your suggestion inside the macro please? I understand it but couldn't modify the macro.

PaigeMiller
Diamond | Level 26

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.

--
Paige Miller
Cruise
Ammonite | Level 13
🙂 Ok. I'll try and post it back in here
Cruise
Ammonite | Level 13

@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; 

rounded.png

Cruise
Ammonite | Level 13

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;

weekly.png

SAS Innovate 2025: Call for Content

Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 25. Read more here about why you should contribute and what is in it for you!

Submit your idea!

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
  • 9 replies
  • 1202 views
  • 3 likes
  • 2 in conversation