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

Hi Folks:

 

I'm trying to present 1-year survival by unit of month in the Table below. However, my attempt to modify a macro deisgnated to calculate annual to monthly rate is not successful and I have the column "range" produced with overlapped intervals. Desired output is shown in red. The second patient died before 12-months. 

The macro %lexis is attached to this post and can be found here too: http://www.pauldickman.com/survival/sas/lexis.sas.

 

Could you please help me produce the desired output?

I greatly appreciate your time!

Thanks in advance.

 

desired outcome.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 
;
%include 'C:\lexis.sas';

%lexis (data=have, out=have, breaks = %str( 0 to 1 by 0.083333), origin = dx, entry = dx, exit = exit,
fail = d, scale = 30.4, 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; 

%LEXIS

%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 ;
1 ACCEPTED SOLUTION

Accepted Solutions
Cruise
Ammonite | Level 13

@Reeza 

https://communities.sas.com/t5/SAS-Programming/Rounding-the-fixed-number-to-produce-unoverlapped-int...

 

I think PaigeMiller and I just fixed the problem under the new forum. Please let me know if any comments.

View solution in original post

5 REPLIES 5
Reeza
Super User
I feel like SCALE is supposed to take care of that, but I don't have data to test. You can't really test on two records.

Anyways, the breaks aren't set correctly either for given intervals are they? That seems like it doesn't quite match the data, but I'm not sure I understand that part correctly. I'll see if I have time this weekend to take a further stab, but this isn't a simple or straightforward problem IMO.
Cruise
Ammonite | Level 13

@Reeza 

 

Thanks Reeza.

 

Below is the original macro for an annual rate. They used 365.4 for annual and I used 30.4 for monthly rate and modifying steps in breaks by 1/12=0.083333 and expending 1-10 to 1-12. According to the author's STATA tutorial, 'breaks' specify the cutpoints for the lifetable intervals as an  ascending numlist commencing at zero. The cutpoints need not be integer nor equidistant but the units must be years, e.g., specify breaks(0(0.0833)5) for monthly intervals up to 5 years.

 

It will be great to hear from you back.

 

SAS totorial on the same approach:

http://www.pauldickman.com/survival/sas/relative_survival_using_sas.pdf

 

Dickman's STATA tutorial on the same macro

https://pdfs.semanticscholar.org/71e1/9adf869a8587ee5d03e0af2e5a3bdebcc42a.pdf

 

%lexis (data=&individ.,out=&individ., breaks = %str( 0 to 10 by 1 ), origin = dx,entry = dx, exit = exit,
fail = d,scale = 365.4, right = right,risk = y,lrisk = ln_y, lint = length,cint = w,nint = fu);

As you pointed out, using complete data is important. That is, original %lexis above doesn't produce correct range on the two subject only data. Full data is colon.sas7bdat which can be found here:

http://www.pauldickman.com/survival/sas/survival.sas . 

Full SAS code is here: http://www.pauldickman.com/survival/sas/survival.sas

 

Thanks again.

 

Cruise
Ammonite | Level 13

@Reeza the author wrote me back and basically said the same thing you mentioned. Scale and the length variables. Do I have to round 0.08333 to more decimal places?

 

paul.png

Reeza
Super User
I think you're not understanding breaks correctly because otherwise the example they set doesn't make sense. I think when looking at 0 to 1 that's setting breaks that are too small somehow, since I think the overall units are in days and that breaks it into fractions of days. Not sure what you're referencing above, but when I check the klexi.sas code, it shows breaks as days, such as 2, 5, 50 etc with the length of 365. So I think your breaks parameter is problematic.

FYI - to debug this, I would use the original sample data, change it until I got similar (not exactly the same results) by changing the units to months from years and then using it on my actual data.
Cruise
Ammonite | Level 13

@Reeza 

https://communities.sas.com/t5/SAS-Programming/Rounding-the-fixed-number-to-produce-unoverlapped-int...

 

I think PaigeMiller and I just fixed the problem under the new forum. Please let me know if any comments.

Ready to join fellow brilliant minds for the SAS Hackathon?

Build your skills. Make connections. Enjoy creative freedom. Maybe change the world. Registration is now open through August 30th. Visit the SAS Hackathon homepage.

Register today!
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
  • 5 replies
  • 762 views
  • 2 likes
  • 2 in conversation