Truco SAS: Convertir fechas datetime de Unix a SAS

Como sabemos, los formatos de fechas en SAS y en otros sistemas operativos o bases de datos se interpretan y se almacenan como números que representan el número de días (o de segundos, en el caso de los datetime) transcurridos desde una fecha de origen. Las fechas anteriores a ella se almacenan como números negativos.

Lo que puede cambiar entre estos distintos sistemas es esa fecha origen a partir de la que se empieza a contar, de forma que una misma cifra puede representar fechas distintas dependiendo de qué sistema haya generado esa fecha. Esto da lugar a algunos problemas que debemos resolver cuando se realizan migraciones de datos de un sistema a otro. En nuestro caso, como programadores en SAS, debemos tener cuidado al importar fechas generadas con el timestamp de Unix y tener en cuenta que Unix no comienza a contar las fechas desde la misma fecha origen que SAS. Mientras SAS comienza a contar en ’01jan1960′; Unix comienza a contar en ’01jan1970′. Esos 10 años de diferencias hay que tenerlos en cuenta.

Por ello, si tenemos un datetime generado en Unix será necesario aplicar la siguiente corrección para que al convertir el número en fecha SAS éstas sean equivalentes:

fecha_SAS = fecha_Unix + 315619200;

Esa cantidad que sumamos es el número de segundos que representan esos 10 años. Existe otra forma de hacerlo, quizás un poco más elegante, que es utilizando la función dhms():

fecha_SAS = dhms('01jan1970'd, 0, 0, fecha_Unix);

La función dhms() construye un datetime a partir de una fecha y un número de horas, minutos y segundos. Con la instrucción anterior, se generará un datetime sumando el número de segundos expresados en la fecha_Unix a la fecha origen de Unix, que es el ’01jan1970′. Es una forma más elegante y además resulta más fácil recordar la fecha de origen de Unix, que el número tan grande de segundos que representan esa diferencia.
Si las fechas de Unix están representadas en milisegundos solo debemos utilizar una pequeña variante sobre lo anterior:

fecha_SAS = dhms('01jan1970'd, 0, 0, fecha_Unix/1000);

Trucos SAS: Añadir ceros por la izquierda

Estos son unos pequeños trucos para conseguir añadir tantos ceros por la izquierda como necesitemos para conseguir un tamaño dado para una cadena. Vamos a imaginar, para los ejemplos que vamos a poner, que necesitamos conseguir que nuestro valor tenga siempre 10 caracteres y que en caso de necesitarlo le añadiremos tantos ceros por la izquierda como sea necesario.
Lo primero, no es lo mismo si partimos de un número o de una cadena de texto. Disponemos de un método para cada uno de esos casos. En todo caso, la salida del proceso tiene que ser siempre una cadena de texto.

Añadir ceros por la izquierda a un número

Creamos una tabla NUMEROS con unos cuantos números de ejemplo de distintas longitudes. Deseamos crear una cadena de texto que tenga 10 posiciones y que, en caso de necesitarlo, se complete con tantos ceros por la izquierda como haga falta.

data NUMEROS;
    format num best12.;
    informat num best12.;
    input num;
    datalines;
    646543589
    9684634311
    654634
    6354
    1
run;

data NUM_FORMAT;
    set NUMEROS;
    format cadena $10.;
    cadena = put(num,z10.);
run;

Lo que hemos hecho para resolverlo es un casting del campo original. Con put() le hemos aplicado un formato z10., que es el que se encarga de añadirle ceros por la izquierda. La salida de esto es una cadena de texto, ya que si almacenamos un número, los ceros por la izquierda se perderán.

Añadir ceros por la izquierda a una cadena de texto

En caso de que partamos de una cadena de texto hay varias soluciones posibles, si bien, alguna de ellos no es válida si tenemos caracteres alfanuméricos en la cadena origen. Incluyo algunas en el siguiente código incluyéndolas como distintos campo de la tabla de salida:

data CADENAS;
    format char $10.;
    informat char $10.;
    input char;
    datalines;
    615546241
    A165432113
    135485
    A
run;

data CHAR_FORMAT;
    set CADENAS;
    format cadena1 cadena2 $10.;
    cadena1 = compress(repeat('0',9 - length(char))||char);
    cadena2 = put(input(char,best12.),z10.);
run;

La primera solución utiliza la función repeat() que incluye tantos caracteres, ‘0’ en este caso, como se le indique. Aquí se da una particularidad que no es común en SAS: si se indica 2 se añadirán 3 ceros, por lo que hay que restar 1 a la cantidad de caracteres que queremos añadir. Por ello, el segundo parámetro de repeat() está calculado como 9 (en vez de 10) menos la longitud original de la cadena. Concatenando la salida del repeat() a la cadena original tendremos lo que estamos buscando.
Otra opción es una solución menos general porque solo es válida si la cadena de entrada está formada solo por números. Esto es porque lo que haremos será reformatear la cadena de texto primero como número (formato best12.) para luego convertirla de nuevo en texto con el formato z10. que es el que vimos que se encarga de añadir ceros por la izquierda a números. Tenemos que usar una combinación de put() e input() ya que cada uno de ellos acepta números o cadenas de texto respectivamente.

Salida del código anterior

Truco SAS: Incrementales

Un pequeño truco, ¿cómo podemos crear un nuevo campo incremental con SAS? Un incremental es un campo que contiene un número que se incrementa de valor de 1 en 1 con cada nuevo registro y se utiliza para crear una clave única de una tabla.
En SAS tenemos dos formas para hacer esto: una para pasos data y otra para procedimientos SQL. En el paso data lo más fácil es usar el comando _N_ que te indica el número de registro en el que te encuentras.

data salida;
set entrada;
incremental = _N_;
run;

Pero _N_ solo funciona dentro de un paso data. Para el proc sql debemos usar la función monotonic() que hace exactamente la misma función.

proc sql;
create table salida as
select monotonic() as incremental, a.*
from entrada a;
quit;

SAS: Convertir cadenas de texto a número o fecha

Hay varias operaciones que hay que hacer frecuentemente para convertir cadenas de texto a otros tipos de formato o a la inversa.

Empezaremos con lo más simple. Por ejemplo, para convertir un número a texto debemos de utilizar la función put() que admite como parámetro un número y devuelve el formato indicado (también se puede utilizar para dar formatos de fecha):

data salida;
    b = 1;
    b1 = put(b,$2.);
run;

Resultado de conversión número a texto
Y para la operación contraria, convertir una cadena en número, utilizamos la función input() que hace la operación inversa:

data salida;
    a = '1';
    a1 = input(a,8.);
run;

Resultado de conversión texto a número
Convertir una cadena de texto con una fecha en un campo fecha es un poco más complicado. Necesitamos tomar cada valor de mes, día y año de esa cadena e introducirlos como parámetros de la función mdy(). Pero mdy() solo admite números como parámetros por lo que debemos convertirlos con un input() previamente. Esto quedaría así:

data salida;
    f = '01/12/2020';
    mes = input(substr(f,4,2),8.);
    dia = input(substr(f,1,2),8.);
    anyo = input(substr(f,7,4),8.);
    format fecha ddmmyys10.;
    fecha = mdy(mes,dia,anyo);
run;

Convertir texto a fecha
De forma más directa y resumida podríamos expresarlo de la siguiente manera:

data salida;
    f = '25/12/2020';
    format fecha ddmmyys10.;
    fecha = mdy(input(substr(f,4,2),8.),input(substr(f,1,2),8.),input(substr(f,7,4),8.));
run;

Convertir texto a fecha
Únicamente debemos tener en cuenta con este método qué va a pasar con las fechas en los que el día y el mes vengan expresados por un solo dígito: ¿vendrán como 01 o como 1? Para resolver este punto podemos cambiar nuestro substr() por un scan(). Esta función divide una cadena por el carácter que se le indique y nos devuelve la subcadena que ocupe la posición indicada por parámetro. Para el caso anterior, lo expresaríamos así:

data salida;
    f = '01/12/2020';
    mes = input(scan(f,2,'/'),8.);
    dia = input(scan(f,1,'/'),8.);
    anyo = input(scan(f,3,'/'),8.);
    format fecha ddmmyys10.;
    fecha = mdy(mes,dia,anyo);
run;

Convertir texto a fecha
Convertir una fecha en una cadena de texto se consigue por la combinación de las funciones put() e input(). El put() que va en el interior sirve para poner la fecha en el formato adecuado antes de convertirlo en texto. En caso contrario la fecha se convertirá como el número que es, en texto. Una vez tenemos el formato adecuado lo convertimos a texto con un input().

data salida;
    fecha = '25dec2020'd;
    fechado = input(put(fecha,yymmddn8.),$8.);
run;

Convertir fecha a texto

Truco SAS: Ejecución condicionada con variables de sistema

En ocasiones me ha pasado que estoy trabajando en versionar o modificar un código que ya está planificado y ejecutándose actualmente. Por lo que tengo una versión del programa ejecutando en el servidor y otra versión que desarrollo en el SAS Enterprise Guide. Lo que me pasa es que hay partes que no quiero ejecutar en el Guide, bien porque tardan, bien porque no las necesito o porque solo se pueden ejecutar en el servidor.

Para no tener que estar ejecutando un trozo de código sí, otro no, hay un pequeño truco para dejarlo todo listo para que la misma versión de código pueda correr en ambos entornos pero haciendo en cada uno lo que necesito. Este truco recurre a las variables de sistema de SAS.

SYSUSERID es una variable de sistema de SAS que informa del usuario que está ejecutando. En una instalación típica de SAS, el usuario que utiliza SAS Guide cuando trabajamos con él es «sassrv». Cuando planificamos un proceso en el servidor se ejecuta con «sasbatch». Esta es la diferencia que explotaremos ene l siguiente ejemplo para lograr ejecutar una parte del código solamente en el servidor:

options mlogic;
%macro en_servidor;
    %if "&SYSUSERID"="sasbatch" %then %do;
        data a;
            a=1;
        run;
    %end;
%mend;

%en_servidor;

Si ejecutamos este código en el Guide no sucederá nada: no se creará la tabla A. He añadido la opcion options mlogic; que incluye en el log información sobre las decisiones lógicas que toma (el resultado de los %if). Si revisamos el log podremos ver que indica que el usuario NO ES sasbatch.

MLOGIC(EN_SERVIDOR):  Empezando la ejecución.
MLOGIC(EN_SERVIDOR):  la condición %IF "&SYSUSERID"="sasbacht" es FALSE
MLOGIC(EN_SERVIDOR):  finalizando la ejecución.

Si creamos otra macro en la que modificamos el código utilizando el usuario de SAS Guide (sassrv) veremos que sí se ejecuta:

options mlogic;
%macro en_guide;
    %if "&SYSUSERID"="sassrv" %then %do;
        data a;
            a=1;
        run;
    %end;
%mend;

%en_guide;

Ahora el resultado del %if sí es verdadero, y el paso data se ejecutará.

MLOGIC(EN_GUIDE):  Empezando la ejecución.
MLOGIC(EN_GUIDE): la condición %IF "&SYSUSERID"="sassrv" es VERDADERA
MPRINT(EN_GUIDE):   data a;
MPRINT(EN_GUIDE):   a=1;
MPRINT(EN_GUIDE):   run;

NOTE: The data set WORK.A has 1 observations and 1 variables.
NOTE: Sentencia DATA used (Total process time):
      real time           0.00 seconds
      cpu time            0.00 seconds

MLOGIC(EN_GUIDE):  finalizando la ejecución.