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

I am looking for advice on how to produce quality single graphic image of grouped timelines [SAS v9.3]. Time is measured in days post-inoculation (dpi). The data are for two species each with 4 subjects. There are a couple different types of events that occur for most subjects but each at independent timings.

Envisioning a single graphic image of horizontal timelines stacked vertically by subject within specie. All timelines start at same left vertical position and proceed rightward to end for each subject at the last event. Event types distinguished by different markers.

I've mostly used SAS for stats. But I'm hoping I can also use it just to make this type of timeline graph. All advice greatly appreciated.

Regards,

Dave

Here is code to produce dataset:

data timeline ;
input specie $ subject event dpi ;

datalines;

G 1 Bx-NEG 194

G 1 Bx-NEG 384

G 1 Bx-NEG 452

G 1 Bx-POS 564

G 1 ARF 648

G 1 Cx 994

G 1 Nx-POS 1057

G 2 Bx-NEG 194

G 2 Bx-NEG 384

G 2 Bx-NEG 452

G 2 Bx-POS 564

G 2 ARF 648

G 2 Cx 995

G 2 Nx-POS 1057

G 3 Bx-NEG 194

G 3 Bx-NEG 384

G 3 Bx-NEG 452

G 3 Bx-NEG 564

G 3 ARF 648

G 3 Bx-NEG 662

G 3 Bx-NEG 721

G 3 Nx-POS 748

G 4 Bx-NEG 193

G 4 Bx-NEG 383

G 4 Bx-NEG 451

G 4 Bx-NEG 563

G 4 ARF 647

G 4 Bx-POS 661

G 4 Cx 1032

G 4 Nx-POS 1056

S 1 Bx-NEG 198

S 1 Bx-NEG 387

S 1 Bx-NEG 417

S 1 Bx-NEG 452

S 1 Bx-NEG 485

S 1 Bx-NEG 583

S 1 Nx-POS 747

S 2 Bx-NEG 198

S 2 Bx-POS 387

S 2 ARF 779

S 2 Cx 849

S 2 Nx-POS 908

S 3 Bx-NEG 196

S 3 Bx-NEG 385

S 3 Bx-NEG 415

S 3 Bx-NEG 450

S 3 Bx-NEG 483

S 3 Bx-NEG 581

S 3 Bx-NEG 752

S 3 ARF 777

S 3 Nx-POS 815

S 4 Bx-NEG 192

S 4 Bx-NEG 381

S 4 Bx-NEG 411

S 4 Bx-NEG 446

S 4 Bx-NEG 479

S 4 Bx-NEG 577

S 4 Bx-NEG 748

S 4 ARF 773

S 4 Nx-POS 811

;

run;

1 ACCEPTED SOLUTION

Accepted Solutions
ballardw
Super User

Does this help:

data timeline ;
length specie $ 1 event $ 7 ;
input specie $ subject event $ dpi ;
datalines;
G 1 Bx-NEG 194
G 1 Bx-NEG 384
G 1 Bx-NEG 452
G 1 Bx-POS 564
G 1 ARF 648
G 1 Cx 994
G 1 Nx-POS 1057
G 2 Bx-NEG 194
G 2 Bx-NEG 384
G 2 Bx-NEG 452
G 2 Bx-POS 564
G 2 ARF 648
G 2 Cx 995
G 2 Nx-POS 1057
G 3 Bx-NEG 194
G 3 Bx-NEG 384
G 3 Bx-NEG 452
G 3 Bx-NEG 564
G 3 ARF 648
G 3 Bx-NEG 662
G 3 Bx-NEG 721
G 3 Nx-POS 748
G 4 Bx-NEG 193
G 4 Bx-NEG 383
G 4 Bx-NEG 451
G 4 Bx-NEG 563
G 4 ARF 647
G 4 Bx-POS 661
G 4 Cx 1032
G 4 Nx-POS 1056
S 1 Bx-NEG 198
S 1 Bx-NEG 387
S 1 Bx-NEG 417
S 1 Bx-NEG 452
S 1 Bx-NEG 485
S 1 Bx-NEG 583
S 1 Nx-POS 747
S 2 Bx-NEG 198
S 2 Bx-POS 387
S 2 ARF 779
S 2 Cx 849
S 2 Nx-POS 908
S 3 Bx-NEG 196
S 3 Bx-NEG 385
S 3 Bx-NEG 415
S 3 Bx-NEG 450
S 3 Bx-NEG 483
S 3 Bx-NEG 581
S 3 Bx-NEG 752
S 3 ARF 777
S 3 Nx-POS 815
S 4 Bx-NEG 192
S 4 Bx-NEG 381
S 4 Bx-NEG 411
S 4 Bx-NEG 446
S 4 Bx-NEG 479
S 4 Bx-NEG 577
S 4 Bx-NEG 748
S 4 ARF 773
S 4 Nx-POS 811
;
run;

data plot;
   set timeline;
   retain value 0 ;/* y axis variable*/
   by specie subject;
   if first.specie and first.subject then value+2;
   Else if first.subject then value+1;
   Series=value;
SpecSubTxt = catx(' ',specie,subject);
run;

Proc sql;
create table subjectfmt as
select distinct series as start,SpecSubTxt as label, "SpecSub" as Fmtname
from plot;
quit;
proc format library=work cntlin=Subjectfmt;run;
ods graphics on;
proc sgplot data=plot ;
   yaxis   discreteorder=data display=(noticks novalues);
xaxis values=(0 to 1200 by 100);
   series x=dpi y=Value /group=series legendlabel='Species and Subject'
         datalabel=dpi
;
scatter x=dpi y=value/ group=event  legendlabel='Events';
   label Value="Species and subject"
   Event='Events'
   DPI  = 'Days to Event'
;
format value series SpecSub.;
  keylegend /across = 4;
run;     
title;

ods graphics off;

View solution in original post

10 REPLIES 10
Quentin
Super User

Interesting.  I'm sure you'll get some good suggestions.

Curious what is the meaning of the various events?  Might be useful to consider that in the design of the symbols (e.g make all positives red).

Looking at the data for the Bx results (Bx-Neg and Bx-Pos), it looks like it could be survival analysis.  If, for example, if Bx is Biopsy, it looks like maybe you keep doing biopsies until the first positive biopsy.  For that sort of data, a survival plot might be more informative than the timeline you describe.

--Q.

The Boston Area SAS Users Group (BASUG) is hosting our in person SAS Blowout on Oct 18!
This full-day event in Cambridge, Mass features four presenters from SAS, presenting on a range of SAS 9 programming topics. Pre-registration by Oct 15 is required.
Full details and registration info at https://www.basug.org/events.
das
Obsidian | Level 7 das
Obsidian | Level 7

Thank you for your suggestion, Quentin. While certain aspects of this data could be viewed as a survival plot (time to becoming "POS"), the need is to show when the subject becomes positive in relation to the other events (all biopsy events, necropsy event, a move from one facility to another, and when/if becomes clinical). So I think my need is more a project timeline than a time-to-specific-event (like in survival analysis).

Yes, I think color coding would be very effective, as you describe, the time the subject becomes positive would be either filled and/or red regardless if that through a specific biopsy or at necropsy.

All the best,

Dave

ballardw
Super User

Does this help:

data timeline ;
length specie $ 1 event $ 7 ;
input specie $ subject event $ dpi ;
datalines;
G 1 Bx-NEG 194
G 1 Bx-NEG 384
G 1 Bx-NEG 452
G 1 Bx-POS 564
G 1 ARF 648
G 1 Cx 994
G 1 Nx-POS 1057
G 2 Bx-NEG 194
G 2 Bx-NEG 384
G 2 Bx-NEG 452
G 2 Bx-POS 564
G 2 ARF 648
G 2 Cx 995
G 2 Nx-POS 1057
G 3 Bx-NEG 194
G 3 Bx-NEG 384
G 3 Bx-NEG 452
G 3 Bx-NEG 564
G 3 ARF 648
G 3 Bx-NEG 662
G 3 Bx-NEG 721
G 3 Nx-POS 748
G 4 Bx-NEG 193
G 4 Bx-NEG 383
G 4 Bx-NEG 451
G 4 Bx-NEG 563
G 4 ARF 647
G 4 Bx-POS 661
G 4 Cx 1032
G 4 Nx-POS 1056
S 1 Bx-NEG 198
S 1 Bx-NEG 387
S 1 Bx-NEG 417
S 1 Bx-NEG 452
S 1 Bx-NEG 485
S 1 Bx-NEG 583
S 1 Nx-POS 747
S 2 Bx-NEG 198
S 2 Bx-POS 387
S 2 ARF 779
S 2 Cx 849
S 2 Nx-POS 908
S 3 Bx-NEG 196
S 3 Bx-NEG 385
S 3 Bx-NEG 415
S 3 Bx-NEG 450
S 3 Bx-NEG 483
S 3 Bx-NEG 581
S 3 Bx-NEG 752
S 3 ARF 777
S 3 Nx-POS 815
S 4 Bx-NEG 192
S 4 Bx-NEG 381
S 4 Bx-NEG 411
S 4 Bx-NEG 446
S 4 Bx-NEG 479
S 4 Bx-NEG 577
S 4 Bx-NEG 748
S 4 ARF 773
S 4 Nx-POS 811
;
run;

data plot;
   set timeline;
   retain value 0 ;/* y axis variable*/
   by specie subject;
   if first.specie and first.subject then value+2;
   Else if first.subject then value+1;
   Series=value;
SpecSubTxt = catx(' ',specie,subject);
run;

Proc sql;
create table subjectfmt as
select distinct series as start,SpecSubTxt as label, "SpecSub" as Fmtname
from plot;
quit;
proc format library=work cntlin=Subjectfmt;run;
ods graphics on;
proc sgplot data=plot ;
   yaxis   discreteorder=data display=(noticks novalues);
xaxis values=(0 to 1200 by 100);
   series x=dpi y=Value /group=series legendlabel='Species and Subject'
         datalabel=dpi
;
scatter x=dpi y=value/ group=event  legendlabel='Events';
   label Value="Species and subject"
   Event='Events'
   DPI  = 'Days to Event'
;
format value series SpecSub.;
  keylegend /across = 4;
run;     
title;

ods graphics off;

das
Obsidian | Level 7 das
Obsidian | Level 7

Thank you, ballardw!

Below is the output image that I get with your code. It is very close to what I am interested in. I will study the code you've used and see if I can understand the elements and modify as need arises. Thanks again. Very helpful.

Dave

Gt_Placenta_Timeline.png

ballardw
Super User

I didn't document everything because I was guessing about what you wanted.

The bit about the first species and subject was to create a vertical gap that you may not need to display between the species groups.

I suppressed the vertical axis because of that gap and not knowing how many actual species subject combinations you had a value list was likely not easy to determine. Without the gap, ie makeing the same value+1 for each then the species/ subject code value could appear as tick labels.

The separate scatter plot was to show the event types in a consistent manner across species/subject. Using the data to create a format for combinations works but you may overload the number of supplied line colors defined in your style. At which point there might be options to get more line types, possibly by species, but that might require some additional data reshaping OR moving to a GTL solution.

Quentin
Super User

Hi,

@Ballardw's solution is nice.  I was going to give it a shot tonight, but probably wouldn't have come up with something like that.

Was surprised you hadn't heard from Sanjay Matange on this, but just now noticed his latest blog post where he says he was at PhramaSUG:

Report from PharmaSUG 2014 - Graphically Speaking

More relevant, he summarizes a bunch of graphical papers from the conference, and the third example of a "swimmer plot" looks like almost exactly what you're describing.  So would suggest you check out that paper.

Regards,

-Q.

The Boston Area SAS Users Group (BASUG) is hosting our in person SAS Blowout on Oct 18!
This full-day event in Cambridge, Mass features four presenters from SAS, presenting on a range of SAS 9 programming topics. Pre-registration by Oct 15 is required.
Full details and registration info at https://www.basug.org/events.
das
Obsidian | Level 7 das
Obsidian | Level 7

Quentin,

What timing! I followed the link and you are right that the swimmer plot is very nice. Thank you for passing that information along. Between the two replies I've had already, I think I should be able to produce what I am looking for.

Thank you,

Dave

das
Obsidian | Level 7 das
Obsidian | Level 7

So I gave the sgplot solution a try, too. Here is the code and the output for any that are interested in an example. The SUGI paper was quite helpful in getting this done. Thank you for bringing this to my attention.

Dave

/* The following uses code instruction and example from PharmSUG-2014-DG07.pdf */

data timeline ;
input specie $ subjn Bx1 Bx2 Bx3 Bx4 Bx5 Bx6 Bx7 BxP ARF Cx Nx ;
datalines;
goat 1 194 384 452 . . . . 564 648 994 1057
goat 2 194 384 452 . . . . 564 648 995 1057
goat 3 194 384 452 564 662 721 . . 648 . 748
goat 4 193 383 451 563 . . . 661 647 1032 1056
sheep 5 198 387 417 452 485 583 . . . . 747
sheep 6 198 . . . . . . 387 779 849 908
sheep 7 196 385 415 450 483 581 752 . 777 . 815
sheep 8 192 381 411 446 479 577 748 . 773 . 811
;
run;

footnote  'open circle = negative biopsy';
footnote2 'closed circle = postive biopsy';
footnote3 'closed star = begin clinical signs';
footnote4 'plus sign = moved locations';

proc sgplot data=timeline ;
/* subject timelines as bars colored by specie */
hbarparm category=subjn response=Nx / group=specie barwidth=0.5 ;
yaxis type=discrete display=(novalues noticks) label="Inoculates" ;
xaxis type=linear label="Post-Inoculation (days)" values=(0 to 1200 by 60) ;

/* location move */
scatter x=ARF y=subjn / markerattrs=(symbol=plus size=12 color=black) ;

/* start clinical signs (filled star) */
scatter x=Cx y=subjn / markerattrs=(symbol=starfilled size=12 color=black) ;

/* NEG Biopsies (open circles) */
scatter x=Bx1 y=subjn / markerattrs=(symbol=circle size=9 color=black) ;
scatter x=Bx2 y=subjn / markerattrs=(symbol=circle size=9 color=black) ;
scatter x=Bx3 y=subjn / markerattrs=(symbol=circle size=9 color=black) ;
scatter x=Bx4 y=subjn / markerattrs=(symbol=circle size=9 color=black) ;
scatter x=Bx5 y=subjn / markerattrs=(symbol=circle size=9 color=black) ;
scatter x=Bx6 y=subjn / markerattrs=(symbol=circle size=9 color=black) ;
scatter x=Bx7 y=subjn / markerattrs=(symbol=circle size=9 color=black) ;

/* POS Biopsies (filled circles) */
scatter x=BxP y=subjn / markerattrs=(symbol=circlefilled size=9 color=black) ;

run;

Gt_Placenta_Timeline2.png

Jay54
Meteorite | Level 14

You can add offsetmin=0 on XAXIS to make the bars touch the Y axis.

You could put the symbols in a separate legend, instead of the "Open Circle" etc using footnotes.  Name the scatter statements with NAME='NB' and add legend labels using LEGENDLABEL='Negative Biopsy'.  Include the names in another KEYLEGEND statement.  There are multiple examples of this in Graphically Speaking blog, one such is:  http://blogs.sas.com/content/graphicallyspeaking/2014/02/16/layered-graphs/

das
Obsidian | Level 7 das
Obsidian | Level 7

Thank you for those suggestions and link. That's very helpful. I will give these a try as I spruce up this figure for a pub. I'll give them a try and re-post if I have some lingering issues.

Dave

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
  • 10 replies
  • 3127 views
  • 6 likes
  • 4 in conversation