Importar Excel con SAS

¿Cómo importar un Excel que nos han pasado para utilizar esos datos con SAS? Esa es la pregunta que vamos a resolver hoy. Lo más directo es importar directamente el Excel que nos interesa con un proc import. Este procedimiento permite importar varios tipos de ficheros para convertirlos en tablas SAS.

La forma más básica para importar un Excel es:

proc import out=tabla
    datafile = "../ficheros/datos.xlsx"
    dbms=xlsx;
    getnames=YES;
run;

En el OUT indicamos la tabla SAS donde queremos guardar la información contenida en el Excel, indicamos la ruta del Excel en el DATAFILE. Es necesario indicar la ruta completa del Excel para evitar errores. El parámetro DBMS indica qué tipo de fichero se está importando y también como se va a transcodificar el contenido del Excel. Para Excel podemos utilizar XLS (para los formatos anteriores a la versión 2007) y XLSX. (Hay otros DBMS para Excel). Finalmente GETNAMES indica que se tomarán los nombres de las columnas contenidas en la primera fila del Excel para los nombres de los campos de la tabla.
Si tenemos un Excel con varias pestañas es necesario entonces indicar qué pestaña deseamos usar. Usaremos el parámetro SHEET.

proc import out=tabla
    datafile = "../ficheros/datos.xlsx"
    dbms=xlsx;
    sheet='numeros_de_telefono';
    getnames=YES;
run;

Cuando los datos colocados en el Excel no comienzan en la casilla A1 sino que están más abajo o más a la derecha puede suceder que la tabla resultante tenga registros vacíos o columnas sin valores. Para evitar esto se puede, entonces, indicar el rango de celdas a importar con el parámetro RANGE y la misma notación que se usa en Excel:

proc import out=tabla
    datafile = "../ficheros/datos.xlsx"
    dbms=xlsx;
    sheet='numeros_de_telefono';
    getnames=YES;
    range = "B2:G1000";
run;

Si no sabemos precisar cuantas filas o columnas tiene la tabla a importar o estas son variables con el tiempo podemos utilizar un rango abierto como por ejemplo: range="B2:"

Para evitar tener que importar muchas pestañas podemos definir una librería apuntando al Excel de forma que las pestañas se visualizarán como tablas dentro de esa librería que representará al Excel entero. La pega de este método es que los parámetros de importación se establecerán por defecto y no podremos personalizar la importación, tendremos que tratar los datos luego. Para definir esta librería utilizamos un libname indicando después del nombre de la librería el parámetro XLSX y luego la ruta y nombre del fichero.

libname excel xlsx "../ficheros/datos.xlsx";

SAS: Uso de macrovariables

Las macrovariables de SAS son las variables que en SAS Base se utilizan, como en otros lenguajes de programación para almacenar ciertos valores. En SAS hay una particularidad que hace que su manejo sea distinto que en otros lenguajes que seguramente ya conocemos, y es que las variables de SAS no tienen formato. Así que cuando almacenamos un número, un texto o una fecha en una macrovariable se almacena exactamente ese contenido sin alteración.

Esta particularidad tiene dos consecuencias: debemos saber tratar cada variables en función del tipo de valor que almacena cuando queremos compararlo con otros valores para comparar cosas del mismo tipo. Y por otro lado no podemos asignar el resultado de una función a una macrovariable directamente.

Manejar macrovariables con números es fácil porque se pueden comparar directamente con números o con el contenidos de campos numéricos porque los números no se expresan con ningún formato especial:

%let a = 1;

data tabla1;
    numero = 1;
    if numero = &a then valida1 = 1;
    if &a = 1 then valida2 = 1;
run;

En el caso de los textos, estos tienen que ir entre comillas (dobles o simples da igual) y hay que tener en cuenta eso cuando utilicemos variables con SAS. Además hay que tener en cuenta que una macrovariable se resuelve si va sin comillas o dentro de comillas dobles, pero no lo hace si va dentro de comillas simples. Veremos esto primero porque puede generar algunas confusiones:

%let texto = Ojo por ojo y el mundo acabará ciego;
ComandoResultado
%put &texto;Ojo por ojo y el mundo acabará ciego
%put «&texto»;«Ojo por ojo y el mundo acabará ciego»
%put ‘&texto’;‘&texto’

Como veis el hecho de incluir las dobles comillas o no afecta al contenido de la macrovariable y eso hay que tenerlo en cuenta al usar la macrovariable y compararla con otro valor. Por ejemplo, tomemos estas variables:

%let ciudad1 = Madrid;
%let ciudad2 = "Barcelona";
%let ciudad3 = 'Sevilla';
OperaciónResultado
&ciudad1 = «Madrid»FALSEciudad1 contiene Madrid, y así escrito SAS lo confunde con el nombre de un campo de una tabla.
&ciudad1 = MadridTRUEInesperado resultado. Tanto el contenido de la macrovariable como el valor con el que se compara están sin comillas. SAS confunde eso con el nombre de un campo y está un campo consigo mismo. Si esta comparación estuviera en un if dentro de un paso data, se crearía el campo Madrid a nulo en la tabla de salida y esta comparación sería cierta.
«&ciudad2» = «Barcelona»TRUEEsto es correcto ambas partes de la igualdad están entre comillas dobles.
«&ciudad2» = ‘Barcelona’TRUEEste caso también es correcto porque un texto debe estar delimitado entre comillas, da igual si son simples o dobles.
‘&ciudad2’ = «Barcelona»FALSELo contrario no funciona porque no se resuelve el contenido de una macrovariable dentro de comillas simples: estaría comparando el nombre de la macrovariable con la cadena de texto.
&ciudad3 = «Sevilla»TRUELa variable ciudad3 contiene una cadena entre comillas simples lo que es comparable a un valor de texto. Este caso parece muy parecido al anterior ya que el contenido de la variable está entre comillas simples, pero nótese que no es lo mismo poner las comillas simples conteniendo la variable que si está dentro de la variable.

Con todo lo anterior, lo que os recomiendo es que utilicéis siempre las macrovariables de texto de la misma manera en vuestro código de forma general para no confundiros en como debéis expresar las igualdades. Por ejemplo, esta forma de hacerlo es una de las válidas:

%let nombre= Jose Luis;
data salida1;
    if "&nombre" = "Jose Luis" then sexo = 'H';
run;

Con las fechas pasa algo similar a lo anterior teniendo en cuenta que las fechas se indican entre comillas (dobles o simples) con una -d- detrás si es fecha o con -dt- si es una fecha-hora. Para trabajar con fechas recomiendo asignar a la macrovariable un valor como si estuviera en formato date9., pero sin las comas ni la -d-.

%let fecha= 15may2021;
data salida2;
    if '1jan2021'd <= "&fecha"d <= "31dec2021"d then anyo = 2021;
run;

De todo lo anterior se desprende que una macrovariable guarda todo lo que se le asigna sin interpretarlo y sin tener en cuenta su formato. Por esa misma razón una macrovariable puede contener también una función o un pequeño trozo de código que puede construirse y hacerse ejecutar en tiempo de ejecución… aunque esto lo veremos en otro momento.

SAS: Operaciones con cadenas de texto

La verdad es que SAS tiene un montón de funciones para tratar cadenas de texto. Y algunas de ellas son muy divertidas a la hora de programar por la cantidad de trabajo que te ahorran.
Vamos a empezar por algunas de las más típicas, las funciones para limpiar cadenas y quitar espacios. Vamos a utilizar la cadena como ejemplo:

%let cad = '  En un lugar de la  Mancha...  ';
Función Resultado Comentario
strip(&cad) ‘En un lugar de la Mancha…’ Elimina los espacios de los extremos, pero no los duplicados en el medio de la cadena.
compress(&cad) ‘EnunlugardelaMancha…’ Elimina todos los espacios de la cadena.
compbl(&cad) ‘ En un lugar de la Mancha… ‘ Elimina los espacios duplicados dejando uno solo. Deja al menos un espacio en los extremos si existían previamente.

Probablemente en algunas ocasiones sea bueno combinar algunas de ellas, como por ejemplo, para eliminar los espacios del inicio y del final y asegurarse de que no existen espacios dobles en la cadena podemos utilizar: strip(compbl(&cad)).

Tenemos también funciones para manejar mayúsculas y minúsculas.

%let cad = 'Iñigo fernández gonzález';
Función Resultado Comentario
upcase(&cad) ‘IÑIGO FERNÁNDEZ GONZÁLEZ’ Transforma la cadena a mayúsculas.
lowcase(&cad) ‘iñigo fernández gonzález’ Transforma la cadena a minúsculas.
propcase(&cad) ‘Iñigo Fernández González’ ‘Capitaliza’ todas las palabras de la cadena.

Otro tipo de funciones de texto son las que obtienen una subcadena a partir de una dada:
%let cad = 'Hélice de Watson & Crick';
%let fecha = "12/04/2021";

Función Resultado Comentario
substr(&cad,11,14) Watson & Crick Genera una cadena desde la posición 11 de la original y con 14 caracteres de longitud.
scan(&fecha,2,’/’) 04 Genera una cadena tomando el segundo trozo resultante de dividir la cadena indicada (&fecha) en trozos en función del carácter «/».

También tenemos funciones de búsqueda de una subcadena o de un carácter dentro de una cadena. Estas funciones pueden devolvernos la posición de la cadena buscada o sustituir la cadena buscada por otro valor:

%let cad = 'El sr. y la sra. Smith';
%let fecha = "12/04/2021";
Función Resultado Comentario
find(&cad,’Smith’) 18 Devuelve la posición de la primera ocurrencia de la subcadena buscada dentro de la cadena original.
index(&cad,’Smith’) 18 Devuelve la posición de la primera ocurrencia de la subcadena indicada.
translate(&fecha,’-‘,’/’) ’12-04-2021′ Sustituye uno a uno, uno o varios caracteres por otros en la cadena indicada.
tranwrd(&cad,’sr.’,’señor’) ‘El señor y la sra. Smith’ Hace lo mismo que la anterior, pero con grupos completos de caracteres.

Continuo mostrando más de estas funciones en la segunda parte de este artículo.

Cómo detener la ejecución en SAS

Hola de nuevo a todos. El tema de hoy es cómo detener el flujo de ejecución de un programa SAS. Normalmente lo haremos cuando se den determinadas condiciones, por ejemplo cuando un fichero no esté disponible, cuando se haya superado una hora de ejecución, etc.
Hay dos formas principales de detener la ejecución y utilizar una y otra depende del contexto dentro del programa:
Si estamos dentro de un paso data podemos utilizar el comando STOP.

data salida;
set entrada;
if _N_ = 100 then stop;
run;

Este ejemplo detendrá el proceso al llegar al registro número 100. La tabla de salida se obtendrá, pero sin el registro 100, por lo que se obtiene una tabla de 99 registros. El comando stop detiene la ejecución del paso data en el sentido de que no se procesan más registros pero no detiene la ejecución totalmente (ya que la tabla se genera).
En cambio, si estamos dentro de una macro utilizaremos una etiqueta y un comando %goto. Las etiquetas son identificadores a lo largo del código que identifican un punto determinado. En este caso tendríamos que situar esa etiqueta antes del %mend que termina la macro, y una condición que si se cumple lleve a esa etiqueta:


%macro ejecucion;
data salida1;
set entrada1;
run;
%if not %sysfunc(exist(entrada2)) %then %goto fin;
data salida2;
set entrada2;
run;
data salida3;
set entrada3;
run;
%fin:
%mend;
%ejecucion;

En este ejemplo el programa comprueba que exista la tabla «entrada2» antes de seguir con el proceso porque en caso contrario lleva el control de ejecución al final de la macro y la ejecución termina.

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

SAS: Operaciones con fechas

Trabajar con fechas y con formatos de fechas es una de las cosas a las que más cuesta acostumbrarse a aquellos que comienzan a trabajar con SAS. Aquí quiero recoger un pequeño resumen de las funciones de fecha más habituales en SAS, cómo funcionan y cuales son las diferencias entre ellas. Aquí veremos solo funciones de fecha, no entraré en las funciones fecha-hora.

Lo primero que hay que saber de las fechas en SAS es que, como en muchos otros lenguajes de programación, en realidad se representan internamente por un número y la fecha que vemos es una máscara o formato de representación de ese número. Al ser números se pueden realizar operaciones aritméticas con ellas directamente sin problema, por lo que el día de ayer se puede calcular como date()-1.

La fecha 0 es en SAS el 1 de enero de 1960. En otros lenguajes la fecha 0 puede ser otra. El 31 de diciembre de 1959 tiene el valor -1.

Para expresar una fecha en SAS podemos hacerlo por un número o por la notación '1jan2021'd. La fecha actual se obtiene con date().

  • datedate()
    Da la fecha del sistema, o sea, la fecha de la máquina en la que corre SAS. El resultado, sin aplicar ningún formato, será un número.

    Ejemplo Resultado
    date() 22240
    put(date(),date9.) 21NOV2020
  • datepartdatepart([datetime])
    Convierte un valor tipo fecha-hora en una fecha.

    Ejemplo Resultado
    datepart(datetime()) 22240
    put(datepart('15apr2020:00:00:00'dt),date9.) 15APR2020
  • intnxintnx([intervalo], [fecha], [incremento], [alineamiento])
    La función intnx() calcula la fecha resultante de aplicar un incremento (positivo o negativo) a la «fecha» indicada. El «incremento» representa el número de unidades del tipo definido en el parámetro «intervalo» que hay que añadir a esa fecha. Este «incremento» puede ser positivo o negativo. El «alineamiento» puede tomar los valores ‘B’ inicio del periodo; ‘E’ fin del periodo; ‘M’ medio del periodo y ‘S’ mismo día del periodo, y significa que si vamos a sumar x meses a una fecha si indicamos «B», la fecha resultante será el primer día del mes después de sumar x meses a la fecha dada. Lo vemos mejor en unos ejemplos:

    Ejemplo Resultado
    intnx('month','31aug2020'd,1) 1SEP2020
    intnx('month','31aug2020'd,1,'S') 30SEP2020
    intnx('month','18jan2020'd,-1,'E') 31DEC2019
    intnx('year','5jun1999'd,0,'B') 01JAN1999
    intnx('week','18apr2020'd,1,'S') 25APR2020
    intnx('qtr','18apr2020'd,0,'E') 30JUN2020
  • intckintck([intervalo], [fecha-inicio], [fecha-fin], [método])
    La función intck() calcula la diferencia entre las dos fechas indicándolo en función de la unidad temporal expresada en el parámetro intervalo. El ‘método’ sirve para indicar cómo se va a realizar el cálculo: puede tomar los valores ‘CONTINUOUS’ o ‘C’ para calcular el número de aniversarios sucedidos antes de la fecha-fin, o ‘DISCRETE’ o ‘D’ que toma el tiempo como una variable discreta y cuenta cuantas unidades hay entre las dos fechas proporcionadas. Esto último se ve mejor en el siguiente ejemplo donde se observa que cuando comienza un nuevo periodo temporal natural (en este caso estoy contando años) se incrementa la cuenta que ofrece el método ‘DISCRETE’.

    Ejemplo Resultado
    intck('year','1jul2000'd,'1jan2001'd,'D'); 1
    intck('year','1jul2000'd,'31dec2001'd,'D') 1
    intck('year','1jul2000'd,'1jan2002'd,'D') 2
    intck('year','1jul2000'd,'1jan2002'd,'C') 1
    yrdif('1jul2000'd,'1jan2002'd) 1
  • DAY(), MONTH(), YEAR()
    Estas funciones dan como resultado un número que representa, respectivamente, el día el mes y el año de la fecha que se indica entre paréntesis.

    Ejemplo Resultado
    day(date()) 21
    month('25may2020'd) 5
    year('25may2020'd) 2020
  • WEEK(), WEEKDAY()
    Estas funciones devuelven, respectivamente, el número que ocupa esa fecha dentro de un año (entre 1 y 53); y el número del día de la semana desde 1-domingo hasta 7-sábado.

    Ejemplo Resultado
    week('21nov2020'd) 46
    weekdate('21nov2020'd) /*sábado*/ 7
    weekdate('22nov2020'd) /*domingo*/ 1
  • QTR()QTR([fecha])
    Da como resultado un entero que indica el trimestre del año (de 1 a 4) en que se encuentra la fecha.

    Ejemplo Resultado
    QTR('21nov2020'd) 4
  • YRDIFYRDIF([fecha-inicio],[fecha-fin])
    YRDIF da la diferencia exacta en años entre dos fechas. El resultado es un número con decimales que representa exactamente la diferencia en años. Se puede añadir un tercer parámetro que hace que se consideren años de meses de 30 días y 360 días por año y otras opciones.

    Ejemplo Resultado
    yrdif('1jul2000'd,'1jan2002'd) 1.504109589

Formatos de fecha en SAS

Existen infinidad de formatos de fecha o fecha-hora en SAS, literalmente cientos, en todos los idiomas y en todos los tipos de calendarios. Creo que conocer lo que SAS nos puede ofrecer en cuanto a estos formatos nos puede ayudar mucho. Y además, si esto no es suficiente para ti, aún te puedes crear un formato personalizado.

Vamos a hacer un repaso de los formatos de fecha que me parecen más interesantes. No creo que nadie utilice todos, pero desde luego aquí hay para todas las preferencias. Os los ofrezco clasificados por el uso más natural que me parece que tiene cada formato. Incluyo también algunos formatos de fecha en español.

Formato Resultado Uso
Partes de la fecha
DAY. 19 Día del mes.
WEEKDAY. 1 Día de la semana (1=domingo).
DOWNAME. Sunday Nombre del día de la semana en inglés.
ESPDFDWN. domingo Nombre del día de la semana en español.
MONTH. 4 Número del mes.
MONNAME. April Nombre del mes en inglés.
ESPDFMN. abril Nombre del mes en español.
QTR. 2 Número del trimestre.
YEAR. 2020 Año
Fechas solo con números.
DDMMYY10. 19/04/2020 Compatible con Excel.
DDMMYYB10. 19 04 2020
DDMMYYC10. 19:04:2020
DDMMYYD10. 19-04-2020 Compatible con Excel.
DDMMYYP10. 19.04.2020
YYMMDDS10. 2020/04/19
YYMMDDB10. 2020 04 19
YYMMDDC10. 2020:04:19
YYMMDDD10. 2020-04-19 Compatible con Excel.
YYMMDDP10. 2020.04.19
Fechas con el nombre del mes.
DATE. 19apr20
DATE9. 19apr2020 Formato habitual de trabajo.
ESPDFDE. 20abr2020 Igual al anterior, en español.
Para fechar nombres de fichero o tabla
DDMMYYN10. 19042020 Para fechar (orden invertido).
YYMMDDN10. 20200419 Para fechar ficheros.
YYMMN6. 202004 Para fechar ficheros mensuales.
Fechas para informes
YYWEEKW7. 2020W04 Informes semanales.
YYMMN6. 202004 Informes mensuales.
YYMMS. 2020/04 Informes mensuales.
MMYYS. 04/2020 Informes mensuales.
YYMON. 2020APR Informes mensuales.
MONYY. APR20 Informes mensuales.
YYQ6. 2020Q2 Informes trimestrales
YYS6. 2020S1 Informes semestrales.
Fechas para texto o documentos.
WORDDATE. April 19, 2020 Fecha en texto en inglés.
WORDDATX. 19 April 2020
ESPDFWDX. 30 de enero de 2011 Como los anteriores pero en formato español.
WEEKDATE. Sunday, April 19, 2020 Fecha en texto con día de la semana en inglés.
ESPDFWKX. domingo, 30 de enero de 2011 Como el anterior pero en formato español.

Hay muchísimos más formatos de fecha que se pueden encontrar en la documentación de SAS.

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.

Importar ficheros XML con SAS

En otra entrada previa hemos visto cómo importar ficheros json, en esta nos vamos a fijar en la importación de ficheros xml.

Empezaré diciendo que XML significa «Extensible Markup Language». Son ficheros de texto donde el contenido viene marcado con unas etiquetas con una sintaxis parecida a la del HTML, lo que le confiere una estructura a la información que contiene. En realidad XML es la generalización del lenguaje HTML. El contenido de un fichero XML puede ser tan complejo que más que ser una tabla es más bien una pequeña base de datos con varias tablas relacionadas entre si.

El procedimiento de importación de un XML es parecido al de un json. Se crea una librería donde se importan las distintas tablas de la estructura del fichero.

Para este ejemplo tomaré un fichero de la Agencia Española de Meteorología que publica a través de su web Opendata y revisando la documentación del fichero que publica una organización de estándares llamada OASIS obtengo un fichero XSD que contiene la definición del fichero XML.

Con todo ello cargo los dos ficheros XML y XSD con un filename y a través de un libname creo la librería AEMET que contendrá los ficheros. Estos pueden ser copiados luego normalmente a cualquier otra librería si tienen que ser manipulados.

filename AEMET "&ruta/input/Z_CAP_C_LEMM_20200322101800_AFAZ691703COCO2321.xml";
filename map "&ruta/CAP.xsd";
libname AEMET xmlv2 xmlmap=map automap=replace;

Lo importante aquí en el comando libname es que el parámetro que indica el motor (o tipo) de la librería puede ser xml o xmlv2. Yo he conseguido hacerlo funcionar con la versión 2 y además he tenido que añadir el parámetro automap porque tras varios intentos de importar el XML no me estaba funcionando porque el mapeo del fichero que hizo SAS en un principio parece que no se refrescaba. Esta opción fuerza que vuelva a mapear el fichero con el fichero que le pasamos con el xmlmap.

Este es el resultado de la importación del fichero: una nueva librería AEMET con seis tablas:

libreria_AEMET

El fichero XSD sirve además, normalmente, para hacer una validación del contenido y estructura del XML. Lamentablemente libname no realiza esa función en SAS 9.4.

SAS: Bucles do y %do

En SAS se pueden utilizar tres tipos de bucles que además pueden utilizarse en código abierto o en lenguaje macro. Los tipos de bucles son: do, while y until. Lo relevante es que el uso de estos bucles en código abierto o lenguaje macro es muy diferente.

Lo primero que hay que saber es que los bucles de macro solo se pueden utilizar dentro de una macro y debido al momento en que se resuelve el código hay que tener ciertas consideraciones.

código abierto lenguaje macro

do x=1 to 10 by 1;
[comandos]
end;

%do x=1 %to 10 %by 1;
[comandos]
%end;

El bucle do (y %do) repite la ejecución de una parte de un código un número de veces que va representado por el valor de la variable indicada (x, en este caso) que comienza en 1 y termina con valor 10, saltando valores de 1 en 1. Tanto 1 como 10 se ejecutan dentro del bucle.

La diferencia entre usar el bucle do con código abierto o lenguaje macro está en que la x que se itera, es un campo en código abierto (que saldrá en la tabla de destino) y una macrovariable en lenguaje macro. Esto afecta a la forma de uso:

código abierto lenguaje macro

data salida1;
    do x=1 to 10;
        a=x;
        output;
    end;
run;

%macro iter_do;
    data  salida2;
        %do x=1 %to 10;
                a=&x;
                output;
        %end;
    run;
%mend;
%iter_do;

Hay también limitaciones en lenguaje macro en el sentido en que no se puede incluir como condición del bucle una referencia o cálculo sobre un campo de la tabla de entrada. La razón es que el código SAS que se va a ejecutar se «escribe» en el momento de interpretar la macro, pero no se ejecuta, por lo que la referencia al contenido de la tabla no existe aún. En este caso, por ejemplo, donde se usa el campo «a» de la tabla que antes hemos creado:

%macro iter_do;
    data  salida2;
        set salida1;
        %do x=1 %to a;
                a=&x;
                output;
        %end;
    run;
%mend;
%iter_do;

Obtenemos como salida un error del tipo:

"ERROR: A character operand was found in the %EVAL function or 
%IF condition where a numeric operand is required. The condition was: a".

Se puede superar este error declarando una macrovariable que tome el valor de "a" antes del inicio del bucle, pero no puede hacer referencia a un valor que no sea constante para todos los registros de la tabla. Si esto te pasa es síntoma de que tienes que estar utilizando un bucle do, no uno %do.

En otras ocasiones en el %to se incluyen ciertos cálculos que no son resueltos correctamente en la declaración del bucle. Esto puede requerir declarar una macrovariable previamente o utilizar un %sysfunc() por cada función de SAS que estemos utilizando. %sysfunc permite utilizar la mayoría de las funciones SAS en tiempo de ejecución macro. (Existen notables excepciones como put, que no admite %sysfunc). Un ejemplo de esto sería:

%let inicio=22000;  /* Fecha de 26mar2020 */;

%macro iter_do;
    data  salida3;
        %do x=&inicio %to date();
                a=put(&x,date9.);
                output;
        %end;
    run;
%mend;
%iter_do;

Que daría un error como el que sigue:

ERROR: Required operator not found in expression: date() 
ERROR: The %TO value of the %DO X loop is invalid.
ERROR: The macro ITER_DO will stop executing.

La solución es usar %sysfunc(date()). Es necesario usar un %sysfunc para cada función de SAS que esté incluida en la declaración del bucle de forma que se anidarán unas dentro de otras tanto como sea necesario. Atención a los paréntesis en las estructuras más complejas.