Adrian04Feb2017
Adverse Event TFL programming
SAP give details about AE TFLs(tables, listings, figures) display instruction. Please note that there will be inconsistent between SAP definition and shells configuration. Make sure they are the same, if not, put comments to leader programmer/statistics/client. For instance, SAP needs a summary ae table about leading to death, but this picture is not exist in shell, so at this time, confirmation is necessary. The following tips are general rules for TFL programming. It is a good habit to keep them in mind in our daily working scope. Also I will provide some reference codes to help understanding, and they are only the part of the programs. I strongly suggest you write codes by yourself once you get the idea.
1) AE overall summary table
Attentions:
1. The first column label usually contains follow items: any subject with at least one adverse event, serious adverse event, adverse event leading to death, adverse event related to study treatment. What you do is to select appropriate matched subjects and make small calculations.
2. Safety Analysis (Adverse Events, Laboratory Data, Vital Signs…) use safety population, so SAFFL=’Y‘ is required when reading data from ADaM. Sometimes AETRTEM=’Y’ is also needed if All AEs is coded using MedDRA dictionary.
data adae;
set adam.adae;
where saffl = 'Y' and aetrtem = 'Y';
run;
3. TFL codes will be different according to ADaM derivation. Generally, category flag variable is recommended to derive in ADaM, if not, we shall write by ourselves. Of course, another analysis indication variable like ANL01FL, ANL02FL….is required added in ADaM.
proc sql;
create table aeall as
select count(distinct usubjid) as count, 1 as row
from adae;
create table death as
select count(distinct usubjid) as count, 2 as row
from adae
where aeout='FATAL'; /* dthfl='Y' */
quit;
4. For table counts patients with any AEs, clause DISTINCT is used in PROC SQL;
5. For events, then ignore it. Sometimes events is also required for summary and ‘n’, ‘(%)’, ‘Events’ should be concatenated into one variable to shown up.
**** Treatment-emergent Adverse Event for subjects level and events.;
proc sql;
create table TOT as
select count(distinct USUBJID) as CNT1, count(*) as CNT2, TRTPN
from ADAE
group by TRTPN;
create table SOC as
select count(distinct USUBJID) as CNT1, count(*) as CNT2, AEBODSYS, TRTPN
from ADAE
group by AEBODSYS, TRTPN;
create table SOCPT as
select count(distinct USUBJID) as CNT1, count(*) as CNT2, AEBODSYS, AEDECOD, TRTPN
from ADAE
group by AEBODSYS, AEDECOD, TRTPN;
quit;
6. Table shell likes “n (%)”. Here “n” indicates category subjects, % =100*n/population. As Footnote says: n = Number of patients, Percentages are based on the total number of subjects in the safety population. If treatment arms are given, denominator will consider this analysis population by treatment. This value will be equal to “N=XX” in column title. Note TFLs are analyzed by treatment group usually.
7. If population is a macro variable obtained in the prior programs, take highly notice this variable is a character value. So if you use it in next calculation, try to avoid the statements like this: IF COUNT=&TRTA THEN PCT=’100’. As the variable COUNT is numeric, there will be log issue “Convert…” after submit the codes. And this issue should be clear. Notice codes like “COUNT/&TRTA” is ok because SAS perform automatic mathematic calculation with no warning.
data final;
set ae;
length col1 $200;
if count in (0 .) then col1 = '0';
else col1 = strip(put(count,3.)) || ' (' || put(100*count/&n.,5.1) || ')';
col1=tranwrd(col1,'100.0','100');
run;
2) Adverse Events by System Organ Class and Preferred Term
Table shell like this:
Any Adverse Events
SOC1
PT1
PT2
…
SOC2
…
Attentions:
1. Sort by descending frequency of the total for SOC and preferred term within SOC. That is to say, first descending order by SOC, then descending order by PTs in each SOC. This display is help to review for statistics which AEs occurrence is mostly happen.
proc sql noprint;
create table tot as
select count(distinct usubjid) as count
from adae;
create table soc as
select aebodsys, count(distinct usubjid) as count
from adae
group by aebodsys;
create table pt as
select aebodsys, aedecod, count(distinct usubjid) as count
from adae
group by aebodsys, aedecod;
quit;
data tab0;
set tot soc pt;
run;
proc sort data = tab0;
by aebodsys aedecod;
run;
data tab1;
set tab0;
by aebodsys aedecod;
length label $200. col1 $50.;
**** Set order ****;
retain group row;
if first.aebodsys then group = count;
if first.aedecod then row = count;
if aebodsys = '' then label = 'Any subject with at least one Treatment-Emergent Adverse Event';
else if aedecod = '' then label = aebodsys;
else label = '^w^w' || aedecod;
run;
proc sort data = tab1 out = final;
by descending group aebodsys descending row aedecod;
run;
2. SOC uses AEBODSYS, PT uses AEBODSYS. If there are missing value of them due to unmatched AE coding, then setting to “Uncoded” is necessary to avoid empty row.
If aebodsys=’’ then aebodsys=’Uncoded’;
3. Most of time there will be no records read in matched the table or listing, another explanation line is necessary to reflect this. So an empty data set with at least one row is required for report. Use &nodata in proc report procedure.
%let nodata=%str(No applicable data were reported);
proc sql noprint;
select count(*) into:numobs from adae;
quit;
%if &numobs=0 %then %do;
proc sql;
create table final
(label char(200),
col1 char(50),
aebodsys char(200),
aedecod char(200),
group num,
row num);
insert into final
set group=.;
quit;
%end;
%if &numobs=0 %then %do;
compute before page/style(lines)={just=c};
line "";
line "&nodata";
endcomp;
%end;
3) Adverse Events by System Organ Class, Preferred Term, and Relationship to Study Treatment
Shell:
SOC/PT Relationship to Study Drug
Any Adverse Event Unrelated
Unlikely
Possible
Probable
SOC1
Overall
….
PT1
…
Attention:
1. Note there is another column added into table, so two points need to consider: one is that if there is no subjects in certain relationship within SOC/PT, an empty line should be kept with “count=0” mapped. If there is no subjects included at treatment A and other treatment arm B is mapped with numbers, then set ‘0’ for arm A column.
2. Two is that adding another group-by variable to summary the data in SQL statement.
3. As for order rules, keep it the same as prior comments. It will be a little complicated for this sorting. For example, within a SOC/PT, count each subject in 4 relationship, and get summation of them, then use this value to sort order.
4. Notice here the number of subjects within SOC/PT should be unique. That is to say, if a subject is in ‘Unlikely’, then it cannot be in ‘Prossible’.
**** Get number of subjects within SOC/PT for ordering ****;
**** XXXn is related dataset to hold subjects ****;
proc sort data=adae out=adae1 nodupkey;
by aebodsys aedecod usubjid;
run;
proc sql;
create table tot as
select count(distinct usubjid) as count, areln
from adae
group by areln;
create table totn as
select count(distinct usubjid) as count
from adae1;
create table soc as
select count(distinct usubjid) as count, aebodsys, areln
from adae
group by aebodsys, areln;
create table socn as
select count(distinct usubjid) as count, aebodsys
from adae
group by aebodsys;
create table socpt as
select count(distinct usubjid) as count, aebodsys, aedecod, areln
from adae
group by aebodsys, aedecod, areln;
create table socptn as
select count(distinct usubjid) as count, aebodsys, aedecod
from adae
group by aebodsys, aedecod;
quit;
4) Adverse Events by System Organ Class, Preferred Term, and Maximum Severity
Shell:
SOC/PT Maximum Severity
Any Adverse Event
Mild
Moderate
Severe
SOC1
Overall
….
PT1
…
The core step is the same as 3). Just select another variable to analysis.
Attention:
1. Generally, the maximum severity record will be used for display within SOC/PT. For example, if the same SOC/PT happen several times for one subject, first it is ‘Mild’, later it comes up with ‘Severe’, then we flag the maximum record for the latter one. If no flag variable is available in ADaM, then we will handle this in TFL programs.
2. Sample codes:
**** Count once at the maximum severity for total.;
proc sort data = adae out = adae1 ;
by usubjid asevn;
run;
data adae1;
set adae1;
by usubjid asevn;
if last.usubjid;
run;
**** End ***;
**** Count once at the maximum severity per SOC.;
proc sort data = adae out = adae2;
by aebodsys usubjid asevn;
run;
data adae2;
set adae2;
by aebodsys usubjid asevn;
if last.usubjid;
run;
**** End ***;
**** Get dummy SOC-PT-SEV structure.;
proc sort data = tab0 out = dummy(keep = aebodsys aedecod) nodupkey;
by aebodsys aedecod;
run;
data dummy;
set dummy;
do asevn = 1 to 3;
output;
end;
run;
data tab1;
merge dummy tab0;
by aebodsys aedecod asevn;
run;
Ok, that is all for common adverse event tables. And general rules here is widely used in other similar tables.
|