| David Franklin, Blog | |
| 2017 Archive | |
| Back | dfranklinnh@gmail.com |
|
JanuaryThere are many ways to combine a date variable and a time variable into a datetime variable but the easist way is using the DHMS funtion using the following: datetime = DHMS ( datevar, 0 , 0 , timevar ); While this does seem to be strange at first it is important to note that a time variable is the number of seconds from mid-night, hence using the time variable value in the seconds parameter. Hope this helps. FebruaryBack to PROC SQL, it is easy to do some analysis on a variable and put it into a macro variable (in one step), as shown below:
proc sql;
select min(age), max(age)
into :minage, :maxage
from sashelp.class;
quit;
run;
The example above calculates the minimum and maximum age from the dataset SASHELP.CLASS and stores them in macro variables MINAGE and MAXAGE. Hope this helps. MarchHere are some useful ideas for data conversion, i.e. numeric to character or vice versa. Lets start out with the following: cost=10.51; Typically we would out this in a text variable using cost_txt=put(cost,8.2); But two other functions do this a different way. Lets first define the format: format cost 8.2; Using the VVALUE function we can write cost_txt=vvalue(cost); It is possible to use the value of one variable from another variable using VVALUEX as shown below: usevar='cost'; cost_txt=vvaluex(usevar); Hope this helps. AprilLooking for extreme values in a variable is necessary for understanding the data. Below is a PROC SQL call that can display that can help
proc sql;
select *,avg(weight) as mean,
std(weight) as sd
from sashelp.class
group by gender
having abs(mean-score)>2*sd;
quit;
run;
This uses a clever way of selecting records that are greater than two standard deviations from the mean. Hope this helps. MaySometimes PROC CONTENTS just displays too much information, but you can access the information directly using the VCOLUMN Dictionary table, as shown below:
proc sql;
select memname, varnum, name, label
from sashelp.vcolumn
where libname='SASHELP' and memname='CLASS'
order by varnum;
quit;
run;
The MEMNAME variable selects the dataset to look at, and the ORDER BY VARNUM makes certain that the output is in variable number order but this can be changed to NAME if variable name order is requested. Hope this helps. JuneSometimes we need to count the number of variables that are missing in a record. The CMISS function is useful as it will count the number of variables that have missing values, regardless of whether the variables are numeric or character:
data _test;
infile cards;
input name $ 1-6 sex $ 8 age 10-11;
MissingCount=cmiss(of name--age);
put MissingCount=;
cards;
SARAH F 26
STUART M
AMANDA 27
MURRAY
;
run;
which results in the output to the SAS LOG of _N_=1 MissingCount=0 _N_=2 MissingCount=1 _N_=3 MissingCount=1 _N_=4 MissingCount=2 Hope this helps. JulyWhen creating character variables use the LENGTH statement or LENGTH option in the ATTRIB statement where possible. Reason is that if a new character variable is not defined then on some systems it will have a length of 200 characters by default, which may be too long (inefficient for the datasets, i.e. be bigger than it needs to be) or too short (occasionally, if a text is longer than 200 characters it will be truncated to 200 characters). Hope this helps. AugustInstead of using a cascading IF...THEN...ELSE, try the SELECT/WHEN/OTHERWISE statement grouping instead:
select(sex)
when('M') sexn=1;
when('F') sexn=2;
otherwise put 'WAR' 'NING: SEX is either missing or invalid, please review, ' subjid= sex=;
end;
For numeric ranges, it is possible to put that range in the selection clause, as shown below:
select;
when(age>=65) agen=2;
when(age>=18) agen=1;
otherwise put 'WAR' 'NING: AGE is invalid or missing ' subjid= age=;
end;
Hope this helps. SeptemberSurvival analysis finding the number of Patients at Risk, with events, censored, Kaplan Meier estimate and Median Survival Time, by visit intervals can be time consuming. See below the mock for what it should look like:
Patients Survival
Patients with Patients KM estimate Time (days)
Visit Interval at risk events censored % [95% CI] % [95% CI]
0 to <4 days xx xx xx xx [xx,xx] xx [xx,xx]
4 to < 8 days. xx xx xx xx [xx,xx] xx [xx,xx]
8 to < 12 days xx xx xx xx [xx,xx] xx [xx,xx]
12 to <16 days xx xx xx xx [xx,xx] xx [xx,xx]
Using SAS you have to do some work. For purposes of this article here is the data that is being used:
data _indat;
infile cards;
input patid t status;
cards;
1 1 1
2 2 0
3 3 0
4 4 1
5 5 0
6 10 1
7 12 0
;
run;
The T variable is the number of days, while the STATUS variable gives the status of the at the end of the study with 1=an event, 0=ended study without event (i.e. censored). The first part of the table deals with the number at risk during each interval, and this is calculated using the following code:
** Calculate At Risk numbers at start of each time peiod;
proc lifetest data=_indat atrisk
timelist=0 4 8 12 16;
time T * Status(0);
ods output productlimitestimates=pi (keep=timelist numberatrisk);
run;
Note use of the ATRISK and TIMELIST options which tell SAS we want the number at risk at those timepoints. We will get the actual variables needed Next is the number of failed and censored:
** Calculate survival results for
days 0, 4, 8, 12 and 16;
proc lifetest data=_indat intervals=0 to 16 by 4
method=lt maxtime=12
outsurv=si (keep=T survival sdf_lcl sdf_ucl);
time T * Status(0);
ods output lifetableestimates=ki (keep=uppertime failed censored);
run;
Two datasets are created here -- dataset KI has thenumber that failed (had event) and were censored, while SI has the KM estimate with 95% CI. The of the LIFETEST calls brings in the median survival time:
** Median Survival;
proc lifetest data=sashelp.bmt;
time T * Status(0); where group='ALL';
ods output quartiles=qi (keep=percent estimate lowerlimit upperlimit);
run;
Bringing these datasets together now is the tricky part. The time (variable TIMELIST) in PI refers to the time at the start of the interval, which the time variables in datasets KI and SI refer to the period immediately before the end of the time in question – we actually have to realign them to the start of the interval, which can be easily done by:
data ki0;
set ki;
timelist=uppertime-4;
data si0;
set si;
timelist=t-4;
run;
The next task is to get the median survival data and align it to the first interval starting time, i.e. TIMELIST=0 as in the PI dataset:
data qi0;
set qi (where=(percent=50));
timelist=0;
run;
Now we bring this altogether:
proc sort data=pi;
by timelist;
proc sort data=ki0;
by timelist;
proc sort data=si0;
by timelist;
proc sort data=qi0;
by timelist;
data all0;
merge pi ki0 si0 qi0;
by timelist;
if timelist in(0,4,8,12);
run;
What we have now is the single dataset needed, with a little more manipulation, the results we requested. Note that we have restricted the dataset to deal with only the time intervals we are dealing with – this will remove a few rows that have been created when we realigned the time intervals and merged the data. The variables relating to Patients at risk, Patients with events, and Patients censored, all refer to numbers, but the last two have results from two or more variables that need to be set,
data all1;
length _kmestimate _median $60;
set all0;
_kmestimate=put(survival,4.2)||' ['||put(sdf_lcl,4.2)||','||put(sdf_ucl,4.2)||']';
if timelist=0 then
_median=put(estimate,4.1)|| '['||put(lowerlimit,4.1)|| ','||put(upperlimit,4.1)||']';
run;
Now we have our dataset it is just time to add a format for our first column
proc format;
value timef 0='0 to <4 days' 4='4 to <8 days'
8='8 to <12 days' 12='12 to <16 days';
run;
and then output our table, in this case just using PROC PRINT although you would generally use PROC REPORT:
proc print data=all1 noobs label split='|';
var timelist numberatrisk failed censored _kmestimate _median;
format timelist timef.;
label timelist='Visit Interval'
numberatrisk='Patients|at|risk'
failed='Patients|with|events'
censored='Patients|censored'
_kmestimate='KM estimate|%|[95% CI]'
_median='Median Survival|Time|(days) [95% CI]';
run;
getting Patients Patients Median Survival at with Patients KM estimate Time Visit Interval risk events censored % [95% CI] (days) % [95% CI] 0 to <4 days 7 1 2 0.83 [0.27,0.97] 10.0 [ 1.0, . ] 4 to < 8 days. 4 1 1 0.60 [0.11,0.89] 8 to < 12 days 2 1 0 0.30 [0.01,0.72] 12 to <16 days 1 0 1 0.30 [0.01,0.72]As you can see this is not an easy table to produce, but the code given here is a basis for it. Try it. Hope this helps. OctoberPROC SQL has a little secret - it can wrap long lines of text onto multiple lines of output using a FLOW option, as seen below:
options ls=64 nonumber nodate;
title1 "Ford Sports Cars (USA)";
proc sql;
reset flow;
select strip(model) label='Model', msrp,
enginesize, mpg_city, mpg_highway
from sashelp.cars
where make='Ford' and type='Sports';
reset noflow;
quit;
run;
will produce the output
Ford Sports Cars (USA)
Engine MPG MPG
Model MSRP Size (L) (City) (Highway)
Mustang 2dr $18,345 3.8 20 29
(convertible)
Mustang GT Premium $29,380 4.6 17 25
convertible 2dr
Thunderbird Deluxe $37,530 3.9 17 24
convert w/hardtop 2d
Note that the Model column has been wrapped for when there are multiple lines – the FLOW option using the RESET statement switched this option on. Also note the line under the column labels. Hope this helps. NovemberThe MERGENOBY controls whether a message is issued when MERGE processing occurs without an associated BY statement. There are three options; NOWARN (specifies that no warning/error message is issued), WARN (specifies that a warning message is issued, and ERROR (specifies that an error message is issued). The default is NOWARN, but strongly suggest that users use WARN as it will pick up any issues in most log checking software. Hope this helps. DecemberProtecting the Raw and Derived (ADS) datasets we produce from unintentional overwrite is something that concerns every programmer. One helpful hint is to use the READONLY option in the LIBNAME statement, as shown below: libname RAW "project-area\DATA\RAW" access=readonly; libname ADS "project-area\DATA\ADS" access=readonly; libname ADSWRITE "project-area\DATA\ADS"; If you use ADSWRITE only when creating an ADS dataset in your program, and use ADS and RAW everywhere else, the chances of inadvertently overwriting RAW and ADS datasets is much reduced. Hope this helps. |