SQL dentro do SAS

O SQL é uma linguagem utilizada comumente na manipulação de dados. É bastante intuitiva e fácil de utilizar. Para selecionar, por exemplo, uma coluna denominada Nome contendo os nomes de clientes da tabela XYZ, o comando a ser utilizado é praticamente a frase “selecionar nome da tabela XYZ”, mas em inglês:

SELECT Nome from tabela XYZ

Ou então, você pode selecionar todas as colunas da base XYZ com o comando ‘*’:

SELECT * from tabela XYZ

Como você pode ver, é bem intuitivo.

O SQL é uma das linguagens embutidas no SAS e você pode acioná-la utilizando o comando proc sql seguido pelo tradicional ponto e vírgula e finalizá-lo com o comando run do SAS. Para você ver como pouco muda, o comando de SQL utilizado acima ficaria da seguinte forma no SAS:

proc sql;
    select * from tabela XYZ;
run;

Caso você já acompanhe o blog, ou tenha lido alguns outros códigos, você deve ver muitas vezes ao invés do asterisco sozinho, algo como a.*. Isso ocorre porque quando temos duas tabelas, nós as denominamos de ‘a ‘e ‘b’ (ou t1, t2, etc.), sendo assim você precisa mencionar de qual tabela você está selecionando a coluna. O código acima, caso quiséssemos chamar a tabela XYZ de ‘a‘, ficaria da seguinte forma:

proc sql;
    select a.* from tabela XYZ as a;
run;

Veja que para uma tabela só não tem muita diferença, mas imagine com duas ou três como já complicaria escrever simplesmente select Nome. Se o SAS pudesse falar, ele diria: seleciono de onde? De XYZ ou das outras tabelas?

Veja que a linguagem de SQL embutida no SAS facilita bastante a manipulação dos dados e fornece aos usuários alternativas quando a lógica com o data step for mais complicada.

Como o código acima vai apenas mostrar para você a seleção feita, para utilizarmos a informação gerada dentro do próprio SAS podemos criar uma tabela. E, novamente, o código é uma mera tradução do inglês: Create Table:

proc sql;
    create table Tabela_Nomes as
    select Nome from tabela XYZ;
run;

Vamos utilizar a tabela abaixo com algumas pessoas, a renda que elas possuem e a origem dessa renda, para demonstrar outros comandos utilizados no proc sql:

Os principais comandos a serem lembrados no SQL, além do select, são:

  • Where: Um tipo de filtro, semelhante ao if. Vamos supor que a base contenha a coluna renda com o salário dos clientes e você queira apenas o nome de quem possui renda superior a mil reais. Agora, você terá que selecionar duas colunas, Nome e Renda, e a tabela com esses clientes seria criada da seguinte forma:
proc sql;
    create table Tabela_Renda as
    select id, Nome, Renda
    from tabela_exemplo
    where renda > 1000;
run;

  • Group by: Serve para agrupar os dados por algum campo em comum. Vamos pensar agora nos clientes que possuem várias fontes de renda, aquela pessoa que além do salário da empresa também possui rendas com aluguéis ou trabalhos de freelancer. Sendo assim, a base terá várias linhas com rendas diferentes para esse cliente. Se você quiser a renda total dele, você terá que somar essas rendas diferentes e agrupar pelo nome:
proc sql;
    create table Tabela_Renda_Cliente as
    select id, Nome, sum(Renda) as Renda_Total
    from tabela_exemplo
    group by id, nome
    having Renda_Total > 1000;
run;

  • Having : Bem semelhante ao where, é utilizado para o filtro depois de alguma tratativa. Como queríamos selecionar os clientes com renda acima de mil reais, e com o where só considerávamos a renda de um emprego, agora podemos filtrar os clientes com renda total acima de mil reais utilizando o having:
proc sql;
    create table Tabela_Renda_Cliente_2 as
    select id, Nome, sum(Renda) as Renda_Total
    from tabela_exemplo
    group by id, nome
    having Renda_Total > 1000;
run;

Order By: Ordena a tabela de acordo com algum campo, na ordem crescente.

Poderíamos ter gerado a renda dos clientes, mas ordenando pela renda:

proc sql;
    create table Tabela_Renda_Cliente_3 as
    select id, Nome, sum(Renda) as Renda_Total
    from tabela_exemplo
    group by id, nome
    having Renda_Total > 1000
order by Renda_Total;
Run;

Como combinar tabelas no SAS utilizando left join e full join

Provavelmente a tarefa mais comum de alguém que trabalha com dados seja combinar diferentes tabelas para se obter toda a informação que precisa. Por exemplo, se em uma tabela você tiver o nome dos seus clientes e a informação de idade em uma base de dados, e em uma segunda tabela tiver informações como endereço e sexo, é bem provável que ao fazer um estudo, um modelo estatístico, você tenha que combinar estas duas tabelas. Vamos ver agora como combinar duas tabelas de algumas formas diferentes.

Vamos utilizar como exemplo duas turmas da faculdade, uma do curso de Cálculo e outra do curso de Estatística.Podemos ter alguns alunos matriculados em uma delas e não matriculados na outra e vice-versa. As duas tabelas abaixo contêm os alunos de cada turma, o sexo, a idade e a nota final da disciplina:

Caso alguém ainda não saiba criar sua própria tabela para treinar, segue o código utilizado para gerar as que serão utilizadas aqui:

data turma_calc;
    length nome $22.;
    input Nome $ Sexo $ Idade Nota_Calc;
    datalines;
      Roberto M 22 10
      Maria F 24 10
      Pedro M 17 6
      Renata F 18 9
      Andre M 23 9
      Marcos M 27 8
      Patricia F 19 7.5 
      Luciana F 19 6.2
      Adriana F 22 4.5 
      Fernando M 21 6.6 
      Felipe M 20 7
      Flavia F 20 6
      Fabio M 18 1
;
run;

data turma_estat;
    length nome $22.;
    input Nome $ Sexo $ Idade Nota_Estat;
    datalines;
      Roberto M 22 4 
      Maria F 24 3
      Jose M 19 6 
      Renata F 18 8
      Andre M 23 9
      Alexandre M 17 8
      Patricia F 19 6 
      Luciana F 15 6
      Fernanda F 29 5
      Fernando M 21 6 
      Marcelo M 17 5 
      Flavia F 20 7 
      Fabio M 18 5
;
run;

Vamos supor que você queira completar a primeira tabela com as notas de estatística para os alunos que cursaram as duas matérias, mas quer manter os demais também na tabela. Ou seja, pensando em dois conjuntos, você quer adicionar a informação que está na intersecção dos conjuntos A e B à informação do conjunto A. Abaixo temos a representação desse caso em um Diagrama de Venn seguido pelo código em SAS e a tabela de saída deste código:

* traz as notas de estatisticas para a tabela com os alunos de calculo;
proc sql;
create table exemplo_1 as
    select a.*, b.*
    from turma_calc as a
    left join turma_estat as b
    on a.nome = b.nome;
run;
E se você quiser apenas os alunos que cursaram somente cálculo, excluindo quem cursou a outra disciplina:
* traz os alunos que cursaram apenas Calculo;
proc sql;
    create table exemplo_2 as
    select a.*, b.*
    from turma_calc as a
    left join turma_estat as b
    on a.nome = b.nome
    where b.nome is NULL;
run;
Caso você não quisesse essa última coluna, bastaria não ter selecionado nada da tabela b:
proc sql;
    create table exemplo_2b as
    select a.*
    from turma_calc as a
    left join turma_estat as b
    on a.nome = b.nome
    where b.nome is NULL;
run;
Se quiser trazer os alunos das duas turmas:
* traz todos os alunos das duas turmas;
proc sql;
    create table exemplo_3 as
    select coalesce (a.nome, b.nome) as Nome
             , coalesce (a.sexo, b.sexo) as Sexo
             , coalesce(a.idade, b.idade) as Idade
             , a.*
             , b.*
    from turma_calc as a
    full join turma_estat as b
    on a.nome = b.nome;
run;
 
Se você não quiser os alunos que cursaram as duas matérias, quiser apenas quem cursou uma:
* traz os alunos que cursam somente um dos dois cursos;
proc sql;
    create table exemplo_4 as
    select coalesce (a.nome, b.nome) as Nome
             , coalesce(a.sexo, b.sexo) as Sexo
             , coalesce(a.idade, b.idade) as Idade
             , a.*
             , b.*
    from turma_1 as a
    full join turma_2 as b
    on a.nome = b.nome
    where a.nome is NULL or b.nome is NULL;
run;
Se você já programou em sas ou sql, também já ouviu falar do right join, que muda muito pouco com relação ao left join. Como o próprio nome implica, você vai inverter a ordem das tabelas que serão unidas. Veja abaixo uma imagem completa que descreve todas as funções join do sql:
Imagem retirada do site Guia DBA

 

Dúvidas? Erros? Deixe um comentário ou um e-mail.

Como obter a frequência de uma variável no SAS

Mencionei o comando proc freq do SAS no post Frequência no R utilizando o pacote Hmisc. Agora, vamos nos aprofundar um pouco e ver o que é possível fazer com este comando.

O proc freq traz a frequência das variáveis que você quer analisar ou fazer qualquer tipo de análise descritiva, ou seja, traz o número de vezes que determinado valor da variável aparece. A sintaxe mais simples do comando é:
proc freq data= base_de_dados; 
    table variavel1 variavel2 ... variaveln; 
run;
Vamos utilizar os dados de crédito german_credit_2 como exemplo.
Assim como já fizemos no R, no post Árvore de Decisão no R, vamos discretizar as variáveis Creditability e CreditAmount, para só depois gerarmos as frequências.
No caso de Creditability, a discretização serve para visualizar melhor quem é bom e quem é mau pagador. No caso de CreditAmount, não conseguimos gerar uma frequência que seja útil, pois a variável é contínua. Pior ainda, se a base for muito grande, o comando muito provavelmente não irá funcionar. Afinal de contas, você estará trazendo o número de vezes que cada valor aparece. Separar em intervalos é o ideal nesse caso.
Note que no meu caso, a base foi importada para o SAS com o nome german_credit_21.
**** discretiza variaveis creditability e amount ****;
data german_credit_21_v2;
    set german_credit_21;

    length d_creditability $4.;
    if creditability = 1 then
    d_creditability = 'good';
    else d_creditability = 'bad';

    length d_creditAmount $9.;
    if creditAmount <= 2500 then
    d_creditAmount = '0000-2500';
    else if creditAmount > 2500 and creditAmount <= 5000 then
    d_creditAmount = '2500-5000';
    else d_creditAmount = '+5000';

run;

**** Frequencia de Creditability e Amount ****;
proc freq data= german_credit_21_v2;
    table d_creditability d_creditAmount;
run;
Na primeira tabela é possível ver a frequência e qual o percentual que representa do todo. Temos 300 clientes maus, sendo que isso representa 30% da nossa população. Além disso, temos também a frequência acumulada. Temos, na segunda tabela, 537 clientes que possuem dívida entre 0 e 2500, e 812 clientes que possuem entre 0 e 5000, sendo que esses clientes representam 81,20% da nossa população.
É possível também obter a frequência cruzada, ou seja, quantos clientes bons e ruins há em cada faixa de dívida:
**** Frequencia Cruzada de Creditability e Amount ****;
proc freq data= german_credit_21_v2 ;
    table d_creditability*d_creditAmount;
run;
E se você quiser deixar a tabela um pouco menos poluída, mantendo apenas a frequência, utilize o norow, nocol e/ou nopercent:
**** Nao traz frequencia por linha, coluna nem percentual ****;
proc freq data= german_credit_21_v2;
    table d_creditability*d_creditAmount / norow nocol nopercent;
run;
Também é possível salvar a tabela de frequência utilizando o out:
**** Cria uma tabela de output chamada tabela_frequencia ****;
proc freq data= german_credit_21_v2;
    table d_creditability*d_creditAmount / out= tabela_frequencia norow nocol nopercent;
run;
E se você quiser ordenar a tabela de frequência, basta acrescentar o order = Freq:
**** Sem ordenar ****;
proc freq data= german_credit_21_v2 ;
    table Guarantors;
run;

**** Ordena pela frequencia ****;
proc freq data= german_credit_21_v2 order= freq;
    table Guarantors;
run;
Assim como fizemos para o Creditability, nós poderíamos ter atribuído nomes ao invés de índices ao Guarantors. Porém, para não alterarmos nossa base original, poderíamos simplesmente aplicar uma formatação:
**** formata o nome ****;
proc format;
   value formatacao
        1 = 'none'
        2 = 'co-applicant'
3 = 'guarantor';
run;

**** gera frequencia e apresenta com nome formatado ****;
proc freq data = german_credit_21;
   tables Guarantors;
   format Guarantors formatacao.; 
run;

Trabalhando com espaços e acentos no SAS

A pior coisa é utilizar campos com espaços em branco e acentos quando se está programando. Não é recomendável e muitas vezes resulta em erros. Porém, às vezes temos que lidar com isso porque alguém construiu uma base com campos dessa forma e não quer mudar.

No SAS é mais fácil lidar com isso, basta acrescentar aspas e a letra n. É bem semelhante quando você utiliza data, só que precisa trocar o ‘d’ por ‘n’.

Vamos supor que você receba os dados da turma de alunos do post Visualizando seus dados: histograma e queira criar uma marcação chamada Reprovação que receberá o número 1 para alunos que tiraram notas menor que 6, basta você criar o campo ‘Reprovação’n:

data dados_v2;
    set dados;
    if notas < 6 then 'Reprovação'n = 1;
    else 'Reprovação'n = 0;
run;

Poderíamos também cair em uma situação que seria necessário criar um campo Data de Atualização com a data do dia de hoje. Para isso, temos duas opções:

## primeira opcao:
data dados_v2;
    set dados;
    'Data de Atualização'n = today();
run;

## segunda opcao:
data dados_v2;
    set dados;
    'Data de Atualização'n = '13JUL2016'd;
run;

Contador e função oposta ao lag no SAS

Veja que exercício de lógica interessante:

Pense em uma base com clientes que possuem vários carros. Você tem linhas contendo o nome do cliente, o nome do carro que ele possui e a data de compra do veículo. Você precisa descobrir quais clientes possuem apenas um carro. A que eu vou demonstrar talvez não seja a mais eficiente, até porque eu precisei de alguns dados ao longo do estudo, como por exemplo qual a ordem em que o veículo foi comprado. Sem isso, não sei se eu teria pensado de outra forma. O legal da forma que foi feito o tratamento é que trabalha o raciocínio lógico e ainda apresenta alguns algoritmos úteis.

O passo a passo utilizado é o seguinte: ordene seus dados pelo nome e data de compra do veículo; crie um contador para representar se é o primeiro carro que aparece do cliente; crie uma coluna adicional para saber qual o contador da linha seguinte. Veja que se você tiver na linha 10 o valor 1 no contador, e na linha 11 você tiver o valor 2, é sinal de que o cliente possui pelo menos dois veículos. No entanto, se você tiver o valor 1 no contador da linha 10 e o valor 1 no contador da linha 11, o seu cliente da linha 10 possui apenas 1 carro.

Foi falado um pouco sobra a função lag() do SAS no post Média Móvel (Bônus: Código SAS e função lag). Ela simplesmente pega o valor da linha de cima da coluna que você quer. Por exemplo, se eu fizer lag(conta_carro), eu vou saber qual o valor da coluna conta_carro da linha anterior a que estou verificando. No caso apresentado nesse post, precisamos de uma função que faça o contrário da lag, ao invés de retornar o valor da linha anterior, deve retornar o valor da linha seguinte. Você verá que não existe uma função pronta para esse caso, mas o script é bem simples e se encontra na Parte 5 do código abaixo.

Veja o exemplo completo que você pode simular e acompanhar o passo a passo explicado acima:
**** Parte 1: Cria uma base de clientes ****;
data base_clientes;
    input nome $ carro $ DtCompra mmddyy8.;
    cards;
    Andre Onix 02/05/15
    Andre Palio 12/10/13
    Andre Uno 01/05/14
    Paulo Idea 10/06/15
    Jose Vectra 01/04/16
    Jose Eco 12/05/16 28
    Maria Corsa 10/25/14
    Maria Celta 02/01/16
    Renata Onix 02/09/12
    Renata Palio 06/05/11
    Renata Fit 02/11/16
    Renata Civic 05/22/14
    Michael Fit 07/13/15
    Bianca Clio 09/05/15
    Thais March 08/05/16
    Renan Palio 11/05/13
    Renan Gol 11/05/14
    ;
run;

**** Parte 2: Trata a variavel data ****;
data base_clientes_v2;
    set base_clientes;
    format DtCompra date9.;
run;

**** Parte 3: Ordena pelo nome do cliente e data da compra do veiculo ****;
proc sort data= base_clientes_v2; by nome DtCompra; run;

**** Parte 4: Cria um contador do numero de carros = conta qtas vezes o nome aparece****;
data base_clientes_v3;
    set base_clientes_v2;
    by nome;
    primeiro = first.nome;
    ultimo = last.nome;
    if primeiro = 1 then
    conta_carro = 1;
    else conta_carro+1;
run;

**** Parte 5: Cria uma coluna com o valor da linha seguinte da coluna conta_carro ****;
data base_clientes_v4;
    recno=_n_+1;
    set base_clientes_v3 end=last;
    if not last
    then set base_clientes_v3 (keep=conta_carro rename=(conta_carro=next_conta_carro)) point=recno;
    else call missing(next_conta_carro);
run;

**** Parte 6: Mantem apenas quem tem conta_carro = 1 e que a proxima linha eh 1 tb ****;
data clientes_com_um_carro;
    set base_clientes_v4;
    if conta_carro = 1 and next_conta_carro = 1;
run;

Seja mais ágil utilizando firstobs e inobs no SAS

Quando se trabalha com milhões de dados, qualquer tratamento vai consumir alguns bons minutos – ou até horas – do seu dia. Ou seja, se você escrever seu código, rodar e ele apresentar algum erro, lá se vai uma parte do seu dia. Como ninguém é perfeito, esse processo de escrever + executar + problemas se repete várias vezes ao longo do dia. Logo, você vai gastar horas para escrever um código, muitas vezes simples, e vai passar horas ocioso. Porque você vai escrever um trecho do código, vai colocar para rodar, vai esperar alguns minutos e aí sim vai resolver um problema. Para depois fazer isso de novo. E aí segue seu dia, chato e improdutivo.

Evitar o problema descrito é mais simples do que parece. Ao menos parte dele é solucionado testando em uma amostra. Por exemplo, você quer converter o formato de data da sua base. Não escreva o código e fique lá 20 minutos rodando enquanto você não faz nada, ou finge fazer. Teste essa conversão em 10 linhas da base de dados e veja o que ocorre. Se o processo for grande, envolver várias etapas, siga testando com essas 10 linhas fazendo todos os tratamentos, seja enriquecendo a base com dados de outras fontes, seja tratando os campos. Para pegar apenas 10 linhas, você tem as duas opções abaixo, uma para quem usa data step e outra para quem usa proc sql:

* primeira opcao via data step
* seleciona do primeiro ao decimo elemento
* util caso voce queira selecionar elementos do meio da tabela;
data seleciona_amostra;
    set base (firstobs = 1 obs = 10);
run;

* segunda opcao via proc sql;
proc sql inobs = 10;
    select * from base;
run;

Bem simples. Desta forma não precisa mais escrever um trecho, deixar rodando por 1h para só depois checar se deu certo.

Espero que tenha ajudado. Dúvidas, sugestões ou críticas é só escrever aí embaixo.

Dica rápida: Acrescentando zero na frente do número no SAS

Sabe aquele cpf que algum sem noção mandou em formato numérico e veio com menos de 11 dígitos na sua base?

Para deixá-lo com 11 dígitos e com zero na frente é simples, use o put() e o ‘z11.’ na sequência.

Por exemplo, você recebeu a tabela Clientes, na qual a coluna DOCUMENTO veio em formato numérico, ou seja, muitos não vão ter 11 dígitos, e você pode querer o CPF correto por diversos motivos. A conversão é simples, você utiliza um length apenas para garantir o comprimento da variável correto e manda bala no put:

data clientes_v2;
    set clientes;
    length cpf $11.;
    cpf = put(documento, z11.);
run;

Utilizando datas dentro do seu script no SAS

Já falei de como trabalhar com datas no SAS em posts antigos, como em Formatando Datas no SAS. No entanto, nem sempre queremos trabalhar só com as datas que já estão nos nossos dados. Vamos supor que a gente queira trabalhar apenas com os alunos nascidos a partir de 31/01/1999 dos dados no post Como calcular a diferença entre duas datas no SAS?

Se você quiser pegar as informações cuja data é maior que 31/01/1999, os exemplos abaixo não funcionarão:

  1. if dt_nasc < “1999-01-31”;
  2. if dt_nasc < “31/01/1999”;

O SAS não vai entender que o que está à direita é uma data, para ele é um texto qualquer. Para trabalhar com essa data você precisaria utilizar as iniciais do mês e um d após o fechamento das aspas. No nosso exemplo, o que funcionaria seria:

if dt_nasc > "31jan1999"d;

Lembre-se que você deve colocar as iniciais do mês em inglês. Ou seja, para setembro, use “31sep2015″d ao invés de “31set2015″d!

Criando classes / categorias no SAS

Já expliquei no post Percentil – Conceito e Código SAS o que é percentil e como obtê-lo no SAS. Agora, uma coisa que é interessante, e eu precisei usar recentemente, é criar classes no SAS. Há uma forma bem direta de fazer isso através do proc rank. Porém, já havia um código mais “roots” para fazer isso e acho legal compartilhar. É interessante ver alternativas ao que já está preparado (outros softwares podem não ter o comando).

Para demonstrar melhor como criar classes, eu aumentei minha base de dados contendo as alturas dos indivíduos de uma amostra. Para não confundir com a tabela AMOSTRA, eu criei uma nova tabela chamada POPULACAO, contendo 60 alturas. O que vamos fazer no exemplo é criar uma coluna adicional na tabela com as letras de A a J indicando a qual percentil a altura do indivíduo pertence. Isso pode ser útil para obter dummies para regressões e até mesmo para tomar estatística descritiva dos indivíduos, e desse jeito é mais fácil do que utilizando o IF.

Para obter as classes, há dois passos:

  • Primeiro você deve obter o valor de cada percentil, nesse exemplo, comecei do 10° percentil e utilizei intervalos de 10 em 10, mas você pode dividir como for melhor para sua análise:
proc univariate data= work.populacao;
    var altura; output OUT=work.altura 
       pctlpre=p_altura_ pctlpts= 10 to 100 by 10;
run;
  • Agora, vou utilizar o CASE WHEN, que é uma função para lidar com condições no SAS. Veja abaixo que a gente inicia o código com o proc sql seguido de ponto e vírgula como sempre, até aí nenhuma novidade. Em seguida ‘dizemos’ para o SAS criar uma tabela (create table) com o nome populacao_classes na nossa work. Como nós queremos gerar essa nossa nova base a partir da base populacao criada anteriormente, é preciso dizer ao SAS quais campos ele deve trazer da base original. O asterisco após o select indica que o SAS vai trazer todos os campos da base de origem, que no caso é a tabela populacao que está na work. Em seguida vem o case when, que dá o comando ao SAS para atribuir uma letra caso a altura do indivíduo esteja no percentil em questão:
proc sql;
    create table populacao_classes as select *,

      case
      when altura <= (select p_altura_10 from work.altura) then 'A'
      when altura <= (select p_altura_20 from work.altura) then 'B'
      when altura <= (select p_altura_30 from work.altura) then 'C'
      when altura <= (select p_altura_40 from work.altura) then 'D'
      when altura <= (select p_altura_50 from work.altura) then 'E'
      when altura <= (select p_altura_60 from work.altura) then 'F'
      when altura <= (select p_altura_70 from work.altura) then 'G'
      when altura <= (select p_altura_80 from work.altura) then 'H'
      when altura <= (select p_altura_90 from work.altura) then 'I'
      when altura <= (select p_altura_100 from work.altura) then 'J'
      else 'outros'
    end AS classe_altura

    from work.populacao;
proc print;
run;

Para os que ainda estão com dúvidas, segue uma imagem para facilitar o entendimento (veja que as bases na imagem abaixo vão até a linha 25, e não até a linha 60 que é a última linha):