Transposing wide table into long format in SAS - sas

I have a dataset that looks roughly like this:
data wide;
input id age gender nationality a_active b_active a_eligible b_eligible;
cards;
1 33 M X 0 1 1 0
;
run;
Desired output:
id
age
gender
nationality
active_label
active_value
eligible_label
eligible_value
1
33
M
X
a
0
a
1
1
33
M
X
b
1
b
0
I tried using proc transpose but I can't seem to figure out how to have multiple labels. I can do this with one label, not sure if that's the right way:
proc transpose data=wide out=long pefix=active_label;
by id age gender nationality;
var a_active b_active;
run;

You can achieve your required result with 2 proc transpose steps. The labels can be split out in a data step in between.
data wide;
input id age gender $ nationality $ a_active b_active a_eligible b_eligible;
cards;
1 33 M X 0 1 1 0
;
run;
* First transpose into a long format. The name column automatically will be
called _NAME_ and the value column gets named COL1;
proc transpose data=wide out=long ;
by id age gender nationality;
var a_: b_:;
run;
* now separate the _NAME_ variable into label (A,B) and varlabel (active,eligible);
data long;
set long;
label=scan(_name_,1,'_');
varlabel=scan(_name_,2,'_');
run;
* now transpose into a wider format, but keeping the label as a single column;
proc transpose data=long out=want (drop=_name_);
by id age gender nationality label;
id varlabel;
var col1;
run;

I suppose you could do it in multiple steps and then merge the results together:
/* Transpose active */
proc transpose data=wide name=active_label out=long1(rename=(COL1 = active_value) );
by id age gender nationality;
var a_active b_active;
run;
/* Transpose eligible */
proc transpose data=wide name=eligible_label out=long2(rename=(COL1 = eligible_value) );
by id age gender nationality;
var a_eligible b_eligible;
run;
/* Merge both results together */
data want;
merge long1 long2;
by id age gender nationality;
/* The label is the first word in each label column */
active_label = scan(active_label, 1, '_');
eligible_label = scan(eligible_label, 1, '_');
run;

This is one of those cases where a data step may be easier. Especially if you need to extend it to more variables.
data wide;
input id age gender $ nationality $ a_active b_active a_eligible b_eligible;
cards;
1 33 M X 0 1 1 0
;
run;
data long;
set wide;
array _active (*) a_active b_active;
array _eligible(*) a_eligible b_eligible;
do i=1 to dim(_active);
label=scan(vname(_active(i)), 1, "_");
active_value=_active(i);
eligible_value=_eligible(i);
output;
end;
drop a_: b_: i;
run;
Or another transpose option that's a touch more dynamic than either of the solutions so far:
data wide;
input id age gender $ nationality $ a_active b_active a_eligible b_eligible;
cards;
1 33 M X 0 1 1 0
;
run;
proc transpose data=wide out=long1(rename=(COL1 = value) );
by id age gender nationality;
var a_active b_active a_eligible b_eligible;
run;
data long2;
set long1;
label = scan(_name_, 1, "_");
var_name = scan(_name_, 2, "_");
run;
proc sort data=long2;
by id age gender nationality label;
run;
proc transpose data=long2 out=wide1 (drop = _name_) ;
by id age gender nationality label;
id var_name;
var value;
run;

Related

How can I stack analysis variables on vertically in a PROC REPORT?

I am trying to stack multiple variables vertically in a PROC REPORT. I am tied to PROC REPORT over TABULATE or FREQ, so a solution using REPORT would be preferable.
I've tested out other solutions, but unable to find success using my data.
proc format library = library ;
value AGE
1 = '18 to 29'
2 = '30 to 45'
3 = '46 to 64'
4 = '65 and over'
9 = 'NA' ;
value SEX
1 = 'Male'
2 = 'Female'
9 = 'NA' ;
value Q16F
1 = 'EXCELLENT'
2 = 'VERY GOOD'
3 = 'GOOD'
4 = 'FAIR'
5 = 'POOR'
8 = 'DON''T KNOW'
9 = 'NA/REFUSED' ;
DATA CHSS2017_sashelp (keep = q16 sex age);
SET CHSS2017.CHSS2017_sashelp;
FORMAT q16 q16f.;
FORMAT sex SEX.;
FORMAT age AGE.;
RUN;
proc report data = CHSS2017_sashelp nowindows headline;
columns sex n, (q16);
define sex / group;
define q16 / across;
run;
The expected result would be a stacked REPORT table with multiple variables:
expected output
If you are fine with repeated headings/variable names then you can use two report procedures. See code below, I have used sample sas data and customized the formats a bit:
proc format ;
value AGE
1-10 = '1 to 10'
11-12 = '11 to 12'
13-High = '13 and over'
;
value $SEXv
'M' = 'Male'
'F' = 'Female'
;
value Q16F
1 = 'EXCELLENT'
2 = 'VERY GOOD'
3 = 'GOOD'
4 = 'FAIR'
5 = 'POOR'
8 = 'DON''T KNOW'
9 = 'NA/REFUSED' ;
run;
%macro RandBetween(min, max);
(&min + floor((1+&max-&min)*rand("uniform")))
%mend;
data class;
set sashelp.class;
q16 = %RandBetween(1, 9);
FORMAT q16 q16f.;
FORMAT sex $SEXv.;
FORMAT age AGE.;
run;
proc report data = class nowindows headline;
columns age n, (q16);
define age/ group;
define q16 / across;
run;
proc report data = class nowindows headline;
columns sex n, (q16);
define sex / group;
define q16 / across;
run;
the macro RandBetween is only for this code, you don't have to use it

SAS Demographic Table

I have been trying to create a demographic table like below this but I can't seem append the different tables. Please advise on where I can make adjustments in the code.
Group A Group B
chort 1 cohort 2 cohort 3 subtotal cohort 4 cohort 5 cohort 6 subtotal
Age
n
mean
sd
median
min
Gender
n
female
male
Race
n
white
asian
hispanic
black
My Code:
PROC FORMAT;
value content
1=' '
2='Age'
3='Gender'
4='Race'
value sex
1=' n'
2=' female'
3=' male';
value race
1=' n'
2=' white'
3=' asian'
4=' hispanic'
5=' black';
value stat
1=' n'
2=' Mean'
3=' Std. Dev.'
4=' Median'
5=' Minimum';
RUN;
DATA testtest;
SET test.test(keep = id group cohort age gender race);
RUN;
data tottest;
set testtest;
output;
if prxmatch('m/COHORT 1|COHORT 2|COHORT 3/oi', cohort) then do;
cohort='Subtotal';
output;
end;
if prxmatch('m/COHORT 4|COHORT 5|COHORT 6/oi', cohort) then do;
cohort='Subtotal';
output;
end;
run;
data count;
if 0 then set testtest nobs=npats;
call symput('npats',put(npats,1.));
stop;
run;
proc freq data=tottest;
tables cohort /out=patk0 noprint;
tables cohort*sex /out=sex0 noprint;
tables cohort*race /out=race0 noprint;
run;
PROC MEANS DATA = testtest n mean std min median;
class cohort;
VAR age;
RUN;
I know that I would have to transpose it and out it in a report. But before I do that, how do I get the variable out of my proc means, proc freq, etc?

Isolate Patients with 2 diagnoses but diagnosis data is on different lines

I have a dataset of patient data with each diagnosis on a different line.
This is an example of what it looks like:
patientID diabetes cancer age gender
1 1 0 65 M
1 0 1 65 M
2 1 1 23 M
2 0 0 23 M
3 0 0 50 F
3 0 0 50 F
I need to isolate the patients who have a diagnosis of both diabetes and cancer; their unique patient identifier is patientID. Sometimes they are both on the same line, sometimes they aren't. I am not sure how to do this because the information is on multiple lines.
How would I go about doing this?
This is what I have so far:
PROC SQL;
create table want as
select patientID
, max(diabetes) as diabetes
, max(cancer) as cancer
, min(DOB) as DOB
from diab_dx
group by patientID;
quit;
data final; set want;
if diabetes GE 1 AND cancer GE 1 THEN both = 1;
else both =0;
run;
proc freq data=final;
tables both;
run;
Is this correct?
If you want to learn about data steps lookup how this works.
data pat;
input patientID diabetes cancer age gender:$1.;
cards;
1 1 0 65 M
1 0 1 65 M
2 1 1 23 M
2 0 0 23 M
3 0 0 50 F
3 0 0 50 F
;;;;
run;
data both;
do until(last.patientid);
set pat; by patientid;
_diabetes = max(diabetes,_diabetes);
_cancer = max(cancer,_cancer);
end;
both = _diabetes and _cancer;
run;
proc print;
run;
add a having statement at the end of sql query should do.
PROC SQL;
create table want as
select patientID
, max(diabetes) as diabetes
, max(cancer) as cancer
, min(age) as DOB
from PAT
group by patientID
having calculated diabetes ge 1 and calculated cancer ge 1;
quit;
You might find some coders, especially those coming from statistical backgrounds, are more likely to use Proc MEANS instead of SQL or DATA step to compute the diagnostic flag maximums.
proc means noprint data=have;
by patientID;
output out=want
max(diabetes) = diabetes
max(cancer) = cancer
min(age) = age
;
run;
or for the case of all the same aggregation function
proc means noprint data=have;
by patientID;
var diabetes cancer;
output out=want max= ;
run;
or
proc means noprint data=have;
by patientID;
var diabetes cancer age;
output out=want max= / autoname;
run;

Setting names to idgroup

Follow up to
SAS - transpose multiple variables in rows to columns
I have the following code:
data have;
input CX_ID 1. TYPE $1. COUNT_RATE 1. SUM_RATE 2.;
datalines;
1A110
1B220
2A120
;
run;
proc summary data = have nway;
class cx_id;
output out=want (drop = _:)
idgroup(out[2] (count_rate sum_rate)= count sum);
run;
So this table:
CX_ID TYPE COUNT_RATE SUM_RATE
1 A 1 10
1 B 2 20
2 A 1 20
becomes
CX_ID COUNT_1 COUNT_2 SUM_1 SUM_2
1 1 2 10 20
2 1 . 20 .
Which is perfect, but how do I set the names to be
Count_A Count_B Sum_A Sum_B
Or in general whatever the value in the type field of the have table ?
Thank you
A double PROC TRANSPOSE is dynamic and you can add a data step to customize the names easily.
*sample data;
data have;
input CX_ID 1. TYPE $1. COUNT 1. SUM 2.;
datalines;
1A110
1B220
2A120
;
run;
*transpose to long;
proc transpose data=have out=long;
by cx_id type;
run;
*transpose to wide;
proc transpose data=long out=wide;
by cx_id;
var col1;
id _name_ type;
run;

SAS code for calculating IV

I found some code from obseveupdate websit. They are used for IV calculation. When I run it code it goes through, but all IV and Woe are zeros. I changed another data set to try, also get zeros for all variables. Could you help me figure out why?
data inputdata;
length Region $ 20 age $ 20 Gender $ 20;
infile datalines dsd dlm= ':' truncover;
input Region $ age $ Gender $ target ;
datalines;
Scotland:18-25:Male:1
Scotland:18-25:Female:0
Scotland:26-35:Male:0
Wales:26-35:Male:1
Wales:36-45:Female:0
Wales:26-35:Male:1
London:36-45:Male:1
London:26-35:Male:0
London:18-25:Unknown:1
London:36-45:Male:0
Northern Ireland:36-45:Female:0
Northern Ireland:26-35:Male:1
Northern Ireland:36-45:Male:0
Engand (Not London):45+:Female:0
Engand (Not London):18-25:Male:1
Engand (Not London):26-35:Female:0
Engand (Not London):45+:Female:0
Engand (Not London):36-45:Female:1
Engand (Not London):45+:Female:1
;
data _tempdata;
set inputdata;;
n=_n_;
run;
proc sort data=_tempdata;
by target n;
run;
proc transpose data=_tempdata out = _tempdata;
by target n;
var _character_ _numeric_;
run;
proc sort data=_tempdata out=_tempdata;
by _name_ target;
run;
proc freq data=_tempdata;
by _name_ target;
tables col1 /out=_tempdata;
run;
proc sort data=_tempdata;
by _name_ col1;
run;
proc transpose data=_tempdata out=_tempdata;
by _name_ col1;
id target;
var percent;
run;
data IV_Table(keep=variable IV) WOE_Table(keep=variable attribute woe);
set _tempdata;
by _name_;
rename col1=attribute _name_=variable;
_0=sum(_0,0)/100; *Convert to percent and convert null to zero;
_1=sum(_1,0)/100; *Convert to percent and convert null to zero;
woe=log(_0/_1)*100;output WOE_Table;*Output WOE;
if _1 ne 0 and _0 ne 0 then do;
raw=(_0-_1)*log(_0/_1);
end;
else raw=0;
IV+sum(raw,0);*Culmulativly add to IV, set null to zero;
if last._name_ then do; *only _tempdata the last final row;
output IV_table;
IV=0;
end;
where upcase(_name_) ^='TARGET' and upcase(_name_) ^= 'N';run;
proc sort data=IV_table;by descending IV;run;
title1 "IV Listing";proc print data=IV_table;run;
proc sort data=woe_table;
by variable WOE;
run;
title1 "WOE Listing";
proc print data=WOE_Table;run;