sas scan function with macro array - sas

I have a macro array &start_num
+-------+
| start |
+-------+
| 25.5 |
| 33.5 |
| 42.5 |
| 54.5 |
| 98 |
+-------+
but when I am using
%put %scan(&start_num,1);
it returns :25
%put %scan(&start_num,2);
returns me:533
why and how to fix it?

You can specify delimiter in scan function.
For example,if macro variable were initialized from variable start in table:
proc sql noprint;
select start into:start_num separated by ' ' from have;
quit;
START_NUM=25.5 33.5 42.5 54.5 98
Or in %let statement:
%let START_NUM=25.5 33.5 42.5 54.5 98;
Using %scan function:
%let var1 = %scan(&start_num,1,%str( ));
%put &=var1;
VAR1=25.5
%let var2 = %scan(&start_num,2,%str( ));
%put &=var2;
VAR2=33.5

because %scan thinks dot as seperator, that is why you get 25 and 533 respectively. checkout below example
%let start_num= 25.533.545.554.598;
%let var1 = %scan(&start_num,1);
%put value of with dot as separator &var1;
%put &var1 yields 25
%let var2 = %scan(&start_num,2);
%put value of with dot as separator &var2;
%put &var2 yields 533

Related

How to delete variables with huge percent of missings in table in SAS?

I have table in SAS with missing values like below:
col1 | col2 | col3 | ... | coln
-----|------|------|-----|-------
111 | | abc | ... | abc
222 | 11 | C1 | ... | 11
333 | 18 | | ... | 12
... | ... | ... | ... | ...
And I need to delete from above table variables where is more than 80% missing values (>=80%).
How can I do taht in SAS ?
The macro below will create a macro variable named &drop_vars that holds a list of variables to drop from your dataset that exceed missing threshold. This works for both character and numeric variables. If you have a ton of them then this macro will fail but it can easily be modified to handle any number of variables. You can save and reuse this macro.
%macro get_missing_vars(lib=, dsn=, threshold=);
%global drop_vars;
/* Generate a select statement that calculates the proportion missing:
nmiss(var1)/count(*) as var1, nmiss(var2)/count(*) as var2, ... */
proc sql noprint;
select cat('nmiss(', strip(name), ')/count(*) as ', strip(name) )
into :calculate_pct_missing separated by ','
from dictionary.columns
where libname = upcase("&lib")
AND memname = upcase("&dsn")
;
quit;
/* Calculate the percent missing */
proc sql;
create table pct_missing as
select &calculate_pct_missing.
from &lib..&dsn.
;
quit;
/* Convert to a long table */
proc transpose data=pct_missing out=drop_list;
var _NUMERIC_;
run;
/* Get a list of variables to drop that are >= the drop threshold */
proc sql noprint;
select _NAME_
into :drop_vars separated by ' '
from drop_list
where COL1 GE &threshold.
;
quit;
%mend;
It has three parameters:
lib: Library of your dataset
dsn: Dataset name without the library
threshold: Proportion of missing values a variable must meet or exceed to be dropped
For example, let's generate some sample data and use this. col1 col2 col3 all have 80% missing values.
data have;
array col[10];
do i = 1 to 10;
do j = 1 to 10;
col[j] = i;
if(i > 2 AND j in(1, 2, 3) ) then col[j] = .;
end;
output;
end;
drop i j;
run;
We'll run the macro and check the log:
%get_missing_vars(lib=work, dsn=have, threshold=0.8);
%put &drop_vars;
The log shows:
col1 col2 col3
Now we can pass this into a simple data step.
data want;
set have;
drop &drop_vars;
run;

Extract variable number of columns from beginning and end from a SAS dataset

I am having a SAS dataset where I want to keep, let's say, the firt 2 columns and last 4 columns, so to speak. In other words, only columns from beginning and from end.
data test;
input a b c d e f g h i j;
cards;
1 2 3 4 5 6 7 8 9 10
;
Initial output:
What I want is the following -
Output desired:
I checked on the net, people are trying something with varnum, as shown here, but I can't figure out. I don't want to use keep/drop, rather I want an automated way to solve this issue.
%DOSUBL can run code in a separate stream and be part of a code generation scheme at code submit (pre-run) time.
Suppose the requirement is to to slice the columns of a data set out based on meta data column position as indicated by varnum (i.e. places), and the syntax for places is:
p:q to select the range of columns whose varnum position is between p and q
multiple ranges can be specified, separated by spaces ()
a single column position, p, can be specified
negative values select the position downward from the highest position.
also, the process should honor all incoming data set options specified, i.e. keep= drop=
All the complex logic for implementing the requirements could be done in pure macro code using only %sysfunc and data functions such as open, varnum, varname, etc... That code would be pretty unwieldy.
The selection of names from meta data can be cleaner using SAS features such as Proc CONTENTS and Proc SQL executed within DOSUBL.
Example:
Macro logic is used to construct (or map) the filtering criteria statement based on varnum. Metadata retrieval and processing done with Procs.
%macro columns_slice (data=, places=);
%local varlist temp index p token part1 part2 filter joiner;
%let temp = __&sysmacroname._%sysfunc(monotonic());
%do index = 1 %to %sysfunc(countw(&places,%str( )));
%let token = %scan(&places,&index,%str( ));
%if NOT %sysfunc(prxmatch(/^(-?\d+:)?-?\d+$/,&token)) %then %do;
%put ERROR: &sysmacname, invalid places=&places;
%return;
%end;
%let part1 = %scan (%superq(token),1,:);
%let part2 = %scan (%superq(token),2,:);
%if %qsubstr(&part1,1,1) = %str(-) %then
%let part1 = max(varnum) + 1 &part1;
%if %length(&part2) %then %do;
%if %qsubstr(&part2,1,1) = %str(-) %then
%let part2 = max(varnum) + 1 &part2;
%end;
%else
%let part2 = &part1;
%let filter=&filter &joiner (varnum between &part1. and &part2.) ;
%let joiner = OR;
%end;
%put NOTE: &=filter;
%if 0 eq %sysfunc(dosubl(%nrstr(
options nonotes;
proc contents noprint data=&data out=&temp(keep=name varnum);
proc sql noprint;
select name
into :varlist separated by ' '
from &temp
having &filter
order by varnum
;
drop table &temp;
quit;
)))
%then %do;&varlist.%end;
%else
%put ERROR: &sysmacname;
%mend;
Using the slicer
* create sample table for demonstration;
data lotsa_columns(label='A silly 1:1 merge');
if _n_ > 10 then stop;
merge
sashelp.class
sashelp.cars
;
run;
%put %columns_slice (data=lotsa_columns, places=1:3);
%put %columns_slice (data=lotsa_columns, places=-1:-5);
%put %columns_slice (data=lotsa_columns, places=2:4 -2:-4 6 7 8);
1848 %put %columns_slice (data=lotsa_columns, places=1:3);
NOTE: FILTER=(varnum between 1 and 3)
Name Sex Age
1849 %put %columns_slice (data=lotsa_columns, places=-1:-5);
NOTE: FILTER=(varnum between max(varnum) + 1 -1 and max(varnum) + 1 -5)
Horsepower MPG_City MPG_Highway Wheelbase Length
1850 %put %columns_slice (data=lotsa_columns, places=2:4 -2:-4 6 7 8);
NOTE: FILTER=(varnum between 2 and 4) OR (varnum between max(varnum) + 1 -2 and max(varnum) + 1
-4) OR (varnum between 6 and 6) OR (varnum between 7 and 7) OR (varnum between 8 and 8)
Sex Age Height Make Model Type MPG_City MPG_Highway Wheelbase
Honoring options
data have;
array x(100);
array y(100);
array z(100);
run;
%put %columns_slice (data=have(keep=x:), places=2:4 8:10 -2:-4 -25:-27 -42);
1858 %put %columns_slice (data=have(keep=x:), places=2:4 8:10 -2:-4 -25:-27 -42);
NOTE: FILTER=(varnum between 2 and 4) OR (varnum between 8 and 10) OR (varnum between max(varnum)
+ 1 -2 and max(varnum) + 1 -4) OR (varnum between max(varnum) + 1 -25 and max(varnum) + 1 -27) OR
(varnum between max(varnum) + 1 -42 and max(varnum) + 1 -42)
x2 x3 x4 x8 x9 x10 x59 x74 x75 x76 x97 x98 x99
If you don't know number of variables, you can use this macro(you should specify num of first variables and num of last variables to keep in data set, libname and name of dataset):
%macro drop_vars(num_first_vars,num_end_vars,lib,dataset); %macro d;%mend d;
proc sql noprint;;
select sum(num_character,num_numeric) into:ncolumns
from dictionary.tables
where libname=upcase("&lib") and memname=upcase("&dataset");
select name into: vars_to_drop separated by ','
from dictionary.columns
where libname=upcase("&lib") and
memname=upcase("&dataset") and
varnum between %eval(&num_first_vars.+1) and %eval(&ncolumns-&num_end_vars);
alter table &lib..&dataset
drop &vars_to_drop;
quit;
%mend drop_vars;
%drop_vars(2,3,work,test);
Dataset before macro execution:
+---+---+---+---+---+---+---+---+---+----+
| a | b | c | d | e | f | g | h | i | j |
+---+---+---+---+---+---+---+---+---+----+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
+---+---+---+---+---+---+---+---+---+----+
Dataset after macro execution:
+---+---+---+---+----+
| a | b | h | i | j |
+---+---+---+---+----+
| 1 | 2 | 8 | 9 | 10 |
+---+---+---+---+----+
If the names follow a pattern just generate the list using the pattern. So if the names look like month names you just need to know one month to generate the other.
%let last_month = '01JAN2019'd ;
%let first_var = %sysfunc(intnx(month,&last_month,-12),monyy7.);
%let last_var = %sysfunc(intnx(month,&last_month,-0),monyy7.);
data want;
set have(keep= id1 id2 &first_var -- &last_var);
run;
If you cannot find a SAS function or format that generates the names in the style your variables use then write your own logic.
data _null_;
array month_abbr [12] $3 _temporary_ ('JAN' 'FEB' 'MAR' 'APR' 'MAY' 'JUN' 'JUL' 'AUG' 'SEP' 'OKT' 'NOV' 'DEK' );
last_month=today();
first_month=intnx('month',last_month,-12);
call symputx('first_var',catx('_',month_abbr[month(first_month)],year(first_month)));
call symputx('last_var',catx('_',month_abbr[month(last_month)],year(last_month)));
run;

Write out variables in a loop to a dataset

I want to assign a string to a variable in a loop and write out the variable to a dataset on each iteration.
Here is the code that prints out each variable
%macro t_size(inlib=,inds=);
%let one_gig = 5000;
proc sql noprint;
select ceil((nobs*obslen)/&one_gig) into :tsize
from sashelp.vtable where libname=upcase("&inlib") and memname=upcase("&inds");
quit;
%let no_of_tables=%eval(%sysfunc(int(&tsize)));
%if (&tsize gt 1) %then
%do i = 1 %to &no_of_tables;
%put &inds._&i.;
%end;
%else
%do;
%put &inds.;
%end;
%mend;
%t_size(inlib=SASHELP,inds=SHOES);
run;
This produces the required output:
SHOES_1
SHOES_2
SHOES_3
SHOES_4
SHOES_5
SHOES_6
SHOES_7
Instead of printing the variables out to the log I want to write them to a new, empty dataset.
It appears you are attempting to split a data set FOO into N one_gig pieces FOO_1 to FOO_N. Your first step also appears to be creating the FOO target table names. Computing the split names within a DATA step will save the computed names.
Example:
%macro make_split_names(data=, out=split_names, splitsize=5000);
%local lib mem;
%let syslast = &data;
%let lib = %scan(&data,1,.);
%let mem = %scan(&data,2,.);
data parts;
ds = open ('sashelp.cars');
nobs = attrn(ds, 'NOBS');
lrecl = attrn(ds, 'LRECL');
ds = close(ds);
do n = 1 to ceil ( nobs * lrecl / &splitsize );
name = catx("_", "&mem", n);
OUTPUT;
end;
keep name;
run;
%mend;
%make_split_names (data=sashelp.cars)
If you want a dataset then replace your last block of macro logic with a data step.
data member_list ;
length memname $32 ;
if &no_of_tables > 1 then do i=1 to &no_of_tables;
memname=catx('_',"&inds",i);
output;
end;
else do;
memname="&inds";
output;
end;
keep memname;
run;
Solution:
%macro t_size(inlib=,inds=);
%let one_gig = 5000;
proc sql noprint;
select ceil((nobs*obslen)/&one_gig) into :tsize
from sashelp.vtable where libname=upcase("&inlib") and memname=upcase("&inds");
quit;
%let no_of_tables=%eval(%sysfunc(int(&tsize)));
data temp;
length temp $100;
%if (&tsize gt 1) %then
%do i = 1 %to &no_of_tables;
temp= "&inds._&i.";
output;
%end;
%else
%do;
temp= "&inds.";
output;
%end;
run;
%mend;
%t_size(inlib=SASHELP,inds=SHOES);
run;
Just add data step named temp, where input in variable temp.
Output:
+---------+
| temp |
+---------+
| SHOES_1 |
| SHOES_2 |
| SHOES_3 |
| SHOES_4 |
| SHOES_5 |
| SHOES_6 |
| SHOES_7 |
+---------+

Rename all variable sas

I have a database where 12,000 variables are named "A0122_40", "A0122_45", "A0122_50" and so on. I would like to rename them by keeping in the initial name the numbers from 2 to 5. I would then like to create variables adding all the columns with the same name.
CASE 1
If you want to join two tables with same column names, and keep all data:
data class;
set sashelp.class (obs=2);
rename name=A0122_40
Sex=A0122_45
Weight=A0122_50
Height=A0122_55
;run;
Prepared Table:
+----------+----------+-----+----------+----------+
| A0122_40 | A0122_45 | Age | A0122_55 | A0122_50 |
+----------+----------+-----+----------+----------+
| Alfred | M | 14 | 69 | 112.5 |
| Alice | F | 13 | 56.5 | 84 |
+----------+----------+-----+----------+----------+
Code to rename:
%macro renameCols(lb , ds);
proc sql noprint;
select name
into :rn_vr1-
from dictionary.columns
where LIBNAME=upcase("&lb") AND MEMNAME=upcase("&ds")
AND NAME LIKE 'A0122%'
;
%let num_vars = &sqlobs.;
proc datasets library=&lb nolist nodetails nowarn;
modify &ds;
rename
%do i=1 %to &num_vars;
&&rn_vr&i=new_&&rn_vr&i
%end;
;
%mend renameCols;
%renameCols(work,class);
Result Table:
+--------------+--------------+-----+--------------+--------------+
| new_A0122_40 | new_A0122_45 | Age | new_A0122_55 | new_A0122_50 |
+--------------+--------------+-----+--------------+--------------+
| Alfred | M | 14 | 69 | 112.5 |
| Alice | F | 13 | 56.5 | 84 |
+--------------+--------------+-----+--------------+--------------+
As you see, all columns were renamed, exclude Age.
CASE 2
If you want to "append" all A0122_... in one , I suggest the next code:
data class;
set sashelp.class (obs=2);
rename name=A0123_40
Sex=A0123_45
Weight=A0122_50
Height=A0121_55
;
run;
Prepared Table:
+----------+----------+-----+----------+----------+
| A0123_40 | A0123_45 | Age | A0121_55 | A0122_50 |
+----------+----------+-----+----------+----------+
| Alfred | M | 14 | 69 | 112.5 |
| Alice | F | 13 | 56.5 | 84 |
+----------+----------+-----+----------+----------+
Code to rename:
%macro renameCols(lb , ds);
%macro d;
%mend d;
proc sql noprint;
select name
into :rn_vr1-
from dictionary.columns
where LIBNAME=upcase("&lb") AND MEMNAME=upcase("&ds")
AND NAME LIKE 'A012%'
;
%let num_vars = &sqlobs.;
/*Create a lot of tables with one column from one input*/
data
%do i=1 %to &num_vars;
&&rn_vr&i (rename=(&&rn_vr&i=%scan(&&rn_vr&i,1,_)) keep = &&rn_vr&i )
%end;
;
set &lb..&ds.;
run;
/*Count variable patterns and write it to macro*/
proc sql noprint;
select distinct scan(name,1,'_')
into :aggr_vars1-
from dictionary.columns
where LIBNAME=upcase("&lb") AND MEMNAME=upcase("&ds")
AND NAME LIKE 'A012%'
;
%let num_aggr_vars=&sqlobs.;
/*Append all tables that contains same column name pattern*/
%do i=1 %to &num_aggr_vars;
data _&&aggr_vars&i;
set &&aggr_vars&i:;
n=_n_;
run;
%end;
/*Merge that tables into one*/
data res (drop= n);
merge
%do i=1 %to &num_aggr_vars;
_&&aggr_vars&i
%end;
;
by n;
run;
run;
%mend renameCols;
%renameCols(work,class);
The res table:
+-------+-------+--------+
| A0121 | A0122 | A0123 |
+-------+-------+--------+
| 69 | 112.5 | Alfred |
| 56.5 | 84 | Alice |
| . | . | M |
| . | . | F |
+-------+-------+--------+
Is this what you are looking for:
proc contents data=have out=cols noprint;
run;
proc sql noprint;
select distinct substr(name,1,6) into :colgrps separated by " " from cols;
run;
%macro process;
data want;
set have;
%let ii = 1;
%do %while (%scan(&colgrps, &ii, %str( )) ~= );
%let grp = %scan(&colgrps, &ii, %str( ));
&grp._sum = sum(of &grp.:);
%let ii = %eval(&ii + 1);
%end;
run;
%mend;
%process;

Compare value to max of variable

I have a dataset with a lot of lines and I'm studying a group of variables.
For each line and each variable, I want to know if the value is equal to the max for this variable or more than or equal to 10.
Expected output (with input as all variables without _B) :
(you can replace T/F by TRUE/FALSE or 1/0 as you wish)
+----+------+--------+------+--------+------+--------+
| ID | Var1 | Var1_B | Var2 | Var2_B | Var3 | Var3_B |
+----+------+--------+------+--------+------+--------+
| A | 1 | F | 5 | F | 15 | T |
| B | 1 | F | 5 | F | 7 | F |
| C | 2 | T | 5 | F | 15 | T |
| D | 2 | T | 6 | T | 10 | T |
+----+------+--------+------+--------+------+--------+
Note that for Var3, the max is 15 but since 15>=10, any value >=10 will be counted as TRUE.
Here is what I've maid up so far (doubt it will be any help but still) :
%macro pleaseWorkLittleMacro(table, var, suffix);
proc means NOPRINT data=&table;
var &var;
output out=Varmax(drop=_TYPE_ _FREQ_) max=;
run;
proc transpose data=Varmax out=Varmax(rename=(COL1=varmax));
run;
data Varmax;
set Varmax;
varmax = ifn(varmax<10, varmax, 10);
run; /* this outputs the max for every column, but how to use it afterward ? */
%mend;
%pleaseWorkLittleMacro(MY_TABLE, VAR1 VAR2 VAR3 VAR4, _B);
I have the code in R, works like a charm but I really have to translate it to SAS :
#in a for loop over variable names, db is my data.frame, x is the
#current variable name and x2 is the new variable name
x.max = max(db[[x]], na.rm=T)
x.max = ifelse(x.max<10, x.max, 10)
db[[x2]] = (db[[x]] >= x.max) %>% mean(na.rm=T) %>% percent(2)
An old school sollution would be to read the data twice in one data step;
data expect ;
input ID $ Var1 Var1_B $ Var2 Var2_B $ Var3 Var3_B $ ;
cards;
A 1 F 5 F 15 T
B 1 F 5 F 7 F
C 2 T 5 F 15 T
D 2 T 6 T 10 T
;
run;
data my_input;
set expect;
keep ID Var1 Var2 Var3 ;
proc print;
run;
It is a good habit to declare the most volatile things in your code as macro variables.;
%let varList = Var1 Var2 Var3;
%let markList = Var1_B Var2_B Var3_B;
%let varCount = 3;
Read the data twice;
data my_result;
set my_input (in=maximizing)
my_input (in=marking);
Decklare and Initialize arrays;
format &markList $1.;
array _vars [&&varCount] &varList;
array _maxs [&&varCount] _temporary_;
array _B [&&varCount] &markList;
if _N_ eq 1 then do _varNr = 1 to &varCount;
_maxs(_varNr) = -1E15;
end;
While reading the first time, Calculate the maxima;
if maximizing then do _varNr = 1 to &varCount;
if _vars(_varNr) gt _maxs(_varNr) then _maxs(_varNr) = _vars(_varNr);
end;
While reading the second time, mark upt to &maxMarks maxima;
if marking then do _varNr = 1 to &varCount;
if _vars(_varNr) eq _maxs(_varNr) or _vars(_varNr) ge 10
then _B(_varNr) = 'T';
else _B(_varNr) = 'F';
end;
Drop all variables starting with an underscore, i.e. all my working variables;
drop _:;
Only keep results when reading for the second time;
if marking;
run;
Check results;
proc print;
var ID Var1 Var1_B Var2 Var2_B Var3 Var3_B;
proc compare base=expect compare=my_result;
run;
This is quite simple to solve in sql
proc sql;
create table my_result as
select *
, Var1_B = (Var1 eq max_Var1)
, Var1_B = (Var2 eq max_Var2)
, Var1_B = (Var3 eq max_Var3)
from my_input
, (select max(Var1) as max_Var1
, max(Var2) as max_Var2
, max(Var3) as max_Var3)
;
quit;
(Not tested, as our SAS server is currently down, which is the reason I pass my time on Stack Overflow)
If you need that for a lot of variables, consult the system view VCOLUMN of SAS:
proc sql;
select ''|| name ||'_B = ('|| name ||' eq max_'|| name ||')'
, 'max('|| name ||') as max_'|| name
from sasHelp.vcolumn
where libName eq 'WORK'
and memName eq 'MY_RESULT'
and type eq 'num'
and upcase(name) like 'VAR%'
;
into : create_B separated by ', '
, : select_max separated by ', '
create table my_result as
select *, &create_B
, Var1_B = (Var1 eq max_Var1)
, Var1_B = (Var2 eq max_Var2)
, Var1_B = (Var3 eq max_Var3)
from my_input
, (select max(Var1) as max_Var1
, max(Var2) as max_Var2
, max(Var3) as max_Var3)
;
quit;
(Again not tested)
After Proc MEANS computes the maximum value for each column you can run a data step that combines the original data with the maximums.
data want;
length
ID $1 Var1 8 Var1_B $1. Var2 8 Var2_B $1. Var3 8 Var3_B $1. var4 8 var4_B $1;
input
ID Var1 Var1_B Var2 Var2_B Var3 Var3_B ; datalines;
A 1 F 5 F 15 T
B 1 F 5 F 7 F
C 2 T 5 F 15 T
D 2 T 6 T 10 T
run;
data have;
set want;
drop var1_b var2_b var3_b var4_b;
run;
proc means NOPRINT data=have;
var var1-var4;
output out=Varmax(drop=_TYPE_ _FREQ_) max= / autoname;
run;
The neat thing the VAR statement is that you can easily list numerically suffixed variable names. The autoname option automatically appends _ to the names of the variables in the output.
Now combine the maxes with the original (have). The set varmax automatically retains the *_max variables, and they will not get overwritten by values from the original data because the varmax variable names are different.
Arrays are used to iterate over the values and apply the business logic of flagging a row as at max or above 10.
data want;
if _n_ = 1 then set varmax; * read maxes once from MEANS output;
set have;
array values var1-var4;
array maxes var1_max var2_max var3_max var4_max;
array flags $1 var1_b var2_b var3_b var4_b;
do i = 1 to dim(values); drop i;
flags(i) = ifc(min(10,maxes(i)) <= values(i),'T','F');
end;
run;
The difficult part above is that the MEANS output creates variables that can not be listed using the var1 - varN syntax.
When you adjust the naming convention to have all your conceptually grouped variable names end in numeric suffixes the code is simpler.
* number suffixed variable names;
* no autoname, group rename on output;
proc means NOPRINT data=have;
var var1-var4;
output out=Varmax(drop=_TYPE_ _FREQ_ rename=var1-var4=max_var1-max_var4) max= ;
run;
* all arrays simpler and use var1-varN;
data want;
if _n_ = 1 then set varmax;
set have;
array values var1-var4;
array maxes max_var1-max_var4;
array flags $1 flag_var1-flag_var4;
do i = 1 to dim(values); drop i;
flags(i) = ifc(min(10,maxes(i)) <= values(i),'T','F');
end;
run;
You can use macro code or arrays, but it might just be easier to transform your data into a tall variable/value structure.
So let's input your test data as an actual SAS dataset.
data expect ;
input ID $ Var1 Var1_B $ Var2 Var2_B $ Var3 Var3_B $ ;
cards;
A 1 F 5 F 15 T
B 1 F 5 F 7 F
C 2 T 5 F 15 T
D 2 T 6 T 10 T
;
First you can use PROC TRANSPOSE to make the tall structure.
proc transpose data=expect out=tall ;
by id ;
var var1-var3 ;
run;
Now your rules are easy to apply in PROC SQL step. You can derive a new name for the flag variable by appending a suffix to the original variable's name.
proc sql ;
create table want_tall as
select id
, cats(_name_,'_Flag') as new_name
, case when col1 >= min(max(col1),10) then 'T' else 'F' end as value
from tall
group by 2
order by 1,2
;
quit;
Then just flip it back to horizontal and merge with the original data.
proc transpose data=want_tall out=flags (drop=_name_);
by id;
id new_name ;
var value ;
run;
data want ;
merge expect flags;
by id;
run;