Teoria da Relatividade Especial

Albert Einstein

“Após dez anos de reflexões tal princípio emergiu de um paradoxo que eu já tinha antevisto quando tinha 16 anos: se eu perseguir um feixe de luz com a mesma velocidade que uma frente de onda (a velocidade da luz no vácuo) então eu deveria observar este feixe como um campo eletromagnético constante e periódico no espaço. No entanto tal coisa não parece existir, nem com base na experimentação nem de acordo com as equações de Maxwell…” Einstein (1951)

“Daqui por diante o espaço e o tempo estão fadados a desaparecer como meras sombras e apenas um tipo de união entre os dois terá preservada sua realidade independente” Minkowski, 1908.

“As coisas mais maravilhosas que podemos experimentar são as misteriosas. Elas são a origem de toda verdadeira arte e ciência. Aquele para quem essa sensação é um estranho, aquele que não mais consegue parar para admirar e extasiar-se em veneração, é como se estivesse morto: seus olhos estão fechados.”

 

Introdução

(1) O Cálculo foi desenvolvido simultâneamente e de forma independente por Leibniz. Muitos outros matemáticos contribuíram para o aperfeiçoamente desta disciplina.

A Mecânica é o ramo da física que estuda a ação das forças sobre os corpos e o comportamento dos sistemas materiais sujeitos à atuação dessas forças. Seus fundamentos foram lançados por Issac Newton no século XVII, apoiado sobre as contribuições de Galileu, Copérnico e Kepler. Para descrever com precisão a teoria recém elaborada Newton desenvolveu o formalismo matemático do Cálculo Diferencial e Integral(1).A mecânica de Newton é baseada em três axiomas fundamentais:

  • A lei da inércia, esboçada previamente por Galileu: um corpo não submetido à ação de forças externas conserva seu estado de repouso ou movimento.
  • Um corpo de massa \(m\) submetido à ação de uma força externa \(\vec{F}\) modifica estado de movimento de acordo com a relação$$\vec{F} = m \vec{a} \mathbf{,} $$onde \(\vec{a}\) é o vetor aceleração deste corpo. A massa é uma constante de proporcionalidade que exprime a relação entre a força aplicada e a aceleração obtida.
  • A lei de ação e reação: todo corpo A, submetido a uma força aplicada por outro corpo B, aplicará sobre o último uma força de mesma intensidade e sentido contrário.
(2) Esse axioma foi questionado e revisto pela Teoria da Relatividade de Einstein.

Três importantes teoremas de conservação são resultantes destes postulados:

  • Todo sistema físico fechado contém uma quantidade de matéria invariante(2), independentemente dos processos que ali ocorrem.
  • Sistemas com simetria linear em alguma direção exibem conservação do momento linear relativo a esta direção. Sistemas isotrópicos, com simetria por rotações em torno de algum eixo exibem conservação do momento angular relativo a este eixo.
  • A energia total em um sistema fechado é constante.

A mecânica de Newton, ou mecânica clássica, é uma teoria testada com alto grau de precisão para uma ampla faixa de experimentos. Ele descreve com excelente prescisão o movimento de bolas de bilhar, automóveis, satélites artificiais e o movimento planetário. Existe, no entanto, diversos fenômenos observados que não se encaixam dentro do panorama clássico, em particular os fenômenos relativos à átomos e moléculas,bem como às partículas subatômicas, e aqueles que envolvem partículas com velocidades muito altas, comparáveis à velocidade daluz. A primeira classe destes fenômenos foi corretamente descrita no finaldo século XIX e início do século XX por meio da Mecânica Quântica. A segunda foi encontrada por Albert Einstein.

Em 1905 Einstein publicou três artigos que revolucionaram a ciência física e abriram novas frentes em pesquisa fundamental. Um deles tratava do movimento browniano, em outro Einstein apresentava uma solução para o problema do efeito fotoelétrico que representou um impulso na formulação da teoria quântica. No terceiro ele apresentava a solução para uma divergência encontrada há algum tempo entre as teorias do eletromagnetismo de Maxwell e a mecânica de Newton. As duas teorias, embora estivessem ambas bem fundamentadas teórica e experimentalmente, não eram compatíveis entre si. Devido a crença profunda de que a teoria de Newton, capaz de descrever com precisão os movimentos observados na experiência diária, estava correta, a comunidade científica preferia manter inalterada a mecânica clássica e buscar por modificações da teoria eletromagnética.

Einstein, por outro lado, estivera interessado sobre como veria uma frente de onda luminosa se estivesse viajando com ela, na mesma velocidade. Ele compreendeu que a teoria de Maxwell estava correta e que, para altas velocidades quando comparadas à velocidade da luz, a mecânica deveria ser modificada. Desta forma ele desenvolveu a Teoria da Relatividade Especial, que passaremos a designar simplesmente por TRE.

Esta teoria se baseia em uma afirmação fundamental: a velocidade da luz é a mesma para qualquer observador, independentemente de sua velocidade. As consequências disto são curiosas. Um comprimento ao longo da direção do movimento se torna mais curto e relógios em movimento batem mais devagar. Espaço e tempo são aspectos de um mesmo fenômeno. Outro efeito interessante previsto é o de que a massa de um objeto aumenta,tendendo a infinito quando sua velocidade se aproxima da velocidade da luz.Este fenômeno é observado, por exemplo, dentro de um acelerador de partículas. Einstein mostrou ainda que matéria e energia são dois aspectos de uma mesmo princípio, podendo ser transformadas uma na outra, como ocorre dentro de um reator nuclear, de uma bomba de hidrogênio ou no interior de uma estrela.

A descoberta da teoria da relatividade não implica em que a teoria de Newton está incorreta. Pelo contrário, as equações clássicas do movimento estão contidas nas equações relativísticas como um caso particular, em situações onde as velocidades envolvidas são pequenas quando comparadas à velocidade da luz. Elas descrevem corretamente, ou com excelente aproximação, os fenômenos que ocorrem no cotidiano. Para o movimento em altas velocidades, tais como o que acontece dentro dos aceleradores de partículas, nas partículas cósmicas que atingem aatmosfera da Terra ou no interior de estrelas superquentes torna-se necessário usar a TRE que, sob estas condições, tem sido testada em inúmeros experimentos, com grande grau de precisão.

 

Fundamentos Históricos da TRE

Linguagem de Scripts para Linux

Introdução ao BASH

Para interagir com o seu sistema operacional (no nosso caso o Linux) você pode usar interfaces gráficas ou GUI (graphic user interfaceem>, as famosas janelas) ou rodar um programa que permite que os comandos sejam inseridos um a um através de linhas de comando (command line interfaces, CLIs). Existem vários destes programas (ou Shells) mas trataremos aqui de bash (Bourne Again SHell, uma versão muito usada e já instalada na maioria das distribuições linux.

Muitas vezes, quando você usa um computador, uma tarefa pode ser realizada mais facilmente através de um comando de linha, escrito em um terminal. Por exemplo, suponha que queremos obter em um texto toda a lista de músicas que se encontram no diretório (ou pasta). Uma forma de fazer isto seria a seguinte:

Abra o terminal e digite

$ cd ~/Musica
$ ls > minhas_musicas.txt
(1) Listas de comandos podem ser encontradas em Site: SS64 ou Linux commands.

Faça suas experiências com os comandos de linha com cuidado. Nunca execute um comando cuja ação você desconhece. Embora seja difícil que um uusário sem permissão de surperusuário ou admnistrador faça grandes estragos no sistema, você pode apagar dados ou corromper programas instalados dentro de sua conta. Não se esqueça: faça sempre cópias de segurança!

Nestes exemplos $ é o prompt de comandos, a indicação de que o computador está pronto para receber instruções. O comando cd troca o foco da execução para o diretório ~/Musica, onde ~ é um atalho para representar a pasta do usuário, (home/usr). A segunda linha faz uma listagem de todos os arquivos usando ls (list screen) e, ao invés de exibí-los na tela, (devido ao sinal > ou pipe, redirecionador) envia esta lista para um arquivo de nome minhas_musicas.txt. Poderíamos ainda filtrar a lista usando ls *.mp3, e apenas os arquivos com extensão mp3 seriam listados1.

Observe agora que a Shell permite comandos encadeados ou seja, a entrada de vários comandos de uma só vez. É possível inclusive passar resultados de um comando para outro em um único passo. Para fazer isto escrevemos os comandos na mesma linha e os separamos por um ponto e vírgula (;):

$ cd ~/Musica; ls > minhas_musicas.txt 

Essa linha efetuará o mesmo que as duas linhas do exemplo anterior. Para ver o conteúdo no novo arquivo criado digite gedit minhas_musicas.txt ou clique no arquivo de dentro de um gerenciador de arquivos como o Nautilus ou Thunar. Também é possível listar o arquivo de caracteres usando o comando cat minhas_musicas.txt.

Com esta técnica você pode escrever linhas com até 255 caracteres. Se uma sequência de ações for usada com frequência pode ser útil gravá-la em um arquivo, que depois pode ser executado quando necessário. Para isto use um editor qualquer que possa gravar arquivos como uma sequência simples de caracteres ASCII (isto quer dizer: sem comandos de formatação). O Ubuntu (e várias outras distribuições) traz o gedit ou kate (na distribuição KDE). Você também pode usar o geany, bluefish ou outro qualquer de sua escolha.

Uma lista de comandos gravada em um arquivo executável é chamada de script. Para especificar que se trata de um shell script colocamos na primeira linha a seguinte informação:

#!/bin/bash

O sinal de sustenido (#) em uma linha qualquer (exceto na primeira linha) é usado para indicar um comentário, um trecho que não é executado pela shell. Na primeira linha ele possui um significado especial: #! indica que o arquivo a seguir deverá ser executado pela bash shell fornecendo o caminho onde bash está instalado (normalmente em /bin/bash). Outros comandos, a serem executados na ordem em que se encontram, podem ser inseridos em linhas sucessivas. Você ainda pode usar o ponto e vírgula para separar comandos na mesma linha mas, em um script isto não é necessário nem recomendado. É mais fácil ler e compreender scripts onde cada linha contém um único comando.

O seguinte exemplo ilustra o conceito:

#!/bin/bash
# Esta linha é um comentário e não é executada
# Primeiro faça uma lista de músicas (mp3 e wav)
cd ~/Musica
ls *.mp3 > MusicasMp3
ls *.wav > ArquivosWav
# Faça uma lista de livros no formato pdf, na pasta ~/Livros
cd ~/Livros
ls *.pdf > LivrosEmPdf

Evidentemente estas linhas supõem a existências das pastas ~/Musicas e ~/Livros caso contrário uma mensagem de erro será exibida. Comentários são úteis para tornar mais legíveis os scripts, principalmente para o caso de outro usuário (ou você mesmo, no futuro) utilizar o script. Grave seu script com o nome lista_arquivos em uma pasta /.scripts (ou outro nome de sua preferência). Para executar esse script podemos digitar:

# se você já está na pasta scripts:
$ ./lista_arquivos
# se você não está na pasta scripts:
$ ~/.scripts/lista_arquivos

Alternativamente, para que um script seja encontrado a partir de qualquer outro diretório é necessário informar à shell como encontrá-lo. A shell usa uma “variável de ambiente” ( environment variable) chamada PATH para localizar onde estão os arquivos a serem executados. Para compreender isto melhor abra um terminal e digite:

$ echo $PATH
/usr/local/bin:/usr/bin:/bin

A segunda linha é a resposta (ou output) do comando echo, destinado a exibir informações, indicando que neste computador a shell fará uma busca nos diretórios /usr/local/bin, /usr/bin e /bin quando um comando for emitido. Observe que nossa pasta de scripts não está incluída. Para que o arquivo lista_arquivos seja executado podemos acrescentar a pasta onde ele reside ao PATH:

PATH=$PATH:/.scripts
$ echo $PATH
/usr/local/bin:/usr/bin:/bin: /.scripts
(2) Lembrando:

. Atalho para o diretório atual
cd ~ Para diretório home.
cd .. Volta para o dir pai

Observe que PATH contém agora a pasta /.scriptscode>. Esta alteração, no entanto, permanece válida apenas durante a sessão do terminal, ou dentro do arquivo de script de onde ela foi emitida. Veremos depois como alterar de forma permanente esta variável.

O ponto2, na primeira linha não comentada, é uma referência ao diretório atual, neste caso /.scripts. O til na outra linha, como já vimos, é um atalho para a pasta do usuário, o mesmo que /home/nome_do_usuário.

O último passo consiste em tornar este arquivo um executável. Para isto use o comando chmod (change mode):

$ chmod u+x teste1
# agora você pode executar o arquivo teste1
$ ./teste1

Se você procurar nas pastas ~/Musicas e ~/Livros deverá encontrar os arquivos recentemente gravados com o conteúdo esperado! O sistema de permissões do Linux (diretamente copiado do Unix) constitui uma das grandes vantagens deste sistema operacional. Por causa dele um usuário só pode alterar arquivos de sua propriedade enquanto arquivos de sistema só podem ser alterados pelo superuser ou gerente do sistema. Mesmo em seu próprio computador você não faz (ou pelo menos, não deve fazer) login como superuser.

Para alterar o PATH de modo definitivo você pode usar os seguintes procedimentos: primeiro verifique seu caminho atual usando o comando: $ echo $PATH. Para alterar a variável PATH para todos os usuários do sistema edite o arquivo /etc/profile (como root) e modifique a linha que começa com "PATH =".

sudo gedit /etc/profile
# forneça seu password
# procure a definição de PATH e edite para: PATH = "$PATH: $HOME/.scripts
(3) O ponto (.) como primeiro caracter do nome do arquivo .bash_profile ou indica que este é um arquivo oculto, um arquivo que não aparece normalmente em listagens requisitadas com o comando ls nem dentro do gerenciador de arquivos. Para listar arquivos ocultos use ls -a ou pressione Ctrl-H de dentro do Nautilus ou do Thunar (ou outro gerenciador de arquivos). Diretórios também podem ser ocultos da mesma forma.

(4) Segundo o manual online de bash (man pages) o arquivo .bash_profile é executado quando o usuário faz login em uma shell. Se você já está logado e abre uma janela de terminal (por exemplo) o arquivo .bashrc é executado.

Para alterar o PATH de um único usuário edite o arquivo /home/nome_do_usuario/.bash_profile ou .bashrc(Veja nota4). A especificação para o caminho no /etc/.bash_profile, ou /etc/.bashrc, é análoga ao caso anterior. Agora você não precisa usar sudo pois o arquivo é de sua propriedade e você tem permissão para alterá-lo.

gedit /etc/.bash_profile
# forneça seu password
# procure a definição de PATH e edite para: PATH = "$PATH: $HOME/scripts
export PATH

A linha de comando PATH = "$PATH: $HOME/scripts pega o conteúdo da variável de ambiente PATH já definida para todos os usuários no arquivo /etc/profile, e acrescenta a ela o nome do diretório $HOME/scripts (ou qualquer outro que você queira incluir). O comando export na última linha serve para que a variável fique visível fora do script onde ela foi definida.

Recapitulando: Para escrever um arquivo executável você dever informar em seu cabeçalho quem o executará (#!/bin/bash no caso de bash, #!/bin/env python no caso de um script Python, etc.

Depois você deverá informar o caminho completo para a sua execução ou colocar o diretório onde seu script está na variável path. Finalmente você deve torná-lo um executável com chmod.

Para saber um pouco mais sobre as permissões5 de um arquivo vamos executar o comando ls -l, onde -l é uma chave (opção) para exibir permissões:

$ ls -l teste1
 -rwxr-xr-- 1
 usuario_1 grupo_1 227 Jun 8 21:22
teste1
(5) Permissões:

r permissão para leitura(read)
w permissão para escrever (write)
x permissão para executar (execute)
- substitui r, w, x se a permissão é negada

As permissões são listadas por meio de 9 caracteres: o primeiro indica o tipo do arquivo (d, se é um diretório, l para links e para arquivos comuns. Em seguido temos 3 grupos formados por 3 letras r, w, x (veja tabela) que se referem ao dono do arquivo (o usuário que o criou), ao grupo a que este usuário pertence, e aos demais usuários do computador ou rede. No exemplo acima teste1 é um arquivo () pertencente à usuario_1, grupo_1. O dono pode ler, escrever (gravar) e executar (rwx), usuários do grupo podem ler e executar (r-x) mas não modificar este arquivo, e os demais usuários podem apenas ler (r–).

Exibindo valores e resultados

Para exibir o valor da variável PATH, uma variável do sistema estabelecida durante o boot, usamos o comando echo $PATH. Também podemos usar echo para exibir mensagens de textos:

$ echo Uma mensagem para seu usuario ...
 Uma mensagem para seu usuario ...
$ echo # pula uma linha
$ echo "Um texto pode ser delimitado por aspas duplas ou simples"
Um texto pode ser delimitado por aspas duplas ou simples
$ echo ou ate mesmo aparecer sem nenhuma aspas ou ate mesmo aparecer sem nenhuma aspas

Observe que não é obrigatório o uso de aspas embora, para alguns usuários, pode ser mais claro ler programas onde as strings estejam delimitadas. Mensagens são úteis em um script, por exemplo, para passar informações sobre o funcionamento ou requisitar a digitação de algum dado.

echo "Os seguintes usuarios estao logados no sistema:"
who

O comando who exibe todos os usuários logados no momento. Para exibir uma informação sem trocar de linha usamos o parâmetro -n.

echo -n "Hoje e: " 	date
# O script acima exibe algo como: 	Hoje e: Wed Jun 22 11:29:23 BRT 2011

Porque estamos falhando no ensino de Matemática?

Neste artigo pretendo analisar os motivos pelos quais o ensino das ciências exatas, em particular a matemática, enfrenta dificuldades, propondo algumas correções e sugerindo o debate em torno do assunto.

Minha afirmação de que há um problema com o ensino destas disciplinas não é o resultado de uma pesquisa aprofundada entre professores e egressos dos bancos escolares. Ela simplesmente vem da experiência em sala de aula e da constatação da dificuldade com que os alunos de cursos superiores, especialmente nos períodos iniciais, enfrentam ao cursar disciplinas tais como matemática e física, e a claríssima falta de formação apresentada por eles. A isto acrescento o argumento de que pessoas adultas, mesmo com curso superior completo em área do conhecimento que não em ciências exatas, muito pouco ou quase nada retém como conhecimento assimilado do conteúdo supostamente ministrado durante as fases de ensino básico e médio. É comum ouvir as pessoas reclamarem de que sofreram muito em seus cursos de matemática e física e, se pouco ficou retido ou acumulado como conhecimento adquirido, resta perguntar: para que todo este sofrimento?

Esta não pretende ser uma crítica aos colegas professores, em suas abordagens particulares do tema em sala de aula, e nem ao aluno que tem dificuldades no aprendizado. Pelo contrário, acredito que existem erros estruturais na abordagem de ensino e que é possível adotar rumos mais eficientes. Defendo que é possível alcançar níveis acadêmicos muito superiores aos atuais e, por isto, proponho um debate sobre como obtê-los. Para tanto separei a discussão em tópicos, consciente de que estes são interligados e se afetam mutuamente.

  • Investimentos insuficientes na educação longo da história do pais.
  • Educação familiar deficiente e excessiva dependência da escola na educação básica das crianças.
  • Escolha infeliz de tópicos na construção de ementas para o ensino básico e médio. Abordagem incompleta da matemática moderna.
  • Gap de gerações, principalmente devido à informatização.

Investimentos na educação insuficientes ao longo da história do pais

Este tópico afeta a educação de forma abrangente e não apenas o ensino de matemática. É evidente que se gasta muito pouco com educação no Brasil. Ensino de boa qualidade custa caro principalmente com remuneração de pessoal qualificado, aquisição de boas instalações e equipamento para laboratórios, computadores, ferramentas auxiliares de exposição e material de apoio. No entanto existe boa convergência entre os analistas de que este gasto é certamente o investimento de melhor retorno que pode fazer uma nação.

Em primeiro lugar há o desestímulo que existe para que uma pessoa abrace a carreira de professor, sabendo que estará submetida a condições de trabalho impróprias e salários defasados em relação as outras profissões. Hoje é bem conhecida a recusa dos jovens em se preparar para o trabalho em sala de aula e consequente falência de inúmeros cursos de licenciatura em ciências exatas nos diversos estados. Cada vez mais os cursos de licenciatura se tornam menos atraentes para os alunos de melhor formação básica. A própria forma de se encarar um curso de licenciatura é sintomática de um problema: alunos que se preparam para o ensino são frequentemente tratados como alunos de segunda classe e recebem apoio e estímulo inferior ao que se dá a seus colegas de bacharelado. Além disso os cursos são de menor duração, sendo realizados em apenas três anos, tempo insuficiente para criar uma base sólida de conhecimento na disciplina específica escolhida e, ao mesmo tempo, em pedagogia. A maioria dos alunos de licenciatura, especialmente nas escolas particulares, frequenta cursos noturnos e trabalha durante o dia, muitas vezes em regime de tempo integral, o que torna impossível para eles uma assimilação mínima do conteúdo. São estes alunos, formados de modo mediano, que compõem o quadro do professorado brasileiro atual, sem mencionar uma grande quantidade de professores sem formação específica nas disciplinas que lecionam.

Como ilustração, considere os níveis mais básicos da escola, oferecidos para as crianças mais jovens. Neste setor do ensino estão os professores com piores remunerações e com formação mais inadequada e insuficiente. As professoras ou “tias” são quase sempre pouco mais que “babás”, à despeito de uma “proposta pedagógica” elegante e bem elaborada que a escola certamente possui e guarda orgulhosa em seus arquivos e que estas professoras desconhecem ou não compreendem. Os estudos mais modernos sobre o desenvolvimento da cognição humana mostram que os anos iniciais de uma criança são marcados por um aprendizado rápido e intenso. Esta é a fase em que toda a base educacional, além do próprio caráter do indivíduo, é construída. Não me parece portanto apropriado entregar às pessoas com menor nível de formação as crianças em sua fase de maior potencialidade.

Reconhecidas as exceções das pessoas mais dedicadas que, por gosto ao ensino ou pela disciplina que ministra, procuram complementar sua formação, pode-se constatar uma formação acadêmica insuficiente nos profissionais do ensino e um apoio à educação continuada muito reduzido. Isto torna difícil uma reformulação de currículos e conteúdos programáticos que é necessária, como pretendo enfatizar.

Muitos outros fatores contribuem para a desestruturação da escola, entre eles a imposição oficial de propostas elegantes e pouco práticas que se alternam e se substituem em ritmo demasiado rápido para que mesmo um professor mais atento se mantenha familiarizado com elas.

Há um aspecto político importante associado a este problema. É muito evidente que o Brasil representa mundialmente apenas um mercado consumidor e que não precisa fazer um grande esforço para se manter atualizado com a rápida evolução científica e tecnológica mundial. O pais produz hoje um número reduzido de artigos e registros de patentes, em comparação com outras nações do mesmo porte. Este conceito, que parece dominar a elite dirigente, infelizmente está incorporado visceralmente pelas famílias e pelos próprios alunos que não assistem de perto à evolução tecnológica e que estão habituados simplesmente a comprar tecnologia pronta, assim como faz o próprio pais. A resistência contra o atingimento ou manutenção de ensino em nível elevado parte também destes alunos que não encontram motivos para se esforçar, tendo em vista um mercado de trabalho que parece valorizar pouco o desempenho acadêmico.
sala de aula

Dentro de um panorama de multi nacionalização irreversível, o esforço para obter bom nível de ensino representa um ato de resistência política, uma luta contra a incorporação de nosso pais que, se envolto pela globalização sem o devido preparo, será engolido e destruído, simplesmente.

Educação familiar deficiente e excessiva dependência da escola na educação básica dos jovens

Outro ponto importante fica explícito na queixa frequente, por parte dos mestres, de que os alunos não recebem uma educação básica em seus núcleos familiares, enquanto os pais exigem muito da escola na reposição desta carência. A “falta de educação” se reflete em relações interpessoais difíceis em sala de aula, com alunos agredindo verbalmente e até fisicamente seus professores que não contam com o apoio da escola, da família ou da própria sociedade e, com toda razão, sentem-se acuados. Esta característica é realimentada pela deficiência técnica dos professores que acabam por não impor respeito a seus alunos por pura e simples falta de boa formação técnica. A comum identificar uma situação em sala de aula onde o professor inseguro de suas respostas prefere adotar a postura de não tratar das perguntas feitas ou respondê-las de forma incorreta. O professor que não tem uma visão ampla do tema que leciona fecha as portas da curiosidade que leva ao aprofundamento e à pesquisa e não engaja o estudante em uma relação de respeito e cordialidade.

Escolha infeliz de tópicos na construção de ementas para o ensino básico e médio. Abordagem incompleta da matemática moderna.

Um ponto que considero ser um entrave para a boa evolução do ensino das ciências exatas está na escolha de tópicos e construção de ementas e grades curriculares. Como professor do ensino superior considero necessária uma reformulação destas ementas. Muitas vezes, em minha experiência em sala de aula, ouvi alunos, pais e até mesmo professores de matemática e pedagogos atribuírem a culpa da queda na qualidade do ensino à adoção da chamada matemática moderna. Estas pessoas costumam afirmar que antigamente os alunos aprendiam a fazer contas e que podiam memorizar com mais eficiência os tópicos elaborados pelo professor. Também é comum ouvir os pais reclamarem que não conhecem esta matemática e, por isto, não podem ajudar seus filhos no processo de aprendizagem.

(1) Entre eles estavam Henry Cartan, Jean Diedonné e André Weyl, no grupo inicial, que se inspirou nos avanços da escola alemã, representada por exemplo, por David Hilbert e Emily Noether. Mais tarde verificamos entre eles a presença de Serge Lang, Laurent Schwartz e vários outros.A história do grupo é fascinante e pode ser lida com algum detalhe no artigo sobre a História do Cálculo, neste site.

Este tema exige uma consideração mais detalhada. A matemática moderna é a designação que se dá a uma reforma do ensino e da própria compreensão da matemática ocorrido na França em torno de 1935 e anos seguintes e que desembarcou no Brasil na década de 1960. Havia naquela época, em toda a Europa, uma carência de professores experientes e com maior titulação, uma vez que muitos haviam morrido durante a primeira guerra mundial. Um grupo de jovens professores se reuniu para criticar os livros didáticos existentes, iniciando pelo livro adotado para o cálculo, e resolveu reescrever textos didáticos imprimindo neles uma maior organização lógica e didática. Os textos eram publicados pelo grupo sob o pseudônimo de Nicholas Bourbaki, um personagem fictício, adotado apenas como brincadeira e para indicar que o resultado era o esforço de um grupo. Mais tarde muitos dos participantes daquela iniciativa mostraram ser grandes matemáticos(1). Estes professores se reuniam e discutiam extensamente todas as contribuições oferecidas e os textos eram reescritos diversas vezes até se encaixarem plenamente dentro da proposta do grupo. Resumidamente o grupo Borbaki considerou que a matemática deveria ser baseada sobre a teoria dos conjuntos e que deveria manter, ao longo do processo de ensino, rigor lógico e simplicidade. Para isto criaram uma nova terminologia e reformularam conceitos ao longo dos tempos.

Congresso Bourbaki em 1939: Simone Weil, Charles Pisot, Andre Weil, Jean Dieudonné, Claude Chabauty, Charles Ehresmann, Jean Delsarte.

Aos poucos a reforma proposta por Bourbaki se instalou na educação francesa e depois se espalhou para todo o mundo. Naquela época era muito comum que matemáticos brasileiros buscassem na França sua titulação mais avançada, de forma que esta reforma logo se instalou no Brasil. O grupo Bourbaki recebeu também muitas críticas, as principais se referindo à ausência de um tratamento mais completo, sob forma de algoritmos, para a solução de problemas e uma supervalorização da álgebra em detrimento do pensamento geométrico. Muito foi dito sobre a ausência de figuras nos textos do grupo.

Embora aceite a afirmação de que não podemos simplesmente copiar uma iniciativa feita há quase um século, defendo aqui que a proposta básica de Bourbaki está correta e que a matemática deve ser inteiramente construída sob a noção básica de conjuntos. Afinal, a matemática é de fato um estudo sobre conjuntos e as relações entre eles. A reforma proposta pelo grupo francês não foi inadequada mas incompleta ou implementada de modo incompleto entre nós. Os alunos modernos deveriam assimilar os conceitos lógicos da matemática e, de posse destes, aprender a resolver problemas, que podem ser de natureza pragmática e aplicada sempre que possível, sem detrimento da formação mais abstrata e teórica. Considerações geométricas podem e devem ser usadas amplamente, assim como a contextualização do conteúdo e aplicação em problemas cotidianos, sempre que aplicável. Além disto, em uma época dos computadores e calculadoras de baixo custo e alta eficiência, não faz sentido sobrecarregar os alunos com operações complicadas e sofridas embora, claro, todos necessitem conhecer os procedimentos ou algorítimos usados para realizar as operação básicas.

Considero que a escolha de tópicos e níveis de abordagens do conteúdo das séries básica e média é inapropriada e ineficaz e necessita de ampla reformulação. A consideração sobre conjuntos deve ser mantida e ampliada. Relações entre conjuntos e membros dos conjuntos devem ser exploradas a cada passo, as operações fundamentais devem ser apresentadas neste contexto. O ensino da matemática em seus níveis mais básicos e fundamentais deve buscar a construção do pensamento lógico, da construção conceitual. Sendo impossível prever quais, entre todos os alunos, buscarão os níveis superiores das ciências exatas, é necessário ter uma cobertura flexível que permita o avanço dos mais inclinados a isto, sem submeter a totalidade dos alunos à exigência da obtenção de competências inatingíveis.

Gap de gerações

A meu ver existe uma dificuldade referente ao ensino que ultrapassa de longe as barreiras nacionais e não é exclusividade de nosso pais. Ela pode ser sentida em sala de aula, quando um professor mal treinado tenta ensinar “informática” para seus alunos pedindo que cliquem em um determinado ícone, ou arrastem, ou copiem e colem textos e imagens. Enquanto o professor termina seu duplo clique os alunos já se conectaram com os amigos em salas de bate papo, já enviaram seus textos repletos de abreviações assassinas da língua portuguesa, já editaram a imagem de uma colega inserindo-a em uma foto sensual e, com um pouco de sorte (ou azar!) algum aluno mais qualificado já invadiu o site de uma grande empresa e deixou lá um recado atrevido.

Existem estudos que mostram que a distância entre gerações pode ser sentida cada vez para diferenças de idade menores. Um aluno jovem hoje se senta para fazer a lição de casa com a televisão ligada, ouvindo música e falando com os amigos em salas de relacionamentos. E ele (ou ela) consegue fazer isto! A informática e a ampliação da disponibilidade da informação por meio da internet estão transformando o mundo de uma forma difícil de assimilar para as gerações com formação consolidada, entre eles pais e professores.

Hoje faz muito pouco sentido, ou talvez nenhum, pedir um trabalho escrito para os alunos, a menos que o professor seja versado em mecanismos de buscas e esteja disposto a passar a madrugada procurando as fontes de onde foram retirados os trabalhos e verificar se eles apresentam alguma criação do aluno ou apenas demostram capacidade de “copiar e colar”. Além de tomar iniciativas primárias (como a de proibir a wikipedia) é necessário aprender a usar a informática a favor da educação. Muitos alunos conseguem adquirir habilidades novas e extraordinárias através da internet, coisas tais como usar um software de edição de imagens ou vídeos ou até mesmo aprender a tocar um instrumento musical.

(2) Por exemplo o uso de softwares algébricos, tais como Mathematica, Maple ou Sage nos cursos de Cálculo não é simples e não foi ainda satisfatoriamente elaborado.Outra escolha interessante é o site Wolfram Alpha.

É claro que o uso do computador, estando em rede ou não, será parte integrante da vida das pessoas no mundo civilizado, e cada vez mais presente. Será preciso então incorporá-lo ao dia a dia das escolas de forma efetiva. Necessário será reconhecer que a plena utilização do computador como ferramenta didática não é plenamente conhecida e muitas iniciativas não apresentaram os resultados esperados(2). Defendo que os alunos devem ter uma informação básica em computação e que deveriam, pelo menos, conhecer os fundamentos da programação. O uso de uma máquina complicada, seja o computador ou outra qualquer, sem a menor noção de seu funcionamento favorece a formação de uma visão obscurantista da sociedade em que vivemos.

Gráfico gerado pelo software algébrico Sage

Nos dias atuais um indivíduo chega em casa e acende uma lâmpada cujo funcionamento só pode ser razoavelmente compreendido em termos de física quântica. Ele uso relógios e telefones onde os elétrons tunelam (atravessam) barreiras clássicas e, se ficar doente, pode fazer uso de um PET (positron emission technology), um aparelho que usa antimatéria (no caso o pósitron ou anti-elétron) para fazer um mapeamento minucioso e em camadas de seu corpo e órgãos internos.

É evidente que não se pode esperar que todos conheçam todos os ramos do conhecimento, mas é desejável que todos tenham uma boa noção sobre o funcionamento dos aparelhos e tecnologias que usam. Caso contrário estaremos usando caixas pretas ou “mágica” no sentido proposto pela terceira lei de Clarke-Asimov: “Qualquer tecnologia suficientemente avançada é indistinguível da magia”.

Aliás, vivemos já em um momento estranho da história da civilização, em que ciência e tecnologia avançada convivem com a miséria e a ignorância. É claro que este problema tem como causa maior a desigualdade na divisão de recursos em todo o planeta, que gera bolsões de extrema pobreza e ignorância. Mas mesmo entre as pessoas e sociedades mais favorecidas persiste e até floresce o obscurantismo sob forma de conservadorismo, de religiões fundamentalistas e outras mazelas do espírito humano desinformado, e de superstição pura e simples. Já há alguns séculos na história humana é impossível que uma pessoa domine todas as áreas do conhecimento. Isto torna ainda mais relevante a escolha de tópicos essenciais que devem prevalecer no esforço educacional. Caso contrário teremos um novo período de trevas em que poucos cientistas e técnicos, geralmente sob o jugo forte do poder econômico, ditarão a forma de vida dos cidadãos comuns, meros consumidores e espectadores do progresso e da evolução.

Para que serve a Matemática?

Os professores de matemática hoje se deparam com uma tarefa difícil: a motivação de seus alunos para os tópicos mais áridos desta ciência. Este problema tem diversas causas que vão desde os problemas com a qualidade geral do ensino até, por exemplo, a crença de que “está tudo pronto”, de que nada mais resta a desenvolver ou a descobrir. É comum ouvir reclamações de que um determinado cálculo pode ser realizado rapidamente em um computador e que, portanto, não seria necessário aprender a utilizar aquela técnica. No entanto sabemos que a tecnologia progride a passos rápidos e que o volume de artigos e novas idéias científicas nunca foi tão grande como hoje. Por isto, procurando contribuir para um melhor entendimento de nosso propósito como professores e estudantes de matemática, me proponho perguntar: por que devemos estudar matemática? Para que serve, afinal, a matemática?


Em primeiro lugar a matemática serve para descrever o mundo de uma forma rigorosa e precisa. Ela é uma linguagem, uma parte essencial na formação de modelos. Um modelo é um conjunto de definições e conceitos que busca descrever de maneira tão completa e fidedigna quanto possível o mundo natural ou uma parte dele, ou ainda processos artificiais criados pela crescente complexidade dos relacionamentos humanos. Esses modelos, além de serem tão completos quanto possível e possuírem coerência lógica, devem ser testados, comparados com o sistema real que ele pretende descrever por meio da observação ou da experimentação. Em caso de disparidades entre a descrição e a observação empírica o modelo deverá ser refeito e aperfeiçoado, ou mesmo abandonado se necessário.

Modelos são representações e não o objeto ou sistema de objetos descritos. Eles podem ser muito simples, como o modelo que representa o conjunto dos números naturais, {1, 2, 3, …}. Estes números foram usados, entre outras coisas, para contar quantas cabeças de gado um homem primitivo tinha e como ele poderia troca-las por alimentos ou outros bens. Nesta contagem ele pode ter usado pedrinhas (daí a palavra cálculo) para representar seus animais, estabelecendo uma relação biunívoca entre animais e pedras. Se possuía menos que uma dezena de bois e vacas, é possível que tenha usado paenas os dedos das mãos (de onde surgiu a palavra dígito). Embora simples este modelo não é trivial. É possível representar com um número natural quantos grãos de areia existem na Terra? (A resposta é sim!) E, principalmente, este modelo é incompleto.

Se pretendermos que nossas negociações incluam dívidas (e, como consequência, o calote!) teremos que expandir o modelo de forma a abarcar os números negativos e o zero, resultando no conjunto dos inteiros. O conjunto dos inteiros é ainda menos óbvio e mais abstrato que o dos naturais pois não temos conhecimento de alguma coisa concreta que exista em quantidades negativas! E mesmo este novo conjunto não é completo e não suficiente. Se quisermos oferecer como parte dos negócios uma fração de um terreno ou um pedaço de um queijo gigante teremos que ampliar o conjunto dos inteiros para outro conjunto que contenha frações, o conjunto dos racionais.


Esse parece agora ser um conjunto bem bonito e completo, o conjunto dos racionais, não tivessem os gregos descoberto que alguns números importantes não se encaixam dentro deles. A diagonal de um quadrado cujos lados medem um (em qualquer sistema de unidades) não é um racional e nem a razão entre a circunferência e o raio de um círculo (igual a 2 pi) não são números racionais. A experiência e a necessidade de descrever coisas pedem um modelo mais amplo. Por isto surgiram os irracionais, os números que não podem ser postos sob forma de uma fração. Racionais e irracionais, juntos, formam o conjunto dos números reais.

Estamos agora, a esta altura do desenvolvimento dos modelos matemáticos, muito longe dos conceitos intuitivos e primários. O conjunto dos números reais possui propriedades intrigantes e muito pouco óbvias. Entre dois números reais quaisquer existe uma infinidade de outros reais. Sua representação gráfica, a reta real, é infinita em ambas as direções e os pontos se empacotam de forma perfeita sem deixar nenhum furo ou imperfeição. O conceito é extremamente poderoso, possui coerência lógica e serve como modelo para a descrição de grande quantidade de objetos do mundo real. No entanto, não é tão claro se existe qualquer objeto no universo real que seja um bom representante desse modelo. Ele é útil para fazer descrições aproximadas de objetos que existem: se medirmos a distância entre duas cidades ou o comprimento de um fio estaremos ignorando, de forma totalmente apropriada e válida, as imperfeições do fio e da estrada que certamente não são contínuos como a reta real. Se ampliarmos com um potente microscópio uma seção do fio, veremos que ele, sendo de metal, é feito de granulações bem organizadas apresentando grandes vãos entre os átomos de sua estrutura. Isto não nos impedirá, no entanto, de usar réguas comuns para medir seu comprimento.

Observamos aqui uma tendência. O conjunto dos reais engloba os racionais, que por sua vez engloba os inteiros, que contém os naturais. O progresso do conhecimento se dá na direção da ampliação dos conceitos e na quebra das antigas barreiras. E, diferente do que se costuma pensar, os conceitos antigos, desde que bem estabelecidos, não são revogados como se revoga uma lei caduca e sim ampliados no que diz respeito a seu domínio de aplicação. Uma observação importante deve ser acrescentada aqui. Neste ponto do desenvolvimento da matemática (e mesmo antes disto, na verdade!), e da civilização humana como um todo, já teremos a necessidade de escolas. Precisaremos tirar as crianças de seus brinquedos e colocá-las em salas de aulas para garantir que o conhecimento acumulado por gerações de estudiosos, teóricos ou pessoas pragmáticas e engenhosas, seja repassado para as novas gerações. E, na medida em que cresce o domínio da ciência e as exigências das aplicações, mais tempo as pessoas deverão se dedicar ao estudo e a preparação para seu desempenho na vida e no ambiente de trabalho. Este é o preço que pagamos por termos descido das árvores a começado a usar ossos como ferramentas, modelar pedras para servir como instrumentos e armas, aprendido a domesticar o fogo.

Os modelos, é claro, passaram a representar objetos de complexidade crescente. Na planilha do engenheiro um prédio é um modelo de equilíbrio de forças onde a matemática permite que os pesos, as tensões no concreto e nos ferros se equilibrem para deixar estável a construção. Podemos descrever como se comporta uma mola mergulhada em um meio viscoso e sujeita a impactos externos, exatamente como existe no sistema de molas e amortecedores de um automóvel. O sistema é simples mas sua descrição completa exige um tópico matemático sofisticado, o das equações diferenciais. Queremos saber como uma corrente de elétrons se move dentro de materiais semicondutores. Para isto precisamos de um modelo bastante elaborado da física, a mecânica quântica. Com ela construímos relógios digitais, computadores e discos rígidos, entre outras máquinas diversas.

Grande parte das pessoas hoje, exceto aqueles excluídos da modernidade pela pobreza, usa direta ou indiretamente um satélite artificial para telecomunicações colocado em órbita geo-estacionária. Esses satélites giram em torno de nosso planeta com uma velocidade tal que parecerá, para um observador fixo na terra ou para a antena de seu receptor de TV, como estacionário em pleno ar. Para colocar um artefato desses em órbita é necessário usar o modelo da gravitação universal criado por Newton e, em alguns casos, será até mesmo necessário fazer correções usando o modelo da relatividade de Albert Einstein. Muita matemática está envolvida e provavelmente computadores sofisticados serão empregados nessas operações.

Exemplos de modelos mais prosaicos, mas igualmente úteis, podem ser encontrados na economia, no estudo das variações de preços dos produtos oferecidos ao consumidor, da inflação, do valor de um depósito feito meses atrás na caderna de poupança ou outra aplicação mais rentável. Modelos análogos serão usados para compreender a disseminação de uma doença, o contágio por um vírus ou a divulgação de um boato. Um modelo pode ser simples, como aquele que descreve os valores disponíveis em uma aplicação bancária com rendimento fixo, ou complicado e extenso como seria o modelo, ainda não desenvolvido, que descreve as oscilações nas bolsas de valores.

Tais modelos são úteis no presente, essenciais para a manutenção da vida moderna, complexa como ela se tornou. Mas eles têm uma habilidade extra: nos permitem prever o futuro. Um bom modelo descreve o que existe hoje e aponta para o que existirá amanhã, mesmo que esta previsão só possa ocorrer em termos probabilísticos, em alguns casos.

Um astrônomo poderá ver hoje em seu telescópio uma grande pedra varrendo o espaço em grande velocidade e decidir, usando os modelos matemáticos à sua disposição, se esta pedra colidirá ou não com nosso planeta. Como exemplo, a colisão do asteróide Shoemaker-Levi com o planeta Júpiter foi prevista com grande antecedência. Um bom modelo estelar será hábil para dizer, supondo conhecidas as condições atuais da estrela, em que estágio de sua evolução ela se encontra e por que etapas passará no futuro. Podemos, é claro, optar por uma visão poética dessa mesma estrela e isto será, sem dúvida, muito bom de se fazer. Mas, teremos perdido a habilidade de descobrir que essa estrela terá um dia esgotado seu combustível nuclear, que explodirá e poderá se tornar um buraco negro.

Finalmente chegamos àquela que considero ser a utilidade mais fina e essencial da matemática. Supridas as necessidades básicas do ser humano, garantida sua sobrevivência, seu anseio pela procriação e preservação da espécie e seu nível mínimo de conforto, a mente se volta para o conhecimento pelo conhecimento. Em um nível mais refinado não tem sentido perguntar para que serve a matemática. Por um lado um teorema serve porque é correto, porque é uma verdade. Por outro lado inúmeras teorias matemáticas foram desenvolvidas de forma puramente acadêmica, ou filosóficas, e muito mais tarde foram usadas em aplicações espetaculares.

Chegamos hoje a um estado de desenvolvimento da civilização onde a diversidade parece ser essencial. Precisamos de técnicos, de mão-de-obra braçal, de teóricos e de filósofos para enfrentar os desafios múltiplos e prementes por que passamos hoje. Um exemplo simples pode ser dado para corroborar esta afirmação: um pouco de ética bastaria para resolver grande parte das mazelas em nosso pais e conflitos pelo mundo afora e, neste sentido, precisamos de cidadãos filósofos. A experiência da história mostra que os povos que fizeram uso puramente pragmático da matemática entraram, ou já estavam, em declínio, enquanto os tempos áureos de qualquer povo, como na Grécia clássica, foram sempre pontuados pela livre investigação em todas as áreas a eles acessíveis, particularmente na matemática.

Vivemos em um período extraordinário da história da civilização. Temos hoje a habilidade para construir modelos científicos que descrevem o universo globalmente, que lançam perguntas sobre sua origem e destino e apontam para suas respostas. Estamos desvendando o código primário da existência humana através do projeto Genoma. Por outro lado, possuímos armas de destruição em massa e o poder para alterar de forma radical o clima no planeta. Os meios de transporte e as telecomunicações estão destruindo as barreiras nacionais e este processo não é suave ou indolor, particularmente para as nações mais pobres e com desenvolvimento tecnológico pouco consolidado.

A inserção em um mundo sem fronteiras exige profissionais de primeira linha, com formação simultaneamente profunda e ampla. Refletir sobre o avanço da ciência e da tecnologia, sobre os problemas que ela resolve e outros que ela causa, e participar deste progresso é essencial para que a sociedade brasileira possa se inserir na cidadania global em nível de igual participação e oportunidade.

O Efeito Placebo

Suponha que você tome um comprimido grande e amargo para uma dor de cabeça, uma depressão ou um problema na pele e verifica que o remédio se mostra eficiente e resolve o seu problema. Mais tarde você descobre que tomou apenas uma pílula inerte, sem nenhum medicamento, composta de farinha ou lactose e algum elemento para dar um gosto amargo. Pronto, você acaba de encontrar o “efeito placebo”.

* Não estou afirmando que a acunpuntura seja apenas um placebo.

A experiência clínica indica, que quanto maior o comprimido e quanto mais amargo, maior será seu efeito. Comprimidos grandes, triangulares ou de cores intensas funcionam melhor que pílulas pequenas, de formato comum e sem cor. Tratamentos complexos, envolvendo máquinas, correntes elétricas ou aparatos tecnológicos funcionam melhor do que uma simples massagem, a menos que o paciente atribua ao massagista ou terapeuta algum dom ou virtude extraordinária. Placebos podem ser aplicados sob a forma de pílulas, cremes, inalantes, choques elétricos e até aparelhos de ultra som. A acunpuntura*, com agulhas aplicadas em pontos aleatórios, e eletrodos supostamente (mas não de fato) implantados no cérebro também demonstraram ser placebos eficientes. Placebos são importantes até mesmo em relação à substâncias comprovadamente eficazes, sendo que a reação do paciente consciente de que está recebendo uma droga é diferente da reação daquele que recebe a mesma droga sem o saber.

Placebo. Fórmula: farinha de mandioca, açúcar de beterraba e jiló

Uma ampla discussão sobre o uso deliberado do placebo para fins terapéuticos tem ocorrido ao longo dos anos sendo a crítica mais importante baseada na ética questionável da ilusão e engano do paciente. Por outro lado é difícil negar que muitas práticas terapéuticas antigas e outras não tão antigas, hoje consideradas ineficazes, resultaram em benefício de quem procura um médico ou curador.

A expressão “efeito placebo” foi usada originalmente para descrever um engano ou uma ilusão quanto ao uso de medicamentos ou tratamentos médicos diversos. Hoje, e cada vez mais, ela adquire um significado novo e importante, que não pode ser desprezado. O placebo cura e este fato precisa ser estudado e explorado em benefício de quem sofre de alguma forma. Claramente o efeito indica a importância do cérebro e sistema nervoso central para a manutenção da saúde e sua restauração, no caso de doenças. Para designar o efeito contrário ao desejado, quando um placebo causa desconforto e piora do paciente, foi inventada a palavra “nocebo”. A existência de nocebos não faz mais que reafirmar a importância do fenômeno.

Placebos são tão importantes que a pesquisa e a industria farmacéutica, ao investigar o funcionamento de uma nova substância, precisa realizar testes comparativos entre a substância estudada e um placebo, inerte. Este é o procedimento denominado “duplo cego randomizado”, onde uma parte dos pacientes recebe placebo e a outra recebe o medicamento. A palavra “randomizado” vem de random (do inglês) que significa aleatório. A expressão duplo cego significa que nem os aplicadores nem os pacientes podem saber que lote contém a substância em teste uma vez que, como já se sabe, este conhecimento (exatamente por conta do efeito placebo) altera o resultado da pesquisa. Os médicos aplicadores acompanham seus pacientes sem saber se eles estavam sob efeito do medicamento ou do placebo e enviam seus resultados para os pesquisadores. A conclusão final deve ser obtida por meio de um rigoroso estudo estatístico. Talvez seja uma surpresa para muitos saber que alguns medicamentos (os antidepressivos, por exemplo!) funcionam muito pouco acima dos placebos contra os quais foram comparados!

A abordagem “duplo cego” é importante em outros contextos além da pesquisa farmacêutica, por exemplo em contextos que envolvem comportamento e reações humanas. É bem conhecido, por exemplo, o efeito de cura obtido por um médico atencioso que dedica algum tempo simplesmente ouvindo seu paciente e o reconfortando, ou do médico otismista em oposição aquele que praticamente desengana seu paciente, mesmo que involuntariamente. Também as “cirurgias simuladas”, independentemente de sua validade ética, demonstram fazer efeitos sobre os pacientes. Pode até mesmo ocorrer que um paciente passe por um processo de cura mesmo sabendo que está recebendo placebos, embora a crença ou confiança na validade do tratamento seja uma parte essencial deste efeito.

Cabe ainda notar que o efeito tem importância diferente para diferentes doenças e que nem todos os médicos e pesquisadores estão completamente confiantes em seu poder. Por outro lado ele oferece uma “explicação” razoável para algumas curas milagrosas, tratamentos paranormais, poder da prece e de fé, entre outros.

A questão do tratamento estatístico é importante e de difícil compreensão para os leigos na área de pesquisa. Muito pouco adianta uma, duas ou dez pessoas afirmarem que um determinado medicamento é eficaz ou não. A natureza pouco intuitiva de alguns comportamentos estatísticos faz com que seja essencial um tratamento matemático rigoroso.

George Price e o Altruísmo Biológico

O que você faria se, como pesquisador, concluísse que o altruísmo puro e desinteressado não existe embora, por motivo de fé acreditasse e defendesse que ele existe? Até que ponto você levaria seu esforço para ser generoso, mesmo que isto significasse a sua própria derrocada?

Muitas vezes na história da ciência um pensador não recebe em vida o crédito e o reconhecimento merecido. Foi o que ocorreu com o cientista americano George Robert Price. Price foi uma pessoa interessante e controversa, até recentemente pouco conhecida do público em geral. Nascido em 6 de outubro de 1922, se formou em química e trabalhou em áreas diversas, passando pelo Projeto Manhattan, depois pela Bell Labs. Mais tarde trabalhou na IBM onde foi um dos precursores no desenvolvimento de sistemas CAD (computer aided design). Price também contribuiu para o jornalismo científico escrevendo diversos artigos, alguns deles atacando com pseudocientífica a pesquisa em paranormalidade, muito em voga na época. Em torno de 1966 ele foi diagnosticado com câncer na tireoide e passou por uma cirurgia que deixou seu ombro esquerdo parcialmente paralisado e dependente de medicamentos. Price então se mudou para Londres e se envolveu no estudo da genética tentando resolver um problema importante da biologia evolucionária, com consequências importantes para a compreensão do comportamento dos animais e do ser humano. Por que alguns organismos se sacrificam para o benefício de outros organismos?


Anteriormente à descoberta do câncer e a consequente cirurgia Price sempre fora uma pessoa cética e um ateu convicto. Ele era pouco sensível para com familiares, tendo abandonado esposa e filhas e sua mãe já idosa. Mais tarde, principalmente afetado pela doença e a depressão causada pela tireoide debilitada, ele se converteu ao cristianismo e se dedicou inteiramente a demostrar, por meio do exemplo pessoal, que o altruísmo é possível. De completo cético ele assumiu uma atitude radical fazendo aquilo que acreditava ser a vontade de Deus e supondo que este o proveria com os hormônios necessários. Sua postura de altruísmo radical o levou à pobreza completa que, por sua vez, provocou uma piora no estado de sua doença. Price se suicidou em 6 de janeiro de 1975, totalmente pobre e tomado pela depressão.

Altruísmo biológico

Para compreender o problema que atormentava George Price é necessário saber o que se entende por altruísmo em biologia. O altruísmo ocorre quando qualquer organismo atua de forma a beneficiar outro organismo no que diz respeito à sua saúde ou possibilidade de sobrevivência, em prejuízo para seu próprio bem-estar. Não é necessário que exista intenção, consciente ou não. O altruísmo psicológico, por sua vez, é algo bastante diferente, sendo uma ação que necessariamente envolve intenção. Apesar de serem conceitos diversos é cabível discutir se existe conexão entre as duas formas de desprendimento. O cérebro humano, assim como todo o resto de nosso corpo, é o resultado da evolução e da seleção natural. Grande parte de nosso comportamento, atitudes e reações, mesmo aquelas que julgamos serem puramente culturais, são na verdade derivadas de propriedades adquiridas e inseridas ao longo de eras evolutivas em nossas características genéticas.

Pelo processo da seleção natural organismos mais aptos para a sobrevivência, com maior probabilidade, se reproduzirão e gerarão maior número de descendentes. Características biológicas favoráveis para a preservação da espécie incluem a eficiência na obtenção dos recursos para a manutenção da saúde e da vida em si, para escapar de predadores naturais ou superar desafios tais com alterações no meio ambiente e ataques externos de doenças. A habilidade para atrair parceiros do sexo oposto ou superar competidores neste mesmo processo também são decisivos para a escolha de quais genes serão replicados ou não. Estas propriedades são passadas adiante para a prole do indivíduo bem sucedido, enquanto características debilitantes levam o indivíduo à morte ou a diminuição de sua capacidade reprodutora. Isto não só seleciona indivíduos dentre os de uma mesma espécie com também estabelece níveis hierárquicos entre espécies diversas. O mecanismo básico de seleção, a “sobrevivência do mais apto”, é uma competição muitas vezes feroz que separa a eficiência da ineficiência.

Apesar da competição predominante na natureza também são observados atos de altruísmo, de auto-sacrifício de indivíduos ou grupos em relação a outros. Muitos exemplos de altruísmo podem ser observados na natureza: alguns pássaros aceitam chocar ovos que não os seus próprios e indivíduos estéreis ajudam na criação dos filhotes (como ocorre entre abelhas e formigas), abelhas se matam ao ferroar um inimigo para proteger a colônia e pássaros ariscam a própria vida para avisar o bando da aproximação de um falcão. Um caso extremo ocorre com a aranha Stegodyphus cujos filhotes recém nascidos devoram a mãe como estratégia de sobrevivência.

Isto levanta uma questão importante: como pode ter surgido o altruísmo entre seres vivos? Por que alguns organismos se sacrificam em benefício de outros, às vezes até mesmo não-parentes?

Os atos mais comuns e extremos de altruísmo animal são realizados pelos pais, especialmente mães, para com os filhos. Pais e mães criam os filhos em seus ninhos, às vezes em seu próprio corpo, os alimentam com grande custo para si mesmos e se arriscam ao protegê-los dos predadores. Um exemplo disto são as aves que simulam fraqueza para atrair para longe do ninho um predador. Uma ave manca na frente de uma raposa, estendendo uma asa como se estivesse quebrada para atraí-la para longe de seus filhotes, colocando sua própria vida em risco.

Para explorar mais extensamente a natureza do problema de altruísmo considere uma comunidade onde existem indivíduos altruístas e outros egoístas. Os indivíduos egoístas podem ter sua vida facilitada pelo altruísmo dos demais e ser mais bem sucedidos em sua sobrevivência e reprodução. Com o passar dos tempos a comunidade estará formada principalmente por uma maioria egoísta. Por outro lado, entre dois grupos diferentes, um grupo de altruístas pode ser mais eficiente na estratégia de sobrevivência por estarem em cooperação mútua, enquanto o grupo de egoístas pode dificultar sua própria sobrevivência por meio da competição exacerbada. Este é um caso de conflito em diferentes hierarquias biológicas, coberto pela equação proposta por Price.

Do ponto de vista do gene, de acordo com Haldane, compreender o altruísmo não é tão complicado. Um indivíduo pode perfeitamente sacrificar sua vida para salvar um filho ou um parente, de forma a que este sacrifício represente a perpetuação dos genes que ambos partilham. Neste caso houve um sacrifício para que o gene se beneficiasse.


Nos humanos o altruísmo tem suas raízes no sentimento de empatia que surge quando observamos pessoas em situações de angústia ou emergência. Esta capacidade nos humanos (e mamíferos em geral) está relacionada ao aleitamento pelas mulheres de sua prole. Isso explica porque as mulheres mostram ser, em geral, mais empáticas que os homens. A empatia é estimulada pela oxitocina, um hormônio envolvido no parto e aleitamento. Em experimentos de laboratório humanos de ambos os sexos exibem um aumento em suas respostas empáticas quando se aplica a oxitocina em suas narinas.

A oxitocina é produzida pelo hipotálamo e tem a função de promover as contrações musculares uterinas durante o parto e a ejeção do leite na amamentação. Ela é responsável por um aumento no sentimento de empatia, de reconhecimento social, na ligação entre casais, supressão da ansiedade e ampliação do sentimento de uma mãe para com seu filho. Ela também causa a sensação de prazer que uma mãe tem ao dar a luz o seu bebê, quando um pai segura o filho nos braços ou simplesmente quando as pessoas ligadas por afeto se abraçam. Como ocorre com a prolactina, a concentração de oxitocina aumenta depois de uma relação sexual. Por outro lado a carência da oxitocina no organismo está associada à sociopatias e psicopatias.

Após descobrir sua doença e ser operado sem grande sucesso G. Price se mudou para Londres e enviou uma carta para William Hamilton, considerado um dos estudiosos da teoria da evolução mas importantes desde Darwin. Nele ele pedia uma cópia de um artigo e informava que estava interessado na questão do altruísmo, se propondo a resolver o problema em aberto. Hamilton, que estava de saída para uma expedição de estudos no Brasil, enviou o artigo requisitado sem dar maior atenção ao pedido uma vez que Price não tinha nenhuma formação em biologia e era um desconhecido na área. No entanto, ao voltar alguns meses depois ele encontrou um texto de Price onde estava apresentada a equação, hoje conhecida como Equação de Price. Esta equação descreve a dinâmica da seleção natural, útil para a compreensão de sistemas de organismos onde existem conflitos entre interesses do gene e do indivíduo ou entre interesses do indivíduo e do grupo. O altruísmo consiste em um exemplo clássico deste conflito.

Quando Price encontrou sua equação ele foi imediatamente se consultar com os biólogos da UCL perguntando se aquilo era uma novidade ou algo já conhecido. E lá descobriu que a equação representava uma novidade e um grande avanço na área. Por isto foi quase imediatamente admitido como professor naquela Universidade, obtendo um local de trabalho e uma bolsa de pesquisa. Ele passou a trabalhar em cooperação com William Hamilton e John Maynard Smith, dois dos maiores teóricos ingleses da biologia evolucionária naquele tempo.

Durante o período em que colaborou com os pesquisadores em biologia ele fez três contribuições importantes: encontrou novo desenvolvimento para o trabalho de W. Hamilton sobre a seleção de parentesco, o que resultou em sua Equação de Price, introduziu com John Maynard Smith o conceito de estratégia evolucionária estável (um conceito central para a teoria dos jogos) e formalizou o Teorema Fundamental de Fischer para a Seleção Natural.


Embora bem sucedido em sua busca e tendo seus artigos mais importantes publicados Price se afastou do pensamento científico e assumiu uma vida de ascetismo cristão. Obcecado por coincidências numéricas, ele fazia operações matemáticas usando dados de sua própria vida, como datas e números de documentos pessoais, e concluiu dos resultados obtidos que não poderia haver coincidência naqueles números. Em sua visão ele estava recebendo de Deus a ordem de realizar alguma tarefa. Muitos de seus colegas relataram perceber nele, neste período, um comportamento autista e antissocial.

Ao explorar as consequências da equação por ele mesmo desenvolvida Price ficou profundamente impressionado com o fato de que o altruísmo sempre envolvia algum tipo de ganho para o indivíduo ou sua espécie. Parecia não existir altruísmo puro e incondicional. Desapontado, ele resolver mostrar por meio do exemplo pessoal que a teoria poderia ser suplantada pelo esforço e boa vontade humana. Por isso saiu percorrendo as ruas de Londres procurando por pessoas desvalidas, alcoólatras, moradores de rua ou qualquer um que precisasse de apoio, decidido a iniciar um programa radical de interferência altruísta na vida destas pessoas. Ele cedia seu dinheiro, comprava comida e oferecia ajuda para qualquer tipo de problema que aquelas pessoas pudessem ter, ajudando-as inclusive com seus problemas legais ou com a polícia. Com o passar do tempo Price passou a abrigar os sem-teto em casa sendo obrigado a dormir no laboratório quando sua casa se tornou cheia demais. Esta situação perdurou até que seus próprios recursos foram esgotados e ele se tornou mais um dos destituídos, tendo que morar nas ruas junto com as mesmas pessoas que pretendeu ajudar. Aos poucos ele entrou em um processo de degeneração, desenvolvendo mais uma vez uma forte depressão, em parte causada pela impossibilidade de adquirir seus medicamentos. Sua degeneração foi atribuída à interrupção no uso dos medicamentos e à sua decepção frente à incapacidade de transcender as forças biológicas a atingir o auto-sacrifício e o altruísmo.


George Price morreu completamente pobre e abandonado. Poucas pessoas compareceram a seu enterro, dentre elas alguns companheiros de vida nas ruas, gente que o adorava por ter recebido dele ajuda em momentos de dificuldades. Em meio ao grupo estavam William Hamilton e John Maynard Smith, em reconhecimento pela sua genialidade. Seu corpo foi enterrado em um túmulo não identificado no cemitério de St. Pancras, em Londres, onde permanece até o presente. Mais tarde suas filhas providenciaram a instalação de uma lápide onde se lia: “Pai, altruísta, amigo. Um cientista brilhante responsável pela equação de Price para a evolução”

A comunidade científica demorou a perceber o alcance das contribuições de Price, em parte porque ele estava inaugurando a abordagem de um tratamento mais matemático para questões biológicas. Apesar da importância da fórmula por ele proposta, “a fórmula de covariância aplicada à seleção de grupos”, o próprio Price nunca buscou reconhecimento ou procurou atrair a atenção da comunidade para sua obra. Nas palavras de Hamilton, um pouco antes de morrer “É como se você houvesse descoberto o cálculo e o atirasse em um de seus artigos obscuros sem nunca explicar para as pessoas como ele é útil”. Em 1975 Hamilton escreveu um artigo onde tentava popularizar o tratamento de Price, mas o artigo recebeu pouca atenção e a abordagem original de Price continuou deesconhecida. Apenas mais recentemente a contribuição de Price vem recebendo reconhecimento e atenção.

Referências:

Oren Harman. The Price of Altruism: George Price, New York, W. W. Norton & Company, 2010.

Oren Harman. The Price of Altruism, Youtube.

Franz de Wall. For Goodness’ Sake, The New York times: 9 jun 2010.

James Schwartz. Death of an Altruist: Was the Man who found the Selfless Gene Too Good for this World? Lingua Franca, Volume 10, No. 5 – July/August 2000

Biological Altruism The Stanford Encyclopedia of Philosophy, 3 de junho de 2003; revisado em julho de 2013

Discussão: George Price e o Altruísmo Biológico

Dimas enviou em 06/09/2013:
Li todo o artigo e conclui que: Parece muito com a politica Brasileira, “politicos “egoístas” se aproveitando do “Povo altruísta”…

Guilherme enviou em 06/09/2013
Dimas,
acredito existir uma patologia, algo como a falta de empatia entre pessoas que julgam poder se apropriar de recursos da comunidade. Assim como as escolas atraem pedófilos o setor público, por onde circula o dinheiro, atrai psicopatas desonestos.

Eu trocaria a expressão “povo altruísta” por povo desinformado e apático.

Samuel S. Ferreira enviou em 16/10/2013
Olá. Li o teu comentário, compreendo porem discordo. Os politicos não se aprovietam de um povo autruista mais sim ignorante, acomodado e manipulável.

Fátima Bezerra enviou em 16/03/2013
Olá,
Minha curiosidade sobre Jeorge Price despertou quando assisti um filme na TV Futura, comentando sobre a sua vida e as suas pesquisas no campo da biologia. Então pus-me a busca de algo a mais consistente sobre essa figura tão peculiar. Sua vida conturbada, sua inteligência, suas contribuição a ciência faz-nos pensar se estamos aqui para contribuir com nossos genes para melhora e perpetuação da raça humana (altruismo genético) ou se realmente fazemos algo por intenção racional(altruismo psicológico). Pois se tudo estiver em nossas células, somos máquinas maravilhosas e perfeitas, mas sem dúvidas existirão aqueles que com seu egoísmo exacerbado se aproveitarão para impor sua supremacia.

Será que neste caso, a biologia suplantará a sociologia? Um abraço. Fátima Bezerra.

Guilherme enviou em 06/09/2013
Fátima,
me parece que a pergunta mais correta seria: podemos suplantar as tendências biológicas inerentes à condição humana?

As forças biológicas, até agora, sempre ou quase sempre suplantaram todas as nossas outras tendências e inclinações. Acho importante reconhecermos que somos bichos e agimos como tais.

Acredito, no entanto, que temos a capacidade para refletir e suplantar algumas destas forças puramente animais. Um exemplo disto está no uso do controle da natalidade, prática hoje amplamente difundida em quase todos os grupos e níveis sociais.

GuilhermeRodinely Sousa enviou em 08/08/2013
Um dos poucos artigos encontrados na internet em língua portuguesa sobre a vida desse homem fantástico chamado George R. Price, que alternou de forma singular entre a genialidade e a mais profunda loucura. Parabéns e muito obrigado.

Samuel de S Ferreira em 16/10/2013

Ola.
Passei a ter interesse pela obra de George R. Price após ver um documentario de tv no Discovery Chanel, assim como vc, e como conclusão me fiz a mesma pergunta será que neste caso, a biologia suplantará a sociologia? Que situação não? A resposta que tenho até o presente momento é para mim desanimadora, é bem mais facil e comum ser bem sucedido ao adotar comportamentos egoistas, não é preciso muita força para isto notar, basta ter espelho.

George R. Price, que figura facinante!

Rafael enviou em 06/11/2013
O Sr. Price era um ateu que se achava um Deus de uma matemática a qual nem ele acreditava.

Guilherme: George Price era inicialmente ateu. Mais tarde, aos 48 anos ele julgou ter passado por uma experiência religiosa e se tornou um fervoroso defensor e estudioso do Novo Testamento. Esta experiência, de acordo com seus biógrafos, se derivou de ele julgar que em sua vida ocorriam um número muito grande de coincidências.

Alessandro enviou em 28/11/2013
para mim é claro que George Price buscou a religião como uma fuga de seu estado doentio e depressivo.

É curioso como, ao perceber que na natureza não existe generosidade (ou se existe é muito rara) ele tenha perdido o controle sobre seus atos. É claro que seus atos de altruísmo foram excessivos e o levaram à falência e, finalmente, à morte.

É possível que a pressão originada em seu estado doentio tenha contribuído para esta busca de conforto na religião. Mais um exemplo de como a religião pode ser nociva na vida de uma pessoa!

Hana enviou em 10/01/2014
Gostaria de ler mais artigos assim,você teria mais? Se não, poderia postar?

Guilherme enviou em 17/01/2014
Hana,
a proposta aqui é ir incluindo artigos que despertem a vontade de pensar e dabater. Sempre que posso acrescento alguma coisa portanto sugiro que você dê uma olhada de vez em quando. Você já conferiu os outros artigos deste site? Há alguma tema em particular sobre o qual você está interessada?

Programação com R

Se você decidir ler esta seção mais tarde, pule para a seção Aquisição de Dados.

Esta seção apresenta uma formalização um pouco mais rigorosa dos conceitos do R como linguagem de programação. Em uma primeira leitura, para aqueles que ainda estão se familiarizando com a linguagem, ela pode ser pulada e lida mais tarde. Ela contém um pouco de repetição do material já visto, para fins de completeza.

Objetos

R é uma linguagem de programação de array, funcional e orientada a objeto. Todos os elementos de R, variáveis de dados e funções, são objetos. Não se pode acessar locais da memória diretamente e todos os objetos usados na execução de um programa são armazenados em memória RAM. Isso acaba tendo um peso importante quando se processa um grande volume de dados.

Linguagens de programação de arrays (também chamadas de linguagens vetoriais ou multidimensionais) são linguagens onde operações sobre objetos multidimensionais (vetores, matrizes, etc.) generalizam as operações sobre escalares de forma transparente. Elas permitem um código mais conciso e legível.

Todos os objetos possuem atributos que são meta-dados descrevendo suas características. Estes atributos podem ser listados com a função attributes() e definidos com a função attr(). Um desses atributos, bastante importante, é a classe de um objeto pois as funções de R usam essa informação para determinar como o objeto deve ser manipulado. A classe de um objeto pode ser lida ou alterada com a função class().

Existem os seguintes tipos de dados: Lógico ou booleano (logic), numérico (numeric), inteiro (integer), complexo (complex), caracter (character) e Raw.

Estes dados podem ser agrupados em estruturas de dados. Existem dois tipos fundamentais de estruturas: vetores atômicos e vetores genéricos. Vetores atômicos são matrizes de qualquer dimensão contendo um único tipo de dados. Vetores genéricos são também chamados de listas e são compostas por vetores atômicos. Listas são recursivas, no sentido de que podem conter outras listas.

Uma variável não precisa ser inicializada nem seu tipo declarado, sendo determinado implicitamente a partir do conteúdo do objeto. Seu tamanho é alterado dinamicamente.

Não existe o tipo “escalar” em R. Um escalar é simplesmente um vetor com um único elemento. Portanto a atribuição u <- 1 é apenas um atalho para u <- c(1).

Uma matriz é um vetor atômico acrescentado de um atributo dim com dois elementos (o número de linhas e de colunas). No exemplo seguinte, um vetor é transformado em uma matriz e depois recuperado como vetor:

> v <- 1:12
> print(v)
 [1]  1  2  3  4  5  6  7  8  9 10 11 12
> class(v)
[1] "integer"
> x <- c(1,2,3,4,5,6,7,8)
> class(x)
[1] "numeric"
> attr(v, "dim") <- c(2,6)
> print(v)
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    3    5    7    9   11
[2,]    2    4    6    8   10   12
> class(v)
[1] "matrix"
> # Um atributo arbitrário pode ser incluído
> attr(v, "nome") <- "minha matriz"

> attributes(v)
$dim
[1] 2 6
$nome
[1] "minha matriz"

> attr(v, "nome") <- NULL   # o atributo é removido
> attributes(v)
$dim
[1] 2 6
> # Um atributo pode ser alterado
> dim(v) <- c(3,4)
> print(v)
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12
> # Removido o atributo "dim" v volta a ser um vetor
> attr(v, "dim") <- NULL
> v
 [1]  1  2  3  4  5  6  7  8  9 10 11 12

A atribuição v <- 1:4 é idêntica à v <- c(1:4) e análoga, mas não idêntica à v <- c(1, 2, 3, 4). Nos dois primeiros casos o resultado é um vetor de inteiros. No terceiro temos um vetor numérico (de ponto flutuante).

Existem funções para a marcação de atributos: dim(), dimnames(), names(), row.names(), class() e tsp() (usado para a criação de séries temporais). Estas funções são preferíveis à simplesmente usar attr(vetor, "atributo") porque fazem um tratamento e análise dos parâmetros usados, emitindo notificações de erros mais detalhadas.

A igualdade entre objetos atômicos pode ser testada com o uso do operador ==, que verifica recursivamente a identidade de cada um dos elementos dos objetos comparados, ou da função identical(), que verifica a igualdade completa entre os dois objetos.

> a <- c(1,3,5); b <- c(1,3,5); c <- c(1,2,5)
> a==b
[1] TRUE TRUE TRUE
> a==c
[1]  TRUE FALSE  TRUE
> identical(a,b)
[1] TRUE
> identical(a,c)
[1] FALSE

Listas e Data Frames

As listas são coleções de vetores atômicos, não necessariamente de mesmo tipo. Elas são recursivas no sentido de que podem ter outras listas como seus elementos. Data frames são listas onde todos os vetores possuem o mesmo comprimento. Muitas funções recebem listas como argumentos ou retornam listas.

Para exemplificar vamos usar uma lista contendo as 5 primeiras observações do data frame warpbreaks, com 3 variáveis.

> quebras <- head(warpbreaks, n=5)
> quebras
  breaks wool tension
1     26    A       L
2     30    A       L
3     54    A       L
4     25    A       L
5     70    A       L

> # Usamos unclass() para ver seus componentes
> unclass(quebras)
$breaks
[1] 26 30 54 25 70

$wool
[1] A A A A A
Levels: A B

$tension
[1] L L L L L
Levels: L M H

attr(,"row.names")
[1] 1 2 3 4 5

> # Usamos attributes() para ver seus atributos
> attributes(quebras)
$names
[1] "breaks"  "wool"    "tension"

$row.names
[1] 1 2 3 4 5

$class
[1] "data.frame"

A igualdade entre objetos não atômicos não é implementado com o operador ==. Neste caso é necessário usar a função identical(), que verifica a igualdade completa entre os dois objetos.

> u <- list(v1=1,v2=2); v <- u
> v==u
Error in v == u : comparison of these types is not implemented
> identical(u,v)
[1] TRUE

> # identical testa a identidade entre quaisquer dois objetos:
> f <- function(x,y) x+y; g <- function(x,y) x+y
> identical(f,g)
[1] TRUE

A função unclass() retorna uma cópia de seu argumento sem seus atributos de classe. attributes() retorna uma lista com os atributos de seu argumento.

Selecionar partes de uma lista é uma operação importante em R. Para ilustrar algumas operações vamos usar o data frame iris que é uma lista contendo 5 vetores atômicos. Ela contém os campos (ou observações) Sepal.Length, Sepal.Width, Petal.Length, Petal.Width e Species. Relembrando, a função unclass(iris) exibe todos os valores em cada campo e seus atributos, separadamente. A função attributes(iris) exibe apenas os atributos. No exemplo abaixo aplicamos o agrupamento K-means usando a função kmeans(). Em seguida exploramos o objeto retornado que é uma lista.

> head(iris, n=2)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa

> # Para selecionar apenas os 4 primeiros campos usamos iris[1:4]

> kGrupo <- kmeans(iris[1:4],3)
> typeof(kGrupo)    # para ver de que tipo é o objeto
[1] "list"
> length(kGrupo)    # kGrupo é uma lista com 9 elementos
[1] 9
> print(kGrupo)     # para listar todos os elementos do objeto
K-means clustering with 3 clusters of sizes 50, 38, 62

Cluster means:
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     5.006000    3.428000     1.462000    0.246000
2     6.850000    3.073684     5.742105    2.071053
3     5.901613    2.748387     4.393548    1.433871

Clustering vector:
  [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [31] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 2 3 3 3 3 3 3 3
 [61] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 3 3 3 3 3 3 3 3 3 3 3 3
 [91] 3 3 3 3 3 3 3 3 3 3 2 3 2 2 2 2 3 2 2 2 2 2 2 3 3 2 2 2 2 3
[121] 2 3 2 3 2 2 3 3 2 2 2 2 2 3 2 2 2 2 3 2 2 2 3 2 2 2 3 2 2 3

Within cluster sum of squares by cluster:
[1] 15.15100 23.87947 39.82097
 (between_SS / total_SS =  88.4 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss" "betweenss"
[7] "size"         "iter"         "ifault"

> str(kGrupo)
List of 9
 $ cluster     : int [1:150] 1 1 1 1 1 1 1 1 1 1 ...
 $ centers     : num [1:3, 1:4] 5.01 6.85 5.9 3.43 3.07 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : chr [1:3] "1" "2" "3"
  .. ..$ : chr [1:4] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width"
 $ totss       : num 681
 $ withinss    : num [1:3] 15.2 23.9 39.8
 $ tot.withinss: num 78.9
 $ betweenss   : num 603
 $ size        : int [1:3] 50 38 62
 $ iter        : int 2
 $ ifault      : int 0
 - attr(*, "class")= chr "kmeans"

> # A lista contém os seguintes atributos
> attributes(kGrupo)
$names
[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"

$class
[1] "kmeans"

> # sapply(objeto, class) exibe a classe de cada elemento na lista
> # A função sapply é tratada com maiores detalhes na próxima seção

> sapply(kGrupo, class)
     cluster      centers        totss     withinss tot.withinss    betweenss
   "integer"     "matrix"    "numeric"    "numeric"    "numeric"    "numeric"
        size         iter       ifault
   "integer"    "integer"    "integer"

> # Podemos visualizar simultaneamente o segundo elemento, "centers"
> # que fornece uma matriz com os valores do centro de cada agrupamento
> e size, 7º elemento, com o número de pontos em cada grupo

> kGrupo[c(2,7)]
$centers
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     5.006000    3.428000     1.462000    0.246000
2     6.850000    3.073684     5.742105    2.071053
3     5.901613    2.748387     4.393548    1.433871

$size
[1] 50 38 62

> # Para visualizar o segundo componente da lista kGrupo,
> # que é uma matriz, usamos
> kGrupo[2]
$centers
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     5.006000    3.428000     1.462000    0.246000
2     6.850000    3.073684     5.742105    2.071053
3     5.901613    2.748387     4.393548    1.433871

> # Para ver apenas os componentes desta matriz:
> kGrupo[[2]]
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     5.006000    3.428000     1.462000    0.246000
2     6.850000    3.073684     5.742105    2.071053
3     5.901613    2.748387     4.393548    1.433871

> # O mesmo resultado seria obtido por kGrupo$centers
> # Para listar a primeira linha da matriz
> kGrupo[[2]][,1]
       1        2        3
5.006000 6.850000 5.901613
> # Para listar a primeira coluna da matriz
> kGrupo[[2]][1,]
Sepal.Length  Sepal.Width Petal.Length  Petal.Width
       5.006        3.428        1.462        0.246
> # Para listar o primeiro elemento da primeira linha
> kGrupo[[2]][1,1]
[1] 5.006
> # que é o mesmo que kGrupo$centers[1,1]

Funções em R

Quase tudo em R é uma função. Até os operadores comuns são funções. A declaração 2 + 3 é, na verdade, uma forma sintética para "+"(2, 3).

> '+'(2,3)
[1] 5
> "*"(13, 9)
[1] 117

Em funções, parâmetros são passados por valor e não por referência. Isso significa que um objeto passado como parâmetro é copiado e a cópia passada para a função. O objeto original não é alterado. Além disso variáveis definidas no corpo de funções são locais e não podem ser usadas fora dela. Para tornar globa uma variável usada dentro de uma função podemos usar o operador de "super atribuição" <<-. Considere, por exemplo, o código abaixo.

> f <- function(x) x <- x^2
> u <- c(1,2,3)
> v <- f(u)
> v
[1] 1 4 9
> u
[1] 1 2 3

> #  x é local à função
> print(x)
Error: object 'x' not found

> # Se necessário tornar x global fazemos

> f <- function(x) x <<- x^2
> v <- f(x)
> x
[1] 1 4 9

Funções podem ser usadas recursivamente (ou sejam, podem fazer chamadas a si mesmas). Dois exemplos são mostrados abaixo: o primeiro calcula o fatorial de um inteiro, o segundo exibe a sequência de Fibonacci com n elementos.

> fatorial <- function(x) {
     if (x == 0) return (1)
     else return (x * fatorial(x-1))
 }
> fatorial(0)
[1] 1
> fatorial(6)
[1] 720

> # Obs.: a mesma função poderia ser definida em forma mais compacta como
> fatorial <- function(x) ifelse (x == 0, 1, x * fatorial(x-1))

> # A sequência de Fibonacci:
> fibonacci <- function(n) {
     if(n <= 0)  return("Nada")
     fib <- function(m) ifelse(m <= 1, m, fib(m-1) + fib(m-2))
     seq <- c(0)
     if (n>1) { for(i in 1:(n-1)) seq[i+1] <- fib(i) }
     print("Sequência de Fibonacci:")
     print(seq)
 }
 > fibonacci(9)
[1] "Sequência de Fibonacci:"
[1]  0  1  1  2  3  5  8  13  21

Funções apply(), lapply(), sapply(), tapply()

A função apply() recebe como argumentos uma coleção de objetos (data frame, lista, vetor, etc.), o parâmetro MARGIN (que informa onde será aplicada a função) e uma função (qualquer função pode ser usada). Ela serve para executar alguma operação sobre essa coleção. Seu objetivo é principalmente o de evitar o uso de laços ou loops. Ela tem a seguinte estrutura:

apply(X, MARGIN, FUN)
onde:
x: uma matriz ou array
MARGIN=n : onde n = 1 ou 2, definindo onde a função será aplicada:
se n=1: a função será aplicada nas linhas
se n=2: função aplicada nas colunas
se n=c(1,2): função aplicada nas linhas e colunas
FUN: define função a ser usada.
Podem ser funções internas (mean, median, sum, min, max, ...)
ou definidas pelo usuário
> # Usando a matriz v, já definida:
> print(v)
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12
> # A média das colunas
> apply(v,2,mean)
[1]  2  5  8 11
> # A soma das colunas
> apply(v,2,sum)
[1]  6 15 24 33
> minimosNasLinhas <- apply(v, 1, min)
> print(minimosNasLinhas)
[1] 1 2 3

A função lapply() recebe como argumentos uma coleção de objetos (data frame, lista, etc.) e uma função. Ela executa a função sobre todos os elementos da coleçao e retorna um objeto do tipo lista:

lapply(X, FUN)
onde:
X: vetor, lista, data frame, ...
FUN: Função a ser aplicada a cada elemento de X
Podem ser funções internas ou definidas pelo usuário

Para exemplificar aplicaremos a função tolower() para reduzir a letras minúsculas todas as palavras de um vetor de caracteres:

> partes <- c("RODAS","MOTOR","CARBURADOR","PNEUS")
> partesMinuscula <- lapply(partes, tolower)
> print(partesMinuscula)
[[1]]
[1] "rodas"
[[2]]
[1] "motor"
[[3]]
[1] "carburador"
[[4]]
[1] "pneus"

> # Esta lista pode ser convertida em um vetor usando-se unlist:
> partesMinuscula <- unlist(partesMinuscula)
> print(partesMinuscula)
[1] "rodas"      "motor"      "carburador" "pneus"

A função sapply() recebe como argumentos uma coleção de objetos (data frame, lista, etc.) e uma função. Ela age da mesma forma que lapply() mas retorna um vetor ou uma matriz:

sapply(X, FUN)
onde:
X: vetor, lista, data frame, ...
FUN: Função a ser aplicada a cada elemento de X
Podem ser funções internas ou definidas pelo usuário

Usaremos a função sapply() com o data frame cars que traz uma coleção de observações sobre velocidades e distâncias percorridas até repouso em cada velocidade em automóveis (em 1920) para encontrar os valores mínimos em cada coluna:

> # para ver a estrutura do data frame:
> str(cars)
 'data.frame':	50 obs. of  2 variables:
  $ speed: num  4 4 7 7 8 9 10 10 10 11 ...
  $ dist : num  2 10 4 22 16 10 18 26 34 17 ...
> lMinimos <- lapply(cars, max)
> sMinimos <- sapply(cars, max)
> print(lMinimos)
$speed
[1] 25
$dist
[1] 120

> print(sMinimos)
speed  dist
   25   120

O exemplo abaixo mostra o uso de lapply() e sapply() junto com uma função do usuário. Ela retorna os valores do data frame que estão abaixo da média em cada coluna. Neste caso elas retornam valores iguais, como se pode ver com o uso de identical():

> abaixoDaMedia <- function(x) {
                   media <- mean(x)
                   return(x[x < media])
                   }
> abaixoDaMedia(c(1,2,3,40,50))
 [1] 1 2 3
> minSapply <- sapply(cars, abaixoDaMedia)
> minLapply <- lapply(cars, abaixoDaMedia)
[1] TRUE
> minSapply
$speed
 [1]  4  4  7  7  8  9 10 10 10 11 11 12 12 12 12 13 13 13 13 14 14 14 14 15
[25] 15 15
$dist
 [1]  2 10  4 22 16 10 18 26 34 17 28 14 20 24 28 26 34 34 26 36 20 26 32 40
[25] 32 40 42 36 32

> # Os valores retornados são iguais (embora em objetos distintos):
> identical(minSapply, minLapply)
 [1] TRUE

A função tapply() calcula um valor usando uma função (mean, median, min, max, ...) sobre os dados de um objeto agrupados para cada valor de uma variável de fator dada.

tapply(X, INDEX, FUN = NULL)
onde:
X: um objeto, geralmente um vetor
INDEX: uma lista contendo fatores
FUN: a função a ser aplicada sobre os elementos de X

Para ilustrar o uso desta função vamos usar o data frame irisCalculamos primeiro a média dos comprimentos de sépalas para todas as espécies. Depois calculamos as médias para cada espécie em separado, setosa, versicolor, virginica.

Em seguida usamos o data frame mtcars para calcular o consumo médio dos carros, agrupados por número de cilindros (cyl = 4, 6, 8) e tipos de transmissão, am = 0 (automático), 1 = (manual).

> attach(iris)
> # O comprimento médio de todas as sépalas é
> mean(Sepal.Length)
[1] 5.843333
> # O comprimento médio das sépalas agrupadas por espécie:
> tapply(Sepal.Length, Species, mean)
    setosa versicolor  virginica
     5.006      5.936      6.588
> detach(iris)
> # Usando mtcars:
> attach(mtcars)
> # O consumo médio para todos os carros é
> mean(mtcars$mpg)
[1] 20.09062
> # O consumo médio dos carros, agrupados por cilindros e tipo de transmissão
> tapply(mpg, list(cyl, am), mean)
       0        1
4 22.900 28.07500
6 19.125 20.56667
8 15.050 15.40000

> # Para efeito de conferência, calculamos a media de mpg para am=0 e cyl=8
> L <- mtcars[cyl==8 & am==0,]
> # L contém apenas carros com  am=0 e cyl=8
> mean(L$mpg)
[1] 15.05
> detach(mtcars)

Lembramos que em R os índices começam em 1 e não 0, como em muitas outras linguagens.

Ambientes (environments) e escopo

R armazena seus objetos em memória RAM dentro de ambientes ou environments. Um environment fica definido por uma lista que associa os nomes dos objetos nele carregados com seus valores. Eles existem principalmente para organizar estes objetos e a forma como R os encontra. Cada ambiente está ligado a um pai (um parent environment) fazendo com que os ambientes formem uma estrutura de árvore que termina no ambiente de nível mais alto que se chama R_EmptyEnv. Quando se inicia uma sessão o R se encontra no ambiente global, global environment, denominado R_GlobalEnv, também chamado de área de trabalho do usuário. Quando o nome de um objeto é invocado em código o interpretador de R busca na lista do ambiente atual, que pode ser visto com a função environment(). Se não encontrado o nome é procurado no ambiente pai, e assim sucessivamente, até que o último é alcançado.

Um novo ambiente pode ser criado com a função new.env() e objetos dentro deste ambiente com a função assign(). Estes objetos podem ser recuperados através da função get() ou da notação ambiente$variavel. A função exists("variavel", envir = ambiente) verifica a existência de variavel no ambiente, enquanto os objetos em um ambiente são listados com ls(ambiente), como se ilustra abaixo:

> environment()                            # exibe ambiente atual
<environment: R_GlobalEnv>
> var <- "este objeto está em Global Env"  # cria objeto em Global_Env
> novoEnv <- new.env()
> assign(var, "novo objeto em novoEnv", env=novoEnv)
> ls()
 [1] "novoEnv" "var"
> var
[1] "este objeto está em Global Env"
> get(var, env=novoEnv)
[1] "novo objeto em novoEnv"

> # A notação de "$" pode ser usada:
> novoEnv$var <- " outro valor para objeto em novoEnv"
> var
[1] " este objeto está em Global_env"
> novoEnv$var
[1] " outro valor para objeto em novoEnv"

> cat("var em global_env -->", var, "\nvar em novoEnv -->", novoEnv$var)
var em global_env --> este objeto está em Global Env
var em novoEnv --> novo objeto em novoEnv

> # Para ver o ambiente pai de novoEnv
> parent.env(novoEnv)
<environment: R_GlobalEnv>
> novoEnv$x <- 1   $ insere nova variável no ambiente
> ls(envir=novoEnv)
[1] "var" "x"
> exists("x", envir = novoEnv)
[1] TRUE

> # Um ambiente pode ser criado como filho de qualquer outro ambiente
> e2 <- new.env(parent = outroEnv)
> parent.env(e2)

> # Uma variável será criada em e2
> e2$teste <- 123
> # e2 é filho de novoEnv que está em R_GlobalEnv
> # A variável teste não será encontrada em R_GlobalEnv (pois reside em um nível abaixo)
> teste
Error: object 'teste' not found
> # O objeto está no ambiente e2
> e2$teste
[1] 123
> # Para testar se um objeto é um ambiente
> is.environment(e2)
[1] TRUE

> # Observe que a variável que contém o ambiente global é .GlobalEnv
> is.environment(.GlobalEnv)
[1] TRUE

> # Seu atributo name é "R_GlobalEnv"
> environmentName(environment())
[1] "R_GlobalEnv"

A função abaixo percorre os ambientes de modo hierárquico à partir de R_GlobalEnv subindo para os pais até o último ambiente, R_EmptyEnv que é o último ambiente, sem pai. A função search() exibe os ambientes na ordem hierárquica, a mesma ordem usada para a procura de um objeto.

> exibirArvore <- function() {
      a <- environment()
      repeat {        
          print(environmentName(a))
          if (environmentName(a) == "R_EmptyEnv") break
          a <- parent.env(a)
     }
 }
> exibirArvore()
[1] ""
[1] "R_GlobalEnv"
[1] "tools:rstudio"
[1] "package:stats"
[1] "package:graphics"
[1] "package:grDevices"
[1] "package:utils"
[1] "package:datasets"
[1] "package:methods"
[1] "Autoloads"
[1] "base"
[1] "R_EmptyEnv"

> # A função environment() permite descobrir em que
> # ambiente está uma função:
> environment(exibirArvore)
<environment: R_GlobalEnv>

> search()
 [1] ".GlobalEnv"        "tools:rstudio"     "package:stats"     "package:graphics" 
 [5] "package:grDevices" "package:utils"     "package:datasets"  "package:methods"  
 [9] "Autoloads"         "package:base" 	

Observe que a primeira chamada à função environmentName(a) retorna um string vazio, que é o nome do ambiente interno à função. Quando uma função é criada ela gera a criação de um ambiente próprio onde ficam as variáveis definidas em seu corpo. Para exemplificar a existência deste ambiente dedicado à definição da função criamos abaixo a função minhaFuncao(x) que retorna outra função que soma x ao seu argumento. O valor de x passado na definição da função não é alterado com uma definição de seu valor fora do corpo da função.

> f <- function() {
          x <- 1
          print(environment())
          print(parent.env(environment()))
 }
> f()
<environment: 0xd31ee80>
<environment: R_GlobalEnv>

> minhaFuncao <- function(x) { function(y) x+y }
> h <- minhaFuncao(100)
> h(10)
[1] 110
> x <- 3
> h(2)
[1] 102
> # Internamente ao ambiente de h, x = 100
> # h vive no ambiente environment(h).
> # Neste ambiente existe apenas a variável:
> ls(environment(h))
[1] "x"

No caso acima o R criou o ambiente "0xd31ee80" que é filho de R_GlobalEnv. A variável x só existe dentro do ambiente da função.

Uma função (e seu ambiente) podem ser colocados em qualquer outro ambiente usando-se a função environment(funcao ) <- outroAmbiente. No exemplo abaixo a variável anos é definida em .GlobalEnv e dentro do corpo da função quantosAnos. Três outras funções são definidas dentro desta primeira: anosLocal (que usa a variável local, a=10), anosGlobal (que usa a variável em globalEnv, a=10). Na execução da função semBusca a variável está localmente definida, a=1 e nenhuma busca é necessária para a sua execução.

> anos <- 100
> quantosAnos <- function() { 
      anos <- 10
      anosLocal <- function() { print( anos + 5 ) } 
      anosGlobal <- function() { print( anos + 5 ) } 
      semBusca <-  function() { anos <- 1; print( anos + 5 ) } 
      environment( anosGlobal ) <- .GlobalEnv 
      anosLocal() 
      anosGlobal()
      semBusca() 
  }
> quantosAnos()
[1] 15
[1] 105
[1] 6

O exemplo abaixo mostra que quando a função f1 é gerada seu ambiente foi armazenado junto com ela. Nele estão as variáveis a=2, b=3. Este ambiente fica inalterado mesmo depois que a variável global a foi alterado.

> funcaoSoma <- function(a, b) {
     f <- function(x) return( x + a + b )
     return( f )
 }
> a <- 2; b <- 3
> f1 <- funcaoSoma(a, b)
> f1(3)
[1] 8
> a <- 4
> f2 <- criarFuncao(a, b)
> f2(3)
[1] 10
> f1(3)
[1] 8
> # Para forçar a permanência de uma variável após
> # a conclusão da função usamos a atribuição "<<-"
> f <- function(){w<-13}
> f()  # não há retorno
> w
Error: object 'w' not found
> f <- function(){w<<-13}
> f()
> w
[1] 13

Para alocar explicitamente variáveis para um determinado ambiente, além da notação de "$" pode ser usado:

> ls(outroEnv) # o ambiente está vazio
character(0)
> with(outroEnv, {a <- 1; txt <- "texto" })
> ls(outroEnv)
[1] "a"   "txt"
> with(outroEnv, {print(a); print(txt)})
[1] 1
[1] "texto"
> # Alternativamente,
> outroEnv$a; outroEnv$txt
[1] 1
[1] "texto"

Se o nome da biblioteca onde está uma função é previamente conhecido é possível evitar a busca pela definição de uma função usando o operador ::. O mesmo procedimento pode ser usado para forçar o uso de uma função específica quando existem outras de mesmo nome definidas. Se o pacote não está carregado o operador ::: pode ser usado.

> x <- c(123, 234, 345, 242, 34, 100)
> stats::sd(x)
[1] 113.9731

> Wilks
Error: object 'Wilks' not found
> stats:::Wilks
> # ... A definição da função é exibida

> # Para verificar o que significa o operador :::
> `:::`
function (pkg, name) 
{
    pkg <- as.character(substitute(pkg))
    name <- as.character(substitute(name))
    get(name, envir = asNamespace(pkg), inherits = FALSE)
}


Decorre do que foi dito que o escopo de uma variável em R é o seguinte: a variável deve estar definida no ambiente local em que é usada ou em algum ambiente pai. Se variáveis com o mesmo nome estão definidas dentro da hierarquia de ambientes será usada aquela que for encontrada primeiro, ou seja, no ambiente de menor posição.

Muitas linguagens de programação não permitem (ou dsencorajam) o uso de variáveis globais pois elas podem tornar tornar o código mais frágil, sujeito a erros. No R elas podem ser usadas e funções podem acessar variáveis em ambientes acima delas. Mas essa não é sempre uma boa prática. Para projetos com algum nível de complexidade é recomendado que se passe todas as variáveis necessárias na definição da função ou se faça uma verificação rigorosa de escopos, oferecendo alternativas para o caso em que essas variáveios estão ausentes ou tenham tipos não apropriados. Caso as variáveis globais sejam usadas é uma boa prática dar a elas nomes identificadores tais como global.var para evitar que conflituem com outras definidas localmente.

Otimização e Pesquisa de Erros (debugging)

É possível pré-compilar uma função usando a biblioteca compiler (e sua função cmpfun()) que gera uma versão em byte-code. Nas linhas abaixo, fizemos uma medida dos tempos gastos nas funções f e sua versão pré-compilada g.

Algumas práticas podem ser aplicadas se um código estiver demorando muito para ser executado. Uma delas consiste em envolver o código a ser verificado com os comandos Rprof() e Rprof(NULL) e depois executar a função summaryRprof() para ver um resumo dos tempos gastos na execução de cada funcão.

> library(compiler)
> f <- function(n, x) { for (i in 1:n) x <- x + (1 + x)^(-1)}
> g <- cmpfun(f)
> medirTempos <- function() {
      Rprof()
      inicio <- Sys.time()
      f(10000000,1)
      duracao1 <- Sys.time() - inicio
      print(duracao1)
      inicio <- Sys.time()
      g(10000000,1)
      duracao2 <- Sys.time() - inicio
      print(duracao2)
      print(duracao1 - duracao2)
      Rprof(NULL)
      summaryRprof()
  }

> # Executamos a função para medir os tempos gastos  
> medirTempos()
Time difference of 1.003972 secs
Time difference of 0.9667881 secs
Time difference of 0.03718424 secs
$by.self
    self.time self.pct total.time total.pct
"f"      1.00    51.02       1.00     51.02
"g"      0.96    48.98       0.96     48.98

$by.total
              total.time total.pct self.time self.pct
"medirTempos"       1.96    100.00      0.00     0.00
"f"                 1.00     51.02      1.00    51.02
"g"                 0.96     48.98      0.96    48.98

$sample.interval
[1] 0.02

$sampling.time
[1] 1.96

A função compilada g é um pouco mais rápida que sua original. Em blocos maiores e mais demorados de código a diferença pode ser significativa.

Quando dados são importados para uma sessão de R sempre é uma boa prática ler apenas os campos necessários. Por exemplo, suponha que se deseje importar dados de uma tabela contido em um arquivo de texto arquivo.txt que contém 5 variáveis, a primeira de caracter e as 4 demais numéricas, mas apenas as duas primeiras serão usadas. A importação seletiva pode ser obtida usando-se o parâmetro colClasses. Colunas associadas com NULL serão ignoradas:

> # Para importar todos os dados usamos:
> dfLeitura <- read.table(arquivo.txt, header=TRUE, sep=',')
> # Seria mais eficiente e rápido selecionar apenas os campos desejados:
> dfLeitura <- read.table(arquivo.txt, header=TRUE, sep=',',
               colClasses=c("character", "numeric", NULL, NULL, NULL))

A execução de uma operação vetorializada é mais ágil do que percorrer um laço sobre os elementos de um vetor ou matriz. Isso é obtido com o uso de funções projetadas para lidar com vetores de forma otimizada. Alguns exemplo na instalação básica são as funções colSums(), colMeans(), rowSums(),
e rowMeans()
. O pacote matrixStats, plyr, dplyr, reshape2, data.table também incluem diversas funções otimizadas para esse tipo de operação.

Para mostrar isso usamos, desta vez, a função system.time(operação) que mede o tempo de execução da operação.

> partes <- 1:100000000
> soma1 <- function(x) print(sum(x))
> soma2 <- function(x) {
     s <- 0
     for (u in x) s <- s + u
     print(s)
 }

> system.time(soma1(partes))
[1] 5e+15
   user  system elapsed 
      0       0       0 
> system.time(soma2(partes))
[1] 5e+15
   user  system elapsed 
  4.775   0.000   4.775 

Em outro exemplo fazemos a soma dos elementos de uma matriz com 1000 colunas e 1000 linhas (portanto com 1 milhão de elementos).

> set.seed(1234)
> # Cria uma matriz 10000 x 10000
> matriz <- matrix(rnorm(100000000), ncol=10000)
> # Cria função para somar elementos de cada coluna
> somar <- function(x) {
           somando <- numeric(ncol(x))
           for (i in 1:ncol(x)) {
               for (k in 1:nrow(x)) {
                   somando[i] <- somando[i] + x[k,i]
               }
           }
  }
> # Executa a função e mede o tempo gasto
> system.time(somar(matriz))
   user  system elapsed 
 17.231   0.000  17.230
> # mede o tempo de execução de colSums  
> system.time(colSums(matriz))
   user  system elapsed 
  0.108   0.000   0.107 

Como vimos o cálculo é realizado aproximadamente 160 vezes mais rapidamente pela função vetorializada. Essa diferença pode ser muito maior, dependendo da situação analisada.

Sempre é mais eficiente inicializar um objeto em seu tamanho final e depois preenchê-lo de que partir de um objeto vazio e ajustar seu tamanho progressivamente.

> set.seed(1234)
> u <- rnorm(1000000)
> uQuadrado <- 0
> system.time(for (i in 1:length(u)) uQuadrado[i] <- u[i]^2)
   user  system elapsed 
  0.361   0.000   0.361

> # Tempo de execução para a mesma operação com
> # a variável inicializada em seu tamenho final   
> rm(uQuadrado)
> uQuadrado <- numeric(length=1000000)
> system.time(for (i in 1:length(u)) uQuadrado[i] <- u[i]^2)
   user  system elapsed 
   0.11    0.00    0.11

> # Usando a função vetorializada
> uQuadrado <- numeric(length=1000000)
> system.time(uQuadrado <- u^2)
   user  system elapsed 
  0.002   0.000   0.001     

A operação é muito mais rápida quando se usa a função vetorializada. Além da exponenciação, as funções adição, multiplicação e outras operações binárias do tipo são todas vetorializadas.

Gerenciamento de memória

Como já mencionado, R mantém em memória RAM todos os seus objetos em uso, o que pode introduzir lentidão ou mesmo a impossibilidade de realizar alguma operação. Mensagens de erro sobre a insuficiência de espaço de memória indicam que o limite foi excedido. Este limite depende, é claro, do hardware usado, do sistema operacional e da compilação de R (a versão de 64 bits é mais eficiente). Para grandes volumes de dados é preciso procurar escrever um código eficiente para acelerar a execução com o eventual armazenando dados em meio externo para diminuir a sobrecarga na memória RAM e através do uso de rotinas estatísticas especializadas, escritas para maximar a eficiência no manipulação de dados.

Para uma programação mais eficiente é recomendável aplicar operações sobre vetores sempre que possível. As funções internas para manipulação vetores, matrizes e listas (tais como ifelse, colMeans e rowSums) são mais eficientes que loops (for e while). Matrizes usam menos recursos que data frames. No uso de read.table() para carregar dados externos para um data frame especifique as opções colClasses e nrows explicitamente, defina comment.char = "" e marque como NULL as colunas não necessárias. Ao ler dados externos para uma matriz, use a função scan().

Como mencionado, sempre que possível crie objetos com seu tamanho final ao invés de aumentar seu tamanho gradualmente, inserindo valores. Teste seu código usando uma amostra de dados menor para otimizá-lo e remover erros. Exclua objetos temporários ou desnecessários usando rm(objeto). Após a remoção use gc() para iniciar a coleta de lixo. Use a função .ls.objects() para listar objetos no espaço de trabalho e encontrar o que ocupa mais memória e o que pode ser removido.

Use as funções Rprof(), summaryRprof() e system.time() para cronometrar o tempo e gasto em cada função e descobrir qual delas você deveria procurar otimizar. Rotinas externas compiladas podem ajudar a acelerar a execução do programa. Com o pacote Rcpp você pode transferir objetos de R para funções C++ e voltar quando são necessárias sub-rotinas otimizadas.

Para volumes de dados for muito grandes existem bibliotecas que incluem a funcionalidade de descarregar dados em bancos de dados externos ou arquivos binários simples e acessar parte deles. Alguns exemplos são:

Biblioteca Descrição
bigmemory grava e acessa matrizes em arquivos no disco.
ff fornece estruturas de dados que podem ser grabadas em disco, agindo como se permanecessem em RAM.
filehash implementa uma base de dados simples tipo chave-valor gravada em disco
ncdf, ncdf4 fornece interface para arquivos Unidata netCDF
RODBC, RMySQL, ROracle, RPostgreSQL, RSQLite acesso aos respectivos DBMS externos.

No que se refere à análise dos dados em grandes volumes estão disponíveis:

Pacotes biglm e speedglm: ajuste de modelos lineares lineares e generalizados para grandes conjuntos de dados de uma maneira eficiente em termos de memória. Incluem as funções lm() e glm() para lidar com grandes conjuntos de dados.

Diversos pacotes oferecem funções para operações sobre grandes matrizes produzidas pelo pacote bigmemory. biganalytics oferece agrupamento k-means, estatísticas de coluna e um wrapper para biglm. O pacote bigrf pode ser usado para se adequar às florestas de classificação e regressão. bigtabulate fornece funcionalidade table(), split() e tapply(). O pacote bigalgebra inclui funções avançadas da álgebra linear.

biglars oferece cálculo de regressão para conjuntos grande, usado juntamente com o pacote ff.

O pacote data.table introduz uma versão melhorada de um data frame, com métodos mais rápidos e eficientes para: agregação de dados; junções de intervalo; adição, modificação e exclusão de colunas por referência (sem cópias). Um data.table pode ser usado em qualquer função que receba um data frame como argumento.

Depuração de Erros (debugging)

Qualquer projeto de programação com algum grau de complexidade está sujeito a erros. Depuração de erros ou debugging é o processo de se encontrar e resolver as falhas no código. Por mais interessante que seja escrever um bloco de código para resolver algum problema, encontrar erros pode ser tedioso e demorado. Existem erros que impedem a execução do código causando a emissão de mensagens de erros. Estes são, em geral, os mais fáceis de se encontrar. Mas também existem situações em que o código roda perfeitamente mas produz resultados inesperados e incorretos.

As táticas de debugging envolvem rodar as linhas de código interativamente verificando o valor das variáveis, testar o efeito sobre um conjunto de dados que produzem resultados conhecidos, análise do fluxo do código e do estado da memória a cada instante da execução.

Na programação em R erros são geralmente causados por digitação incorreta do nome de variáveis ou funções e chamadas à funções com parâmetros de tipo incorretos, inclusive quando objetos importados de fontes externas contém partes que são NULL, NaN ou NA e são passados como parâmetros para funções que não fazem a verificação para a existência desses valores.

Função Efeito
debug() Marca uma função para debugging.
undebug() Desmarca uma função para debugging.
browser() Permite percorrer o código de execução de uma função passo a passo.
trace() Modifica a função para permite a inserção temporária de de código auxiliar.
untrace() Cancela a função anterior e remove o código temporário.
traceback() Imprime a sequência de chamadas a funções que produziram o último erro não capturado.

Durante a depuração com o uso de browser() a tecla executa a linha sob o cursor e passa o foco para a próxima linha. Teclar força a execução até o final da função sem pausas. Digitar exible a pilha de execução (call stack) e interrompe a execução e desloca o foco para o nível imediatamente superior. Também é possível usar comandos como ls(), print() e atribuições no prompt do depurador.

Atualizando R e suas Bibliotecas

A atualização de R pode ser um pouco trabalhosa. Seguem algumas sugestões para usuários de Windows e Linux.

No Windows

Como eu não utilizo o Windows esta opção está mencionada aqui como uma sugestão, que eu não experimentei. Ela foi extraída da página R-statistics blog.

Uma forma possível e prática para atualizar a instalação do R no Windows consiste em usar a biblioteca installr. Para isso a bliblioteca deve ser instalada e executada de dentro do próprio console (ou do Rstudio, ou outra IDE).

> # instalando e carregando a biblioteca
> install.packages("installr")
> require(installr)
> updateR()

A função updateR() iniciará o processo de atualização, verificando se novas versões estão disponíveis. Se a versão instalada for a mais recente a função termina e retorna FALSE. Caso contrário será perguntado se o usuário deseja prosseguir, após a exibição de um sumário das novidades na versão.

Será oferecida ao usuário a opção de copiar as bibliotecas instaladas para a nova versão e, em seguida, a de atualizar estas bibliotecas.

Mac e Linux

A atualização pode ser feita manualmente usando pacotes no website da CRAN.

$ sudo apt-key adv --keyserver keyserver.ubuntu.com
--recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
$ sudo add-apt-repository
'deb https://cloud.r-project.org/bin/linux/ubuntu bionic-cran35/'
$ sudo apt update
$ sudo apt install r-base

As linhas acima, para cada entrada no prompt, não devem ser quebradas.

Mais informações sobre instalações no Debian, Red Hat, SUSE e Ubuntu no site See CRAN-R Linux.

Para compilar à partir do código fonte consulte a página CRAN-R Installation and Administration.

Atualizando as bibliotecas

Tanto no Windows quanto no Linux para atualizar apenas as bibliotecas que foram instaladas com install.packages() basta usar, no console a função update.packages(). A função perguntará quais as bibliotecas você deseja atualizar. Para executar a atualização de todas elas, sem o prompt de consulta digite update.packages(ask = FALSE).

Além de update.packages() existem as funções old.packages() que informa quais as bibliotecas possuem versões mais atuais nos repositórios versões aplicáveis e new.packages() que procura por novas bibliotecas disponíveis e ainda não instaladas, oferecendo a opção de instalá-las.

Obs.: Pacotes instalados por devtools::install_github() não são atualizados pelos procedimento descritos. No Windows eles podem ser atualizados por installr.

No RStudio

Para atualizar o RStudio use o item de Menu: Help > Check for Updates. Para atualizar as bibliotecas use Menu: Tools > Check for Packages updates.


Aquisição de Dados

Aprofundando as técnicas sobre gráficos

Vários outros pacotes estão disponíveis para a geração de gráficos em R. Entre eles estão os pacotes grid, lattice e ggplot2 que visam expandir as habilidades do sistema gráfico básico. grid fornece acesso de baixo nível às capacidades gráficas, geralmente usada por programadores, enquanto lattice fornece uma abordagem intuitiva para a análise de dados multivariados. Ambos são utilizadas por outros pacotes de geração gráfica e são instaladas por padrão na instalação do de R. Focaremos aqui nossa atenção sobre ggplot2.

Gráficos com ggplot2

ggplot2 é um pacote de visualização de dados criado por Hadley Wickham em 2005. Ele amplia e extende as funções gráficas básicas de R e contém vários padrões para exibição na web e para a impressão. O pacote é baseado no conceito de gramática de gráficos onde se pode construir todos os gráficos necessários à partir de alguns poucos componentes: o conjuntos de dados, informações para a elaboração estética do gráfico, elementos geométricos (marcas visuais para representar pontos plotados) e um sistema de coordenadas (cartesiano, polar, mapa, etc.). Apresentaremos aqui um resumo das funções. Para referências mais completas consulte os links no final este capítulo.

ggplot2 deve ser instalado separadamente ou dentro de um pacote de utilitários mais amplo que o inclui, o tidyverse.

As partes ou componentes de um gráfico são desenhadas em camadas. Todos os gráficos plotados são iniciados com uma chamada à função ggplot(). Em seguida, ou na mesma chamada, se fornece a fonte de dados (que deve ser um data frame) e as informações sobre a estética, especificados por aes(). Depois são acrescentadas as camadas, escalas, coordenadas e ângulo de perspectiva, usando-se +. Gráficos são gravados em disco com a função ggsave(). Observe que, diferente das outras funções gráficas vistas, ggplot() não aceita vetores como argumentos mas apenas data frames.

Hadley Wickhan, em seu livro R for Data Science, propõe uma forma forma geral ou template para se compreender a estrutura de ggplot2. Ele resume o pacote da seguinte forma:

ggplot(data = <DATA>) +
<GEOM_FUNCTION> (
mapping = aes(<MAPPINGS>),
stat = <STAT>,
position = <POSITION> ) +
<COORDINATE_FUNCTION> +
<FACET_FUNCTION>

O princípio subjacente é o de que qualquer gráfico pode ser construído com esses elementos (embora nem todos sejam obrigatórios). As funções facet permitem dividir o gráfico em partes que são plotadas juntas. É possível que em novas camadas se insira outra fonte de dados, diferente daquela passada na primeira camada.

ggplot() Cria um gráfico
+; %+% operador para inserção de camadas
aes() insere informações sobre eixos e estética do gráfico
ggsave() grava um objeto ggplot
qplot() quickplot() plotagem simplificada

Para ilustrar o conceito do gráfico construído por camadas considere o seguinte código:

> install.packages("tidyverse")
> library(ggplot2)
> data("mtcars")
> g <- ggplot(mtcars) # Inicializa o gráfico
> # Uma camada, contendo pontos, é inserida com geom_point.
> # Em aes() mapeamos as variáveis, definimos cores e tamanho dos pontos
> g <- g +
       geom_point(aes(x = hp, y = mpg, color = factor(am)), size = 3)

> # Para alterar as cores
> g <- g +
      scale_color_manual("Tipo",
                          values = c("darkred", "deepskyblue4"),
                          labels = c("Manuais", "Automáticos"))
> # Rótulos
> g <- g +
       labs(title = 'Consumo comparado de potência de carros automáticos e manuais',
            y = 'Consumo', x = 'Potência')
> print(g)

O gráfico é plotado:

> library(ggplot2)
> cidades <- data.frame(nome=c("SP", "Rio", "Bsb", "Salv", "BH"),
                        populacao=c(12.106, 6.520, 3.040, 2.953, 2.523))
> graf <-ggplot(cidades, aes(x=nome, y=populacao))
        + geom_bar(stat = "identity")
        + labs(title="População, em milhões")
> print(graf)        # gráfico-1 é plotado

> # Usando mtcars$cyl como fator
> a <- ggplot(mtcars, aes(factor(cyl)))
> b <- a + geom_bar()
> c <- a + geom_bar(fill="red")
> d <- a + geom_bar(fill="red", colour = "black")
> e <- a + geom_bar(fill=rainbow(3), colour = "black")

> print(b)           # gráfico (b) é plotado
> print(c)           # gráfico (c) é plotado
> print(d)           # gráfico (d) é plotado
> print(e)           # gráfico (e) é plotado


> f <- ggplot(data=mtcars, aes(x=wt, y=mpg)) + geom_point() +
              labs(title="Carros", x="Peso", y="Consumo: Miles/Galão")

> g <- ggplot(data=mtcars, aes(x=wt, y=mpg)) +
              geom_point(pch=20, color="steelblue", size=2) +
              geom_smooth(method="lm", color="red", linetype=2) +
              labs(title="Automóveis", x="Peso", y="Consumo")

> mtcars$am <- factor(mtcars$am, levels=c(0,1), labels=c("Automatico", "Manual"))
> mtcars$vs <- factor(mtcars$vs, levels=c(0,1), labels=c("Motor-V", "Motor Comum"))
> mtcars$cyl <- factor(mtcars$cyl)

> h <-ggplot(data=mtcars, aes(x=hp, y=mpg, shape=cyl, color=cyl)) +
             geom_point(size=3) + facet_grid(am~vs) +
             labs(title="Carros: por tipo de motor", x="Potência", y="Consumo")

> print(f)           # gráfico (f) é plotado
> print(g)           # gráfico (g) é plotado
> print(h)           # gráfico (h) é plotado

Nos códigos acima ggplot() inicializa o gráfico, informa que mtcars será o data frame a ser usado. aes() (que fornece a estética ou aparência do plot), mapeia o wt (peso) com o eixo x e mpg (milhas por galão, consumo) com o eixo y. Os objetos geométricos (geoms) são os responsáveis pelos elementos visíveis sobre os eixos coordenados, incluindo pontos, linhas, barras, caixas e áreas sombreadas. No gráfico (f) geom_point(), por padrão, marca pontos em (x, y) desenhando um gráfico de dispersão. A função labs() insere texto para os eixos.
No gráfico (g) geom_point() torna os pontos em esferas (pch=20), e define cor e tamanho. A função geom_smooth() insere uma linha vermelha tracejada (linetype=2) com ajuste linear definido pelo método (method=”lm”). A área sombreada representa intervalos de 95 % de confiança (default). Os gráficos plotados são mostrados abaixo:

Em (h) são traçados gráficos separados para os tipos de transmissão automática versus manual e tipo de motor. A cor e símbolo usado indicam o número de cilindros do carro (cyl) que também é a variável agrupadora.

No último gráfico (h) as cores para o parâmetro cyl foram escolhidas automaticamente. Para controlar manualmente este comportamento podemos usar scale_color_manual() como exibido abaixo:

> ggplot(iris,
         aes(x = Petal.Length, y = Petal.Width, color = Species)) +
         geom_point()
> ggplot(iris,
         aes(x = Petal.Length, y = Petal.Width, color = Species)) +
         geom_point() +
         scale_color_manual(values = c("steelblue", "gold3", "darkorange"))

Os seguintes gráficos são gerados:

A função scale_color_manual() foi usada porque a variável Species é categórica. Outras funções são usadas para controlar escalas de cor usando diferentes tipos de variiáveis. Existem outras como scale_color__discrete(), scale_color_continuous(), scale_color_gradient(), etc. Igualmente se pode controlar outras propriedades usando-se scale_fill, scale_x etc.

As funções geom

Através da função ggplot() determinamos a fonte dos dados e as variáveis a serem plotadas. Já as diversas funções geom (algumas delas listadas abaixo) informa como elas devem ser representadas graficamente.

Função Plota Opções
geom_bar() gráfico de barras color, fill, alpha
geom_boxplot() gráfico de caixas color, fill, alpha, notch, width
geom_density() gráfico de densidades color, fill, alpha, linetype
geom_histogram() histograma color, fill, alpha, linetype, binwidth
geom_hline() linhas horizontais color, alpha, linetype, size
geom_jitter() pontos espalhados color, size, alpha, shape
geom_line() gráfico de linhas colorvalpha, linetype, size
geom_point() gráfico de dispersão color, alpha, shape, size
geom_rug() gráfico “rug” color, side
geom_smooth() ajuste de linhas method, formula, color, fill, linetype, size
geom_text() anotações em texto Many; see the help for this function
geom_violin() gráfico violino color, fill, alpha, linetype
geom_vline() linhas verticiais color, alpha, linetype, size

As opções mais comuns são:

Opção Controla
color Cor de pontos, linhas e bordas
fill Cor de áreas preenchidas
alpha transparência de cores, de 0 (transparent) até 1 (opaco)
linetype padrão de linhas (1 = sólido, 2 = tracejado, 3 = pontos, 4 = ponto-traço, 5 = traço longo, 6 = duplo traço)
size tamanho de pontos e largura de linhas
shape símbolo do ponto (igual pch: 0 = quadrado vazio, 1 = círculo vazio, 2 = triângulo, …)
position posição de objetos plotados (barras e pontos)
binwidth largura da caixa de histograma
notch booleano, se caixas devem ser recortados
sides colocação de “rugs” (“b” = abaixo, “l” = esquerda, “t” = acima, “r” = direita, “bl” = abaixo à esquerda, etc
width largura de gráficos de caixas

Para os exemplos seguintes usaremos o data frame singer (incluído no pacote lattice) que contém alturas (em polegadas) e faixa vocal para cada cantor e cantora do “New York Choral Society”. As faixas são: Bass 2, Bass 1, Tenor 2, Tenor 1, Alto 2, Alto 1, Soprano 2 e Soprano 1.

> data(singer, package="lattice")
> ggplot(singer, aes(x=height))
        + geom_histogram(fill="red", colour = "black")
        + labs(title="Coral de New York", x="Altura", y="Contagem")
> # O gráfico (i) é plotado
> ggplot(singer, aes(x=voice.part, y=height))
        + geom_boxplot(fill="steelblue", colour = "black")
        + labs(title="Coral de New York", x="Faixa vocal", y="Altura")
> # O gráfico (j) é plotado

Note que no gráfico de histograma apenas a coordenada x foi especificada pois, nesse caso, o valor default de y é a contagem de observações incluídas dentro de cada retângulo. Como aes() está inserido dentro de ggplot() todos os parâmetros definidos ali serão globais, valendo para todas as camadas (se não for novamente informado dentro de outra função de camada).

Para o exemplo que se segue usaremos o data frame Salaries que contém salários de algumas categorias de professores do ensino superior dos EUA no período de 2008-2009 (apenas 9 meses). Ele contém observações sobre algumas variáveis, incluindo sex e salary.

> library(car)
> # Para listar o sexo em português inserimos um novo campo
> Salaries$sexo <- ifelse(Salaries$sex=="Male", "Homem", "Mulher")

> ggplot(Salaries, aes(x=sexo, y=salary)) +
    geom_boxplot(fill="skyblue3", color="black", notch=TRUE) +
    geom_point(color="yellow", alpha=.5) +
    geom_rug(sides="r", color="darkgrey") +
    geom_jitter(position = position_jitter(width = .1),
                alpha = 0.5, color="darkred") +
    labs(title="Salário de professores (EUA)", x="Sexo", y = "Salário")

O seguinte gráfico é plotado:

O gráfico inclui caixas chanfradas (notch) azuis com bordas pretas. Os pontos, correspondentes às observações, são plotados em amarelo, com transparência .5. Observe que todos os pontos amarelos se acumulam sobre a linha vertical correspondentes aos dois sexos, uma vez que todos possuem uma das duas coordenadas no eixo x. Uma nova camada foi inserida através de um espalhamento aleatório (jitter) sobre estes pontos, usando-se geom_jitter() que são plotados em vermelho escuro. Este espalhamento não tem significado estatístico e serve apenas para facilitar a visualização dos pontos. Os dois conjuntos foram mantidos aqui apenas para efeito didático. Poderíamos ter inserido o efeito usando geom_point(position="jitter") junto que os demais parâmetros e omitindo geom_jitter(). Nesse caso os pontos amarelos ficariam dispersos. geom_rug() insere marcas em cinza na lateral direita, correspondentes às posições (y) dos pontos. Os lados podem ser sides = "rltd", alguma das letras ou combinações delas: right, left, top, down.

Agrupamentos

Muitas vezes é útil observar no mesmo gráfico grupos de observações diferentes. Já vimos que podemos agrupar dados em R usando fatores (ou variáveis de categorias). No pacote ggplot2 os agrupamentos ficam definidos pela associação de variáveis com características visuais como forma, cor, preenchimento, tamanhos e tipo de linha, em geral definidas dentro da função aes().

> ggplot(Salaries, aes(x=rank, fill=sexo)) +
       geom_bar(position="stack") +
       labs(title='position="stack"')

> ggplot(Salaries, aes(x=rank, fill=sexo)) +
       geom_bar(position="dodge") +
       labs(title='position="dodge"')

> ggplot(Salaries, aes(x=rank, fill=sexo)) +
       geom_bar(position="fill") +
       labs(title='position="fill"')

O código resulta, respectivamente nos seguintes gráficos, onde o agrupamento se deu por meio do parâmetro fill:

Mais de uma propriedade podem ser usadas para um agrupamento, como se vê no gráfico plotado pelo código abaixo.

> ggplot(data=Salaries, aes(x=salary, fill=rank)) +
       geom_density(alpha=.3) +
       labs(title="Salários de Professores",
             x="Salário", y="Número de Professores")
ggplot(data=Salaries, aes(x=salary, fill=rank, color=sexo)) +
       geom_density(alpha=.3) +
       labs(title="Salários Masc x Fem", x="Salário",
       y="Número de Professores")

No segundo gráfico o agrupamento foi feito através dos parâmetros fill e color:

Apesar de estar pouco nítido e não muito útil para uma análise, o gráfico representa 6 distribuições diferentes para as combinações de sexo =”Masculino” e “Feminino” e rank = “Prof”, “AssistProf” e “AssocProf”. O rank está representado pelo preenchimento (fill) e o sexo pela cor da borda (color) em cada distrinuição.

Gráficos em subplos (facet)

O último gráfico plotado apreenta 6 distribuições sobrepostas, pouco úties para uma visualização dos dados. Pode ser mais interessante analisar grupos diferentes olhando gráficos separados, apresentados lado a lado. No ggplot2 estes gráficos são chamados de facetados (faceted graphs) e são criados com as funções facet_wrap() e facet_grid():

Função

Resultado
facet_wrap(~var, ncol=n) gráficos separados para cada nível de var, dispostos em colunas
facet_wrap(~var, nrow=n) gráficos separados para cada nível de var, disposto em linhas
facet_grid(rowvar~colvar) gráficos separados para cada nível de rowvar e colvar
facet_grid(rowvar~.) gráficos separados para cada nível de rowvar, dispostos em uma coluna
facet_grid(.~colvar) gráficos separados para cada nível de colvar, dispostos em uma linha

Na tabela var, rowvar, colvar são fatores, rowvar representa linhas colvar representa colunas.

Nos exemplos abaixo usamos o mesmo gráfico congestionado do caso anterior, onde 6 distribuições estavam representadas. No primeiro caso desdobramos as plotagens em 2 linhas, separadas por sexo. No segundo 3 gráficos são dispostos em uma coluna, 3 linhas, separados por rank.

> ggplot(data=Salaries, aes(x=salary, fill=rank, color=sexo)) +
          geom_density(alpha=.3) +
         labs(title="Salários Masc x Fem",
               x="Salário", y="Número de Professores") +
         facet_wrap(~sexo, nrow=2)

> ggplot(data=Salaries, aes(x=salary, fill=rank, color=sexo)) +
         geom_density(alpha=.3) +
         labs(title="Salários Masc x Fem",
              x="Salário", y="Número de Professores") +
         facet_wrap(~rank, nrow=3)

Em uma ordenação de facetas dividida por um fator com 8 elementos, distribuídos em 4 linhas, ficam dois plots lada a lado em cada linha:

> ggplot(data=singer, aes(x=height)) + geom_histogram() +
         labs(title="Cantores", x="Altura", y="Número de cantores") +
         facet_wrap(~voice.part, nrow=4)

Adicionando curvas e ajustes estatísticos

Além da construção de gráficos customizados, ggplot2 permite incluir nesses gráficos informações processadas por meio de funções estatísticas de análise. Estas funções permitem o agrupamento de dados, o cálculo de densidades, contornos e quantis. Usando a função geom_smooth() podemos adicionar aos gráficos de dispersão linhas suavizadas (linear, não linear e não paramétricas) e sombreamentos para intervalos de confiança.

Parâmetro Descrição
method= método de suavização: lm, glm, smooth, rlm, e gam (linear, linear generalizado, loess, linear robusto ou aditivo generalizado). smooth é o default.
formula= fórmula para a função de suavização. Exemplos: y~x (default), y~log(x), y~poly(x,n) para ajuste a polinômio de n-ésimo grau e y~ns(x,n) para um ajuste de spline com n graus de liberdade.
se booleano, default=TRUE. Plota intervalos de confiança.
level nível para intervalos de confiança (default de 95%).
fullrange booleano, default=FALSE. Se o ajuste deve incluir toda a faixa do plot (TRUE) ou apenas os dados.

LOESS suavização de dispersão estimada localmente (locally estimated scatterplot smoothing).

> library(dplyr)
> carros <- mtcars %>% mutate(carro=rownames(mtcars))

> carro1 <- ggplot(carros, aes(x=disp, y=mpg)) + geom_point() + geom_smooth()

> carro2 <- ggplot(data=carros, aes(x=disp, y=mpg, color=carb)) +
            geom_smooth(method=lm, formula=y~x, size=1) +
            geom_point(size=2)

> carro3 <- ggplot(data=carros, aes(x=disp, y=mpg, color=carb)) +
            geom_smooth(method=lm, formula=y~poly(x,2), size=1) +
            geom_point(size=2)

> carro4 <- ggplot(data=carros, aes(x=disp, y=mpg, color=carb)) +
          geom_smooth(method=lm, formula=y~poly(x,2), size=1, se=FALSE) +
          geom_point(size=2)
          
> print(carro1)
`geom_smooth()` using method = 'loess' and formula 'y ~ x'
> print(carro2)
> print(carro3)
> print(carro4)
> # os gráficos carro1, carro2, carro3 e carro4 são plotados.

Para o gráfico carro1 nenhum parâmetro foi fornecido. geom_smooth() adotados os defaults method = 'loess', formula 'y ~ x', como é informado no console. Em carro2 uma reta de melhor ajuste é plotada. Em carro3 uma curva de segundo grau de melhor ajuste é plotada.
A mesma curva é repetida em carro4 sem a representação dos intervalos de confiança.

Observe que o data frame carros foi criado à partir de mtcars com a função mutate() da biblioteca dplyr. Ela permite a criação de uma nova variável para o data frame existente. Além usamos o pipe:

> mtcars %>% mutate(carro=rownames(mtcars))
> # que é o mesmo que:
> mutate(mtcars, carro=rownames(mtcars))

Temas

ggplot2 traz em sua instalação alguns temas prontos que modificam a aparência de um gráfico e a possibilidade da modificação ou criação personalizada destes temas. As opções da função theme() permitem o ajuste de fonts, planos de fundo, cores, linhas de grade, tamanho da fonte do eixo x, posição da legenda, etc. Para cada elemento do tema existe um tipo de objeto que realiza as alterações. Por exemplo, o estilo do título do eixo x (axis.title.x) é alterado com a função element_text() que possui diversos parâmetros (família da fonte, tipo da fonte, cor, tamanho, alinhamento etc.). As principais funções para se alterar elementos de um tema são element_text(), element_line(), element_rect() e element_blank(). O útilmo é usado para que nada seja desenhado no elemento que recebe esta função.

Um exemplo simples de uso de temas é mostrado, através da aplicação do tema theme_dark():

> library(lattice)   # Para usar o data frame singer
> ggplot(data=singer, aes(x=height, fill=voice.part)) +
         geom_density() +
         facet_grid(voice.part~.)
> ggplot(data=singer, aes(x=height, fill=voice.part)) +
         geom_density() +
         facet_grid(voice.part~.) + theme_dark()

Para quem usa o RStudio existe um add-in que permite a customização gráfico do ggplot2 através de uma interface com o usuário que usa atalhos de teclado e interação com o mouse chamada ggThemeAssist. Com ela o usuário pode alterar temas do forma WYSIWYG, usando tentativa e erro. O pacote deve ser instalado com install.packages("ggThemeAssist").

Para usar este add-in é necessário criar um objeto gráfico do ggplot2 e depois usá-lo como parâmetro em ggThemeAssistGadget. Uma janela é aberta com acesso à vários elementos da geometria do gráfico. Quando a janela é fechada a função retorna uma linha de comando contendo os parâmetros para que seja plotado o gráfico escolhido. O procedimento e resultado aparecem no código abaixo.

> library(ggThemeAssist)
> pp <- ggplot(data=singer, aes(x=height, fill=voice.part)) +
               geom_density() + facet_grid(voice.part~.)
> ggThemeAssistGadget(pp)
> # Uma janela é aberta onde os parâmetros podem ser alterados interativamente.
> # A linha abaixo é retornada:
> pp + theme(plot.subtitle = element_text(colour = "bisque4",
             vjust = 1), plot.caption = element_text(vjust = 1),
             axis.title = element_text(family = "Bookman",
             size = 14), plot.title = element_text(family = "Bookman"),
             legend.title = element_text(family = "Bookman"),
             panel.background = element_rect(fill = "cornsilk2"),
             legend.key = element_rect(colour = "antiquewhite4"),
             legend.background = element_rect(fill = "lavenderblush1")) +
             labs(title = "Número de cantores x Altura", x = "Altura",
             y = "Número de cantores",
             subtitle = "Exemplo de Uso do ggThemeAssistGadget",
             caption = "Gráfico demonstrativo")
> # O gráfico plotado aparece na imagem abaixo.

Múltiplos gráficos por página e salvando gráficos

Para os gráficos plotados pelo sistema base de R é possível combinar vários gráficos em um único com o uso da função layout() e o parâmetro mfrow. Com o pacote ggplot2 gráficos podem ser combinados em uma figura única com a função grid.arrange().
O código ilustra este procedimento.

> install.packages("gridExtra")
> library(gridExtra)
> data(Salaries, package="car")
> library(ggplot2)
> p1 <- ggplot(data=cars, aes(x=speed)) + geom_bar(fill=rainbow(19))
> p2 <- ggplot(data=cars, aes(x=dist)) + geom_bar() 
> p3 <- ggplot(data=cars, aes(x=speed, y=dist)) + geom_point(color="red")
> grid.arrange(p1, p2, p3, ncol=3)

> ggsave(file="grafico-3.png", plot=p3, width=5, height=4)
> print(p1)
> ggsave(file="grafico-1.pdf")

Observe que neste caso estamos plotando gráficos independentes em uma simples figura, o que é diferente do que foi feito com a função facet onde se representava gráficos construídos sobre um mesmo data frame mas separados por variáveis categóricas.

A função ggsave() é usada para gravar os gráficos. A primeira chamada acima grava, na pasta ativa, uma imagem grafico-3.png com 5×4 polegadas. A segunda, onde se omitiu o parâmetro plot, grava um arquivo pdf com o último gráfico plotado, no caso o gráfico p1.

 


Aquisição de Dados

Gráficos

Gráficos com plot

Uma parte importante da análise de dados esta na visualização destes dados em forma gráfica. A representação visual de dados permite, muitas vêzes, o reconhecimento de padrões que dificilmente seriam percebidos apenas com tabelas e números. R fornece várias funções para representar dados graficamente, tanto em gráficos bidimensionais quanto tridimensionais. Em particular dá-se ênfase aos gráficos estatísticos, tais como histogramas, curvas de distribuições, gráfico de barras e outros. Existem métodos gerais que se aplicam à diversas formas básicas de gráficos. Pode-se incluir títulos, nomes para os eixos, cores, representações por pontos linhas e sinais variados e anotações.

A função plot() é a mais básica na geração de um gráfico.

> x <- -10:10;   y <- x^2;  plot(x,y)    # resultado na figura 1
> # Parâmetro para tornar a linha contínua
> plot(x,y, type="l")                    # resultado na figura 2

Alguns dos parâmetros são listados abaixo:

plot(x, y, ...)

x coordenadas horizontais dos pontos. Pode ser um objeto numerado.
y coordenadas verticais dos pontos. Omitido se x é um objeto numerado.
Argumentos adicionais (parâmetros gráficos).

Entre os argumentos adicionais:

type = “p” (pontos), “l” (linhas), “b” (ambos),
“h” (histograma), “s” (degraus), “n” (em branco)
main = título principal
sub = subtítulo
xlab = título para eixo x
ylab = título para eixo y
asp = aspecto (razão y/x).

Observe que, se u é um objeto ordenado, então plot usa como coordenada x a ordem dos elementos. Por exemplo:

> u <- rnorm(10)
> # Os dois gráficos seguintes são idênticos
> plot(u)
> plot(1:10, u)

Algumas funções sobreescrevem o gráfico já traçado. É o caso da função lines. É possível alterar características das linhas com os parâmetros lwd (largura da linha) e lty (tipo da linha), como se mostra no exemplo:

> a <- 1:20; b <- a^2
> plot(a, .1*b ,type="l")   # linha 1
> lines(a, .2*b , lwd=4 )   # linha 2
> lines(a, .3*b , lwd=2)    # linha 3
> lines(a, .4*b , lty=3)    # linha 4
> points(a,.5*b, pch=3)     # pontos 5
> text(10, 2, "Título do gráfico")  # título na posição 10 , 2

As linhas acima resultam no gráfico:

Se vários gráficos devem ser analisados ao mesmo tempo, uma nova instância da janela de saída gráfica pode ser aberta com o comando dev.new() ou X11() (apenas para sistemas tipo Unix). Pode-se navegar entre “devices” gráficos abertos usando dev.new(), dev.next(), dev.prev(), dev.set() e dev.off().

> plot(rnorm(10))      # plota o primeiro gráfico
> dev.new()            # abre nova janela
> plot(rnorm(20))      # plota o segundo gráfico

Os gráficos podem ser enviados diretamente para arquivos nos formatos JPEG, BMP, PDF, TIFF, PNG, entre outros. Como exemplo, direcionamos a saída gráfica para um arquivo JPEG:


> # grava arquivo hiperbole.jpg
> jpeg(file='hiperbole.jpg')
> # plota gráfico
> plot(x<- -100:100, 1/x, type='l', main="hipérbole")
> # fecha janela gráfica
> dev.off()
> # Grava arquivo jpeg com a imagem à direita.

Outras funções capazes de escrever por cima de um gráfico já plotado são locator(n) e identify(). A primeira serve para que o se selecione regiões do gráfico utilizando o botão esquerdo do mouse até que se tenha um número n de pontos selecionados (ou até pressionar o botão direito do mouse, para terminar). A cada clique dado com o botão esquerdo do mouse a função retorna no console as coordenadas do clique. Por exemplo:


> x <- 1:100; y <- sqrt(x)
> plot(x,y, type="l")
> text(locator(1), "x é aqui!")
> text(locator(1), "y é aqui!")
> text(locator(5), paste("<", 1:4, ">")
> # A linha acima marca 4 pontos
> # no gráfico, com o texto:
> # "<1>", "<2>", "<3>", "<4>"
> # respectivamente

Para os próximos passos usaremos o dataframe carregado por padrão no R de nome mtcars. Este é um conjunto de dados sobre automóveis com campos mpg, cyl, disp, hp, drat, wt, qsec, vs, am, gear, carb. Antes de prosseguir, observe que as quatro formas de notação abaixo são equivalentes:

> # ------------------------------ (1)
> plot( mtcars$mpg, mtcars$cyl)
> # ------------------------------ (2)
> attach(mtcars)
> plot( mpg, cyl)
> detach(mtcars)
> # ------------------------------ (3)
> attach(mtcars)
> plot(cyl ~ mpg)
> detach(mtcars)
> # ------------------------------ (4)
> plot( cyl ~ mpg, data= mtcars )

Na quarta forma usamos y ~ x para representar x como variável independente, y como variável dependente.

Aproveitamos a oportunidade para conhecer uma sintaxe especial. O comando plot( cyl ~ ., data= mtcars ) realiza a plotagem de todos os gráficos de cyl como função de todos os demais campos de mtcars. O prompt Hit to see next plot: aparece entre as operações.


> attach(mtcars)
> plot(mpg ~ wt)
> abline(lm(mpg ~ wt))
> title("Regressão Linear de Consumo por Peso")
> detach(mtcars)
Para enviar este gráfico para um arquivo pdf basta cercar todas as linhas acima pelos comandos pdf("NomeArquivo.pdf") e dev.off().

A função abline(a,b) traça uma reta sobre o gráfico, recebendo os parâmetros a como a interseção da reta com o eixo y (x = 0) e b como a inclinação da reta. Ela recebe como argumento lm(y~x) que retorna diversos dados sobre a regressão linear da função y~x, entre eles os parâmetros a e b necessários para definar a reta.

A função abline possui outros parâmetros. Entre eles:

abline(a=NULL, b=NULL, h=NULL, v=NULL, col=, …)
a = interseção com eixo y
b = inclinação da reta
h = y (traça reta horizontal por y)
v = x (traça reta vertical por x)
col = cor: “red”, “blue”, etc; ou rgb(x,y,z), onde x, y, z ∈ [0, 1]
ou hexadecimal #abcdef; a, …, f ∈ [0, f], hexadecimal.

Alguns exemplos de uso de abline(). O código seguinte gera os gráficos abaixo:

> plot(x<- 0:10, x) # plotar uma reta
> title("Reta y = x")
> abline(v=4)       # reta vertical por x = 4
> abline(v=6, col="blue") # reta vertical por x = 6, azul
> abline(h=8, col="#779900") # outra cor
> # Usando a tabela cars (embutida em R)
> dev.new()
> plot(cars)
> abline(v=c(15,20), col=c("blue", "red"), lty=c(1,2), lwd=c(1, 3), h=40)

Parâmetros Gráficos

O conjunto de parâmetros para construção de gráficos podem ser lidos e (alguns deles) alterados através da função par(). Podem ser características como fonts, cores, eixos e títulos.

par(…, no.readonly = FALSE)
Argumentos:
no.readonly = Booleano. Se TRUE apenas parâmetros que podem ser
alterados pelo usuário são exibidos.
… Outros parâmetros são passados na forma:
par1 = valor1, …, parn = valorn

Se nenhum parâmetro for fornecido par() exibe uma lista dos parâmetros atuais, par(no.readonly = TRUE) exibe uma lista dos parâmetros que podem ser alterados. Valores alterados dessa forma permanecem válidos durante a sessão.

Para exemplificar suponha que pretendemos ter nossos gráficos plotados com quadrados sólidos ligados por retas. O seguinte código pode ser usado:

> # parâmetros default são armazenados
> oldPar <- par(no.readonly=TRUE)
> par(lty=1, pch=15)
> plot(cars$dist ~ cars$speed, type="b")
> title("Usando quadrados e retas")
> # parâmetros default são restaurados
> par(oldPar)
> # Claro que o mesmo efeito seria obtido com
> plot(cars$dist ~ cars$speed, type="b", lty=1, pch=15)

Alguns parâmetros são listados na tabela:

Parâmetro Descrição
pch símbolo usado para marcar pontos.
cex tamanho do símbolo, relativo ao default. 1 = default, 1.5 is 50% maior, etc.
lty tipo da linha.
lwd largura da linha, relativa ao default. Ex.: lwd=2 dupla largura.
Valores de pch, lty

As cores que podem ser alteradas nos gráficos estão listas na tabela seguinte:

Parâmetro Descrição
col cor default do gráfico.
col.axis cor para texto nos eixos.
col.lab cor para labels nos eixos.
col.main cor do título.
col.sub cor do subtítulo.
fg cor do primeiro plano.
bg cor de fundo.

Para o parâmetro col algumas funções aceitam valores reciclados. Por ex., se col=c("blue", "green") e três curvas são exibidas então a primeira e a terceira serão azuis, a segunda verde. Cores podem ser especificadas por índice, nome, valores hexadecimais, RGB e HSV. A função colors() exibe uma lista de todas as cores disponíveis, por nome.

Algumas funções permitem a criação de palhetas, vetores com n cores contíguas:

rainbow(n, s = 1, v = 1, start = 0, end = max(1, n – 1)/n, alpha = 1,
heat.colors(n, alpha = 1),
terrain.colors(n, alpha = 1),
topo.colors(n, alpha = 1),
cm.colors(n, alpha = 1))

Parâmetros:
n número (≥ 1) de cores no vetor.
s, v “saturação” e “valor” no formato HSV.
start [0,1] cor inicial para o arco-íris (rainbow).
end [0,1] cor final para o arco-íris (rainbow).
alpha [0,1], transparência.

Vamos explorar o uso destas palhetas de cores na próxima seção.

Para especificar o estilo, tamanho e família das fontes os seguintes parâmetros gráficos podem ser usados:

Parâmetro Descrição
cex magnificação do texto: 1 = default, 1.5 = 50% maior; 0.5 = 50% menor, etc.
cex.axis magnificação dos eixos, relativo a cex.
cex.lab magnificação do texto nos eixos, relativo a cex.
cex.main magnificação do texto do título, relativo a cex.
cex.main magnificação do texto do subtítulo, relativo a cex.
font inteiro para fonte: 1 = simples, 2 = negrito, 3 = itálico, 4 = negrito itálico, 5=símbolo.
font.axis fonte nos eixos.
font.lab fonte nos labels de eixos.
font.main fonte nos títulos.
font.sub fonte nos subtítulos.
ps tamanho do ponto na fonte (~1/72 polegada).
family família da fonte. Os padrões são serif, sans e mono

Por exemplo, após a aplicação dos parâmetros:
par(cex.main=3, cex.lab=1.5, font.lab=2, font.main=4, font.sub=3)
o texto nos gráficos serão representados com: títulos com fontes 3 vezes maior que o padrão definido em cex, eixos magnificados em 1.5, labels em negrito nos eixos, títulos em negrito itálico e subtítulos em itálico.

Para controle das dimensões do gráfico e margens usamos:

Parâmetro Descrição
pin largura e altura do gráfico, em polegadas.
mai vetor com larguras das margens, c(inferior, esquerda, superior, direita) em polegadas.
mai vetor com larguras das margens, c(inferior, esquerda, superior, direita) em linhas. [default = c(5, 4, 4, 2) + 0.1].

Função barplot()

A função barplot() permite a exibição de gráficos de barras. Um resumo de seus parâmetros está mostrado abaixo.

barplot(height, width = 1, space = NULL, names.arg = NULL,
horiz = FALSE, density = NULL, col = NULL, border = par(“fg”),
main = NULL, sub = NULL, xlab = NULL, ylab = NULL, axes = TRUE)

Parâmetros:
height vetor ou matriz contendo altura das barras.
width vetor com largura das barras.
space espaço deixado antes da barras (uma fração da largura).
names.arg vetor de nomes para barras.
horiz booleano. FALSE = barras verticais; TRUE = barras horizontais.
density vetor, densidade do hachurado. NULL= sem hachura.
col vetor de cores das barras.
border cor das bordas das barras.
main,sub título e subtítulo
xlab texto para o eixo x.
ylab texto para o eixo y.
axes booleano. Se eixos são desenhados

O código acima gera o gráfico de barras abaixo:

Gráfico de barras

A função bar plot pode receber uma matriz como argumento. Para ilustrar vamos usar a função table() para tabelar dados no data frame mtcars. Este data frame possui o campo mtcars$carbs que lista o número de carburadores de uma lista de automóveis. Em seguida criamos uma tabela com um teste clínico hipotético para o tratamento da gripe usando um antiviral, vitammina C e um “chazinho”.

> carburadores <- table(mtcars$carb)
> carburadores
 1  2  3  4  6  8
 7 10  3 10  1  1
> # A tabela mostra que existem 7 modelos com 1 carburador, 10 com 2, etc.
> barplot(carburadores, main="Modelos x carburadores", horiz=TRUE,
          names.arg=c("1", "2", "3","4", "6", "8"), xlab="Quantos modelos",
          ylab="Número de carburadores", col=rainbow(6))

> testeClinico <- matrix(c(45,9,12,4,31,31,1,10,7), ncol=3, byrow=TRUE)
> cores <-c("#5FC0A0", "#DE7A6B", "#6BA0DE")
> colnames(testeClinico) <- c("Antiviral","Vitamina C","Chazinho")
> rownames(testeClinico) <- c("Melhorou","Sem alteração","Piorou")
> testeClinico
              Antiviral Vitamina C Chazinho
Melhorou             45          9       12
Sem alteração         4         31       31
Piorou                1         10        7

> resultado <- as.table(testeClinico)
> barplot(resultado, main="Gripe: teste clínico", xlab="Medicamento",
          ylab="Eficácia", col=cores, legend=rownames(resultado))

O código acima gera os gráficos:

O mesmo gráfico, com os dados agrupados por tipo de medicamento testado pode ser obtido ao se acrescentar o parâmetro beside=TRUE que força a exibição de dados lado à lado para uma mesma coluna:


cores <-c("#5FC0A0", "#DE7A6B", "#6BA0DE")
> barplot(resultado,
          main="Gripe: teste clínico",
          xlab="Medicamento",
          ylab="Eficácia",
          col=cores, beside=TRUE)

Usando funções de agregamento e passando o resultado para barplot() pode-se representar médias, medianas, desvios padrões e outros em gráficos de barras.

Para experimentar com esta funcionalidade usaremos o dataset embutido com o R denomidado states (US State Facts and Figures). Ele contém dados antigos sobre os 50 estados americanos. Em particular usaremos state.region, um fator contendo as regiões de cada estado (Northeast, South, North Central, West) e state.x77, uma matriz com 50 linhas e 8 colunas com informações sobre os estados. O campo state.x77$Illiteracy contém taxas de analfabetismo nos estados americanos em 1970, como porcentagem da população.

> # Carregamos uma palheta de 4 cores
> cor <- c("#F3E16E", "#6EC6F3", "#6FF36E", "#F36E84")

> reg <- state.region
> levels(reg)   # as regiões estão em inglês
[1] "Northeast"     "South"         "North Central" "West"
# Para traduzir para o português alteramos os levels:
> levels(reg) <- c("Nordeste","Sul","Central Norte","Oeste")
> levels(reg)
[1] "Nordeste"   "Sul"   "Central Norte"   "Oeste"

> # Usamos apenas a 3a. coluna de state.x77 (analfabetismo %)
> analfabetismo <- state.x77[,3]
> # Criamos um dataframe com informações: regiões x analfabetismo
> estados <- data.frame(reg, analfabetismo)

> # Agregamos a informação sobre analfabetismo por região,
> # usando a função mean (média)
> media <- aggregate(estados$analfabetismo, by=list(estados$reg), FUN=mean)
> # para alterar os nomes das colunas
> names(media) <- c("regiao", "taxa")

> # Ordenamos o resultado por taxa de analfabetismo
> media <- media[order(media$taxa),]
> View(media)   #  resulta na tabela 1

> # plotando o gráfico de barras
> barplot(media$taxa, names.arg=media$regiao, col=cor)
> title("Analfabetismo nos EUA / por região"
> #  resultado no gráfico abaixo

O código acima gera o gráfico:

Função pie()

Gráficos de setores ou gráficos de pizza (pie charts) também são úteis para a representação de dados.

pie(x, labels = names(x), edges = 200, clockwise = FALSE,
init.angle = if(clockwise) 90 else 0,
col = NULL, main = NULL, …)

Parâmetros:
x vetor de valores, exibidos como áreas dos setores no gráfico.
labels nomes para legendas dos setores. NA ou “” = sem legenda.
edges borda externa é um polígono com este número de lados.
clockwise booleano. Sentido horário ou não das fatias.
init.angle ângulo inicial (da primeira fatia).
col vetor de cores de preenchimento das fatias.
main título do gráfico.
> z <- (-10:10)^2 - 50
> barplot(z, col=rainbow(25), main="Gráfico de barras", ylab="y=x^2-50")
> dev.new()

> legenda <- paste("fatia ",1:16) # gera vetor fatia 1, ..., fatia 16
> cores <- c("#F3E16E", "#6EC6F3", "#6FF36E", "#F36E84")
> pie(rep(1,16), col=cores, labels=legenda, main="Setores")

O seguinte gráfico é gerado:

Gráfico de Setores

O código abaixo gera três gráficos de setores (pie charts). As populações listadas para os cinco países mais populosos são dadas em milhões. No gráfico-2 os percentuais (apenas entre estes 5 países) são exibidas. No terceiro gráfico a package plotrix é usada para desenhar um gráfico em 3 dimensões.

> populacao <- c(1420, 1368, 329, 269, 212)
> pais <- c("China", "India", "EUA", "Indonesia" , "Brasil")
> pie(populacao, labels=pais, main = "população em milhões")
> # Gera Gráfico-1
> pc <- round(populacao/sum(populacao)*100)
> pc  # porcentagem de população (entre estes 5 países)
[1] 39 38  9  7  6
> label <- paste(pais, "-", pc, "%", sep="")
> label
[1] "China-39%"  "India-38%"  "EUA-9%"  "Indonesia-7%"  "Brasil-6%"
> pie(populacao, labels=label, col=rainbow(length(labels)), main = "população em milhões (%)")
> # Observe que length(labels)=5 e temos 5 cores geradas
> # Gera Gráfico-2
> library(plotrix)   # deve ser instalado com install.packages("plotrix")
> pie3D(populacao, labels=label,explode=0.1, main="3D Gráfico setores")
> # Gera Gráfico-3

Os gráficos de setores são podem ser úteis para uma visualização rápida de uma relação entre valores. No entanto podem dificultar a análise mais minuciosa destes dados. Por exemplo, se dois setores tem aproximadamente o mesma área pode ser difícil perceber qual é maior. Em geral o uso de barras é mais recomendado.

Uma alternativa atraente é o fan.plot, carregado junto com a library plotrix. Neste tipo de gráfico os setores são sobrepostos e seus raios variados para que todos apareçam na representação.


> library(plotrix)
> populacao <- c(1420, 1368, 329, 269, 212)
> pais <- c("China", "India", "EUA",
            "Indonesia" , "Brasil")
> fan.plot(populacao, labels=pais,
           main = "Usando o fan.plot",
           col=rainbow(5))
> # O gráfico à direita é gerado.

Histogramas com a função hist()

Histogramas (ou distribuições de frequências) são uma forma de exibir a distribuição de uma variável contínua. A faixa de valores a serem analisados é dividida em classes (que podem ser ou não uniformes). A base de cada retângulo na representação é dada pela extensão da classe e a altura pela quantidade de dados (frequências) dentro de cada classe. Histogramas são criados com a função hist(v) onde v é um vetor numérico.
O parâmetro freq=FALSE gera um gráfico baseado em densidades de probabilidade e não em frequências. O parâmetro break informa em quantos classes os dados devem ser divididos. Por default as classes são divididas uniformemente.

> distUniforme <- runif(1000, 0, 10)
> # gera 1000 valores distribuídos uniformemente, com média 0 e desvio padrão 1
> hist(distUniforme, col=rainbow(10))
> # plota o histograma dessa distribuição

> distNormal <- rnorm(100000, 0, sd=2)
> # gera 10^5 valores distribuídos aleatóriamente com distribuição normal,
> # com média 0 e desvio padrão 2
> hist(distNormal, col=rainbow(12))
> # plota o histograma dessa distribuição

Para os exemplos que se seguem usaremos o data frame faithful, embutido na instalação do R. Este é um data frame contendo 272 observações, registradas em 2 variáveis numéricas: eruptions, tempo de erupção, e waiting intervalo entre erupções ambas em minutos.

> attach(faithful)
> hist(eruptions)
> # Gera o primeiro histograma abaixo
> hist(faithful$eruptions, seq(1.6, 5.2, 0.2), prob=TRUE, col=rainbow(18))
> lines(density(faithful$eruptions, bw=0.1))
> rug(faithful$eruptions)
> # Gera o segundo histograma abaixo
> # Os efeitos de lines() e rug() estão marcados no gráfico
> detach(faithful)

Gráficos de densidade kernel

Dada uma variável aleatória discreta, a estimativa de densidade kernel (EDK) é uma técnica para se estimar a função de densidade de probabilidade que melhor se ajusta à esta variável. Ela busca suavizar dados discretos fazendo inferências sobre uma amostra finita de dados. Desta forma é possível extrapolar dados discretos, fazendo previsões para valores não diretamente medidos. O kernel (ou núcleo) é uma função simétrica, suave. Tipicamente se usa a gaussiana, embora outras funções podem ser escolhidas. No R um gráfico de densidade kernel para o vetor x pode ser obtido com plot(density(x, )).


> attach(mtcars)
> # Construimos uma densidade usando
> # mtcars$mpg (milhas por galão)
> d <- density(mpg)
> plot(d,
       main="Milhas/galão (densidade kernel)")
> # Para colorir a área sob a curva
> polygon(d, col="lightblue", border="black")
> # Para inserir marcas nos valores
> # discretos que geraram a densidade
> rug(mpg, col="red")
> detach(mtcars)

A função polygon() desenha um polígono com vértices x, y, neste caso os pares fornecidos pela densidade. rug() marca os valores presentes no vetor mtcars$mpg.

Gráficos de densidade kernel podem ser usados para comparar dados em grupos distintos. Para isso usaremos o pacote sm. Nesse pacote usamos
a função sm.density.compare() para sobrepor gráficos nos grupos dentro de fatores cyl.f, que são, no caso, 4, 6 e 8. O formato é sm.density.compare(x, factor) onde x é um vetor numérico e o fator fornece a variável de agrupamento.

> install.package("sm")
> library(sm)
> attach(mtcars)
> cyl.f <- factor(cyl, levels= c(4,6,8),
           labels = c("4 cilindros", "6 cilindros", "8 cilindros"))
> sm.density.compare(mpg, cyl, xlab="Milhas por galão")
> title(main="Consumo x Cilindros")
> cores<-c(2:(1+length(levels(cyl.f))))
> legend(locator(1), levels(cyl.f), fill=cores)
> # locator(1) faz com que o quadro de legendas
> # fique ancorado no ponto clicado pelo usuário.
> detach(mtcars)

O código acima gera o gráfico:

Gráficos de caixas boxplot()

Um gráficos de caixas (boxplot()) é uma ferramenta muita usada para analisar e comparar a variação de uma variável entre diferentes grupos de dados. Ela representa uma variável traçando as mesmas informações obtidas em sumário de cinco números:
o mínimo, o quartil inferior (percentil 25), a mediana (percentil 50), o quartil superior (percentil 75) e o máximo. Ele também pode ser usado para mostrar outliers (ou discrepantes, que são valores fora do intervalo de ± 1,5 * IQR, onde IQR é o intervalo interquartil definido como o quartil superior menos o menor quartil).
Aproveitamos, nas linhas de código abaixo, para rever algumas funções estatísticas básicas, como median(), quantil() e summary().

> u <- mtcars$mpg
> min(u)
[1] 10.4
> max(u)
[1] 33.9
> median(u)
[1] 19.2
> quantile(u)
    0%    25%    50%    75%   100%
10.400 15.425 19.200 22.800 33.900
> quantile(u, .25)
   25%
15.425
> quantile(u, .75)
 75%
22.8
> quantile(u, .25, .5,.75)
   25%
15.425
> quantile(u, c(.25, .5, .75))
   25%    50%    75%
15.425 19.200 22.800
> fivenum(u)
[1] 10.40 15.35 19.20 22.80 33.90
> summary(u)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
  10.40   15.43   19.20   20.09   22.80   33.90
> boxplot(u, main="Box plot", ylab="Milhas/galão")

O gráfico é gerado:

Boxplots podem ser usados para comparar grupos de variáveis dentro de um dataframe ou lista. O formato para isto é: boxplot(formula, data=dataframe) onde formula é uma relação entre campos do dataframe. Um exemplo de fórmula é y ~ A, onde A é uma variável categórica. Neste caso um plot separado de y é traçado para cada valor de A. A fórmula y ~ A*B resultaria em plots separados de y para cada combinação dos níveis nas variáveis categóricas A e B.

> boxplot(mpg ~ cyl, data=mtcars,
          main="Dados de Consumo",
          xlab="Número de Cilindros",
          ylab="Milhas/galão", col=c("red", "blue", "green"), varwidth=TRUE)
> legend(locator(1), levels(cyl.f), fill=c("red", "blue", "green"))


O opção varwidth=TRUE faz com que as caixas tenham larguras proportionais à raiz quadrada do tamanho das amostras. O parâmetro horizontal=TRUE (não usado no gráfico acima) produz a reversão da orientação dos eixos.

Visualizações interativas

O R fornece muitas formas de exibir gráficos que podem ser modificados por interações com o usuário. Vamos exibir aqui apenas alguns exemplos.

Gráficos interativos com iplots

> library(iplots)
> attach(mtcars)
> cyl.f <- factor(cyl)
> gear.f <- factor(gear)
> ihist(mpg) # histograma
> ibar(carb) # gráfico de barras
> iplot(mpg, wt) # gráfico de pontos
> ibox(mtcars[c("qsec","disp","hp")]) # boxplots
> ipcp(mtcars[c("mpg","wt","hp")]) # coordenadas paralelas
> imosaic(cyl.f,gear.f) # gráfico mosaico

Gráfico gerado por ihist(mpg). O colorido foi feito após a geração do gráfico usando-se o item de menu View > Set color(rainbow).

Leaflet

O leaflet é uma biblioteca javascript voltada para a visualização interativa de mapas. O código abaixo carrega uma sessão com o leaflet. A função addTiles() insere uma camada com um mapa ao leaflet inicializado.

> library(dplyr)
> library(leaflet)
> leaflet() %>% addTiles()
> # O gráfico 1 é desenhado.
> # Inserindo a latitude e a longitude da
> # Praça da Liberdade, em Belo Horizonte, MG.
> # (que foi encontrada no Google Maps)
> pcaLiberdade <- data.frame(longitude = -43.938023, latitude= -19.931743)
> pcaLiberdade$titulo <- "Praça da Liberdade, BH!"
> # pcaLiberdade é um data frame com campos:
> pcaLiberdade
       longitude      latitude        titulo
1      -43.93802     -19.93174        Praça da Liberdade, BH!
> leaflet(pcaLiberdade)
          %>% addTiles()
          %>% addMarkers(lat = ~latitude, lng = ~longitude, popup = ~titulo)
> # O gráfico 2 é desenhado

Lembrando: as bibliotecas dplyr e leaflet devem ser instaladas. Observe as linhas de retorno na instalação para verificar sucesso ou erro. A biblioteca iplots, por exemplo, depende de Java.

Biblioteca shiny

> library(shiny)
> ui <- basicPage(
        plotOutput("plot1", click = "plot_click"),
        verbatimTextOutput("info")
  )
> server <- function(input, output) {
     output$plot1 <- renderPlot({
         plot(mtcars$wt, mtcars$mpg)
     })

     output$info <- renderText({
         paste0("x=", input$plot_click$x, "\ny=", input$plot_click$y)
     })
}
> shinyApp(ui, server)
Listening on http://127.0.0.1:6260

A url mostrada (no caso “http://127.0.0.1:6260”) deve ser visualizada no Browser. A cada clique de mouse as coordenadas do cursor são exibidas na caixa abaixo, como mostra a figura.

Biblioteca plotly

O código abaixo utiliza o data frame diamonds que contém informações sobre cor, clareza, medidas, carat, preço de diamantes. O ponto clicado abre um pop-up com dados sobre a posição no gráfico e a clareza do diamante.

> library(plotly)
> set.seed(100)
> d <- diamonds[sample(nrow(diamonds), 1000), ]
> plot_ly(d, x = carat, y = price, text = paste("Clareza: ", clarity),
          mode = "markers", color = carat, size = carat)

Sobre operadores em R

Em R o programador pode criar aperadores ou alterar o significado de operadores nativos usando o sinal “`” (backtick ou acento grave).
Por exemplo:
`+` <- function(a, b) paste(a, b, sep="")
"a"+"v" # retorna "av"

O sinal "+" se transformou no operador de concatenação.
Em geral se pode programar %X% (qualquer X) para qualquer funcionalidade.
`%@%` <- function(a, b) a^b
`%*%` <- function(x, y) x/y
2 %@% 3 # retorna 8
15 %*% 3 # retorna 5

As bibliotecas magrittr e dplyr definem o operador %>% com o seguinte significado:
`%>%` <- function(x, FUN) FUN(x)

Isso quer dizer que
x %>% hist
é o mesmo que
hist(x)
Por exemplo:
iris$Sepal.Length %>% hist # traça o histograma do vetor
mtcars$mpg %>% hist(col=rainbow(5)) # histograma de mtcars$mpg usando 5 cores.

Gráficos tridimensionais

Existem muitas bibliotecas em R para a geração de gráficos 3D. Entre eles estão: RGL, car, lattice e scatterplot3d (e muitos outras).

Gráfico de dispersão em 3D com scatterplot3d

scatterplot3d é uma biblioteca de uso simples, com formato básico:

scatterplot3d(x, y=NULL, z=NULL)
onde x, y, z são as coordenadas dos pontos a serem plotados. Os argumentos y e z são opcionais, dependendo da estrutura de x.

  • Se x é uma fórmula (como em zvar ~ xvar + yvar) então xvar, yvar e zvar são usados como valores para x, y e z.
  • Se x é uma matriz com pelo menos 3 colunas então as variáveis x, y e z são lidas diretamente da matriz.
> library("scatterplot3d")
> data(iris)
> flor <- iris[1:50,1:3]  # 50 linhas, 3 primeiras colunas
> names(flor) <- c("comprimentoSepala", "larguraSepala", "comprimentoPepala")
> # A forma mais simples de uso:
> scatterplot3d(flor)
> scatterplot3d(flor, pch = 20,
                main="Gráfico dispersão 3D",
                xlab = "Comprimento sétala (cm)",
                ylab = "Largura sétala (cm)",
                zlab = "Comprimento pétala (cm)", color="steelblue")
> # O gráfico 1 é gerado. (pch=20 usa símbolo bola cheia)

> scatterplot3d(flor, pch = 8, main="pch = 8 -> estrela",
                color="#E8582D", angle=55, grid=TRUE, box=FALSE)
> # O gráfico 2 é gerado. pch=8 usa símbolo estrela,
> # o gráfico é girado de 55º, com grid e sem a caixa envelope.

> z <- seq(-10, 10, 0.01)
> x <- cos(z)
> y <- sin(z)
> scatterplot3d(x, y, z, highlight.3d = T, col.axis = "blue",
              col.grid = "lightblue", main = "Hélice", pch = 20)


O gráfico ao lado é gerado.

Mais informações sobre scatterplot3d no site STHDA.

Mais informações sobre 3d scatterplots no site STHDA.

Outras visualizações em 3D com scatter3d

O formato básico para scatter3d, com alguns de seus parâmetros, é o seguinte:

scatter3d(formula, data, subset, radius, xlab, ylab, zlab, ...)
ou
scatter3d(x, y, z,
xlab, ylab, zlab, revolutions=0, speed=1,
bg.col=c("white", "black"), axis.scales=TRUE,
axis.col, surface=TRUE, surface.col=carPalette()[-1],
fill=TRUE, point.col="yellow", text.col=axis.col,
radius=1, groups=NULL, fill=TRUE, grid=TRUE,
ellipsoid=FALSE, sphere.size=1, radius=1, threshold=0.01,
parallel=TRUE, ellipsoid=FALSE, id=FALSE, ...)

onde

formula fórmula y ~ x + z. Para plotar os pontos por grupos use y ~ x + z | g onde g é o fator que distingue grupos.
data data frame usado para avaliação da fórmula.
x, y, z coordenadas dos pontos a serem plotados. Os argumentos y e z são opcionais, dependendo da estrutura de x.
subset expressão definindo subconjunto das observações a serem usadas.
xlab, ylab, zlab labels nos eixos.
radius raios das esferas representando pontos.
axis.scales Se TRUE, nomeia valores nas pontas dos eixos.
revolutions quantas revoluções a figura fará (animação).
bg.col cor de fundo.
axis.col cores para eixos.
surface.col vetor de cores para os planos.
point.col cores dos pontos.
text.col cores dos eixos.
grid.col colour of grid lines on the regression surface(s).
surface lógico, plotar superfícies.
fill lógico. Preencher superfícies com cores.
grid lógico. Plotar lines da grade nas superfícies de regressão.
grid.lines número de linhas nas grades.
speed velocidade de revolução.
fov controla ângulo da perspectiva.
groups Se NULL nenhum grupo é definido. Se um fator uma superfície diferente é desenhada para cada nível.
parallel lógico. Se as superfícies para grupos devem ser paralelas.
ellipsoid lógico. Concentração elipsóide para pontos.
labels texto para labels nos pontos. Default são os índices da observação.
col cores para labels em pontos.
outros argumentos.
> install.packages(c("rgl", "car"))
> library(rgl, car)
> data(iris)
> sep.l <- iris$Sepal.Length
> sep.w <- iris$Sepal.Width
> pet.l <- iris$Petal.Length
> scatter3d(x = sep.l, y = pet.l, z = sep.w)           # plota Gráfico-1
> scatter3d(x = sep.l, y = pet.l, z = sep.w,
            point.col = "steelblue", surface=FALSE)    # plota Gráfico-2

> scatter3d(x = sep.l, y = pet.l, z = sep.w,
            groups = iris$Species)                     # plota Gráfico-3

> scatter3d(x = sep.l, y = pet.l, z = sep.w, groups = iris$Species,
         grid = FALSE, fit = "smooth")                 # plota Gráfico-4
         
> scatter3d(x = sep.l, y = pet.l, z = sep.w, groups = iris$Species,
           surface=FALSE, ellipsoid = TRUE)            # plota Gráfico-5

> scatter3d(x = sep.l, y = pet.l, z = sep.w,
            groups = iris$Species, surface=FALSE,
            grid = FALSE, ellipsoid = TRUE)            # plota Gráfico-6

> # Para gravar estes gráficos nos formatos png e pdf podemos usar

> rgl.snapshot(filename = "plot.png")
> rgl.postscript("plot.pdf",fmt="pdf")

Todos os gráficos podem ser girados e redimensionados com o arraste de mouse.

Continuaremos o estudo sobre gráficos na próxima sessão, usando ggplot2.

 


Aprofundando as técnicas sobre Gráficos

Aquisição de Dados


Como a principal motivação para o uso do software R está na análise de dados e exibição gráfica de resultados é necessário ter formas eficientes para promover a leitura de dados para dentro de nosso ambiente.

Edição básica de tabelas

Já vimos que objetos como data frames podem ser editados por meio dos comandos edit(objeto) ou fix(objeto) que abrem uma janela para a alteração em forma de grade, permitindo inclusive a inserção de novos campos ou a alteração de nomes dos campos já existentes. Esta pode ser uma boa estratégia para se fazer pequenas alterações nas tabelas.

Outra função usada para leitura de dados do usuário ou à partir da leitura de um arquivo é scan().

scan(file = "", what = double(), n = -1, sep = "")
Valores listados para os parâmetros são default. Existem muitos outros parâmetros.
file = "" indica que a leitura será feita do teclado. Se file = "arquivo" este arquivo será lido.
what indica o tipo de dado a ser lido. what=character() significa que strings serão lidas.
n é o número de dados que serão inserido. n = -1 significa um número ilimitado. Neste caso a inserção (para n=-1) termina com dois <ENTER> seguidos.
sep = "" é o tipo de separador esperado. O default é um espaço em branco.
> x <- scan(n=3) # insere 3 valores do teclado
 1: 12 2: 23 3: 34
 Read 3 items
> x
[1] 12 23 34

Arquivos CSV

Para a leitura de bases de dados mais extensas outras formas estão disponíveis. Uma delas consiste em realizar a leitura de um arquivo csv (valores separados por vírgula, em inglês comma separated values). Este tipo de arquivo consiste em uma lista de linhas, cada uma delas contendo um número constante de valores, separados por vírgula (ou outro sinal). Estes arquivos podem ser lidos por gerenciadores de planilhas tais como o Excel ou o CALC, do Libre Office. Eles podem também ser gerados por estes aplicativos.

Vamos criar um arquivo para efeito de aprendizado dessa importação de dados. Em um editor de texto ASCII qualquer digitamos os valores seguintes:

id, Nome,  Sobrenome, Idade, Sexo
1,  Marta, Rocha,     24,    F
2,  Pedro, Souza,     12,    M
3,  José,  Marciano,  15,    M
4,  Joana, Santos,    21,    F
5,  Lucas, Pereira,   20,    M

O espaçamento entre os campos não é necessário. Digamos que gravamos este arquivo com o nome alunos.csv na pasta de trabalho em uso (ou em outra qualquer).

Estes dados podem ser lidos com o comando read.table:

read.table("nomeArquivo.csv", header=TRUE, sep=",", dec=".")
Aqui o parâmetro header=TRUE indica que a primeira linha do arquivo contém títulos para as colunas, sep="," indica que as valores estão separados por vírgula (poderiam estar separados por outro caracter, como “;”) e dec="." indica que o ponto é o separador numérico de decimais.

Se o arquivo não estiver na pasta de trabalho atual o nome completo ("caminho/nomearquivo.csv") deve ser fornecido.Para a conveniência do usuário, diversas funções do R são acompanhadas de outras com nomes diversos que realizam as mesmas operações mas usam parâmetros default diferentes. É o caso de read.table() e read.csv().

Consulte a ajuda para ver quais são estes parâmetros.

Por default ítens numéricos são lidos como variáveis numéricas e texto como fatores, embora este comportamento possa ser alterado se necessário. A primeira linha (o cabeçalho) alimenta os valores de nomes de colunas.

> dir()
[1] "alunos.csv"
> alunos <- read.table("alunos.csv", header=TRUE, sep=",", dec=".")
> alunos
  id    Nome  Sobrenome Idade  Sexo
1  1   Marta      Rocha    24     F
2  2   Pedro      Souza    12     M
3  3    José   Marciano    15     M
4  4   Joana     Santos    21     F
5  5   Lucas    Pereira    20     M
> class(alunos)
[1] "data.frame"
> fix(alunos)     # permite a edição em uma tabela de alunos
> names(alunos)   # lista propriedades names
[1] "id"        "Nome"      "Sobrenome" "Idade"     "Sexo"
> dim(alunos)     # dimensões da lista (5 linhas com 5 campos)
[1] 5 5
> alunos[1,]      # primeira linha da lista
  id    Nome Sobrenome Idade  Sexo
1  1   Marta     Rocha    24     F

> # O parâmetro row.names permite usar uma coluna para nomear as linhas:
> outroAlunos <- read.table("alunos.csv", header=TRUE, row.names="id", sep=",")
> outroAlunos
   N Nome   Sobrenome  Idade  Sexo
   1 Marta      Rocha     24     F
   2 Pedro      Souza     12     M
   3 José    Marciano     15     M
   4 Joana     Santos     21     F
   5 Lucas    Pereira     20     M

Em muitos casos precisamos executar a operação inversa: exportamos os dados em uma tabela para um arquivo csv para transferir dados e utilizá-los em outro aplicativo. Para isso usaremos, a seguir, a função write.csv()

> # Para recordar, criamos uma nova tabela, semelhante à alunos:
> alunos2 <- data.frame(
                   id =1:5,
                   Nome = c("Marta","Pedro","José","Joana","Lucas"),
                   Sobrenome = c("Rocha","Souza","Marciano","Santos","Pereira"),
                   Idade = c(24, 12, 15, 21, 20),
                   Sexo = c("F", "M","M", "F", "M")
                   )
> # Para gravar esta tabela em disco, como um arquivo csv:
> write.csv(alunos2, file="alunos2.csv")
> # Se o parâmetro file for omitido a saída é para o console
> write.csv(alunos2)
   "","id", "Nome", "Sobrenome", "Idade","Sexo"
   "1", 1, "Marta", "Rocha",      24,    "F"
   "2", 2, "Pedro", "Souza",      12,    "M"
   "3", 3,  "José", "Marciano",   15,    "M"
   "4", 4, "Joana", "Santos",     21,    "F"
   "5", 5, "Lucas", "Pereira",    20,    "M"

Para gravar o objeto alunos2 (uma lista) para uso futuro usamos save(). O objeto pode ser recuperado para o projeto através da função load().

> save(alunos2, file="alunos2.Rdata")
> dir()                   # Para verificar quais são os aqruivos na pasta
[1] "alunos.csv"       "alunos2.Rdata"
> rm(alunos2)             # alunos2 não existe mais na sessão
> load('alunos2.Rdata')   # recupera alunos2
> str(alunos2)
'data.frame':	5 obs. of  5 variables:
 $ id       : int  1 2 3 4 5
 $ Nome     : Factor w/ 5 levels "Joana","José",..: 4 5 2 1 3
 $ Sobrenome: Factor w/ 5 levels "Marciano","Pereira",..: 3 5 1 4 2
 $ Idade    : num  24 12 15 21 20
 $ Sexo     : Factor w/ 2 levels "F","M": 1 2 2 1 2

Alternativamente, podemos ler uma variável de texto para dentro de uma tabela.

> dados <- " idade sexo altura 13 F 1.25 15 F 1.60 10 M 1.40 "
> tabela <- read.table(header=TRUE, text=dados)
> str(tabela)
'data.frame':	3 obs. of  3 variables:
 $ idade : int  13 15 10
 $ sexo  : Factor w/ 2 levels "F","M": 1 1 2
 $ altura: num  1.25 1.6 1.4

Nos exemplos anteriores os tipos das colunas foram inferidos à partir dos dados lidos. Os campos de texto foram convertidos em fatores. O parâmetro colClasses permite que sejam informados previamente o tipo de cada coluna lida.

> alunosNotas <- "
     id| aluno |nota |bolsista
     1 | Marco | 5.2 |sim
     2 | Ana   | 7.5 |nao
     3 | Celia | 2.5 |sim"
> notas <- read.table(header=TRUE, text=alunosNotas,
         row.names="id", sep="|",
         colClasses=c("numeric", "character", "numeric", "character"))
> str(notas)
'data.frame':	3 obs. of  3 variables:
 $ aluno   : chr  " Marco " " Ana   " " Celia "
 $ nota    : num  5.2 7.5 2.5
 $ bolsista: chr  "sim" "nao" "sim"
> # A coluna bolsista foi importada como strings.
> # Para transformá-la em uma coluna de valores lógicos podemos fazer
> notas$bolsista <- notas$bolsista=="sim"
> str(notas)
'data.frame':	3 obs. of  3 variables:
 $ aluno   : chr  " Marco " " Ana   " " Celia "
 $ nota    : num  5.2 7.5 2.5
 $ bolsista: logi  TRUE FALSE TRUE

Se um arquivo *.csv se encontra na web, disponível através de protocolo http (como o arquivo na url usada abaixo, do site Sample Videos) podemos usar sua url:

> url <- "https://www.sample-videos.com/csv/Sample-Spreadsheet-10-rows.csv"
> dadosCsv <- read.csv(url)
> # A função carrega um data frame em dadosCsv

Se a página da web usa o protocolo https (mais seguro que o anterior) podemos usar o pacote RCurl. Como exemplo vamos baixar um arquivo diponibilizado pela cidade de Seattle, EUA (King County Open Data), contendo dados sobre animais de estimação perdidos:

> install.packages("RCurl")
> library (RCurl)
> url <- "https://data.kingcounty.gov/api/views/yaai-7frk/rows.csv?accessType=DOWNLOAD"
> dw <- getURL(url)
> dados <- read.csv (text = dw)
> class(dados)
[1] "data.frame"
> View(dados)

Com frequência dados baixados da internet contém falhas como, por exemplo, uma entrada em texto em uma coluna numérica. Estes dados precisam ser visualizados e tratados antes de uma análise de sua informação. Podemos vizualizar estes dados de forma gráfica usando a função View() que exibe em tabela um dataframe.

Importando planilhas

O pacote xlsx depende para seu funcionamento dos pacotes rJava e xlsxjars, bem como uma instalação funcional do Java em seu computador.

Para importar uma planilha do Excel ou Libre Office Spreadsheet podemos exportar estes dados para um arquivo *.csv e importá-lo usando as técnicas já descritas. Alternativamente é possível importar diretamente estas planilhas usando o pacote xlsx, que deve ser instalado antes do uso. Planilhas podem ser importadas com as funções read.xlsx e read.xlsx2 que têm a seguinte sintaxe:

read.xlsx(file, sheetIndex, sheetName=NULL, rowIndex=NULL,
startRow=NULL, endRow=NULL, colIndex=NULL,
as.data.frame=TRUE, header=TRUE, colClasses=NA,
keepFormulas=FALSE, encoding=”unknown”, password=NULL, …)

read.xlsx2(file, sheetIndex, sheetName=NULL, startRow=1,
colIndex=NULL, endRow=NULL, as.data.frame=TRUE, header=TRUE,
colClasses=”character”, password=NULL, …)

Argumento Descrição
file arquivo (com caminho) a ser lido.
sheetIndex número da planilha dentro da pasta de trabalho.
sheetName nome da planilha.
rowIndex vetor numérico indicando linhas a serem extrarídas. Se NULL todas as linhas, exceto se startRow, endRow são especificados.
colIndex vetor numérico indicando colunas a serem extrarídas. Se NULL todas as colunas.
as.data.frame valor lógico. Se TRUE os dados serão montados em um data.frame. Se FALSE, uma lista, com um elemento por coluna.
header valor lógico indicando que a primeira linha contém os nomes das colunas.
colClasses (read.xlsx) vetor de strings com a classe de cada coluna.
keepFormulas valor lógico. Se TRUE as fórmulas do excel são mostradas como texto e não avaliadas.
encoding codificação para strings na planilha.
startRow numérico, especificando índice da 1ª linha. (Ativo se rowIndex=NULL).
endRow numérico, especificando índice da última linha. Se NULL, todas as linhas. (Ativo se rowIndex=NULL).
password senha para a pasta de trabalho.
outros argumentos para a data.frame. Ex. stringsAsFactors

A função read.xlsx procura adequar o tipo lido com o da planilha de acordo com cada coluna, preservando o tipo de dado lido.
read.xlsx2 é mais rápida, adequada para ser usada em planilhas muito grandes, acima de 100 mil células. Ambas podem ser usadas para ler arquivos *.xlsx ou *.xls.

> library(xlsx)
> xlFrame <- read.xlsx("planilha.xlsx",1, header=TRUE); xlFrame
     Data Local        Crédito   Débito
  1 43223 Casa Coral   1002.56   65.45
  2 43224 Fornecedor 1   23.34   NA
  3 43225 Cliente 2      24.34   33.00
  4 43226 Fornecedor 2   15.23   54.00
> # Valor não existente na planilha foi lido como 'NA'
> # A data foi lida como um campo numérico.

Gravando dados em uma planilha Excel

As funções write.xlsx e write.xlsx2 podem ser usadas para gravar dados de uma tabela em uma pasta de trabalho Excel. A segunda delas atinge uma performance melhor para planilhas longas, acima de 100 mil células.

Elas têm a sintaxe:

write.xlsx(x, file, sheetName=”Sheet1″,
col.names=TRUE, row.names=TRUE,
append=FALSE, showNA=TRUE, password=NULL)write.xlsx2(x, file, sheetName=”Sheet1″,
col.names=TRUE, row.names=TRUE,
append=FALSE, password=NULL, …)

São seus argumentos:

Argumento Descrição
x data.frame a ser escrito como pasta de trabalho.
file arquivo (com caminho) a ser escrito.
sheetName nome da planilha.
col.names valor lógico. Se TRUE os nomes das colunas de x são escritos no arquivo.
row.names valor lógico. Se TRUE os nomes das linhas de x são escritos no arquivo.
append valor lógico. Se TRUE o arquivo é lido no disco e incrementado.
showNA valor lógico. Se FALSE valores NA são gravados em branco.
password senha para a pasta de trabalho.
outros argumentos passados para addDataFrame (no caso de read.xlsx2).

Para exibir o comportamento destas funções usamos o data.frame USArrests (que vem instalado no pacote básico de R).
Primeiro criamos uma planilha com nome USA-ARRESTS. Depois gravamos em outra planilha na mesma pasta o dataframe alunos que temos carregado em nossa sessão.

> write.xlsx(USArrests, file="pastaTrabalho.xlsx",
             sheetName="USA-ARRESTS", append=FALSE)
> write.xlsx(alunos, file="pastaTrabalho.xlsx",
             sheetName="alunos", append=TRUE)

O resultado é a gravação, em disco, de uma pasta de trabalho com duas planilhas com nomes USA-ARRESTS e alunos.

Existem outros pacotes destinados à manipulação de arquivos de planilhas. Entre eles citamos os pacotes XLConnect e openxlsx. Este último não depende de Java.

Manipulação de arquivos XML

XML (Extensible Markup Language) é um formato de transmissão de dados bastante usado na internet e computação em geral, usando apenas texto puro (ASCII). Ele contém tags de marcação que descrevem a estrutura dos dados.

Instale o pacote usando

install.packages("XML")

No R se pode ler e escrever em arquivos XML usando o pacote "XML". Para experimentar com a biblioteca usaremos um arquivo ASCII com o conteúdo abaixo, que gravaremos no disco com o nome livros.xml.

<biblioteca>
    <livro>
        <id>1</id>
        <titulo>A Dança do Universo</titulo>
        <autor>Marcelo Gleiser</autor>
    </livro>
    <livro>
        <id>2</id>
        <titulo>DNA: O Segredo da Vida</titulo>
        <autor>James D. Watson</autor>
    </livro>
    <livro>
        <id>3</id>
        <titulo>Uma breve história do tempo</titulo>
        <autor>Stephen W. Hawking</autor>
    </livro>
    <livro>
        <id>4</id>
        <titulo>Como a mente funciona</titulo>
        <autor>Steven Pinker</autor>
    </livro>
    <livro>
        <id>5</id>
        <titulo>A falsa medida do homem</titulo>
        <autor>Stephen Jay Gould</autor>
    </livro>
    <livro>
        <id>6</id>
        <titulo>O último teorema de Fermat</titulo>
        <autor>Simon Singh</autor>
    </livro>
</biblioteca>

O código seguinte carrega este arquivo para um objeto do R e o manipula.

> # Carrega os pacotes necessários
> library("XML", "methods")
> # Importa dados para um objeto de R
> livros <- xmlParse(file="livros.xml", encoding="UTF8" )
> class(livros) # "XMLInternalDocument" "XMLAbstractDocument"
> print(livros)
<biblioteca>
  <livro>
    <id>1</id>
    <titulo>A Dança do Universo</titulo>
    <autor>Marcelo Gleiser</autor>
  </livro>
  ... (outros 5 livros)
</biblioteca>
> xmlTop <- xmlRoot(livros) # node principal > class(xmlTop)
[1] "XMLInternalElementNode" "XMLInternalNode" "XMLAbstractNode"
> xmlName(xmlTop)             # nome do node principal
[1] "biblioteca"
> xmlSize(xmlTop)             # tamanho do node principal
[1] 6
> xmlName(xmlTop[[1]])        # nome do primeiro node
[1] "livro"
> xmlSize(xmlTop[[1]])        # tamanho do primeiro node
[1] 3
> print(xmlTop[[2]])          # exibe o segundo node            
<livro>
  <id>2</id>
  <titulo>DNA: O Segredo da Vida</titulo>
  <autor>James D. Watson</autor>
</livro> 
> xmlTop[[3]][[2]]            # segundo ítem do terceiro node
<titulo>Uma breve história do tempo</titulo>
> # Dados podem ser recuperados usando-se o nome dos nodes
> xmlTop[["livro"]][["autor"]]
<autor>Marcelo Gleiser</autor>
> # Convert um objeto xml para um dataframe
> dfLivros <- xmlToDataFrame(livros) > # Visualiza o dataframe
> View(df.livros, "Dataframe Livros")

A função View() exibe o dataframe como na figura abaixo:

Conexão com banco de dados

Naturalmente, grande parte dos dados a serem analisados estão armazendos em bancos de dados relacionais. R pode se relacionar com diversos dos sistemas de gerenciamento, entre eles os mais populares como SQL Server, Access, MySQL, Oracle, PostgreSQL e SQLite. Existem pacotes que permitem o acesso direto aos drivers nativos destes sistemas e outros que permitem esse acesso via ODBC ou JDBC. Desta forma o poder das consultas SQL amplia bastante o potencial de R na análise de dados.

Usando a interface ODBC

Uma forma comum de acessar bancos de dados de dentro do R é através do pacote RODBC que permite a conexão com qualquer DBMS (Data Base Management System) que admite conexão com o driver ODBC (como é o caso de todos os sistemas listados acima). Para isso é necessário instalar o driver ODBC apropriado para o banco de dados a ser usado, na plataforma específica. Em seguida se instala o pacote ODBC, usando install.packages("RODBC"). As funções básicas do pacote são:

Função Descrição
odbcConnect(dsn,uid=””,pwd=””) abre uma conexão com o driver ODBC
sqlFetch(channel,sqltable) lê uma tabela e a carrega em um data frame
sqlQuery(channel,query) submete uma consulta sql e retorna os resultados
sqlSave(channel,mydf,tablename= sqltable,append=FALSE) escreve ou atualiza um data frame para tabela da base
sqlDrop(channel,sqltable) remove uma tabela do bando de dados
close(channel) fecha a conexão

O pacote RODBC permite a comunicação bidirecional entre R e o banco de dados, que pode ser lido ou alterado. Suponha que um banco de dados possua duas tabelas possua, digamos debito e credito. É possível importá-las para dentro de uma sessão fazendo:

> library(RODBC)
> conn <- odbcConnect("dsn", uid="usr", pwd="senha")
> debito <- sqlFetch(conn, debito)
> credito <- sqlQuery(conn, "select * from credito")
> close(myconn)
Nem todos os pacotes estão disponíveis em todas as plataformas. Confira a documentação do CRAN.

A função sqlQuery() pode ser usada para a aplicação de qualquer instrução SQL, permite uma seleção detalhada de variáveis, a criação de novos campos, alteração e inserção de dados no banco.

Usando o pacote DBI

O pacote DBI (DataBase Interface) fornece outra forma de acesso à DBMS com suporte à diversos drivers. Entre eles estão os pacotes RJDBC (acesso ao driver JDBC), RMySQL, ROracle, RPostgreSQL e RSQLite.

Para o exemplo que se segue usamos o banco de dados chinook.db que pode ser baixado no site do SQLite Tutorial.

> library(DBI)
> library(RSQLite)
> banco <- "chinook.db"
> driver <- dbDriver("SQLite")
> db <- dbConnect(driver, banco)
> # Para exibir qual banco está associado a este objeto:
> db
<SQLiteConnection>
  Path: /home/guilherme/Projetos/R/Aprendendo/chinook.db
  Extensions: TRUE
> # Lista de tabelas em chinook.db
> dbListTables(db)
[1] "albums"         "artists"   "customers"    "employees"       "genres"
[6] "invoice_items"  "invoices"  "media_types"  "playlist_track"  "playlists"
[11]"sqlite_sequence" "sqlite_stat1" "sqlite_stat4"   "tracks"
> # Lista de campos na tabela 'albums'
> dbListFields(db, "albums")
[1] "AlbumId"  "Title"    "ArtistId"
> sql <- "SELECT AlbumId, Title FROM albums"
> db <- dbConnect(driver, banco)
> rs <- dbSendQuery(db, sql)
> rs
<SQLiteResult>
  SQL  SELECT AlbumId, Title FROM albums
  ROWS Fetched: 0 [incomplete]
       Changed: 0
> dbColumnInfo(rs)
     name      type
1 AlbumId   integer
2   Title character
> dbGetStatement(rs)
[1] "SELECT AlbumId, Title FROM albums"
> albuns <- fetch(rs, n = 5) > albuns
  AlbumId                                 Title
1       1 For Those About To Rock We Salute You
2       2                     Balls to the Wall
3       3                     Restless and Wild
4       4                     Let There Be Rock
5       5                              Big Ones

> # Lista de campos na tabela 'artists'
> dbListFields(db, "artists")
[1] "ArtistId" "Name"
> sql <- "SELECT a.Title, b.Name FROM albums
               a INNER JOIN artists b ON a.ArtistId = b.ArtistId
               WHERE b.Name LIKE \"Iron%\""
> # O resultado de uma consulta fica armazenado em rs:
> rs <- dbSendQuery(db, sql)
> # rs tem as seguintes colunas
> dbColumnInfo(rs)
   name      type
1 Title character
2  Name character
> # rs foi gerada pela consulta (sql query)
> dbGetStatement(rs)
[1] "SELECT a.Title, b.Name FROM albums a
     INNER JOIN artists b
     ON a.ArtistId = b.ArtistId
     WHERE b.Name LIKE \"Iron%\""
> # O número de colunas alteradas
> dbGetRowsAffected(rs)
[1] 0
> # Para ler 5 linhas deste resultado
> linhas <- fetch(rs, n = 5) > linha
                       Title        Name
1 A Matter of Life and Death Iron Maiden
2            A Real Dead One Iron Maiden
3            A Real Live One Iron Maiden
4            Brave New World Iron Maiden
5             Dance Of Death Iron Maiden
> dbGetRowCount(rs)   # quantas colunas
[1] 5
> # Liberando o 'resultset' e a conexão
> dbClearResult(rs); dbDisconnect(db)

No código abaixo está mostrado como abrir e manipular um banco de dados PostgreSQL que deve estar instalado na máquina local. Ele lê uma tabela no banco de nome Notas que tem uma tabela categorias com campos id, idPai, categoria. Em seguida ele usa o data frame alunos que já está carregado na sessão de R com campos id, Nome, Sobrenome, Idade, Sexo e grava esta tabela no banco de dados, com nome “alunos”. Finalmente uma consulta de atualização é feita usando a conexão aberta.

> library(DBI)
> conn <- dbConnect(odbc::odbc(),
                driver = "PostgreSQL Unicode",
                database = "Notas",
                uid = "nomeUsuario",
                pwd = "senhaUsuario",
                host = "localhost",
                port = 5432)
> # Este db possui uma tabela 'categorias'
> categorias <- dbReadTable(conn, "categorias")
> categorias
  id idPai             categoria
1 10     0               Ciência
2 14     0            Literatura
3 15    14     Ficção Científica
4 11    10                Física
...
> # Uma consulta SQL
> sql <- "SELECT id, categoria FROM categorias ORDER BY categoria"
> categ <- dbSendQuery(conn, sql)
> primeiros_3 <- dbFetch(categ, n = 3)  # primeiros 3 registros
> primeiros_3
  id             categoria
1 13              Biologia
2 10               Ciência
3 17            Phylos.net
> restante <- dbFetch(catg) # lê os demais registros
> # Gravando o data frame alunos no banco de dados, com nome "alunos"
> # data contém booleano com sucesso da operação
> data <- dbWriteTable(conn, "alunos", alunos)
> # Uma query de atualização na tabela Categorias
> sql <- "UPDATE Categorias SET categoria ='Nova categoria' WHERE id=17"
> dbSendQuery(conn, sql) # altera categoria com id = 17

O código a seguir cria um banco de dados virtual (que existe apenas na memória). Ele pode ser útil para teste, para operações provisórias ou mesmo para a criação completa de um banco até que ele esteja pronto para ser gravado em disco.

> library(DBI)
> # Cria um banco de dados SQL virtual
> conn <- dbConnect(RSQLite::SQLite(), dbname=":memory:") > dbListTables(conn)
character(0)
> dbWriteTable(conn, "mtcars", mtcars)
> dbListTables(conn)
[1] "mtcars"
> dbListFields(conn, "mtcars")
 [1] "mpg"  "cyl"  "disp" "hp"   "drat" "wt"
 [7] "qsec" "vs"   "am"   "gear" "carb"
> dbReadTable(conn, "mtcars")
    mpg cyl  disp  hp drat    wt  qsec vs am gear carb
1  21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
2  21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
3  22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
> # Todas as linhas podem ser recuperadas de uma vez:
> rs <- dbSendQuery(conn, "SELECT * FROM mtcars WHERE cyl = 4")
> dbFetch(rs)
    mpg cyl  disp  hp drat    wt  qsec vs am gear carb
1  22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
2  24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
3  22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
4  32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
5  30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
(... continua, 32 linhas ...)

> dbClearResult(res)
> # Lendo o banco por partes
> rs <- dbSendQuery(conn, "SELECT * FROM mtcars WHERE cyl = 4")
> while(!dbHasCompleted(res)) {
           parte <- dbFetch(res, n = 5)
           print(nrow(parte))
           }
  [1] 5 [1] 5 [1] 1
> # Fecha o resultset e a conexão
> dbClearResult(rs); dbDisconnect(conn)

Um resumo das funções disponíveis no pacote DBI estão listadas abaixo. Para uma lista completa, com descrição detalhada de cada função e seus parâmetros consulte a R Database Interface (versão 0.5-1) na página R Documentation.

Função Descrição
dbDriver carrega e descarrega drivers
dbColumnInfo informa sobre tipos em resultados
dbExecute executa uma query e fecha o result set
dbCallProc chama uma “stored procedure”
dbClearResult limpa uma result set
dbDisconnect fecha uma conexão
dbConnect cria conexão com uma DBMS
dbDataType determina o tipo (SQL) de um objeto
dbGetStatement verifica a query associada a um result set
dbGetRowCount número de linhas recuperadas até o momento
dbGetQuery envia query, retorna resultado e limpa o result set
dbHasCompleted status de realização de uma query
dbGetRowsAffected número de linhas afetada
dbExistsTable booleano, retorna existência da tabela
dbIsValid booleano, o DBMS é um objeto válido?
dbListConnections lista conexões abertas
dbListResults lista resultados pendentes
dbListFields lista nomes de campos de uma tabela
dbSendQuery executa consulta sobre uma conexão
dbRemoveTable remove uma tabela do banco de dados
dbFetch recupera registros de consulta já realizada
transactions inicia/commit/rollback transações SQL
sqlAppendTable insere linhas em uma tabela
sqlCreateTable cria uma tabela

Obtendo dados na Internet

Uma grande quantidade de dados se encontra hoje disponível na internet. Por isso é importante aprender a acessá-los e selecionar aqueles que nos interessam. Esta busca e seleção de dados é denominada web scraping.

Embora existam dados em formato estruturado, como tabelas e até mesmos bancos de dados, muita informação na Web está sob formato não estruturado, como ocorre em muitas páginas de texto HTML. É necessário, portanto, converter essa informação em formatos mais úteis.

Em muitos casos copiar e colar conteúdo de uma página em arquivo local pode ser suficiente. Em arquivos pequenos eles podem ser organizados manualmente e os dados postos em forma de uma tabela, por exemplo com os campos separados por vírgula. Em outros casos uma página pode ser analisada por meio de reconhecimento de padrões, usando expressões regulares ou outro processo.

Muitos sites importantes, como Facebook, Twitter e LinkedIn, fornecem APIs públicas ou privadas, que facilitam a leitura de seus dados. Além disso as páginas da web são alimentadas para os browers dentro de estruturas DOM (Document Object Model), o que facilita a garimpagem de dados.

Se os dados já estão estruturados, sob a forma de um arquivo csv (por exemplo) então eles podem ser importados para a sessão de R com a função read.table ou read.csv. Em seguida eles podem ser manipulados de acordo com a conveniência da análise desejada.

> # Há um arquivo csv de teste no endereço abaixo:
> url <- "https://www.sample-videos.com/csv/Sample-Spreadsheet-100-rows.csv"
> dados <- read.table(url, sep=",")
> # A lista está disponível. Os dois primeiros elementos do campo V3 são:
> head(dados$V3, n=2)
[1] Muhammed MacIntyre Barry French
> # Arquivo sem títulos nas colunas: os campos ficam nomeados V1 até v10
> # Independente da formatação uma página pode ser baixada com:
> download.file("https://endereco_url.html", "caminhoOndeSalvar/arquivo.html")

Usando o pacote rvest

O pacote rvest foi escrito por Hadley Wickham e é inspirado em bibliotecas como a Beautiful Soup, do Python. Um bom tutorial pode ser encontrado na página do Data Camp sobre o rvest.
 
XPath (XML Path Language) é uma especificação de pesquisa em nodes de um documento XML. Existem aplicativos e plugins nos principais browsers para facilitar esta localização. Para o Chrome uma boa ferramenta é o SelectorGadget.
No Firefox podemos usar o inspector, um ítem de menu em web developer, que pode ser aberto com CTRL-SHIFT-C.

Para ler uma página na web e analisar seu conteúdo podemos usar o pacote rvest. Nele encontramos a função read_html() que retorna um documento XML que contém toda a informação sobre a página.

Primeiro procuramos uma página na web contendo as informações que desejamos extrair. Para efeito de nosso aprendizado usamos uma páginas simples onde se exibe uma tabela dos estados brasileiros com suas populações e PIBs. Usaremos a página Lista de Estados Brasileiros com população e PIB em Excel. Ignoramos, claro, a possibilidade disponível nessa página de baixar diretamente a tabela em formato Excel. Abrindo esta página no browser abrimos (no Firefox) o web developer, inspector. Na janela de inspecção procuramos a tabela desejada. Ao movimentar o cursor do mouse sobre o elemento html a tabela fica sombreada. Clique na tabela e pressione o botão direito selecionando copy xpath. xpath é um localizador de posição dentro da página, que fica armazenado na área de transfrência. No nosso caso temos xpath = "/html/body/div[3]/div[1]/table", indicando que queremos extrair a tabela única dentro do primeiro div, dentro do terceiro div no corpo do documento html. Em seguida baixamos o conteúdo sob forma xml e depois selecionamos o node desejado usando html_nodes(xpath).

> library(rvest)
> url <- "http://www.servicos.blog.br/listas/lista-de-estados-brasileiros-com-populacao-e-pib-em-excel/"
> xPath <- "/html/body/div[3]/div[1]/table"
> # O seguinte comando armazena o conteúdo do elemento em xPath como 
> populacao <- url %>%
    read_html() %>%
    html_nodes(xpath=xPath) %>%
    html_table()
> # Da forma como foi obtida, populacao é uma lista com um elemento
> # Este elemento único é a tabela desejada
> populacao <- populacao[[1]]
> View(populacao)

O útimo comando abre a visualização da tabela:

Veremos em breve um pouco mais sobre o funcionamento do pipe %>%. Por enquanto basta saber que ele faz parte do pacote magrittr e facilita a notação para operações encadeadas, como a composição de funções:

  • x %>% f é equivalente a f(x)
  • x %>% f(y) é equivalente a f(x, y)
  • x %>% f %>% g %>% h é equivalente a h(g(f(x)))

Portanto a linha contendo pipes é idêntica aos seguintes comandos:

> pop <- read_html(url)
> pop2 <- html_nodes(pop, xpath=xPath)
> pop3 <- html_table(pop2) # pop3 é o mesmo que populacao, acima.


Operadores e Funções Internas