Converting numeric variables to character in SAS - sas

I have two datasets, both with same variable names. In one of the datasets two variables have character format, however in the other dataset all variables are numeric. I use the following code to convert numeric variables to character, but the numbers are changing by 490.6 -> 491.
How can I do the conversion so that the numbers wouldn't change?
data tst ;
set data (rename=(Day14=Day14_Character Day2=Day2_Character)) ;
Day14 = put(Day14_Character, 8.) ;
Day2 = put(Day2_Character, 8.) ;
drop Day14_Character Day2_Character ;
run;

Your posted code is confused. Half of it looks like code to convert from character to numeric and half looks like it is for the other direction.
To convert to character use the PUT() function. Normally you will want to left align the resulting string. You can use the -L modifier on the end of the format specification to left align the value.
So to convert numeric variables DAY14 and DAY2 to character variables of length $8 you could use code like this:
data want ;
set have (rename=(Day14=Day14_Numeric Day2=Day2_Numeric)) ;
Day14 = put(Day14_Numeric, best8.-L) ;
Day2 = put(Day2_Numeric, best8.-L) ;
drop Day14_Numeric Day2_Numeric ;
run;
Remember you use PUT statement or PUT() function with formats to convert values to text. And you use the INPUT statement or INPUT() function with informats to convert text to values.

Change the format to something like Best8.2:
data tst ;
set data (rename=(Day14=Day14_Character Day2=Day2_Character)) ;
Day14 = put(Day14_Character, best8.2) ;
Day2 = put(Day2_Character, best8.2) ;
drop Day14_Character Day2_Character ;
run;
Here is an example:
data test;
input r ;
datalines;
500.04
490.6
;
run;
data test1;
set test;
num1 = put(r, 8.2);
run;
If you do not want to specify the width and number of decimal points you can just use the BEST. informat and SAS will automatically assign the width and decimals based on the input data. However the length of the outcome variable may be large unless you specify it explicitly. This will still retain your numbers as in the original variable.

Related

SAS - Changing Existing Character Variable values to Numeric using Input

Have a variable called var1 that has two kinds of values (both as character strings). One is "ND" the other is a number out of 0-100, as a string. I want to convert "ND" to 0 and the character string to a numeric value, for example 1(character) to 1(numeric).
Here's my code attempt:
data cleaned_up(drop = exam_1);
set dataset.df(rename=(exam1=exam_1));
select (exam1);
when ('ND') do;
exam1 = 0;
end;
when ;
exam1 = input(exam_1,2.);
end;
otherwise;
end;
Clearly not working. What am I doing wrong?
A couple of problems with your code. Putting the rename statement as a dataset option against the input dataset will perform the rename before the data is read in. Therefore exam1 won't exist as it is now called exam_1. This will still be defined as a character column, so the input function won't work.
You need to keep the existing column, create a new numeric column to do the conversion, then drop the old column and rename the new one. This can be done as a dataset option against the output dataset.
The tranwrd function will replace all occurrences of 'ND' to '0', then using input with the best12 informat will read in all the data as numbers. You don't have to specify the length when reading numbers (i.e. 2. for 2 digits, 3. for 3 digits etc).
data cleaned_up (drop=exam1 rename=(exam_1=exam1));
set df;
exam_1 = input(tranwrd(exam1,'ND','0'),best12.);
run;
You are using select(exam1) while it should be select(exam_1). You can use select for this purpose, but I think simple if condition can solve this much easier:
data test;
length source $32;
do source='99', '34.5', '105', 'ND';
output;
end;
run;
data result(drop = convertedValue);
set test;
if (source eq 'ND') then do;
result = 0;
end;
else do;
convertedValue = input(source,??best.);
if not missing(convertedValue) then do;
if (0 <= round(convertedValue, 1E-12) <= 100) then do;
result = convertedValue;
end;
end;
end;
run;
input(source,??best.) tries to convert source to number and if it fails (e.g. values contains some word), it does not print an error and simply continues execution.
round(convertedValue,1E-12) is used to avoid precision error during the comparison. If you want to do it absolutely safely you have to use something like
if (0 < round(convertedValue,1E-12) < 100
or abs(round(convertedValue,1E-12)) < 1E-10
or abs(round(convertedValue-100,1E-12)) < 1E-10
)
Try to use ifc function then convert to numeric variable.
data have;
input x $3.;
_x=input(ifc(x='ND','0',x),best12.);
cards;
3
10
ND
;

set missing value with proc format

I'm quite new to SAS and I use the proc format to attribute value to codes :
proc format;
value code_to_value
-1 = .
1 = 0.5
2 = 0.25
3 - high = 0;
run;
I then convert it to a numeric column in my dataset.
DATA foo;
SET bar;
my_var = put(my_var ,code_to_value.);
RUN;
The problem is that this code set all -1 code to ., but as a character, not as a missing value.
How can I set it to missing ?
The put() function creates a character value. Combine with input() if you want to convert back to numeric, eg:
DATA foo;
SET bar;
my_var = input(put(my_var ,code_to_value.),best.);
RUN;
Allan's input(put is a very common and legitimate construct.
Here is more information for a deeper understanding.
The value statement of Proc FORMAT creates a format. Formats always performs a 'to-text' mapping.
A numeric format maps a numeric value to a character value
A character format maps a character value to a character value
formats can be applied programmatically using the PUT, PUTC and PUTN functions
A invalue statement creates an informat.
A numeric informat maps a character value to a numeric value
A character informat maps a character value to a character value
informats can be applied programmatically using the INPUT, INPUTC and INPUTN functions
There is no format/function combination that directly maps a numeric value to a numeric value.
Using INPUT() with a numeric will perform an implicit conversion to character (not a bad thing per se) prior to using the specified numeric informat. This example shows INVALUE and INPUT.
proc format;
invalue code_to_value
-1 = .
1 = 0.5
2 = 0.25
3 - high = 0;
run;
data have;
do my_var = -2 to 4; output; end;
run;
DATA want;
SET have;
my_varx = input(my_var, code_to_value.);
RUN;
----- LOG -----
NOTE: Numeric values have been converted to character values at the places given by:

SAS - Define array of letters

Is there a shorthand in SAS for defining a sequence of letters in an array?
Many languages possess a mechanism for doing so easily and I imagine SAS does too, although I'm unable to find a reference for it.
For instance, in R I could do
> x <- letters[1:4]
> x
[1] "a" "b" "c" "d"
In Python, one way is
>>> import string
>>> list(string.ascii_lowercase[:4])
['a', 'b', 'c', 'd']
In SAS, I currently am having to list the letters explicitly,
data _null_;
array letters (4) $ _temporary_ ('a', 'b', 'c', 'd');
do i = 1 to hbound(letters);
put letters(i);
end;
run;
You can use the COLLATE() to generate a string of single byte characters. If you don't know the ASCII code for the start of the block of characters you want then use the RANK() function.
So if you only want four characters start from 'a' you could do it this way.
length str $4 ;
str = collate(rank('a'));
Or you could also use the optional second parameter to COLLATE() to specify how many characters you want.
length str $4 ;
str = collate(rank('a'),rank('a')+vlength(str)-1);
There is no need for an "array", just use a variable.
data _null_;
length str $4 ;
str = collate(rank('a'));
do i=1 to vlength(str);
ch = char(str,i);
put i= ch= :$quote. ;
end;
run;
Result:
i=1 ch="a"
i=2 ch="b"
i=3 ch="c"
i=4 ch="d"
Not that I'm aware of, but it is trivial to write a macro to do that.
%macro letter_sequence(start=1,end=, lower=1);
%local i addon;
%if &lower=1 %then %let addon=96;
%else %let addon=64;
%do i = &start+&addon. %to &end.+&addon.;
"%sysfunc(byte(&i.))"
%end;
%mend letter_sequence;
data test;
array x[4] $ (%letter_sequence(end=4));
put x[2]=;
run;
Another option is to use the collate function and the call pokelong routine:
/*Upper case*/
data _null_;
array a[26] $1;
call pokelong(collate(65,65+25),addrlong(a1),26);
put _All_;
run;
/*Lower case*/
data _null_;
array a[26] $1;
call pokelong(collate(97,97+25),addrlong(a1),26);
put _All_;
run;
This bypasses all the usual mechanisms for assigning values for individual variables and takes advantage of the default memory layout used by SAS for character arrays, copying the whole alphabet in one go starting at the address for the first element.
N.B. call pokelong might not be available in some locked-down SAS environments, e.g. SAS University Edition. Also, this might not work properly with temporary arrays in SAS 9.1.3 or earlier on some platforms.
I think this is the only way to do this in SAS without either hard-coding your letters or writing some sort of loop.
You can use a combination of the rank function (which converts a character to its ascii value) and the byte function (which converts back the other way).
data _null_;
length seq $51; /* define seq as character variable */
do i = rank('a') to rank('d'); /* loop through ascii values of required letters */
call catx(' ',seq,byte(i)); /* concatenate letters */
end;
put seq; /* print final output */
run;

Convert mm/dd/yy to MonthName DayNumber, YearNumber in SAS

I want to be able to convert an entire column of dates this way. For example, 01/01/2017 to January 1, 2017. I realize there is a convoluted way of doing this but I am not entirely sure how i'd approach that logically. Also, does there happen to be a SAS format that does this? Thanks.
There does happen to be a format you can use. Here is a worked example using test data:
data test;
input datestring $;
datalines;
01/01/2017
;
run;
Using input converts the string value into a SAS date, and then the put function is used to create a character variable holding the representation you are looking for:
data test2;
set test;
date_as_date = input(datestring,ddmmyy10.);
date_formatted = put(date_as_date,worddate20.);
run;
The number 20 is used to describe a length that is long enough to hold the full value, using a lower number may result in truncation, e.g.
date_formatted = put(date_as_date,worddate3.);
put date_formatted=;
dateformatted=Jan
In some cases, the desired date format may NOT exist (in this case, it does 'worddate20.'), but as an example...
You could either write a function-style macro to convert a SAS date to "monname + day, year" format, e.g.
%MACRO FULLMDY(DT) ;
catx(', ',catx(' ',put(&DT,monname.),put(&DT,day.)),put(&DT,year4.))
%MEND ;
data example1 ;
dt = '26jul2017'd ;
fulldate = %FULLMDY(dt) ;
run ;
Or, you could build a custom format, covering all the dates which may exist in your data, e.g.
data alldates ;
retain fmtname 'FULLMDY' type 'N' ;
do dt = '01jan1900'd to '01jan2100'd ;
mdy = catx(', ',catx(' ',put(dt,monname.),put(dt,day.)),put(dt,year4.)) ;
output ;
end ;
rename dt = start
mdy = label ;
run ;
proc format cntlin=alldates ; run ;
data example2 ;
dt = '26jul2017'd ;
format dt fullmdy. ;
run ;

Create data set from macro variable SAS

I have a macro variable which stores a string of names, for example:
%let operation = add subtract divide multiply;
I wanted to transpose each element (to appear as observation) in the macro into a data set variable. So the data set should look like:
<obs> <operation>
<1> add
<2> subtract
<3> divide
<4> multiply
Use the SCAN() function. The default delimiters will work for your example, otherwise you can specify the exact delimiters to use.
%let operation= add subtract divide multiply;
data want ;
length obs 8 operation $20 ;
do obs=1 by 1 until (operation=' ');
operation=scan("&operation",obs);
if operation ne ' ' then output;
end;
run;
I still don't know enough about what you have and what you want. This example is contrived but may give you some help regarding syntax etc.
%let operation = add subtract multiply divide;
data operation;
length &operation 8;
array operation[*] &operation (2 3 10 4);
put 'NOTE: ' (operation[*])(=);
run;
*data set of names;
proc transpose data=operation(obs=0) out=names name=operation;
var &operation;
run;
proc print;
run;